ZigGBA
ZigGBA copied to clipboard
The `_start` function in `gba.zig` no longer compiles correctly, as of sometime between `2024.10.0-mach` and `0.14.0` compiler releases.
This issue relates to the same Discord discussion as #26. (Thank you Sono on the gbadev Discord for being a major help in diagnosing this problem!) https://discord.com/channels/768759024270704641/777683711453298698/1378782104212607176
The problem: ROMs compile with 2024.10.0-mach and run on emulators as expected. With stable 0.14.0 and later, all tested emulators except NanoBoyAdvance fail to run any compiled ROM.
The inline assembly from zig.gba here seems to be the culprit:
export fn _start() noreturn {
// Assembly init code
asm volatile (
\\.arm
\\.cpu arm7tdmi
\\mov r0, #0x4000000
\\str r0, [r0, #0x208]
\\
\\mov r0, #0x12
\\msr cpsr, r0
\\ldr sp, =__sp_irq
\\mov r0, #0x1f
\\msr cpsr, r0
\\ldr sp, =__sp_usr
\\add r0, pc, #1
\\bx r0
);
// Use BIOS function to clear all data
bios.resetRamRegisters(bios.RamResetFlags.initFull());
// Clear .bss
mem.memset32(&__bss_start__, 0, @intFromPtr(&__bss_end__) - @intFromPtr(&__bss_start__));
// Copy .data section to EWRAM
mem.memcpy32(&__data_start__, &__data_lma, @intFromPtr(&__data_end__) - @intFromPtr(&__data_start__));
// call user's main
if (@hasDecl(root, "main")) {
root.main();
}
while (true) {}
}
The rationale behind this approach is documented here: https://martincirice.com/posts/mygbarom/#switching-from-arm-to-thumb
With 2024.10.0-mach, this compiles as a valid program entry point using ARM instructions and then continuing with Thumb instructions in the following function body. Here's what it looks like in the mesen debugger:
As of some release between that and stable 0.14.0, it seems that this block with the .arm directive to write ARM instructions as opposed to Thumb instructions does not function the same way. The instructions are incidentally misinterpreted as SWI (software interrupt) instruction which triggers the Stop Mode interrupt, which then writes to the 0x4000301 HALTCNT Low Power Mode Control register. And in a well-behaved emulator this causes execution of the ROM to immediately stop.
Here's the same section of ROM in the mesen debugger when compiling with 0.41.0:
Which then transfers control flow to this BIOS interrupt handler:
I've been doing some digging and so far have not figured out exactly what changed that the .arm directive no longer seems to be recognized, or what else should be used in its place?
Adding partly for my own reference as I continue to puzzle through this:
The inline assembly resembles that of what is documented here as the official crt0.s (startup routine provided by Nintendo)
https://feuniverse.us/t/gba-start-routine-and-global-variables-initialization/4902
@--------------------------------------------------------------------
@- Reset -
@--------------------------------------------------------------------
.EXTERN AgbMain
.GLOBAL start_vector
.CODE 32
start_vector:
mov r0, #PSR_IRQ_MODE @ Switch to IRQ Mode
msr cpsr, r0
ldr sp, sp_irq @ Set SP_irq
mov r0, #PSR_SYS_MODE @ Switch to System Mode
msr cpsr, r0
ldr sp, sp_usr @ Set SP_usr
ldr r1, =INTR_VECTOR_BUF @ Set Interrupt Address
adr r0, intr_main
str r0, [r1]
ldr r1, =AgbMain @ Start & Switch to 16bit Code
mov lr, pc
bx r1
b start_vector @ Reset
.ALIGN
sp_usr: .word WRAM_END - 0x100
sp_irq: .word WRAM_END - 0x60
I've been poking into the possibility of either: 1. Keeping this entry point in a separate assembly file and figuring out how to link with it or 2. Figuring out how to have two separate zig compilation units, one ARM and one thumb, and moving the inline assembly block to the ARM compilation unit. Nothing really to show for this yet though.
I've been poking into the possibility of either: 1. Keeping this entry point in a separate assembly file and figuring out how to link with it or 2. Figuring out how to have two separate zig compilation units, one ARM and one thumb, and moving the inline assembly block to the ARM compilation unit.
do I understand correctly - you only want the asm to be linked to the arm output? if so, then might I suggest this?
if (@import("builtin").target.cpu.arch == .arm)
asm volatile (
...
);
since builtin.target.cpu.arch is resolved at compile-time, then the inline assembly won't be included in the compiled output unless the compiler is targeting an arm triple. you can force evaluation of the condition at compile time with if (comptime @import...). this should also work with a switch ie switch (comptime @import...) { .arm => asm volatile ( ... ), else => {} }
@dotcarmen I think this is unrelated, but I could just be missing context?
Currently with this project, all Zig code is compiled to 16-bit Thumb instructions, since this is preferable in most cases to compiling to 32-bit ARM instructions.
(The exception is functions that would be compiled to 32-bit ARM, copied during runtime into the system's limited IWRAM, and then called there. This has a performance advantage over the 16-bit Thumb code, though at the cost of the limited IWRAM. But this hasn't been implemented yet for ZigGBA.)
The separate problem that this issue is related to is that, while it's preferable to compile code in general to 16-bit Thumb, the system requires that the entry point be 32-bit ARM. Hence the need for inline 32-bit ARM asm in a compilation unit that is otherwise targeting 16-bit Thumb. I don't think that making the inline asm conditional by triple would help with this?
ohhhh I see (btw sorry for the drive-by comment lol - went on a zig issue tracker rabbit hole and found this issue linked, so i took a look)
doing some more glancing, it doesn't seem the thumb2 feature is enabled in your compiler config - you enable thumb_mode which is the inverse of thumb2 (thumb_mode enables thumb ISA on ARM targets, thumb2 enables ARM ISA on thumb targets)
i'm not sure if that'll fix your problem, that's just a suggestion i have from some more glancing. I'm working on another project atm but if i have time later i can help you investigate this issue :)
I don't think that's what Thumb-2 is? From what I'm reading, Thumb-2 seems to just refer to the addition of more 32-bit instructions besides bl and blx to the (fundamentally 16-bit) Thumb instruction set. And besides which I'm not entirely certain that the GBA CPU supports Thumb-2. (I can't find any clear mention one way or the other, but I do notice that the Thumb-2 variable-length decoding is conspicuously missing from this seemingly comprehensive resource here: https://www.gregorygaines.com/blog/decoding-the-arm7tdmi-instruction-set-game-boy-advance/ )
(I also found this link helpful: https://stackoverflow.com/questions/28669905/what-is-the-difference-between-the-arm-thumb-and-thumb-2-instruction-encodings )
I did try adding a line target.cpu_features_add.addFeature(@intFromEnum(std.Target.arm.Feature.thumb2)); to builder.zig to see what would happen, for what it's worth. It resulted in this compilation error:
install
└─ zig build-exe main ReleaseFast thumb-freestanding
└─ zig build-lib ZigGBA ReleaseFast thumb-freestanding failure
error: LLVM ERROR: Cannot select: 0x25654bdf250: ch = br 0x25654bdf020, BasicBlock:ch<Then.i 0x25654bd9978>, mem.zig:136:18 @[ gba.zig:144:17 ]
In function: _start
Ah, here's something more simple and definitive: The GBA ISA is ARMv4T. Thumb-2 was not introduced until ARMv6T2.
https://felixjones.github.io/gba-tutorial/01_introduction/architecture.html
https://s-o-c.org/differences-between-thumb-and-thumb2-instruction-sets/
- Keeping this entry point in a separate assembly file and figuring out how to link with it
Hi! I'm starting to learn GBA development and got a fork of this project working with zig 0.14.1 by doing this approach. All examples compile and run in mGBA. I basically replaced the linker script, then added the assembly file with the Zig build system.
Feel free to check it out if you go this route. I am also open to upstreaming changes!
Thank you for sharing this @braheezy! I think you'd be welcome to submit a PR, though I think ideally the specific parts needed to link with the assembly entry point would be taken more selectively rather than replacing the linker script wholly with one meant for C and C++ - if you're up for it then go ahead, otherwise I may try to make those tweaks and put together a similar PR myself
Noting for reference that the linker script and assembly entry point look to be originally from this repo: https://github.com/AntonioND/gba-bootstrap
https://github.com/AntonioND/gba-bootstrap/blob/master/template_c/source/sys/gba_cart.ld
https://github.com/AntonioND/gba-bootstrap/blob/master/template_c/source/sys/gba_crt0.s
... I couldn't help myself, I was too curious, and did it anyway. Thank you very much @braheezy for figuring a fix out - I took what you put together and essentially picked out only the strictly necessary changes to compile in 0.14+
Other changes can be considered separately, I think? e.g. I didn't understand the reason for embedding the asm and linker script in builder.zig? It also was unclear to me whether ZigGBA would benefit from some parts that got roped in from gba-bootstrap such as the multiboot header and the C/C++ stack unwinding section.
https://github.com/pineapplemachine/ZigGBA/tree/jesu-demo-zig-0.14
The most important changes are in this commit: https://github.com/wendigojaeger/ZigGBA/commit/cdc95a86bbe0649c568fc9ff99cb296af9ed291c
I did this work in the jesu example branch because it would have been a big pain to do it separately and then resolve conflicts. @vortexofdoom I'll plan to update that branch shortly with your suggested changes and make a new PR to supersede #29 (apart from the one we were still discussing). Since there's some new interest in ZigGBA recently maybe we can try to get that sorted out in the near-ish future?
... I couldn't help myself, I was too curious, and did it anyway.
Very cool, love to see the enthusiasm!
But yeah I prefer your commit, it's more focused and not blindly using the C/C++ linker script is better. The other edits you see in mine, like using addWriteFiles to embed the files, is so I can use this library with the Zig build system instead of cloning it locally. That is definitely a different topic and with ImageConverter I'm not sure it's possible yet. That's the next part I'm going to research.
so I can use this library with the Zig build system instead of cloning it locally.
I'm pretty new to Zig myself, and I don't actually know the difference. Would you explain more what you mean?
with ImageConverter I'm not sure it's possible yet. That's the next part I'm going to research.
In my own project I've actually been writing pipeline tools in Python rather than Zig, after initially doing images with Zig in #29 and not loving incorporating asset conversion into zig build as a single monolithic step, not loving that dependency management doesn't work as one would hope for build time dependencies, and also not loving the ergonomics of Zig for things like image processing. Possibly that might be an option for pipeline tooling in this repo in the future? Though I don't know if that would make the issue you're describing easier or harder to deal with.
In the Zig projects I've worked on, it's been most ergonomic to obtain dependencies with a workflow like this:
- Run this command in your project folder to add
zigimgto yourbuild.zig.zon
zig fetch --save git+https://github.com/zigimg/zigimg.git
- Get the module in your
build.zigfile
const zigimg_dependency = b.dependency("zigimg", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zigimg", zigimg_dependency.module("zigimg"));
The current trunk of ZigGBA has no build.zig.zon and with the git submodule usage, makes it impossible to consume the ziggy way. This challenge is acknowledged in #21.
I don't think the official package manager existed when this project started.
@braheezy Oh I don't mean using zigimg as a runtime dependency, I mean using it as a build-time dependency. I believe it's entirely possible to have Zig grab and manage a zigimg dependency automatically by supplying the GitHub repo link - it just won't be available at build time, hence why this repo uses a git submodule instead of a dependency in build.zig.zon. (Or at least, why it still does so in #32.)