Skip to content

Incorrect SystemV AMD64 ABI call when using aggregate types as parameters #22515

@mhcerri

Description

@mhcerri

Zig Version

0.14.0-dev.2649+77273103a

Steps to Reproduce and Observed Behavior

The zig compiler is generating wrong C ABI calls for amd64 linux when invoking functions with struct arguments.

Given the following source files:

main.c:

const std = @import("std");
const a = @import("a.zig");

pub fn main() !void {
    std.debug.print("begin\n", .{});

    const p1: ?*anyopaque = @ptrFromInt(1);
    const v1: a.vec = .{ .a = 2.0, .b = 3.0 };
    const v2: a.vec = .{ .a = 4.0, .b = 5.0 };
    const f1: f64 = 6.0;
    const s1: a.structure = .{ .a = 7, .b = 8, .c = 9 };
    const p2: ?*anyopaque = @ptrFromInt(10);
    const p3: ?*anyopaque = @ptrFromInt(11);

    a.func(p1, v1, v2, f1, s1, p2, p3);

    std.debug.print("end\n", .{});
}

a.h:

#include <stdint.h>
typedef struct { double a, b; } vec;
typedef struct { uintptr_t a; unsigned int b, c; } structure;
void func(void* p1, vec v1, vec v2, double f1, structure s1, void* p2, void* p3);

a.c:

#include <stdio.h>
#include "a.h"

void func(void *p1, vec v1, vec v2, double f1, structure s1, void *p2, void *p3) {
    printf("p1=%p\n", p1);
    printf("v1.a=%f\n", v1.a);
    printf("v1.b=%f\n", v1.b);
    printf("v2.a=%f\n", v2.a);
    printf("v2.b=%f\n", v2.b);
    printf("f1=%f\n", f1);
    printf("s1.a=%lu\n", s1.a);
    printf("s1.b=%u\n", s1.b);
    printf("s1.c=%u\n", s1.c);
    printf("p2=%p\n", p2);
    printf("p3=%p\n", p3);
}

The following commands produce:

$ zig translate-c ./a.h > ./a.zig
$ zig run ./main.zig ./a.c -lc
begin
p1=0x1
v1.a=2.000000
v1.b=3.000000
v2.a=4.000000
v2.b=5.000000
f1=6.000000
s1.a=10
s1.b=11
s1.c=0
p2=0x7
p3=0x6
end

However the expected output would be:

p1=0x1
v1.a=2.000000
v1.b=3.000000
v2.a=4.000000
v2.b=5.000000
f1=6.000000
s1.a=7
s1.b=8
s1.c=9
p2=0xa
p3=0xb

Since structure has 16 bytes, s1 should be passed via 2 separate 64 bit registers. However the zig compiler seems to be passing s1 via the stack instead. In practice, that causes the s1 fields in the C function to receive the values passed as p2 and p3 .

If I add an additional field to the structure, forcing it to be bigger than 16 bytes, then the SystemV amd64 ABI expects that argument to be passed via the stack and the program works fine.

You can find more information about aggregate types in the SystemV amd64 ABI in the following article:

https://yorickpeterse.com/articles/the-mess-that-is-handling-structure-arguments-and-returns-in-llvm/

Expected Behavior

Expected output:

p1=0x1
v1.a=2.000000
v1.b=3.000000
v2.a=4.000000
v2.b=5.000000
f1=6.000000
s1.a=7
s1.b=8
s1.c=9
p2=0xa
p3=0xb

The 3 fields from the structure should be passed to the C function via 2 separate 64 bit registers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    arch-x86_6464-bit x86backend-llvmThe LLVM backend outputs an LLVM IR Module.bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions