velipso / gvasm Goto Github PK
View Code? Open in Web Editor NEWAssembler and disassembler designed specifically for Game Boy Advance homebrew.
License: BSD Zero Clause License
Assembler and disassembler designed specifically for Game Boy Advance homebrew.
License: BSD Zero Clause License
This:
ldr r5, =0x10101000
is currently translated to:
ldr r5, [#@@data]
@@data: .i32 0x10101000
But testing indicates that this is faster:
mov r5, #0x10000000
orr r5, #0x100000
orr r5, #0x1000
Likewise, a two instruction version is obviously faster too.
Some tests should be performed for thumb instructions too, but that's harder due to no shifted immediate.
Now that we can allocate structs to IWRAM or EWRAM, it would be nice to have the equivalent for code:
.begin irqNone = iwram
.arm
mov r0, #0x04000000
ldr ip, [r0, #0x200]!
and r2, ip, ip, lsr #16
strh r2, [r0, #2]
ldr r3, [r0, #-0x208]
orr r3, r3, r2
str r3, [r0, #-0x208]
bx lr
.end
This should expose:
.printf "%08X", irqNone // the location in IWRAM
.printf "%08X", irqNone._rom // the location in ROM
.printf "%d", irqNone._bytes // the size of the code
So it is up to the user to memcpy the function from ROM to IWRAM, then they can bl irqNone
.
Larger blocks should be supported, and layering too (eventually):
.begin iwramCode = iwram(-1) // all layers
.include './saveCopy.gvasm'
.include './sndFrame.gvasm'
.embed './silence.gvsong'
.end
.begin iwramTemp = iwram(1) // layer 1 only
.include './saveInit.gvasm'
.include './sndInit.gvasm'
.end
.struct Player = iwram(2) // layer 2 only
.i32 health
.end
This means Player
will overlap iwramTemp
. Also, you can copy all of iwramCode
in one memcpy.
Then, iwramCode.saveCopy
should point to RAM. Maybe there should be iwramCopy._rom.saveCopy
too.
This should work:
msr cpsr, #0x12
The ARM7TDMI data sheet specifically forbids this instruction, but Mary (GBAdev discord) says it works and it's in future publications.
I sometimes forget to pop
what I push
ed... why not let the assembler do it.
From:
push {r4-r6}
...
pop {r4-r6}
to:
.push {r4-r6}
...
.pop // automatically pop {r4-r6}
Another variation of this feature...
sub sp, #value
...
add sp, #value
to:
.subsp value
...
.addsp
Maybe just a .defer
would be better?
.begin
push {r4-r6}
.defer pop {r4-r6}
...
.end // -> defers happen in reverse order
// error.gvasm
.printf "about to error"
.error "yo"
// main.gvasm
.import './foo1.gvasm' { FOO1 }
.import './foo2.gvasm' { FOO2 }
// foo1.gvasm
.import './error.gvasm' err
.def FOO1 = 1
// foo2.gvasm
.import './error.gvasm' err
.def FOO2 = 2
This will print "about to error" twice, and ultimately not show the actual error message ("yo").
Word on discord is you can perform a mov in thumb on low registers without setting status. They claim it's undocumented.
I should verify this on real hardware and, assuming it's true, implement it in gvasm somehow.
Gericom: The mov opcode for hi registers (which doesn't update flags) works just fine for low registers on arm7tdmi, but since that's not officially defined behavior it won't be used in the compiler
should use cpy
mnemonic
Make will return 0 (success) for the process, even if there are compilation errors.
Obviously wrong.
[08:36:39] Error:
src/lib/tilesOverlay.gvasm:5:1: Failed to import file: /Users/sean/Sync/2022/dev/tilegame2/src/lib/config.gvasm
[08:36:39] Watching 59 files
NotFound: No path was found. about ["/Users/sean/Sync/2022/dev/tilegame2/src/lib/config.gvasm"]
at new FsWatcher (deno:runtime/js/40_fs_events.js:19:23)
at Object.watchFs (deno:runtime/js/40_fs_events.js:64:12)
at Watcher.watch (https://raw.githubusercontent.com/velipso/gvasm/main/src/watcher.ts:59:26)
at https://raw.githubusercontent.com/velipso/gvasm/main/src/make.ts:104:15
at makeFromFile (https://raw.githubusercontent.com/velipso/gvasm/main/src/make.ts:54:29)
at async makeResult (https://raw.githubusercontent.com/velipso/gvasm/main/src/make.ts:75:3)
at async make (https://raw.githubusercontent.com/velipso/gvasm/main/src/make.ts:157:5)
at async main (https://raw.githubusercontent.com/velipso/gvasm/main/src/main.ts:388:12)
at async https://raw.githubusercontent.com/velipso/gvasm/main/gvasm.ts:10:11
Unknown fatal error
negs r0
== negs r0, r0
This idea isn't fully fleshed out, just recording it for my own posterity.
I would like for people to be able to do something like:
gvasm install 'https://github.com/example/test/someLibrary.json'
This would download the library, and any dependencies, perhaps in a lib/
directory.
Then, the main code could somehow include it via:
.import '~/lib/someLibrary/main.gvasm' { main }
or something like that, where ~
always resolves to the root of the make.
Ok -- that would be somewhat easy to create, but the issue is that how would the code actually work together with user code?
If the library just needs memory, then that can be solved by having the user pass pre-allocated memory to the library. But what if the library needs a timer? Or to install an IRQ handler?
It seems the fundamental issue is that all the code needs to use shared resources intelligently. And it somehow needs to communicate those needs. Memory is a shared resource, but so are timers, IRQ handlers, etc.
So... it would be great if the library could somehow say:
Hey, I need a timer (don't care which one), and I need it to fire an interrupt, and here is my interrupt handler when that happens.
Typically, you would expect the library documentation to say those things, and it would be up to the programmer to handle setup correctly. But that seems strange -- after all, we don't expect a C library to tell us how to place globals into memory. We expect the compiler to figure that out. And we don't expect a TypeScript library to tell us how to perform dynamic memory allocation. Again, that's handled by the environment automatically.
So could we solve this idea generally? We have a shared resource, and want to coordinate usage across different libraries.
Another issue is that the resource usage changes over run-time. Perhaps we only want a timer used for a portion of run-time. How can we verify at compile-time that the library has a timer allocated to it?
So when it comes to static memory, there is at least some prior art that I know about, called overlays.
So how could this ultimately work in gvasm?
One thought is to have a sink script that registers itself as managing a particular resource at compile-time. Something like gvlib-timers.sink
:
var rm = gvasm.registerResourceManager 'timer'
do
var {kind, data} = gvasm.nextEvent {rm}
while kind != gvasm.event.DONE
if kind == gvasm.event.REQUEST
// ?
elseif kind == gvasm.event.FINISH
// ?
end
end
Then you would:
gvasm make -r src/gvlib-timers.sink src/main.gvasm
Now, programs can request access to the 'timer'
resource.
.request timer 'timer'
.func install
.thumb
ldr r0, =timer.REG
// ...
bx lr
.pool
.end
.func uninstall
// ...
.end
.end
And somehow timer.REG
needs to be specified by the gvlib-timers.sink
script file.
Next, the user of the install
function needs to somehow tell the compiler when the timer is in use at run-time.
So maybe something like:
.import './timer.gvasm' { install, uninstall }
.begin main
.thumb
.reserve 'timer'
bl install
// timer is in use here!
bl uninstall
.end
.end
I don't know, something like that :-/
For jumps that are too long, the error line is the label, which isn't really useful -- it should be the line that has the jump
.align 4, nop
should insert mode-equivalent nop
instructions until alignment
needed for veneers for switching mode
.thumb
// thumb code
.align 4, nop
bx pc
.arm
// arm code
// test.gvasm
.import './foo.gvasm' foo
.import './bar.gvasm' bar
// foo.gvasm
.import './fail.gvasm' fail
// bar.gvasm
.import './fail.gvasm' fail
// fail.gvasm
.script
.abort 'oh no'
.end
this will error "oh no" twice
Would be cool to have something like:
.tfunc someName a, b, c, d // thumb
...
.return a, b
.end
.afunc someName a, b, c, d // arm
...
.return
.end
which does a few things...
.regs a, b, c, d, r4-r11
.return <reserved regs>
, it pops the registers and lrAlternative syntax:
.func someName a, b, c, d
// stack variables
.i32 one
.i32 two
.thumb // triggers end of stack variables
...code...
ldrx r0, [sp, #one]
strx r0, [sp, #one]
...code...
.return r0
.end
Since .if
is very restrictive, we need an .assert
statement:
// could refuse to compile due to unknown values
.if foo != bar
.error "foo isn't bar!"
.end
// could be scheduled to be asserted instead
.assert "foo should be bar", foo == bar
There is a commented out test that should pass in ./src/itests/files.ts
Verify this works:
.script
i32 1, 2, {3, 4, {5, 6}}, 7
.end
Wondering if it would be nice if there was a register allocator algorithm.
It could look something like:
.fthumb @lerp ?a, ?b, ?t
subs ?b, ?a
muls ?b, ?t
lsrs ?b, #8
adds ?a, ?b
.ret ?a
.end
.call @lerp 1, 2, 3
Currently .base
can only be set for an entire project.
Instead, it should behave like .arm
and .thumb
, where there is a global setting, and there are scoped settings.
Negs are:
rsbs r0, r1, #0
rsb r0, r1, #0
.struct Player = iwram(-1) // all layers
// ...
.end
Something like iwram(v)
where v
is a 32-bit number that says whether the struct should be allocated to that layer.
So that means there are 32 layers to work with. iwram(-1)
means all layers due to all bits being on.
This complicates the allocation algorithm and could leave gaps, depending on the layering.
Maybe the syntax should be nicer though, like ranges:
.struct Player = iwram(0-4, 8, 10)
// ...
.end
Though would still limit it 0-31, and default to all if no range provided.
These should work in Thumb:
strx r0, [sp, #S.foo]
strx r0, [sp] (S.foo)
ldrx r0, [sp, #S.foo]
ldrx r0, [sp] (S.foo)
Need better distinction between parseDotStatement
and parseBlockStatement
ldr r0, =@foo
If @foo
doesn't exist, gvasm gives the error:
Incomplete statement, missing .pool statement to hold constant
which is confusing
Currently, .thumb
/.arm
is global. Instead, it should be mapped to the scope.
So this should work:
.arm
// ARM mode
.begin
.thumb
// Thumb mode
.end
// back to ARM mode
This doesn't work in v2, but should:
.script foo // blah
say 'hi'
.end
.script // blah
say 'hi'
.end
Tests that perform includes fail on windows due to path.join
using windows path separators.
// file1.gvasm
.def FOO = 1
// file2.gvasm
.import './file1.gvasm' { FOO }
.if FOO
.printf "Foo is true"
.else
.printf "Foo is false"
.end
Changing file1.gvasm FOO = 0
will not re-run file2.gvasm.
Something like this:
// file1.gvasm
.def start = 8
// file2.gvasm
.import './file1.gvasm' { start }
.base start
.i32 _here
Changing .def start = 16
should change file2.gvasm.
In gvasm v2, if you import/include a bad file, then an error is thrown instead of a CompError.
I think this is true inside scripts too.
Something like:
.struct $O
.uint scriptPC
.short originX
.short originY
.end
.struct $G = 0x03000000
.short somethingElse
.end
Then, this should generate a warning:
ldr r0, [base, #$O.originX]
str r0, [base, #$O.originX]
// or, much harder
ldr r1, =$G.somethingElse
ldr r0, [r1]
str r0, [r1]
Because I'm using ldr
instead of ldrh
.
What if macros were just:
.script
export foo = {
{ 'temp:reg', 'shift:int', 'save:regrange' }, // parameters
` // body
ldr $temp, =123
lsls $temp, #$shift
push $save
`
}
.end
// invoked via:
movs r1, #23
%foo r0, 10, {r0-r3}
Would be nice to do something like:
.script
i8 pack.huff pack.lz77 {1, 2, 3, 4, 5}
.end
and also:
.pack huff
.pack lz77
.i8 1, 2, 3, 4
.embed 'file.bin'
.end
.end
BIOS compression algorithms, according to GBATEK:
bit
diff
huff
lz77
rle
Also, maybe a best
option, which would try all of them, and pick whichever is smallest -- decompression works via checking the header.
required to do lsrs rX, rX, #value
, which is silly
These are good names, might as well use them instead of making our own:
https://github.com/LunarLambda/libseven/blob/main/doc/registers.md
lsls r4, #4
This should work in ARM
Asar has handy syntax for relative labels, described here:
https://ersanio.gitbook.io/assembly-for-the-snes/deep-dives/syntax
LDA $10
BEQ +
STA $11
+
RTS
The big feature for v3 should be a system for distributing libraries (sharing code).
I should be able to pull in a music library, or a saving library, and everything should be able to work easily.
One big element of that is iwram
/ ewram
allocation. Specifically:
.base
entirely.I would also like to have bug-free dependency management. That could mean a few specific things:
Looking at the Inky source code, only main.gvasm
has .include
statements. This might need to be required going forward. Specifically, I'm imagining everything is a "library" except the main file. The main file has global config, GBA header, and include order. Then, all libraries can access the global config, or something like that.
So maybe main cannot import, and can only include. Kind of like main is package.json
, and wires up packages and configuration, along with the ability to output some code so that short single-file ROMs can be made.
Maybe .gvmain
(entry point) and .gvasm
(libraries) or something.
// ./inky.gvmain
.begin header
.arm
b main
...
main:
.end
.lib '@src'
.include './src'
.end
.lib '@mem-1'
.include './lib/mem-1.0'
.end
.lib '@mem-2'
.include './lib/mem-2.0'
.end
.lib '@gvsong'
.def MAX_CHANNEL = 6
.def MAX_SFX = 3
.maplib '@mem' = '@mem-1'
.include './lib/gvsong-1.0'
.end
.lib '@gvsave'
.maplib '@mem' = '@mem-2'
.include './lib/gvsave-1.0'
.end
// ./lib/gvsong-1.0/gvsong.gvasm
.dep '@another'
.repo 'https://github.com/velipso/another'
.tag 'branch, tag, or hash'
// this gets constructed into a git submodule command:
// git submodule add -b <tag> <repo> lib/<repo_name>-<tag>
.end
.import './relative/import.gvasm' { asdf }
.import '@/this/library/root.gvasm' { asdf }
.import '@another/library/import.gvasm' { asfd }
This doesn't seem entirely correct... need to think through what it means when a library depends on another library, and how to wire up those versions correctly.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.