Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zig emits malformed load for struct of two fields in 6th parameter position for C ABI #18916

Closed
mpfaff opened this issue Feb 13, 2024 · 2 comments · Fixed by #19018
Closed

Zig emits malformed load for struct of two fields in 6th parameter position for C ABI #18916

mpfaff opened this issue Feb 13, 2024 · 2 comments · Fixed by #19018
Labels
arch-x86_64 backend-llvm The LLVM backend outputs an LLVM IR Module. bug Observed behavior contradicts documented or intended behavior miscompilation The compiler reports success but produces semantically incorrect code.
Milestone

Comments

@mpfaff
Copy link
Contributor

mpfaff commented Feb 13, 2024

Zig Version

0.9.0, 0.10.0, 0.11.0, trunk, 0.12.0-dev.2710+c3eb592a3

Steps to Reproduce and Observed Behavior

View this reproduction on Godbolt/Compiler Explorer. It has equivalent code in C (GCC and Clang), Zig, and Rust.
The Zig compiler produces a different load for the f parameter than the other compilers. I have not checked the spec to confirm, but it seems likely that Zig, being the outlier, is the one emitting the incorrect load.

In my testing (positions in the range [1, 8]), it is critical for the f parameter to be in the 6th position. Any higher or lower and the issue goes away.

This issue does not present itself when testing with a usize parameter type in either the 6th or 7th parameter positions. Interestingly, from that testing, it looks like Zig might be trying to decompose Foo to its components (usize, usize) and treat each as its own parameter with respect to the ABI:

Foo (2 usize fields) as 6th parameter (differs from GCC)

Zig output (you may notice that these loads line up with the loads emitted for a usize 6th and 7th parameter, shown in the other spoilers):

my_func:
 mov    rsi,QWORD PTR [rsp+0x8]
 mov    rdi,r9
 jmp    d <my_func+0xd>

GCC output:

my_func:
        mov     rdi, QWORD PTR [rsp+8]
        mov     rsi, QWORD PTR [rsp+16]
        jmp     use_f
Foo (1 usize field) as 6th parameter (same as GCC)

Zig output:

my_func:
 mov    rdi,r9
 jmp    8 <my_func+0x8>

GCC output:

my_func:
        mov     rdi, r9
        jmp     use_f
Foo (1 usize field) as 7th parameter (same as GCC)

Zig output:

my_func:
 mov    rdi,QWORD PTR [rsp+0x8]
 jmp    a <my_func+0xa>

GCC output:

my_func:
        mov     rdi, QWORD PTR [rsp+8]
        jmp     use_f

Expected Behavior

Zig emits a correct load.

If you require more information, or if there is anything I can do to help investigate the cause, I would be happy to help.

@mpfaff mpfaff added the bug Observed behavior contradicts documented or intended behavior label Feb 13, 2024
@ifreund ifreund added miscompilation The compiler reports success but produces semantically incorrect code. backend-llvm The LLVM backend outputs an LLVM IR Module. arch-x86_64 labels Feb 13, 2024
@ifreund ifreund added this to the 0.12.0 milestone Feb 13, 2024
@mpfaff
Copy link
Contributor Author

mpfaff commented Feb 13, 2024

I just checked the SystemV ABI spec, and this section stood out to me:

The classification of aggregate (structures and arrays) and union types works as fol-
lows:
...
4. Each field of an object is classified recursively so that always two fields 18 are con-
sidered. The resulting class is calculated according to the classes of the fields in the
eightbyte:
...
5. Then a post merger cleanup is done:
(a) If one of the classes is MEMORY, the whole argument is passed in memory

It sounds to me like Zig is not following step 5, while the other compilers are.

@Vexu
Copy link
Member

Vexu commented Feb 14, 2024

Clang LLVM IR:

; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local void @my_func(i64 noundef %0, i64 noundef %1, i64 noundef %2, i64 noundef %3, i64 noundef %4, ptr noundef byval(%struct.Foo) align 8 %5) #0 {
  ...
  %12 = getelementptr inbounds { i64, i64 }, ptr %5, i32 0, i32 0
  %13 = load i64, ptr %12, align 8
  %14 = getelementptr inbounds { i64, i64 }, ptr %5, i32 0, i32 1
  %15 = load i64, ptr %14, align 8
  call void @use_f(i64 %13, i64 %15)
  ret void
}

Zig LLVM IR:

; Function Attrs: nounwind uwtable
define dso_local void @my_func(i64 %0, i64 %1, i64 %2, i64 %3, i64 %4, i64 %5, i64 %6) #0 {
  %8 = alloca %a.Foo, align 8
  ...
  call void @use_f(i64 %12, i64 %14)
  ret void
}

Zig should not split structs into fields after a certain amount of fields.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-x86_64 backend-llvm The LLVM backend outputs an LLVM IR Module. bug Observed behavior contradicts documented or intended behavior miscompilation The compiler reports success but produces semantically incorrect code.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants