Stream: general

Topic: Cross-instance call and capturing full execution context


view this post on Zulip Alice W (Mar 21 2025 at 15:49):

Hi, I’m working on a project using Wasmtime to simulate behavior with fork and exec, and I’m trying to enable one wasm instance to call a function from another instance. Here's the setup:

Execution command: ./instance1.cwasm instance2.cwasm

Instance 1 (C code, and will be compiled to .wasm):

int add_sum(uint64_t a, uint64_t b) {
    return a + b;
}

int main(int argc, char *argv[]) {
    int grateid = getpid();

    pid_t pid = fork();
    if (pid < 0) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        if (argc > 1) {
            if (execv(argv[1], &argv[1]) == -1) {
                perror("execv failed");
                exit(EXIT_FAILURE);
            }
        } else {
            fprintf(stderr, "No argument provided for execv\n");
            exit(EXIT_FAILURE);
        }
    }

    int status;
    waitpid(-1, &status, 0);

    return 0;
}

Instance 2 (C code, and will be compiled to .wasm):

int main(int argc, char *argv[]) {
    int ret = add_sum(1, 2);  // intends to call function from instance 1
    printf("ret = %d\n", ret);
    return 0;
}

In my microvisor (Rust side), I store a closure via threei_test_func() in load_main_module() like this:

let instance_pre = instance_pre.clone();
let grate_store = store.data().clone();
let res = threei_test_func(current_pid, Box::new(move |arg1: u64, arg2: u64| -> i32 {
    let mut gstore = Store::new(instance_pre.module().engine(), grate_store.clone());
    let instance = instance_pre.instantiate_with_lind(&mut gstore, InstantiateType::InstantiateFirst(pid)).unwrap();
    if let Some(grate_entry_func) = instance.get_func(&mut gstore, "add_sum") {
        let typed_func = grate_entry_func.typed::<(u64, u64), i32>(&mut gstore).unwrap();
        typed_func.call(&mut gstore, (arg1, arg2)).unwrap()
    } else {
        -1
    }
}));

I set --export=add_sum compilation flag and this works, but here's a problem:

Because the closure is registered during load_main_module(), any calls from instance 2 to instance 1 will use a freshly instantiated version of instance 1 (only the state before instance 1’s main() is executed).

What I want is for instance 1 to run its main() normally (e.g., spawn child instances and call waitpid()), and for instance 2 to be able to call functions from instance 1 in the middle of that execution, with full state.

I was exploring invoke_func() to inject the closure later, but it seems to only execute the main function directly.

Is there a way to identify and hook into a specific point inside instance 1’s main() (for example right before or at the call to waitpid()) so that later calls from instance 2 can access the "live" state of instance 1?

Would appreciate any suggestions or ideas on how to achieve this!

A lightweight WebAssembly runtime that is fast, secure, and standards-compliant - bytecodealliance/wasmtime

view this post on Zulip fitzgen (he/him) (Mar 21 2025 at 21:43):

Alice W said:

Execution command: ./instance1.cwasm instance2.cwasm

.cwasms are not executables, they are Wasmtime's artifacts for pre-compiled Wasm modules, which happen to be ELF files, but this is an internal implementation detail. they don't include Wasmtime's runtime (e.g. the stuff needed to instantiate the module and allocate+initialize its state or the VM function do a memory.grow operation, etc...)

you can run precompiled .cwasms from the wasmtime CLI via

$ wasmtime run --allow-precompiled path/to/my.cwasm

Last updated: Apr 07 2025 at 11:03 UTC