# How to use Compiler Explorer with Zig

**URL:** <https://ziggit.dev/t/how-to-use-compiler-explorer-with-zig/4601>\
**Category:** Docs\
**Tags:** tools\
**Created:** [June 6, 2024, 8:52pm UTC](https://ziggit.dev/t/how-to-use-compiler-explorer-with-zig/4601 "2024-06-06T20:52:31Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Eisenhauer](https://ziggit.dev/letter_avatar_proxy/v4/letter/e/76d3ee/32.png) [@Eisenhauer](https://ziggit.dev/u/Eisenhauer)\
**Post date:** [June 6, 2024, 8:52pm UTC](https://ziggit.dev/t/how-to-use-compiler-explorer-with-zig/4601/1 "2024-06-06T20:52:31Z")

</div>

# TLDR

- [https://zig.godbolt.org](https://zig.godbolt.org)
- Use `-O ReleaseFast` instead of `-Doptimize=ReleaseFast`
- `export fn` orelse `pub fn main()`

# What is Compiler Explorer?

To quote from Compiler Explorers [Readme.md](https://github.com/compiler-explorer/compiler-explorer): “Compiler Explorer is an interactive compiler exploration website. Edit code in […] 30+ supported languages […], and see how that code looks after being compiled in real time.”

# Start with Zig on Compiler Explorer

For a quick start, head over to [https://zig.godbolt.org](https://zig.godbolt.org). By default you will see two windows. On the left the default Zig example code

```zig
// Type your code here, or load an example.
export fn square(num: i32) i32 {
    return num * num;
}

```

and on the right the resulting assembly (shortened for brevity):

```zig
square:
        push rbp
        mov rbp, rsp
        sub rsp, 16
        mov dword ptr [rbp - 4], edi
        imul edi, edi
        mov dword ptr [rbp - 8], edi
        seto al
        jo .LBB0_1
        jmp .LBB0_2
.LBB0_1:
        movabs rdi, offset __anon_1381
        mov esi, 16
        xor eax, eax
        mov edx, eax
        movabs rcx, offset .L__unnamed_1
        call example.panic
.LBB0_2:
        mov eax, dword ptr [rbp - 8]
        add rsp, 16
        pop rbp
        ret

example.panic:
        push rbp
        mov rbp, rsp
        sub rsp, 32
[...]

```

In case the default example changed since writing this, try [this short URL](https://zig.godbolt.org/z/Y5K5E8Wda).

Note the function name `square` appearing as the label `square:` right at the beginning of its assembly code.

Looking at the source you might wonder why the function is introduced with the `export` keyword. Try to remove it! You will notice the assembly window becomes empty. Why is that? Our example code does not contain any use of the function `square`, so it is omitted in the assembly.

# Ways to make the compiler emit assembly

The `export` keyword states the intent to make the function part of a [C Library](https://ziglang.org/documentation/0.12.0/#Exporting-a-C-Library). The compiler will then generate assembly for it, even if it can not see any use of the function.

There is a drwaback to use the `export` keyword: The function is required to have a signature compatible with C. So no slices, optionals, error unions and other Zig features may appear in exported signatures. In easy cases it is possible to use small adapter functions with C compatible signatures which in turn call the function of interest. Slice parameters can be adapted to a C Pointer and a length, like in [this example](https://zig.godbolt.org/z/9snfTfn9v):

```zig
export fn sumArrayC(array: [*]const u32, len: usize) u32 {
    return sumArrayZig(array[0..len]);
}

pub fn sumArrayZig(slice: []const u32) u32 {
    var sum: u32 = 0;
    for (slice) |item| {
        sum += item;
    }
    return sum;
}

```

If this approach becomes impractical, the next thing to do is to include a `main` function. This way we can analyze whole Zig programs and not only single functions. [Doing this for the previous example](https://zig.godbolt.org/z/ET39xanWa) shows why we might want to stick to the `export` approach as long as that is feasible.

```zig
pub fn main() void {
    const sum = sumArrayZig(&.{1, 2, 3, 4, 5});
    _ = sum;
}

pub fn sumArrayZig(slice: []const u32) u32 {
    var sum: u32 = 0;
    for (slice) |item| {
        sum += item;
    }
    return sum;
}

```

The assembly window now contains well over 100.000 lines, including loads of infrastructure code! Luckily the usual search key combo `<Ctrl> + f` comes to the rescue. Enter the name of the function of interest, here `sumArrayZig`, and hit the Enter key a few times:

```zig
[...]
767 mov esi, 5
768 call example.sumArrayZig
769 mov dword ptr [rbp - 4], eax
[...]
2470  
2471 example.sumArrayZig:
2472 push rbp
2473 mov rbp, rsp
[...]

```

On line 768 we find the call of `sumArrayZig` in `main` and further down on line 2471 the function we are looking for begins.

# Choose different build modes

By default the Zig toolchain uses the [build mode](https://ziglang.org/documentation/0.12.0/#toc-Build-Mode) `Debug`. If we want to see what assembly gets generated for other build modes, we have to explicitly tell the compiler on the command line. On Compiler Explorer we do this by entering the build mode into the text input `Compiler options...`.

Let us return to our initial `square` example with the default build mode set explicitly: [`-O Debug`](https://zig.godbolt.org/z/Wvc9x9cvc). What does our `square` function do again?

```zig
export fn square(num: i32) i32 {
    return num * num;
}

```

For one, the obvious thing: one multiplication. But remember, Zig has safety checks for arithmetic overflow. Can we find both things in the assembly?

```zig
 1 square:
 2 push rbp
 3 mov rbp, rsp
 4 sub rsp, 16
 5 mov dword ptr [rbp - 4], edi
 6 imul edi, edi
 7 mov dword ptr [rbp - 8], edi
 8 seto al
 9 jo .LBB0_1
10 jmp .LBB0_2
11 .LBB0_1:
12 movabs rdi, offset __anon_1381
13 mov esi, 16
14 xor eax, eax
15 mov edx, eax
16 movabs rcx, offset .L__unnamed_1
17 call example.panic
18 .LBB0_2:
19 mov eax, dword ptr [rbp - 8]
20 add rsp, 16
21 pop rbp
22 ret
23 
24 example.panic:
25 push rbp
26 mov rbp, rsp
27 sub rsp, 32
28 mov qword ptr [rbp - 16], rsi
29 mov qword ptr [rbp - 24], rdi
30 mov qword ptr [rbp - 8], rdx
31 call zig_panic@PLT
[...]
38 
39 __anon_1381:
40 .asciz "integer overflow"
[...]

```

The multiplication (`imul`) is located on line 6. Then on line 9 we find a conditional jump on overflow (`jo`) to `.LBB0_1` which starts just below on line 12 loading something called `__anon_1381` which we can find on line 39 marking the text “integer overflow”. Continuing after line 12 leads to calls to `example.panic` (line 17) and ultimately `zig_panic@PLT` (line 31).

Also note, that the whole assembly code is sprinkled with stack operations (use of registers `rbp` and `rsp`) which is common in Debug modes.

How does this change if we change the build mode to [`-O ReleaseSafe`](https://zig.godbolt.org/z/KPM7szsxv) enabling optimizations while keeping safety checks?

```zig
square:
        imul edi, edi
        jo .LBB0_2
        mov eax, edi
        ret
.LBB0_2:
        push rax
        call zig_panic@PLT

```

Not much is left. But the multiplication (`imul`) is there, followed by the overflow check (`jo`) which now quite directly leads to the call of `zig_panic@PLT`. All those stack operations are gone, as well as the descriptive message why the call to `zig_panic@PLT` was made.

Finally, let us look what remains if we even disable safety checks by using the build mode [`-O ReleaseFast`](https://zig.godbolt.org/z/xh7xKczjM):

```zig
square:
        mov eax, edi
        imul eax, edi
        ret

```

The multiplication is still there. As expected, overflows are no longer checked for and consequently no calls to `zig_panic@PLT` are present anymore.

⚠ The Zig toolchain uses two different conventions to specify build modes depending on whether you use the buildsystem (`zig build -Doptimize=<MODE>`) or other subcommands like `zig build-exe -O <MODE>`. On Compiler Explorer we need to use the latter. Be aware though, that the use of `-Doptimize=<MODE>` on Compiler Explorer will seemingly work, but have no influence on the build mode. This will instead define a C macro named `optimize` and set its value to the mode.

# Other platforms

Compiler Explorer runs Zig in `x86_64` Linux containers. The Zig toolchain therefore [autodetects the target](https://ziglang.org/documentation/0.12.0/#Targets) as `x86_64-linux-gnu`. If you are developing for a different platform use the `-target` compiler option. See the `square` example [with explicit `-target x86_64-linux-gnu`](https://zig.godbolt.org/z/hvbzrh76x).

If you want to know what Zig autodetects for your system, an easy way is to run `zig env`. Look for the key `target` in the resulting JSON output. The computer I am writing this on gets detected as `"target": "x86_64-linux.6.5...6.5-gnu.2.35",`. If you do not need it that specific, consider removing the version numbers and version ranges.

# Ask questions about generated Assembly

You played around with code on Compiler Explorer and now want to ask questions or discuss your findings?  
Then consider to include a short link to the code and compiler settings you used. This gives readers all details in case they need them. According to Compiler Explorer’s [FAQ](https://github.com/compiler-explorer/compiler-explorer/wiki/FAQ) short links are designed to stay valid forever.

To emphasize points you want to make, repeat parts of the source code and generated assembly in your post. Aim to write posts that are understandable without following links.

---

<div class="post-metadata">

**Author:** ![AndrewCodeDev](https://ziggit.dev/letter_avatar_proxy/v4/letter/a/278dde/32.png) [@AndrewCodeDev](https://ziggit.dev/u/AndrewCodeDev)\
**Post date:** [June 6, 2024, 9:22pm UTC](https://ziggit.dev/t/how-to-use-compiler-explorer-with-zig/4601/2 "2024-06-06T21:22:56Z")

</div>

Let’s move this to a Doc instead of an explain topic - great work @Eisenhauer

---

<div class="post-metadata">

**Author:** ![haydenridd](https://ziggit.dev/user_avatar/ziggit.dev/haydenridd/32/2375_2.png) [@haydenridd](https://ziggit.dev/u/haydenridd)\
**Post date:** [September 5, 2024, 4:27pm UTC](https://ziggit.dev/t/how-to-use-compiler-explorer-with-zig/4601/3 "2024-09-05T16:27:08Z")

</div>

Amazing work, was exactly what I needed!

~~Do you know if you can pass what is usually passed via `-Dcpu=...` to `zig build`? I tried `-cpu cortex_m0plus` to no avail. Adding `-Dcpu=cortex_m0plus` compiles but I suspect it’s not doing anything as `-Dfoo=cortex_m0plus` also compiles 🙂~~

EDIT: Figured it out, it’s `-mcpu cortex_m0plus`! Can edit the original post if it’s helpful enough!

---

<div class="post-metadata">

**Author:** ![Eisenhauer](https://ziggit.dev/letter_avatar_proxy/v4/letter/e/76d3ee/32.png) [@Eisenhauer](https://ziggit.dev/u/Eisenhauer)\
**Post date:** [September 5, 2024, 5:23pm UTC](https://ziggit.dev/t/how-to-use-compiler-explorer-with-zig/4601/4 "2024-09-05T17:23:56Z")

</div>

How to tell Compiler Explorer what model of CPU to target sounds like a great fit for this topic. Feel free to add it 🙂

---

<div class="post-metadata">

**Author:** ![haydenridd](https://ziggit.dev/user_avatar/ziggit.dev/haydenridd/32/2375_2.png) [@haydenridd](https://ziggit.dev/u/haydenridd)\
**Post date:** [September 5, 2024, 5:54pm UTC](https://ziggit.dev/t/how-to-use-compiler-explorer-with-zig/4601/5 "2024-09-05T17:54:36Z")

</div>

I don’t think I have edit access! I also realized that Godbolt is essentially just calling `zig build-obj` instead of `zig build` which makes a lot of sense, maybe a blurb about using `zig build-obj --help` for more info on supported compile args in there?
