Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An example of how to provide import functions? #185

Open
TUSF opened this issue Nov 29, 2022 · 10 comments
Open

An example of how to provide import functions? #185

TUSF opened this issue Nov 29, 2022 · 10 comments
Labels
documentation Improvements or additions to documentation

Comments

@TUSF
Copy link

TUSF commented Nov 29, 2022

There's an example for invoking a function from an instance, but with so little documentation, one has to dig through the code to figure out how to provide host functions, and even then, I'm not quite sure I understand how to go about it.

@malcolmstill
Copy link
Owner

@TUSF thanks for the issue and apologies for the current lack of documentation.

There are some examples in the testrunner:

Define a function taking the zware.VirtualMachine as a parameter:

fn print_i32(vm: *VirtualMachine) WasmError!void {
    const value = vm.popOperand(i32);
    std.debug.print("print_i32: {}\n", .{value});
}

The function can access parameters via, say, popping off of the wasm stack. With the function written, it also needs to be registered with the runtime. This is quite clunky (at the moment), but you get the gist here:

    var print_i32_params = [_]ValType{.I32} ** 1;
    var print_i32_results = [_]ValType{.I32} ** 0;
    const print_i32_handle = try store.addFunction(Function{
        .params = print_i32_params[0..],
        .results = print_i32_results[0..],
        .subtype = .{
            .host_function = .{
                .func = print_i32,
            },
        },
    });
    const print_i32_name = "print_i32";
    try store.@"export"(spectest_module[0..], print_i32_name[0..], .Func, print_i32_handle);

I would also recommend looking at @leroycep's wasm4-zig-runtime, specifically here: https://github.com/leroycep/wasm4-zig-runtime/blob/dev/src/main.zig which has some very practical examples.

For the future I would like:

  • Add some examples to the examples/ dir for calling host functions
  • Produce some real documentation

@malcolmstill malcolmstill added the documentation Improvements or additions to documentation label Nov 30, 2022
@TUSF
Copy link
Author

TUSF commented Nov 30, 2022

Yes, I had actually found that project right after submitting this issue, and found it helpful in understanding what was going on. I suppose given that there's only one operand stack, any host function would have to pop off all of the parameters on the stack if they want to return any useful results first. Mind if I ask if there was a reason for that?

@malcolmstill
Copy link
Owner

Mind if I ask if there was a reason for that?

At least at the moment, there is fixed function pointer type for these host functions. That doesn't leave much option in terms the signature of these functions and so I've ended up with the only parameter is the VirtualMachine object and then you're on your own to push / pop values from its stack.

Honestly, having implement all of this, I'm a bit close to it and could use some outside perspective. Is there some way that you'd like to define host functions that might be a bit nice ergonomically?

@TUSF
Copy link
Author

TUSF commented Dec 2, 2022

Well, the current way to do things is fairly low level, which I wouldn't say is a bad thing, but I guess I would want a more high-level way to do things. I think some higher level helper functions would be good to have, just to make things easier to read. Something like:

fn addAndPrint(vm: *VirtualMachine) WasmError!void {
    const ParamsType = struct { a: i32, b: i32 };
    const args = vm.popParams(ParamsType);

    const results = .{args.a + args.b};
    vm.pushResults(results);

    std.debug.print("print_i32: {}\n", results);
}

The idea here would be that VirtualMachine.popParams(T: type) T would take a type of a struct (or tuple?) and invoke popOperand for the appropriate types, populating the struct with the values. Similarly, there could be a helper function pushResults for the reverse, which would take a struct (multi-value returns) or other type (single return) which invokes pushOperand for the appropriate values and types.

Further, I think you can make the registration of host functions less clunky by having a single addHostFunction (and an appropriate one for other host elements) with an API like this:

    store.addHostFunction("module", "print_i32",
        print_i32, // The function handler.
        .{.I32}, // Params, as a tuple of ValType
        .{.I32} // Results, as a tuple of ValType
    )

@malcolmstill
Copy link
Owner

Thinking we might also expand test/interface to, given a .wasm file, dump out skeleton implementations + function loading

malcolmstill added a commit that referenced this issue Jul 9, 2023
# Description

Fixes part of #185. Adds a
`addHostFunction` to the `Store` that simplifies loading a host
function.
@malcolmstill
Copy link
Owner

malcolmstill commented Jul 9, 2023

@TUSF I've add your suggestion of a addHostFunction function in #196 (currently explicitly taking slices of ValType but would be nice to use a tuple). Will look at your other suggestion

@malcolmstill
Copy link
Owner

@TUSF I've added in #197 a new program generate_interface that can take .wasm file and generate some stub host functions and a bunch of addHostFunction calls.

@malcolmstill
Copy link
Owner

#199 adds wrappers for exported functions

@malcolmstill
Copy link
Owner

I've renamed generate_interface to zware-gen and moved to tools/zware-gen

@malcolmstill
Copy link
Owner

In #206 zware-gen now doesn't generate stubs for wasi functions, rather we now implement (some of) wasi and by default zware-gen will point the imports at these implementations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants