zig
zig copied to clipboard
Stage2 ARM Thumb Debug: LLD fails with "undefined symbol: __clzsi2"
Zig Version
0.10.0
Steps to Reproduce and Observed Behavior
When compiling embedded firmware targeting ARM Cortex M0+, using the stage2 compiler, in debug mode, the linker fails with this message:
LLD Link... ld.lld: error: undefined symbol: __clzsi2
>>> referenced by compiler_rt
>>> C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a.o:(__udivsi3) in archive C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a
>>> referenced by compiler_rt
>>> C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a.o:(__udivsi3) in archive C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a
>>> referenced by compiler_rt
>>> C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a.o:(__umodsi3) in archive C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a
>>> referenced 1 more times
>>> did you mean: __ctzsi2
>>> defined in: C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a(C:\...\zig\o\abc2490849d1b9cc45a64ab5470e2f9d\libcompiler_rt.a.o)
This doesn't happen when using any of the -Drelease-*
modes, or when using the stage1 compiler. The reason it doesn't happen in release modes may just be that all the integer division and modulo operations are optimized out. I'm not exactly sure where those are coming from, even in debug; the only division I can think of should be comptime only.
It looks like lib/compiler_rt/count0bits.zig
does have an implementation of __clzsi2
specifically for Thumb targets, but somehow the linker doesn't see it.
The specific CrossTarget being used is:
std.zig.CrossTarget{
.cpu_arch = .thumb,
.cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus },
.os_tag = .freestanding,
.abi = .none,
},
It can be reproduced by running zig build
after checking out this commit: https://github.com/bcrist/microbe/tree/0b7404760350ef3504d95eb1200d6900c8b2a374
Running zig build
again after the failure doesn't print the error again, it just triggers a FileNotFound error, but I think that's caused by a different issue: https://github.com/ziglang/zig/issues/13432
Deleting the zig-cache directory and building again shows the linker error again.
Expected Behavior
The linker should succeed and generate a valid ELF binary.
After a little further poking, I wondered if this might be related to lib/compiler_rt/count0bits.zig
declaring const __clzsi2 = switch(...) ...
where sometimes, it resolves to fn (i32) callconv(.C) i32
, but other times to fn (i32) callconv(.Naked) i32
. I copied the switch statement into the comptime block at the top and changed it to export the __clzsi2_thumb1
and __clzsi2_arm32
functions directly, when necessary:
switch (builtin.cpu.arch) {
.arm, .armeb, .thumb, .thumbeb => {
const use_thumb1 =
(builtin.cpu.arch.isThumb() or
std.Target.arm.featureSetHas(builtin.cpu.features, .noarm)) and
!std.Target.arm.featureSetHas(builtin.cpu.features, .thumb2);
if (use_thumb1) {
@export(__clzsi2_thumb1, .{ .name = "__clzsi2", .linkage = common.linkage });
}
// From here on we're either targeting Thumb2 or ARM.
else if (!builtin.cpu.arch.isThumb()) {
@export(__clzsi2_arm32, .{ .name = "__clzsi2", .linkage = common.linkage });
}
// Use the generic implementation otherwise.
else {
@export(clzsi2_generic, .{ .name = "__clzsi2", .linkage = common.linkage });
}
},
else => @export(clzsi2_generic, .{ .name = "__clzsi2", .linkage = common.linkage }),
}
Obviously, this isn't very clean, but I am able to successfully link a working executable in debug mode with this change. I found the new libcompiler_rt.a.o
and threw it into ghidra to verify that it contains the thumb1 version of __clzsi2 that uses a LUT. The old version just contained an extern thunk.
I don't know exactly what this means in terms of the root cause of this bug, but hopefully this helps.
On a side note, does anyone know why CPUs marked with the .thumb2
feature are excluded from using __clzsi2_thumb1
? My understanding was that Thumb2 was a superset of Thumb1 -- technically the STM32G0 chip I'm using is a ~~Thumb2 core,~~ Edit: apparently it's not Thumb2, just Thumb1 with "Thumb-2 Technology"
Based on your follow up I think this is caused by #13465
that's the same number as this one?
Meant #13706