a-shell icon indicating copy to clipboard operation
a-shell copied to clipboard

Shortcuts: In extension: wasm not found

Open personalizedrefrigerator opened this issue 2 years ago • 2 comments

It would be very useful to have access to the 'wasm' command when running commands from shortcuts (in extension)!

Hi, I just tried. We can't use wasm when running in extension (for now, at least) because wasm uses a webView to execute JavaScript and WebAssembly. Since the extension doesn't have a view, it is impossible.

In the future, I plan to add wasm3 (https://github.com/wasm3/wasm3) to iOS_system. It is slower than Apple's own JIT wasm compiler, but still quite good, and it would allow more generic execution for webAssembly (including interactive input and running in extension).

holzschu avatar Jul 19 '21 14:07 holzschu

Hi, I just tried. We can't use wasm when running in extension (for now, at least) because wasm uses a webView to execute JavaScript and WebAssembly. Since the extension doesn't have a view, it is impossible.

In the future, I plan to add wasm3 (https://github.com/wasm3/wasm3) to iOS_system. It is slower than Apple's own JIT wasm compiler, but still quite good, and it would allow more generic execution for webAssembly (including interactive input and running in extension).

wen wasm3

sascha1337 avatar May 18 '22 13:05 sascha1337

because wasm uses a webView to execute JavaScript and WebAssembly

Out of curiosity, is there some reason JS/WASM runs in a web view instead of with JavaScriptCore? JSC would at least have the advantage of not requiring a view, and would probably take fewer resources as well.

Recognition101 avatar Mar 29 '23 04:03 Recognition101

If I remember correctly, the short answer is: "because I haven't (yet) found a way to do it".

To run WebAssembly and have meaningful output, you need to load the WASI environment, which is a lot of files that load each other using the require call. I tried to do that with JSC, and failed miserably. It's also unclear from the documentation whether the fast webAssembly interpreter is available in JSC, or if it's only present in webViews.

holzschu avatar Mar 29 '23 05:03 holzschu

nah, not really need wasi if we got wasm bindgen glue code

elix1er avatar Mar 29 '23 07:03 elix1er

The hard bit is not to run wasm, it's to run wasm and interact with the rest of the system (get input, send output, write files...). By default, wasm runs in a sandbox, which makes it not very useful for shell commands. For that, I haven't seen a replacement for wasi. I can look into "bindgen glue code" if you can provide a link.

holzschu avatar Mar 29 '23 07:03 holzschu

If I remember correctly, the short answer is: "because I haven't (yet) found a way to do it".

I've created a demo of running WASI in JSC here. It's primitive (i.e. it doesn't use @wasmer/wasmfs), but I just wanted to verify that JSC could run @wasmer/wasi. I'm happy to try to make more complex cases work if you have WASM test cases (and are willing to help me out with Swift code - I'm very comfortable with JS, not so much with Swift).

To run WebAssembly and have meaningful output, you need to load the WASI environment, which is a lot of files that load each other using the require call. I tried to do that with JSC, and failed miserably.

require is a function that only exists in the NodeJS runtime (require runs a specified JS file in a new JS context, returning some "exported" object to the original context as the return value of require). It does not exist in JSC/browser/WKWebView runtimes - but I think you're using a library that creates its own require and uses Browser APIs to simulate what NodeJS would do. I took a different approach - I used webpack to concatenate all of the JS files together into a single JS file, removing the need for any require calls (webpack strips them and replaces them with references to the "exported" objects they would have returned in NodeJS). This approach has the advantage of creating a single combined JS file, which is easy to evaluate in JSC.

Even though I got rid of require this way, the code still relied on a few Web and Node APIs that weren't present in JSC (Buffer, TextEncoder, TextDecoder). I fixed this by using JS-reimplementations of those APIs.

The project's README.md explains more details.

nah, not really need wasi if we got wasm bindgen glue code

I'm not 100% sure what this means, but I think it's saying that if we had a command to run JSC with a particular JS file and access to some native bindings, users themselves could mostly take it from there.

I've been meaning to create a Swift library that would run JSC with a few native function calls exposed (reading/writing files, network requests, etc). @holzschu would you be open to including such a command in this app? I've created a detailed minimum-viable-API specification for it which I could share, if you were curious or wanted to take a shot at implementing it.

With the right bindings and the Webpack approach described above, I could implement most WASM use cases on the user side, without updating the app code itself.

Recognition101 avatar Apr 12 '23 03:04 Recognition101

@Recognition101 that looks great! I'm going to look at it in depth.

I think that concatenating everything in a single JS file will also result in faster loading time, even inside the app. As you said, there is also the question of exposing native calls. WebAssembly commands in a-Shell can create files, read files, write to files... on the main file system (something that WebAssembly is very much not supposed to do). I did that by calling JavaScript alerts, which is the only way I found to have synchronous communication between the JS and Swift elements. I think JSC has a less hacky way for communications with the main program, but the difficult part is making sure both parts run synchronously (because commands can be piped into other commands, communicate with other commands, etc).

I'm going to have a look at your code, then merge it into the app. It's probably a good idea to keep both versions for a while, for testing. Users will also be interested in the impact on computation time, so I'll design something to test this as well (Apple's WebAssembly interpreter is not very well documented, except by one presentation at a conference, and it's unclear for me from the documentation if JSC and WkWebView use the same engine for WebAssembly).

holzschu avatar Apr 12 '23 06:04 holzschu

update: in a first set of tests, JavaScriptCore is 12 to 15 times slower (for a CPU-intensive program) than the WkWebView JavaScript engine. That doesn't say anything about their WebAssembly engine, but it's not a good sign.

This made me realize that I already have code running JavaScriptCore in iOS_system: https://github.com/holzschu/ios_system/blob/master/shell_cmds_ios/jsc.swift and apparently it contains a require implementation. I'll see if I can make it run WebAssembly.

holzschu avatar Apr 12 '23 20:04 holzschu

I think JSC has a less hacky way for communications with the main program, but the difficult part is making sure both parts run synchronously (because commands can be piped into other commands, communicate with other commands, etc).

In my demo, I do this with the isDone swift function (in jsc.swift) which is exposed to JS as jscp.done. In JS, calling jscp.done("hello world") will cause the Swift code to store "hello world" into the output variable and print("hello world").

in a first set of tests, JavaScriptCore is 12 to 15 times slower (for a CPU-intensive program) than the WkWebView JavaScript engine. That doesn't say anything about their WebAssembly engine, but it's not a good sign.

You're right, that's not a good sign. It would have been cool to replace the core way WASM runs in a-Shell, but if it's not performant perhaps there's still room for a new, separate command? I mostly wanted JSC capability to run simple (non-performance intensive) JS scripts that I've written myself from a Siri Shortcut. Something like: jscp <filename.js>, which would use JSC to evaluate the given file and provide a gateway to some native functions.

I've attached the native functions API spec table below. Most of the functions would be pretty trivial to write, but some (request and importModule) are pretty complex - which is why I haven't started this project myself yet. I'd be a lot more motivated to start working on it if you think it's something you'd include in a-Shell though!

Recognition101 avatar Apr 13 '23 04:04 Recognition101

So, the good news is that you already have jsc available in Shortcuts extension (try it). It's the JavaScriptCore version. The bad news is that it doesn't have all these fancy functions. I'm going to focus on running WebAssembly first, then add these functions.

holzschu avatar Apr 13 '23 08:04 holzschu

Since this issue is about WebAssembly, I've moved my API proposal for the fancy native-gateway functions over to #616. I've also added a bunch of implementation notes and links to documentation that could help with the implementation of those functions.

Let me know if there's anything I can do to help with either this or that issue, I'm happy to mess around with more proofs-of-concept!

By the way: the current JSC command does work great in shortcuts! Thanks for pointing that out. Unfortunately, without the gateway functions there's not much that can be done in the available sandbox.

Recognition101 avatar Apr 14 '23 05:04 Recognition101

Brief status report: I'm getting close to loading require.js in JavaScriptCore (and thus loading @wasmer with minimal changes). In the process, I learned that JSC is very limited (it didn't have the URL type, for example). It's hard to find a documentation on those limitations.

holzschu avatar Apr 14 '23 05:04 holzschu

I've finally completed porting require.js to JavaScriptCore, but JavaScriptCore does not support WebAssembly ("Can't find variable: WebAssembly"). On the plus side, we now have a working require in JavaScriptCore, which makes things a lot easier.

holzschu avatar Apr 14 '23 15:04 holzschu

In the process, I learned that JSC is very limited (it didn't have the URL type, for example). It's hard to find a documentation on those limitations.

JavaScript is a bit like Lua, if you're familiar with that language. Basically, there is a core spec that defines the language. JSC implements that fully. The language spec defines built-in data types (Number, Boolean, String, Map, Set, ArrayBuffer, etc.) and a very (very) small selection of helper libraries (Math, JSON, maybe a couple more). Runtimes can provide APIs on top of that, and the most popular runtime is the browser runtime, which implements the Web API (the NodeJS runtime is probably the second most popular runtime, whose API provides require among other libraries).

The Web API is sprawling (here's a machine-readable list of every spec, to give you an idea). The reason JSC doesn't implement any of those is that many of those either introduce asynchronicity (ex: the setTimeout function) or pierce the sandbox (ex: the fetch HTTP Request API). JSC has this scope:

  1. JSC does not implement an "event loop" (very complex and necessary for asynchronicity).
  2. Nothing executed in JSC should be able to break out of the sandbox (read/write files, network, etc).

Luckily, many of those APIs (like URL) can be implemented in pure, synchronous, sandboxed JavaScript. I used implementations for TextEncoder, TextDecoder, and Buffer in my demo (you can see I pull in the fastestsmallesttextencoderdecoder and buffer packages for that).

That's a really long way of saying that documentation doesn't exist beyond the specs, because the documentation is the specs. JSC isn't doing anything special here, it's just that most "JavaScript" libraries are written for the browser runtime, not for pure ES262 as correctly implemented by engines like JSC or V8 (because there's not much you can do in those sandboxes, just like there's not much you can do in pure Lua).

I've finally completed porting require.js to JavaScriptCore, but JavaScriptCore does not support WebAssembly ("Can't find variable: WebAssembly").

If you run /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/jsc and type WebAssembly, you get [object WebAssembly], implying JSC does support WebAssembly. Similarly, in my proof-of-concept, I also run WebAssembly. Is this a MacOS vs iOS thing, or are you getting that error on MacOS too? If you're getting it on MacOS, I think something else is going on (since I run WebAssembly code in JSC on macOS in my proof-of-concept).

If you publish a proof-of-concept repository somewhere I'd be happy to look into it.

Recognition101 avatar Apr 14 '23 16:04 Recognition101

Thanks for the explanations.

For WebAssembly support on iOS, I merely loaded wasm.js (https://github.com/holzschu/a-shell/blob/master/wasm.js). It runs fine until it reaches the line:

const module = new WebAssembly.Module(loweredWasmBytes); 

at which point it throws an error: Can't find variable: WebAssembly.

holzschu avatar Apr 14 '23 16:04 holzschu