Skip to content

Commit

Permalink
Refactor main WASI interface to work more like Node for custom uses (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
taybenlor authored Oct 11, 2023
1 parent d2689bd commit bf9e639
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 39 deletions.
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,18 @@ Visit [`@runno/runtime`](https://github.com/taybenlor/runno/tree/main/packages/r

## Quickstart

There are two parts to running a WASI binary with Runno. The `WASI` instance
which does the actual running and the `WASIContext` which sets up an environment
to run the binary in.
The quickest way to get started with Runno is by using the `WASI.start` class
method. It will set up everything you need and run the Wasm binary directly.

Be aware that this will run on the main thread, not inside a worker. So you will
interrupt any interactive use of the browser until it completes.

```js
import { WASI, WASIContext } from "@runno/wasi";
import { WASI } from "@runno/wasi";

//...

const context = new WASIContext({
const result = WASI.start(fetch("/binary.wasm"), {
args: ["binary-name", "--do-something", "some-file.txt"],
env: { SOME_KEY: "some value" },
stdout: (out) => console.log("stdout", out),
Expand All @@ -130,8 +129,6 @@ const context = new WASIContext({
},
},
});

const result = WASI.start(fetch("/binary.wasm"), context);
```

You can see a more complete example in `src/main.ts`.
Expand Down
57 changes: 50 additions & 7 deletions packages/wasi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@ interesting system level events, or hooks into the filesystem.

## Quickstart

There are two parts to running a WASI binary with Runno. The `WASI` instance
which does the actual running and the `WASIContext` which sets up an environment
to run the binary in.
The quickest way to get started with Runno is by using the `WASI.start` class
method. It will set up everything you need and run the Wasm binary directly.

Be aware that this will run on the main thread, not inside a worker. So you will
interrupt any interactive use of the browser until it completes.

```js
import { WASI, WASIContext } from "@runno/wasi";
import { WASI } from "@runno/wasi";

//...

const context = new WASIContext({
const result = WASI.start(fetch("/binary.wasm"), {
args: ["binary-name", "--do-something", "some-file.txt"],
env: { SOME_KEY: "some value" },
stdout: (out) => console.log("stdout", out),
Expand All @@ -43,15 +42,59 @@ const context = new WASIContext({
},
},
});

const result = WASI.start(fetch("/binary.wasm"), context);
```

You can see a more complete example in `src/main.ts`.

_Note: The `args` should start with the name of the binary. Like when you run
a terminal command, you write `cat somefile` the name of the binary is `cat`._

## Custom Instantiation

There are two parts to running a WASI binary with Runno. The `WASI` instance
which represents the emulated system, and the WebAssembly runtime provided by
the browser. If you'd like to customise the way the WebAssembly runtime is
instantiated, you can split these parts up.

```js
import { WASI } from "@runno/wasi";

// First set up the WASI emulated system
const wasi = new WASI({
args: ["binary-name", "--do-something", "some-file.txt"],
env: { SOME_KEY: "some value" },
stdout: (out) => console.log("stdout", out),
stderr: (err) => console.error("stderr", err),
stdin: () => prompt("stdin:"),
fs: {
"/some-file.txt": {
path: "/some-file.txt",
timestamps: {
access: new Date(),
change: new Date(),
modification: new Date(),
},
mode: "string",
content: "Some content for the file.",
},
},
});

// Then instantiate your binary with the imports provided by the wasi object
const wasm = await WebAssembly.instantiateStreaming(
fetch("/binary.wasm"),
{
...wasi.getImportObject(),

// Your own custom imports (e.g. memory)
memory: new WebAssembly.Memory({ initial: 32, maximum: 10000 });
}
);

// Finally start the WASI binary
const result = wasi.start(wasm);
```

## Using the WASIWorker

A worker is provided for using the WASI runner outside of the main thread. It
Expand Down
46 changes: 21 additions & 25 deletions packages/wasi/lib/wasi/wasi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "./snapshot-preview1";
import { Whence as UnstableWhence } from "./unstable";
import { WASIExecutionResult } from "../types";
import { WASIContext } from "./wasi-context";
import { WASIContext, WASIContextOptions } from "./wasi-context";
import { DriveStat, WASIDrive } from "./wasi-drive";

/** Injects a function between implementation and return for debugging */
Expand Down Expand Up @@ -57,43 +57,39 @@ export class WASI implements SnapshotPreview1 {
context: WASIContext;
drive: WASIDrive;

initialized: boolean = false;

static async start(
wasmSource: Response | PromiseLike<Response>,
context: WASIContext
context: Partial<WASIContextOptions> = {}
) {
const wasi = new WASI(context);
const wasm = await WebAssembly.instantiateStreaming(wasmSource, {
wasi_snapshot_preview1: wasi.getImports("preview1", context.debug),
wasi_unstable: wasi.getImports("unstable", context.debug),
});
wasi.init(wasm);
return wasi.start();
const wasm = await WebAssembly.instantiateStreaming(
wasmSource,
wasi.getImportObject()
);
return wasi.start(wasm);
}

constructor(context: Partial<WASIContextOptions>) {
this.context = new WASIContext(context);
this.drive = new WASIDrive(this.context.fs);
}

constructor(context: WASIContext) {
this.context = context;
this.drive = new WASIDrive(context.fs);
getImportObject() {
return {
wasi_snapshot_preview1: this.getImports("preview1", this.context.debug),
wasi_unstable: this.getImports("unstable", this.context.debug),
};
}

init(wasm: WebAssembly.WebAssemblyInstantiatedSource) {
// Internal initialization
private init(wasm: WebAssembly.WebAssemblyInstantiatedSource) {
this.instance = wasm.instance;
this.module = wasm.module;
this.memory = this.instance.exports.memory as WebAssembly.Memory;

this.initialized = true;
}

start(): WASIExecutionResult {
if (!this.initialized) {
throw new Error("WASI must be initialized with init(wasm) first");
}

// TODO: Investigate Google's Asyncify
// https://github.com/GoogleChromeLabs/asyncify
// which allows for async imports/exports so we aren't
// dependent on blocking IO
start(wasm: WebAssembly.WebAssemblyInstantiatedSource): WASIExecutionResult {
this.init(wasm);

const entrypoint = this.instance.exports._start as () => void;
try {
Expand Down

0 comments on commit bf9e639

Please sign in to comment.