Zig Version
0.17.0-dev.955+9aa93a045
Steps to Reproduce, Observed Behavior, and Expected Behavior
Create the following files
repro.zig:
test{if(hiArg(0xffffffff)!=123)returnerror.TestExpectedEqual;}noinlinefnhiArg(_:u32)callconv(.c)u32{returnextend(123);}externfnextend(u16)u32;
repro.c:
#include <inttypes.h>
uint32_t extend(uint16_t x) {
return x;
}
Run zig test -target x86_64-linux -fno-llvm repro.zig -cflags -std=c99 -O -- repro.c -lc and observe the test fail.
The cause is that the self-hosted x86-64 backend writes the argument only to the lower bits of the register, leaving the higher bits untouched, however, LLVM assumes the value is extended.
I expect the test to succeed.
Additionally, here is a diff for a more extensive testcase I wrote up while debugging this:
diff --git a/test/c_abi/cfuncs.c b/test/c_abi/cfuncs.c
index 302503ad4d..0d61d35523 100644
--- a/test/c_abi/cfuncs.c
+++ b/test/c_abi/cfuncs.c
@@ -16226,3 +16226,17 @@ void c_win64_varargs_f64_u64_f64_u64(double a, uint64_t b, double c, uint64_t d)
assert_or_panic(d == UINT64_C(0x4020000000000000));
}
#endif
+
+void zig_extend32_hi_params(uint32_t, uint32_t, uint32_t, uint32_t,
+ uint32_t, uint32_t, uint32_t, uint32_t);
+void zig_extend32(int32_t, uint32_t, int32_t, uint32_t,
+ int32_t, uint32_t, int32_t, uint32_t);
+
+void c_extend32_hi_params() {
+ zig_extend32_hi_params(0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff);
+}
+void c_extend32(int8_t a, uint8_t b, int16_t c, uint16_t d,
+ int8_t e, uint8_t f, int16_t g, uint16_t h) {
+ zig_extend32(a, b, c, d, e, f, g, h);
+}
diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig
index 5184d0458f..7f6cc39067 100644
--- a/test/c_abi/main.zig
+++ b/test/c_abi/main.zig
@@ -17295,3 +17295,29 @@ test "win64 varargs" {
@as(Opv, .{}),
);
}
+
+extern fn c_extend32_hi_params() void;
+extern fn c_extend32(i8, u8, i16, u16, i8, u8, i16, u16) void;
+
+test "32-bit extension" {
+ // C will call zig_extend32_hi_params with the high bits of the registers set.
+ c_extend32_hi_params();
+}
+
+export fn zig_extend32_hi_params(_: u32, _: u32, _: u32, _: u32, _: u32, _: u32, _: u32, _: u32) void {
+ // C will call zig_extend32 with these 32-bit extended. The C compiler should treat the
+ // extensions as no-ops. The values are not compared in their unextended form in C since
+ // the C compiler is likely to only compare partial registers.
+ c_extend32(-2, 0x80, -3, 0xff81, 4, 0x82, 5, 0xff83);
+}
+
+export fn zig_extend32(a: i32, b: u32, c: i32, d: u32, e: i32, f: u32, g: i32, h: u32) void {
+ expect(a == -2) catch @panic("test failure: zig_extend32");
+ expect(b == 0x80) catch @panic("test failure: zig_extend32");
+ expect(c == -3) catch @panic("test failure: zig_extend32");
+ expect(d == 0xff81) catch @panic("test failure: zig_extend32");
+ expect(e == 4) catch @panic("test failure: zig_extend32");
+ expect(f == 0x82) catch @panic("test failure: zig_extend32");
+ expect(g == 5) catch @panic("test failure: zig_extend32");
+ expect(h == 0xff8e) catch @panic("test failure: zig_extend32");
+}
### Zig Version
0.17.0-dev.955+9aa93a045
### Steps to Reproduce, Observed Behavior, and Expected Behavior
Create the following files
`repro.zig`:
```zig
test {
if (hiArg(0xffffffff) != 123)
return error.TestExpectedEqual;
}
noinline fn hiArg(_: u32) callconv(.c) u32 {
return extend(123);
}
extern fn extend(u16) u32;
```
`repro.c`:
```c
#include <inttypes.h>
uint32_t extend(uint16_t x) {
return x;
}
```
Run `zig test -target x86_64-linux -fno-llvm repro.zig -cflags -std=c99 -O -- repro.c -lc` and observe the test fail.
The cause is that the self-hosted x86-64 backend writes the argument only to the lower bits of the register, leaving the higher bits untouched, however, LLVM assumes the value is extended.
I expect the test to succeed.
Additionally, here is a diff for a more extensive testcase I wrote up while debugging this:
```diff
diff --git a/test/c_abi/cfuncs.c b/test/c_abi/cfuncs.c
index 302503ad4d..0d61d35523 100644
--- a/test/c_abi/cfuncs.c
+++ b/test/c_abi/cfuncs.c
@@ -16226,3 +16226,17 @@ void c_win64_varargs_f64_u64_f64_u64(double a, uint64_t b, double c, uint64_t d)
assert_or_panic(d == UINT64_C(0x4020000000000000));
}
#endif
+
+void zig_extend32_hi_params(uint32_t, uint32_t, uint32_t, uint32_t,
+ uint32_t, uint32_t, uint32_t, uint32_t);
+void zig_extend32(int32_t, uint32_t, int32_t, uint32_t,
+ int32_t, uint32_t, int32_t, uint32_t);
+
+void c_extend32_hi_params() {
+ zig_extend32_hi_params(0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff);
+}
+void c_extend32(int8_t a, uint8_t b, int16_t c, uint16_t d,
+ int8_t e, uint8_t f, int16_t g, uint16_t h) {
+ zig_extend32(a, b, c, d, e, f, g, h);
+}
diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig
index 5184d0458f..7f6cc39067 100644
--- a/test/c_abi/main.zig
+++ b/test/c_abi/main.zig
@@ -17295,3 +17295,29 @@ test "win64 varargs" {
@as(Opv, .{}),
);
}
+
+extern fn c_extend32_hi_params() void;
+extern fn c_extend32(i8, u8, i16, u16, i8, u8, i16, u16) void;
+
+test "32-bit extension" {
+ // C will call zig_extend32_hi_params with the high bits of the registers set.
+ c_extend32_hi_params();
+}
+
+export fn zig_extend32_hi_params(_: u32, _: u32, _: u32, _: u32, _: u32, _: u32, _: u32, _: u32) void {
+ // C will call zig_extend32 with these 32-bit extended. The C compiler should treat the
+ // extensions as no-ops. The values are not compared in their unextended form in C since
+ // the C compiler is likely to only compare partial registers.
+ c_extend32(-2, 0x80, -3, 0xff81, 4, 0x82, 5, 0xff83);
+}
+
+export fn zig_extend32(a: i32, b: u32, c: i32, d: u32, e: i32, f: u32, g: i32, h: u32) void {
+ expect(a == -2) catch @panic("test failure: zig_extend32");
+ expect(b == 0x80) catch @panic("test failure: zig_extend32");
+ expect(c == -3) catch @panic("test failure: zig_extend32");
+ expect(d == 0xff81) catch @panic("test failure: zig_extend32");
+ expect(e == 4) catch @panic("test failure: zig_extend32");
+ expect(f == 0x82) catch @panic("test failure: zig_extend32");
+ expect(g == 5) catch @panic("test failure: zig_extend32");
+ expect(h == 0xff8e) catch @panic("test failure: zig_extend32");
+}
```