Returning multiple items from a WASM executable

I’m playing around with Zig as a WASM language. I’m trying to pass a string back (or any more complicated return type). I’m trying to find the best way to do it.

Failed ideas

Multiple return values

It would be great if I could pass back an array. WASM supports multiple return values, so passing back the ptr as an int and the len as an int in a [2]usize would be great, but the compiler doesn’t support it.
Something like:

export fn run(input: [*]u8, in_len: usize) [2]usize {
   ...
   return .{@intFromPtr(output.ptr), output.len};
}

Don’t return, call JS directly

This option was to not try to return a value at all, but to call some external function to handle it. This would probably work, but I ran into another issue. I’ll post that in another thread.
EDIT: I got this option to work. It might be the option I go with.

Potential Options

Null terminated strings

One option I can think of is to go back to c-string land and allocate it as a null terminated string and pass back the pointer. Then in JS l will have to scan through the memory for the null byte and use that.
This seems like it will work, but it seems like extra work.

Pointer to Struct

Another idea that I haven’t tried yet is to allocate a packed struct and return the pointer from the struct. Then in JS I would read the two u32s from that location.

The challenge here is that I now have to allocate 2 items and remember to free them: the struct and the string.

Hardcoded Memory Location

It seems like Zig doesn’t use the first few pages of wasm memory. I could place the return value in this section in a specific spot and return the length of the string.

I don’t like this option because it seems brittle and relies on a “feature” of the compiler that could change any time.

Call for Help

Are there any other options that I missed? Any cons that I missed?

fn foo() struct{usize, usize} {
    return .{a, b};
}

Note that it’s still one value, same applies to your array example, i.e. the values contained have to stay together, limiting optimisations. Would be neat if zig offered real multiple return values, especially since it supports wasm which has it.

Perhaps the compiler could optimise ‘tuples’ (struct without field names) into multi return values, at least for targets that support it (if it doesn’t already).

1 Like

I tried this and the compiler doesn’t like it.

error: return type ‘struct { usize, usize }’ not allowed in function with calling convention ‘wasm_watc’
export fn eval(ptr: [*]u8, len: usize) struct { usize, usize } {

It will take a packed or an extern struct though. Can’t get it to work yet, might be hitting a bug somewhere.

If your goal is to expose a function to the host that returns multiple return values, then I think your only option is to hand-write some inline assembly.

Basically, export a function that calls your Zig functions, and returns their values.

But if your plan is to return an array, there’s a couple ways to go about it. Rather than allocating a struct with a ptr-len pair (which would then have to be freed later as well), one idea is to return a u64 value with the ptr and len encoded in it.

Something like:

const PackedSlice = packed struct (u64) {
    ptr: u32,
    len: u32,
};

export fn output_array() u64 {
    // ...
    return @as(u64, PackedSlice{
        .ptr = @intCast(@intFromPtr(output.ptr)),
        .len = @intCast(output.len),
    });
}

And on the Javascript side, you do some bit-shifting to get the pointer & length.

This assumes your output is wasm32, by the way—which is usually the case.

2 Likes