Stack corruption between C/Zig boundary

This is a MCVE of an issue between C/Zig interaction that it is driving me crazy in the last couple of days. Please, let me know it if is worth a bug report in the ziglang/zig repository or if I’m missing something obvious.

It is triggered by a specific sequence of argument types I still have not fully identified.

zigc.h:

typedef struct {
    char padding[24];
} Pad24;

typedef struct {
    char padding[16];
} Pad16;

int cFunction(Pad24 a, Pad24 b, Pad24 c, Pad24 d, Pad24 e, Pad16 f, int sentinel);

zigc.c:

#include "zigc.h"

int cFunction(Pad24 a, Pad24 b, Pad24 c, Pad24 d, Pad24 e, Pad16 f, int sentinel)
{
    return sentinel;
}

zigc.zig:

const std = @import("std");
const c = @cImport({
    @cInclude("zigc.h");
});

pub fn main() !void {
    const pad24 = std.mem.zeroes(c.Pad24);
    const pad16 = std.mem.zeroes(c.Pad16);

    const expected: c_int = 1234;
    const actual = c.cFunction(pad24, pad24, pad24, pad24, pad24, pad16, expected);
    // On my x86_64-pc-linux-gnu, I get "Expected: 1234, actual 0"
    std.debug.print("Expected: {d}, actual {d}\n", .{ expected, actual });
}

How do you compile this?

This build.zig should work. After a zig build, on my OS ./zig-out/bin/zigc triggers the issue.

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const zigc = b.addExecutable(.{
        .name = "zigc",
        .root_source_file = b.path("zigc.zig"),
        .target = target,
        .optimize = optimize,
    });
    zigc.addCSourceFile(.{ .file = b.path("zigc.c") });
    zigc.addAfterIncludePath(b.path("."));
    b.installArtifact(zigc);
}

From a cursory look at the assembly it seems like the immediate value is placed in %edi but then the C function reads it from %edx. Possibly a bug (with Zig and Clang having different expectations on the ABI here?).

2 Likes

It actually depends on the number of parameters passed. With 6 parameters everything works fine, with 7 it doesn’t. I would also say that this is a bug.

1 Like

Thank you for the feedback: link to the bug report.

4 Likes