Exports from imported but unused files are missing

While playing with WebAssembly, I noticed that if I export a function in a file (called b.zig), and I import this file from my main file (called a.zig), then the function from b.zig is exported only if I use some other things from b.zig.

I made a minimal example here : https://codeberg.org/alberic89/tmp_4c0TFlCZf6

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

export const greetings = "Hello from Zig!";

// This part is either comment or not to produce two different outputs
// comptime {
//     @export(&b.favorite_number, .{ .name = "manual_favorite_number" });
// }
b.zig
pub export const favorite_number: u8 = 42;

The compilation gave me as WASM file. If the manual export is commented or not, I get this from wasm-objdump -x:

without_manual_export.wasm
without_manual_export.wasm:     file format wasm 0x1

Section Details:

Memory[1]:
 - memory[0] pages: initial=17
Global[2]:
 - global[0] i32 mutable=1 - init i32=1048576
 - global[1] i32 mutable=0 <greetings> - init i32=1048576
Export[2]:
 - memory[0] -> "memory"
 - global[1] -> "greetings"
Data[1]:
 - segment[0] memory=0 size=20 - init i32=1048576
  - 0100000: 0400 1000 4865 6c6c 6f20 6672 6f6d 205a  ....Hello from Z
  - 0100010: 6967 2100                                ig!.```
with_manual_export.wasm
with_manual_export.wasm:        file format wasm 0x1

Section Details:

Memory[1]:
 - memory[0] pages: initial=17
Global[4]:
 - global[0] i32 mutable=1 - init i32=1048576
 - global[1] i32 mutable=0 <favorite_number> - init i32=1048576
 - global[2] i32 mutable=0 <manual_favorite_number> - init i32=1048576
 - global[3] i32 mutable=0 <greetings> - init i32=1048580
Export[4]:
 - memory[0] -> "memory"
 - global[1] -> "favorite_number"
 - global[2] -> "manual_favorite_number"
 - global[3] -> "greetings"
Data[1]:
 - segment[0] memory=0 size=24 - init i32=1048576
  - 0100000: 2a00 0000 0800 1000 4865 6c6c 6f20 6672  *.......Hello fr
  - 0100010: 6f6d 205a 6967 2100                      om Zig!.```

You can see that in the first case there is no value favorite_number from b.zig, while in the second case the favorite_number and manual_favorite_number are both present.

What is the rationale behind this behavior ?

My hypothesis is that b.zig is not evaluated if I do not directly use it, so the export is not taken in account.

How to prevent this from appening ?

Is there a magic trick to tell to the compiler to look in all imported files ? Or is there another idiomatic way to do it ?

Zigs lazy evaluation strikes again! zig does not compile things that are not referenced!

Importing alone does not reference the file, you need to reference the const you the import was assigned to! a comptime { _ = my_import; } is enough.

Even the std library needs to do that in order to export the entry symbol.

Once a file is referenced, zig will evaluate any exports or top level comptime blocks; only 2 files will be referenced automatically by zig 1) the root file of your exe/lib/obj 2) the root of std

4 Likes

Thank you!
It’s a little ugly and definitively need a comment in the code, but it does the trick.

It may be ugly, but it is explicit, once you get used to zigs lazy evaluation you will easily be able to follow what code is compiled/analysed.

And then you will ask how tf does the main function work :3.

Main works because it’s referenced from _start.

I am aware, I was commenting that they may ask that question when they understand it better.