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

Support shebang for command-line wasmtime scripts #3715

Closed
JoeStrout opened this issue Jan 23, 2022 · 33 comments
Closed

Support shebang for command-line wasmtime scripts #3715

JoeStrout opened this issue Jan 23, 2022 · 33 comments

Comments

@JoeStrout
Copy link

Feature

Make wasmtime hip to a shebang at the first line of the file, e.g.:

#!/usr/local/bin/wasmtime

followed by the text-format wasm code as usual. Currently, the shell runs wasmtime and feeds it the script as expected, but wasmtime chokes on the first line and fails to run the following script.

Benefit

This would allow us to write scripts that can be executed by simply setting the executable bit and invoking them, like any other shell script. It's a matter of convenience. I have a lot of scripts in a lot of different languages; I don't have to remember what tool to use to run them because they all understand shebang — except for wasmtime.

Implementation

Just special-case it: if the first line of the script file starts with "#!", then ignore it and continue with the rest of the file.

Alternatives

I could write, for every wasm script I want to run, a little 2-line shell script that runs wasmtime with the path to the actual script. That would be annoying on so many levels.

@alexcrichton
Copy link
Member

Personally I don't feel that Wasmtime should support wasmtime-specific extensions to the text format. If this wants to be supported then I think that this should be added to the official grammar of text files in the wasm specification itself.

@JoeStrout
Copy link
Author

I would argue that this is wasmtime's responsibility, as a command-line executable whose function is to run .wat files on the command line. This is something all other script runners (perl, python, miniscript, etc. etc.) do, and the shebang is not part of the grammar of those languages either; it's just something the script runners are prepared to deal with.

Conversely, a shebang doesn't make any sense in other contexts, e.g. in a .wat file that's part of a Visual Studio project. So I wouldn't expect to see it in the .wat spec itself. It only makes sense for people who (like me) are trying to use wasmtime like other command-line scripts, so it ought to just be something that wasmtime does.

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 24, 2022

Shebang's are definitively part of the grammar of a language if an interpreter for it can handle them.

@cfallin
Copy link
Member

cfallin commented Jan 25, 2022

@JoeStrout FWIW I think the main issue with support in wasmtime for this would be that it could potentially normalize "not quite compliant" .wat files -- in other words, it encourages the existence of non-standards-compliant source that fails to parse in other tools/engines.

But since this is Unix, there are probably ways to put together other tools to make a solution... one technique that might help is to write the wat content as a heredoc, something like the following (just tested):

#!/bin/sh

wasmtime run /dev/stdin <<__END__
    (module
     (import "wasi_snapshot_preview1" "proc_exit" (func (param i32)))
     (memory (export "memory") 0 1)
     (func (export "_start")
      i32.const 0
      call 0))
__END__

One could conceivably write a tool that converts .wat to .wat.sh by wrapping it like this.

One could probably also use the "custom binfmt" support in Linux to recognize the magic number in a .wasm and invoke wasmtime as an interpreter; then a WASI .wasm would behave just like a native executable. (IIRC, distributions of qemu often set this up for ELF binaries of other architectures; that might be a place to start looking.)

Anyway, just some ideas -- hope these help!

@iximeow
Copy link
Collaborator

iximeow commented Jan 25, 2022

