Building a WASM shared library that does not import memory from the environment

Hey there :wave:

I’m currently working on a project with dynamic loading of WASM code and native bindings. I used to use WAMR but due to its lack of support for iOS, I decided to give wasm3 a go.

Unfortunately, it’s not really working as expected. My current workflow to build a loadable wasm module is to declare a shared library with Build.addSharedLibrary(...) and I have a very simple zig file with various export and extern functions declared.

The issue now is that the resulting wasm module, when I look at it with wasm2wat, contains an import from “env” of the memory. If I understand correctly, wasm3 is not compatible to such a thing.

Would anyone know if there is a workaround for that ?

I don’t really have a strong knowledge of these concepts yet and thus randomly tried to use the export_memory flag but that seems to be incompatible with shared libraries.

Many thanks :smile:

1 Like

Finally found a way !

For potential readers, you can declare your wasm binary as such in a build.zig file:

const wasm_module = b.addExecutable(std.Build.ExecutableOptions{
    .name = "module",
    .optimize = optimize,
    .target = target,
    .root_source_file = .{ .path = "src/main.zig" },
});

// Allow exported symbols to actually be exported.
wasm_module.rdynamic = true;
// Avoid issue with the linker trying to find an entry point for the executable.
wasm_module.entry = .disabled;
4 Likes

As a alternate method that I used, I have a project layout with a shared lib file which I import in multiple component files. So the exports are defined in the lib file, then exposed from the different components. Where each component must be build output as WASM (and implement the required interface). I think it demonstrates that the Zig builder gives us enough control to deal with custom situations.

My build.zig contains the list of names:

const export_names = [_][]const u8{
    "canonical_abi_free",
    "canonical_abi_realloc",
    "handle-http-request",
};

Also within the build.zig, each component assigns the property for symbols:

// save component
    {
        const saexe = b.addExecutable(.{
            .name = "save",
            .root_source_file = .{ .path = "src/save.zig" },
            .target = target,
            .optimize = optimize,
        });
        saexe.single_threaded = true;
        saexe.export_symbol_names = &export_names;
        saexe.addOptions("build_options", project_level);
        b.installArtifact(saexe);
    }

The shared-module/lib.zig logic that is imported by the component:

// begin exports required by C/host
comptime {
    @export(guestHttpInit, .{ .name = "handle-http-request" });
    @export(canAbiRealloc, .{ .name = "canonical_abi_realloc" });
    @export(canAbiFree, .{ .name = "canonical_abi_free" });
}

Finally, one of the components ‘save.zig’ that imports lib:

const sharedModuleNamespace = @import("shared-module/lib.zig");