Error only while compiling to wasm32-emscripten and using GPA

I get an error only when compiling to wasm32-emscripten and using GeneralPurposeAllocator.

If I compile to x86_64 (i.e. my host machine) and use GPA, then I don’t get any such an error.

If I compile to wasm32-emscripten and use anything other then GPA (i.e. ArenaAllocator or FixedBufferAllocator), then I don’t get any such an error.

Also, the error message isn’t too helpfull.

❯ zig build -Dtarget=wasm32-emscripten
install
└─ install generated/
   └─ run emcc (index.html)
      └─ zig build-lib breakout Debug wasm32-emscripten 1 errors
/nix/store/xfs50swh37h0qj78sfhsg8ppl41gi10z-zig-0.14.1/lib/zig/std/debug.zig:898:24: error: no field named 'base_address' in struct 'debug.SelfInfo.Module__struct_6735'
                module.base_address,
                       ^~~~~~~~~~~~
/nix/store/xfs50swh37h0qj78sfhsg8ppl41gi10z-zig-0.14.1/lib/zig/std/debug/SelfInfo.zig:798:27: note: struct declared here
    .wasi, .emscripten => struct {
                          ^~~~~~

build.zig:

...
        const wasm_target = b.resolveTargetQuery(.{
            .cpu_arch = .wasm32,
            .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
            .cpu_features_add = std.Target.wasm.featureSet(&.{
                .atomics,
                .bulk_memory,
            }),
            .os_tag = .emscripten,
        });

        const raylib_dep = b.dependency("raylib", .{
            .target = target,
            .optimize = optimize,
            .rmodels = false,
        });
        const raylib_artifact = raylib_dep.artifact("raylib");

        const app_lib = b.addLibrary(.{
            .linkage = .static,
            .name = "breakout",
            .root_module = b.createModule(.{
                .root_source_file = b.path("src/main.zig"),
                .target = wasm_target,
                .optimize = optimize,
            }),
        });
        app_lib.linkLibC();
        app_lib.shared_memory = true;
        app_lib.linkLibrary(raylib_artifact);
        app_lib.addIncludePath(.{ .cwd_relative = ".emscripten_cache-4.0.8/sysroot/include" });

        app_lib.root_module.addOptions("config", options); 

        //const assets = [_][]const u8{
        //    "res/paddle.png",
        //    "res/tennis.png",
        //    "res/brick.png",
        //};

        //for (assets) |asset| {
        //    app_lib.root_module.addAnonymousImport(asset, .{ .root_source_file = b.path(asset) });
        //}

        const emcc = b.addSystemCommand(&.{"emcc"});

        for (app_lib.getCompileDependencies(false)) |lib| {
            if (lib.isStaticLibrary()) {
                emcc.addArtifactArg(lib);
            }
        }

        const shell_file = b.path("src/shell.html");

        emcc.addArgs(&.{
            //"-sUSE_OFFSET_CONVERTER",
            "-sFULL-ES3=1",
            "-sUSE_GLFW=3",
            "-sASYNCIFY",
            //"-O3",
            "-fsanitize=undefined",
            "--emrun",


            //"-sAUDIO_WORKLET=1",
            //"-sWASM_WORKERS=1",
            "-sSHARED_MEMORY=1",
            "-sALLOW_MEMORY_GROWTH=1",

            "--shell-file",
        });

        emcc.addFileArg(shell_file);
        emcc.addArg("--embed-file");
        emcc.addArg("res/");
        //const res_file = b.path("res/");
        //emcc.addDirectoryArg(res_file);

        const link_items: []const *std.Build.Step.Compile = &.{
            raylib_artifact,
            app_lib,
        };

        for (link_items) |item| {
            emcc.addFileArg(item.getEmittedBin());
            emcc.step.dependOn(&item.step);
        }

        //emcc.addArg("--pre-js");
        emcc.addArg("-o");

        const app_html = emcc.addOutputFileArg("index.html");
        b.getInstallStep().dependOn(&b.addInstallDirectory(.{
            .source_dir = app_html.dirname(),
            .install_dir = .{ .custom = "www" },
            .install_subdir = "",
        }).step);

        const icon_file = b.addInstallFile(b.path("res/icon-big.png"), "www/icon.png");
        b.getInstallStep().dependOn(&icon_file.step);

        return;
...

Hello,
you need wasi allocator, general purpose is just a native debug allocator in 0.14 which defaults to:

backing_allocator: Allocator = std.heap.page_allocator,

If you want to get nice debug info, you can override the backing allocator on initialization and it should work, didnt test myself on wasi tho.

Robert :blush:

Thx, I didn’t know GPA was just a “pointer” to page_allocator. I will need to delve into this topic more :thinking:

This error message scared the shit out of me - those abstract messages are the worst.

PS. What do you think about my solution, would you change anything?

    var gpa: ?std.heap.GeneralPurposeAllocator(.{}) = null;
    defer {
        if (gpa) |*value| {
            _ = value.deinit();
        }
    }

    const alloc = switch (builtin.target.os.tag) {
        .emscripten => std.heap.wasm_allocator,
        else => blk: {
            gpa = .{};
            break :blk gpa.?.allocator();
        }
    };

GPA was already getting deprecated with its functionality cut to specialized allocators like DebugAllocator.
Overall it will most likely change even more in the future thanks to this being accepted.

Currently, I personally create my own struct with init(), denit() and allocator() methods similar to what AutoAllocator proposal did and put all the decision logic inside. It makes the main much cleaner and if done right has no runtime overhead.

Also you dont have to make the allocator nullable, since you can manipulate types in Zig.

var gpa: if (builtin.target.os.tag == .emscripten) void else std.heap.GeneralPurposeAllocator(.{}) = undefined;

This is not the ideal version, you might for example want to use DebugAllocator backed by the wasi allocator in debug build and just wasi allocator in the other build modes, but it gets the idea over imo.