How to export a function named "main" accepting no arguments?

I’m trying to link libc into a wasm project. The library seems to expect main even though lib.entry is set to .disabled. Oh well, just need to stick an empty function in there I guess. If I put the following in a zig file:

export fn main() callconv(.C) c_int {
    return 0;
}

The linker pulls in an additional function from __main_void.c:

// If the user's `main` function expects arguments, the compiler will rename
// it to `__main_argc_argv`, and this version will get linked in, which
// initializes the argument data and calls `__main_argc_argv`.
__attribute__((__weak__, nodebug))
int __main_void(void) {

wasm-objdump indicates that main has the following signiture:

- type[2] (i32, i32) -> i32

So argc and argv are still there somehow. If I add the through a C file instead, I get the correct behavior:

int main(void) { 
    return 0; 
}
1 Like

It’s not clear what lib is from your description. Is it your wasm artifact? Is it an external library you’re linking your wasm artifact with?
Could you post your full build.zig script for context?

const std = @import("std");
const cfg = @import("./build-cfg.zig");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const arch = if (@hasDecl(@TypeOf(target), "getCpuArch")) target.getCpuArch() else target.result.cpu.arch;
    const is_wasm = switch (arch) {
        .wasm32, .wasm64 => true,
        else => false,
    };
    const lib = b.addSharedLibrary(.{
        .name = cfg.module_name,
        .root_source_file = .{ .path = cfg.stub_path },
        .target = target,
        .optimize = optimize,
    });
    if (is_wasm) {
        lib.rdynamic = true;
    }
    const imports = .{};
    if (@hasDecl(std.Build.Step.Compile, "addModule")) {
        // Zig 0.11.0
        lib.addModule("exporter", b.createModule(.{
            .source_file = .{ .path = cfg.exporter_path },
        }));
        lib.addModule("module", b.createModule(.{
            .source_file = .{ .path = cfg.module_path },
            .dependencies = &imports,
        }));
    } else if (@hasField(std.Build.Step.Compile, "root_module")) {
        // Zig 0.12.0
        lib.root_module.addImport("exporter", b.createModule(.{
            .root_source_file = .{ .path = cfg.exporter_path },
        }));
        lib.root_module.addImport("module", b.createModule(.{
            .root_source_file = .{ .path = cfg.module_path },
            .imports = &imports,
        }));
        if (is_wasm) {
            // WASM needs to be compiled as exe
            lib.kind = .exe;
            lib.linkage = .static;
            lib.entry = .disabled;
        }
    }
    if (cfg.use_libc) {
        lib.linkLibC();
        if (is_wasm) {
            // add empty function expected by libc
            lib.addCSourceFile(.{ .file = .{ .path = "./main.c" }, .flags = &.{} });
        }
    }
    const wf = b.addWriteFiles();
    wf.addCopyFileToSource(lib.getEmittedBin(), cfg.output_path);
    wf.step.dependOn(&lib.step);
    b.getInstallStep().dependOn(&wf.step);
}

If you’re targeting wasm32-wasi, you can set lib.wasi_exec_model = .reactor. This will export a small _initialize function which must be called before any other exported symbols are accessed, but not require your artifact to export any main symbol.

2 Likes

That was it. Thanks!