rylev / wepl

A repl for WebAssembly Components
Apache License 2.0
105 stars 7 forks source link

Unable to run JS wasm compiled by jco #26

Open logaan opened 2 days ago

logaan commented 2 days ago

I'm having some difficulty running js code compiled to wasm using jco in wepl. I've tried to boil this down to a minimal example.

repltest.js

export function test() {
    return "ok";
}

repltest.wit

package local:repltest;

world repltest {
  export test: func() -> string;
}

make-test

#!/usr/bin/env bash

npx jco componentize repltest.js --wit repltest.wit -o repltest.wasm

wepl repltest.wasm

When I run make-test it appears to correctly create a wasm file. The imports include a bunch of wasi interfaces. The exports are the one function that we expect to see. Unfortunately calling that function complains of an unimplemented import:

./make-test 
OK Successfully written repltest.wasm.
World: root:component/root
> .imports
wasi:io/poll@0.2.0: {
    [method]pollable.block: func(self: handle<...>)
    poll: func(in: list<handle<...>>) -> list<u32>
}
wasi:io/streams@0.2.0: {
    [method]input-stream.read: func(self: handle<...>, len: u64) -> result<list<u8>, stream-error>
    [method]input-stream.subscribe: func(self: handle<...>) -> handle<...>
    [method]output-stream.check-write: func(self: handle<...>) -> result<u64, stream-error>
    [method]output-stream.write: func(self: handle<...>, contents: list<u8>) -> result<_, stream-error>
    [method]output-stream.blocking-write-and-flush: func(self: handle<...>, contents: list<u8>) -> result<_, stream-error>
    [method]output-stream.blocking-flush: func(self: handle<...>) -> result<_, stream-error>
    [method]output-stream.subscribe: func(self: handle<...>) -> handle<...>
}
wasi:cli/stdin@0.2.0: {
    get-stdin: func() -> handle<...>
}
wasi:cli/stdout@0.2.0: {
    get-stdout: func() -> handle<...>
}
wasi:cli/stderr@0.2.0: {
    get-stderr: func() -> handle<...>
}
wasi:cli/terminal-stdin@0.2.0: {
    get-terminal-stdin: func() -> option<handle<...>>
}
wasi:cli/terminal-stdout@0.2.0: {
    get-terminal-stdout: func() -> option<handle<...>>
}
wasi:cli/terminal-stderr@0.2.0: {
    get-terminal-stderr: func() -> option<handle<...>>
}
wasi:clocks/monotonic-clock@0.2.0: {
    now: func() -> u64
    resolution: func() -> u64
    subscribe-instant: func(when: u64) -> handle<...>
}
wasi:clocks/wall-clock@0.2.0: {
    now: func() -> datetime
    resolution: func() -> datetime
}
wasi:filesystem/types@0.2.0: {
    [method]descriptor.write-via-stream: func(self: handle<...>, offset: u64) -> result<handle<...>, error-code>
    [method]descriptor.append-via-stream: func(self: handle<...>) -> result<handle<...>, error-code>
    [method]descriptor.get-flags: func(self: handle<...>) -> result<flags<...>, error-code>
    [method]descriptor.get-type: func(self: handle<...>) -> result<descriptor-type, error-code>
    [method]descriptor.stat: func(self: handle<...>) -> result<descriptor-stat, error-code>
    filesystem-error-code: func(err: handle<...>) -> option<error-code>
}
wasi:filesystem/preopens@0.2.0: {
    get-directories: func() -> list<tuple<handle<...>, string>>
}
wasi:random/random@0.2.0: {
    get-random-bytes: func(len: u64) -> list<u8>
    get-random-u64: func() -> u64
}
wasi:http/types@0.2.0: {
    [constructor]fields: func() -> handle<...>
    [static]fields.from-list: func(entries: list<tuple<string, list<u8>>>) -> result<handle<...>, header-error>
    [method]fields.get: func(self: handle<...>, name: string) -> list<list<u8>>
    [method]fields.has: func(self: handle<...>, name: string) -> bool
    [method]fields.set: func(self: handle<...>, name: string, value: list<list<u8>>) -> result<_, header-error>
    [method]fields.delete: func(self: handle<...>, name: string) -> result<_, header-error>
    [method]fields.append: func(self: handle<...>, name: string, value: list<u8>) -> result<_, header-error>
    [method]fields.entries: func(self: handle<...>) -> list<tuple<string, list<u8>>>
    [method]fields.clone: func(self: handle<...>) -> handle<...>
    [method]incoming-request.method: func(self: handle<...>) -> method
    [method]incoming-request.path-with-query: func(self: handle<...>) -> option<string>
    [method]incoming-request.scheme: func(self: handle<...>) -> option<scheme>
    [method]incoming-request.authority: func(self: handle<...>) -> option<string>
    [method]incoming-request.headers: func(self: handle<...>) -> handle<...>
    [method]incoming-request.consume: func(self: handle<...>) -> result<handle<...>>
    [constructor]outgoing-request: func(headers: handle<...>) -> handle<...>
    [method]outgoing-request.body: func(self: handle<...>) -> result<handle<...>>
    [method]outgoing-request.set-method: func(self: handle<...>, method: method) -> result
    [method]outgoing-request.set-path-with-query: func(self: handle<...>, path-with-query: option<string>) -> result
    [method]outgoing-request.set-scheme: func(self: handle<...>, scheme: option<scheme>) -> result
    [method]outgoing-request.set-authority: func(self: handle<...>, authority: option<string>) -> result
    [method]outgoing-request.headers: func(self: handle<...>) -> handle<...>
    [static]response-outparam.set: func(param: handle<...>, response: result<handle<...>, error-code>)
    [method]incoming-response.status: func(self: handle<...>) -> u16
    [method]incoming-response.headers: func(self: handle<...>) -> handle<...>
    [method]incoming-response.consume: func(self: handle<...>) -> result<handle<...>>
    [method]incoming-body.stream: func(self: handle<...>) -> result<handle<...>>
    [constructor]outgoing-response: func(headers: handle<...>) -> handle<...>
    [method]outgoing-response.set-status-code: func(self: handle<...>, status-code: u16) -> result
    [method]outgoing-response.headers: func(self: handle<...>) -> handle<...>
    [method]outgoing-response.body: func(self: handle<...>) -> result<handle<...>>
    [method]outgoing-body.write: func(self: handle<...>) -> result<handle<...>>
    [static]outgoing-body.finish: func(this: handle<...>, trailers: option<handle<...>>) -> result<_, error-code>
    [method]future-incoming-response.subscribe: func(self: handle<...>) -> handle<...>
    [method]future-incoming-response.get: func(self: handle<...>) -> option<result<result<handle<...>, error-code>>>
}
wasi:http/outgoing-handler@0.2.0: {
    handle: func(request: handle<...>, options: option<handle<...>>) -> result<handle<...>, error-code>
}
> .exports
test: func() -> string
> test()
Error: unimplemented import: wasi:clocks/monotonic-clock@0.2.0
Error: error while executing at wasm backtrace:
    0: 0xa31d91 - wit-component:adapter:wasi_snapshot_preview1!clock_time_get
    1: 0xa34ea3 - wit-component:shim!adapt-wasi_snapshot_preview1-clock_time_get
    2: 0x7a8ed0 - <unknown>!<wasm function 12274>
    3: 0x77d826 - <unknown>!<wasm function 9154>
    4: 0x79a0d3 - <unknown>!<wasm function 10706>
    5: 0x343c1f - <unknown>!<wasm function 481>
    6: 0x353b8e - <unknown>!<wasm function 505>
    7: 0x6aecaf - <unknown>!<wasm function 4783>
    8: 0x51bb5d - <unknown>!<wasm function 1762>
    9: 0x3348d8 - <unknown>!<wasm function 460>
   10: 0x7a9dcf - <unknown>!test
> 

I reached out to @rylev on the Bytecode Alliance Zulip and was expecting to be told that I needed to use the .link or .compose commands to satisfy the repltest.wasm imports. I haven't been able to find any examples of how to use .link or .compose, and wasn't able to figure it out from reading the wepl source.

logaan commented 2 days ago

It might also be worth mentioning that I tried using WASI-Virt. With that I was able to create a wasm file with no imports. But I think the code was still calling clock_time_get and so I would see a different error, one coming from virt because it wasn't expecting to be called.