Going from Zig to C

Hi …

I’m wanting to test out writing in Zig for a system that only has a C compiler.
But first, I want to test converting Zig to C.

Simple example code:

pub fn sum(a: i32, b: i32) i32 {
    return a + b;
}

pub fn main() void {
    const result = sum(2, 3);
    @import("std").debug.print("Sum is {d}\n", .{result});
}

I converted to C with this command:
zig build-obj -ofmt=c -target x86_64-linux ./src/main.zig

It produces a very large main.c file.

I attempt to compile the file using
zig cc -o main main.c -I"C:\Program Files\zig\lib"

but I get the following error

main.c:7373:2: error: non-ASM statement in naked function is not supported
 7373 |  goto zig_block_0;
      |  ^
main.c:7365:1: note: attribute is here
 7365 | zig_naked zig_noreturn void start__start__119(void) {
      | ^
C:\Program Files\zig\lib/zig.h:176:34: note: expanded from macro 'zig_naked'
  176 | #define zig_naked __attribute__((naked))
      |                                  ^
1 error generated.

I am unsure how to

  1. reduce the size of the output file
  2. actually compile the output file

I am using zig version 0.14.0-dev.2571+01081cc8e on Windows 10.

Any help would be appreciated.

1 Like

What first comes to mind is that functions that should be exposed to a C interface should be defined with export fn, not pub fn. I’m not sure if that solves your problem, though.

Also, with -O ReleaseSmall (or ReleaseFast) the size of the C file will be much smaller.

1 Like

I’ve just tried this myself (0.14.0-dev.2545+e2e363361/macos aarch64). I think the problem may be that you’re specifying a target (you look like you’re building on Windows, but asking the output C to be for x86_64-linux).

zig build-obj -ofmt=c src/main.zig
zig cc -o main main.c -I...
./main
Sum is 5

Thank you both @mkrieger1 and @tobyjaffey for help

First, -O ReleaseSmall helps make it from 123000 lines to 4000. Thanks for that.

Second, I hopped over to my Ubuntu WSL instance with zig version 0.14.0-dev.2639+15fe99957 to try to compile there.

zig build-obj -ofmt=c ./src/main.zig
zig cc -o main main.c -I/home/draco/zig/lib

ld.lld: error: duplicate symbol: _start
>>> defined at /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crt1.o:(_start)
>>> defined at main.c:794
>>>            /mnt/c/Users/Draco/Documents/Projects/zig/zig-c/.zig-cache/o/e23adaa98d1cff462ecd7bdc927a78e1/main.o:(.text+0x0)

There are two ways you can solve the duplicate _start error. The issue is that, the way you’re currently doing it, both Zig and the C standard library/runtime are trying to provide their own executable entry point (_start) and conflicting with each other.

  1. Tell Zig to link libc when building the initial C output file (add -lc to the first command):

    $ zig build-obj -ofmt=c -lc main.zig
    $ zig cc -o main main.c -I$HOME/src/zig/lib
    $ ./main
    Sum is 5
    

    This also fixes the issue from your original message, which appears to be present in the code Zig is generating for its _start implementation. Given that fact, I’d recommend going with this option.

  2. Tell the C compiler not to use the standard library when building the executable from the C output file (add -nostdlib to the second command):

    $ zig build-obj -ofmt=c -OReleaseSmall main.zig
    $ zig cc -o main main.c -I$HOME/src/zig/lib -nostdlib
    $ ./main
    Sum is 5
    

Now, given you’re using zig build-obj and not zig build-exe, I’m not entirely sure of the rationale for Zig still exporting _start (that is, the reason for the @hasDecl(root, "main") check here: zig/lib/std/start.zig at fb43e91b226a9cde51967455c57989c0371d4b0a · ziglang/zig · GitHub). If that condition weren’t there, then I would expect it to work fine just by adding export to your main function.

3 Likes