(a little bit of pedantry, sorry: bash and python and friends do not have any particular support for #!, that's handled in the kernel as part of exec. by happy coincidence design, those languages treat # as a comment and then ignore the shebang line when executed. thus wasmtime executing a shebang'd file would first require ignoring a preceding #!.*\n, then executing the provided .wat text. that's where the non-standards-compliance comes in, since .wat comments are ;; rather than #.)

@tschneidereit
Copy link
Member

Yeah, I don't think wasmtime should start accepting non-standard extensions to the file format. One way to do this if we somehow absolutely wanted to is to get the standard changed to support shebangs. That's what's happening in JavaScript.

I'm not sure that it makes sense to do this though: the wat format really isn't meant to be used as a production format, and I don't think we'd be well served with making it easier to run wat files than wasm files. And the binfmt support Chris mentions already exists.

@JoeStrout
Copy link
Author

I'm surprised by the negative reactions. All I'm asking for is to skip the first line of the input if it starts with #, rather than fail. If # had some other valid usage here, we would have a problem as it might be difficult to disambiguate the user's intent. But we don't. I think you can confidently say that the user does not intend the result to be a screenful of error messages.

It's not a big ask, and it's a substantial quality-of-life improvement, at least for some of us.

@tschneidereit
Copy link
Member

The "big" part of the ask is to support a non-standard extension to a standardized format, that'd then mean that that format only runs in our runtime, but not others. The right way to go about adding something like this is to propose it in the WebAssembly Community Group.

As I said in my previous comment I also think that this isn't the right thing to do to begin with, but a sufficient number of people in the Community Group might disagree for it to be standardized, in which case we'd implement it.

@alexcrichton
Copy link
Member

I'm going to close this form the discussion above. I think it's still reasonable to propose this extension to the wasm CG and the text format if you're interested though.

@guest271314
Copy link
Contributor

Just encountered this limitation trying to run WebAssembly as a Native Messaging host. To me it seems wasmtime should be able to run using #!/usr/bin/env -S which works for Python, Node.js, Deno, QuickJS, et al. though throws for wasmtime

#!/usr/bin/env wasmtime
(module
    ;; Import the required fd_write WASI function which will write the given io vectors to stdout
    ;; The function signature for fd_write is:
    ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
    (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))

    (memory 1)
    (export "memory" (memory 0))

    ;; Write 'hello world\n' to memory at an offset of 8 bytes
    ;; Note the trailing newline which is required for the text to appear
    (data (i32.const 8) "hello world\n")

    (func $main (export "_start")
        ;; Creating a new io vector within linear memory
        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - The length of the 'hello world\n' string

        (call $fd_write
            (i32.const 1) ;; file_descriptor - 1 for stdout
            (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
            (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
            (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
        )
        drop ;; Discard the number of bytes written from the top of the stack
    )
)
$ ./demo
Error: failed to run main module `./demo`

Caused by:
    0: if you're trying to run a precompiled module, pass --allow-precompiled
    1: expected `(`
            --> ./demo:1:1
             |
           1 | #!/usr/bin/env wasmtime
             | ^

@guest271314
Copy link
Contributor

that it could potentially normalize "not quite compliant" .wat files -- in other words, it encourages the existence of non-standards-compliant source that fails to parse in other tools/engines.

But since this is Unix, there are probably ways to put together other tools to make a solution... one technique that might help is to write the wat content as a heredoc, something like the following (just tested):

#!/bin/sh

wasmtime run /dev/stdin <<__END__
    (module
     (import "wasi_snapshot_preview1" "proc_exit" (func (param i32)))
     (memory (export "memory") 0 1)
     (func (export "_start")
      i32.const 0
      call 0))
__END__

How will that work when the WebAssembly module is expected to be reading stdin from an application, e.g., the browser?

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 21, 2023

How will that work when the WebAssembly module is expected to be reading stdin from an application, e.g., the browser?

You can write the wat file to a temporary file and then run it. Or you could store the wat file next to the shell script that runs wasmtime. You will need to do that anyway if you want to compile the wat file to a wasm file when shipping the extension. (a wat file is several times bigger than the compiled wasm file)

@guest271314
Copy link
Contributor

You can write the wat file to a temporary file and then run it.

That doesn't work as described in this case #5614.

I tried both .wat and .wasm.

wasmtime appears to be a special case executable that cannot be instructed to execute what is below the shebang.

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 21, 2023

Did you try something like

#!/usr/bin/sh

cat >foo.wat <<__END__
(module
    ;; Import the required fd_write WASI function which will write the given io vectors to stdout
    ;; The function signature for fd_write is:
    ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
    (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))

    (memory 1)
    (export "memory" (memory 0))

    ;; Write 'hello world\n' to memory at an offset of 8 bytes
    ;; Note the trailing newline which is required for the text to appear
    (data (i32.const 8) "hello world\n")

    (func $main (export "_start")
        ;; Creating a new io vector within linear memory
        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - The length of the 'hello world\n' string

        (call $fd_write
            (i32.const 1) ;; file_descriptor - 1 for stdout
            (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
            (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
            (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
        )
        drop ;; Discard the number of bytes written from the top of the stack
    )
)
__END__

wasmtime run foo.wat

@guest271314
Copy link
Contributor

Did you try something like

Just tried. The Native Messaging host exits.

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 21, 2023

This specific example doesn't read from stdin and exits as soon as it wrote hello world\n to stdout. As such it is expected that it exits. Furthermore the messages need to be in json format in both directions together with a length prefix (4 byte little endian integer I believe).

@guest271314
Copy link
Contributor

I uploaded the WASm file to https://webassembly.github.io/wabt/demo/wasm2wat/ and substituted that for the wat in your example.

I'm just trying to test WebAssembly version of a Native Messaging host. Thus I asked the WebAssembly experts how to do that after not being successful using wasmtime and learning that wasmtime does not support shebang line.

@guest271314
Copy link
Contributor

@bjorn3 I know for a fact that both https://github.com/guest271314/native-messaging-c and https://github.com/guest271314/native-messaging-cpp work as expected. I compiled the C version to WASM using WASI SDK. I downloaded and installed wasmtime. Now I am stuck trying to run the executable.

@guest271314
Copy link
Contributor

It shouldn't be this complicated that we have to use a shell script to run .wasm and .wat. when we have wasmtime that should be capable of just interpreting a shebang lineand running the code below that line.

At least provide users with the option to use the shebang line.

I mean the line to install wasmtime itself uses Bash.

curl https://wasmtime.dev/install.sh -sSf | bash

Examples of using shebang for Python, Node.js, Deno, QuickJS, Bun Native Messaging hosts:

#!/usr/bin/env -S python3 -u
#!/usr/bin/env -S ./node --max-old-space-size=6 --jitless --expose-gc --v8-pool-size=1
#!/usr/bin/env -S ./deno run --v8-flags="--expose-gc,--jitless"
#!/usr/bin/env -S ./qjs --std
#!/usr/bin/env -S ./bun run --no-install --hot

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 21, 2023

#!/usr/bin/env -S python3 -u

In Python # indicates a comment, so no syntax extension is needed here.

#!/usr/bin/env -S ./node --max-old-space-size=6 --jitless --expose-gc --v8-pool-size=1
#!/usr/bin/env -S ./deno run --v8-flags="--expose-gc,--jitless"
#!/usr/bin/env -S ./qjs --std
#!/usr/bin/env -S ./bun run --no-install --hot

These have decided to extend javascript in a way incompatible with javascript engines not included in this list like browsers, which is not something wasmtime wants to do.

At least provide users with the option to use the shebang line.

As per #3715 (comment) you should suggest adding it to the official wat specification, not as a wasmtime specific extension.

@bjorn3 I know for a fact that both https://github.com/guest271314/native-messaging-c and https://github.com/guest271314/native-messaging-cpp work as expected. I compiled the C version to WASM using WASI SDK. I downloaded and installed wasmtime. Now I am stuck trying to run the executable.

I don't think this issue is related at all to the shebang:

If I try printf '\0\0\0\x02{}' | ./nm_c, I get \0\0\0\x02{} as response back followed by an infinite list of null bytes (as you didn't add an exit condition when 0 bytes are read, which indicates EOF). However when using the wasm version I get \0\0\0\x02{} in repeat. This is caused by an off-by-one error when allocating the message buffer. The following patch fixes this mistake:

diff --git a/nm_c.c b/nm_c.c
index a1afda1..e328d20 100644
--- a/nm_c.c
+++ b/nm_c.c
@@ -26,7 +26,7 @@ uint8_t* getMessage(size_t *inputLength) {
   // `message[0]`).
   // `sizeof(*message)` will hence return the size in bytes of the type 
   // `message` points at, which means it's equivalent to `sizeof(uint_8)`.
-  uint8_t *message = calloc(messageLength, sizeof(*message));
+  uint8_t *message = calloc(messageLength+1, sizeof(*message));
   result = fread(message, sizeof(*message), messageLength, stdin);
   // `inputLength` is a pointer, so we store the length at the memory address it 
   // points at. This way we return 2 values at once from a function!

I suspect that applying this patch will fix the communication issue that lead to the native messaging host exiting.

@guest271314
Copy link
Contributor

I can do this just fine with the C Native Messaging host

globalThis.name = chrome.runtime.getManifest().short_name;

globalThis.port = chrome.runtime.connectNative(globalThis.name);
port.onMessage.addListener((message) => console.log(message));
port.onDisconnect.addListener((p) => console.log(chrome.runtime.lastError));
port.postMessage('');

Thanks for the patch nonetheless. Kindly file a PR explaining why, what, how so we know exactly what is going on on the record of the repository. I am not a C or C++ expert. I am far more fluent in JavaScript. I had to get help to create the C and C++ versions.

As per #3715 (comment) you should suggest adding it to the official wat specification, not as a wasmtime specific extension.

I think we should be able to use /usr/bin/env with .wat or .wasm files where we call wastime as the environment.

I suspect that applying this patch will fix the communication issue that lead to the native messaging host exiting.

Unfortunately that didn't change anything relevant to the Native Messaging host.

How is .wat relevant to the ability to call wasmtime?

I'm just trying to test WebAssembly as a Native Messaging host using the officially endorsed runtime. If you try the above hosts they each echo back the input from the browser. I have no idea how to do that using WebAssembly or wasmtime. This is the first time I've actually successfully compiled a .wasm file.

@guest271314
Copy link
Contributor

I applied the patch, recompiled the C source code to WASM and the host still exited - when using the cat and redirection approach.

@guest271314
Copy link
Contributor

@bjorn3 If/when you have the time and are so inclined perhaps try testing using wasmtime yourself; c. to the other hosts I've linked to above. Perhaps then you will see where I'm at and why I commented here and filed an issue. The instructions are here https://github.com/guest271314/native-messaging-nodejs.

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 21, 2023

Thanks for the patch nonetheless. Kindly file a PR explaining why, what, how so we know exactly what is going on on the record of the repository. I am not a C or C++ expert. I am far more fluent in JavaScript. I had to get help to create the C and C++ versions.

Sure, guest271314/native-messaging-c#2.

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 21, 2023

@bjorn3 If/when you have the time and are so inclined perhaps try testing using wasmtime yourself; c. to the other hosts I've linked to above. Perhaps then you will see where I'm at and why I commented here and filed an issue. The instructions are here https://github.com/guest271314/native-messaging-nodejs.

I will try it.

@guest271314

This comment was marked as off-topic.

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 21, 2023

Got https://github.com/guest271314/native-messaging-c on native working after I moved it out of the /tmp dir and made sure to use a trailing slash for the extension specifier in nm_c.json. I did get a message with an array of exactly 0x33333 zero elements. Is this expected?

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 21, 2023

For me an nm_c.sh with the following contents worked fine:

#!/usr/bin/env bash

wasmtime run nm_c.wasm

Make sure to make the shell script executable though. Otherwise chromium says "Native host has exited.".

@guest271314

This comment was marked as off-topic.

@guest271314

This comment was marked as off-topic.

@guest271314

This comment was marked as off-topic.

@JoeStrout

This comment was marked as off-topic.

@bytecodealliance bytecodealliance locked as off-topic and limited conversation to collaborators Jan 21, 2023
@tschneidereit
Copy link
Member

I locked this topic as all the discussion starting yesterday is off-topic. As laid out last year, the wasmtime project is dedicated to implementing standardized features, and will not accept non-standards, non-interoperable features. This isn't a matter of purity, but a matter of wanting to foster an ecosystem that's wider than this one project.

I'd again encourage those who want this feature to propose it to the WebAssembly Community Group as the right forum for discussions about changes to the standards themselves. If and when such a proposal gains traction there, we'll happily implement it in wasmtime.

(@bjorn3: thank you very much for your diligent work on trying to resolve users' problems—it's much appreciated! ❤️)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants