Skip to content

Commit

Permalink
Implement synchronous instantiation in a more principled way.
Browse files Browse the repository at this point in the history
This is intended to integrate with bytecodealliance/jco#413.
  • Loading branch information
whitequark committed Apr 8, 2024
1 parent 01db971 commit d9987ad
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 29 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ If the application returns a non-zero exit code, the exception `Exit` (exported
- The `code` property indicates the exit code. (Currently this is always 1 due to WebAssembly peculiarities.)
- The `files` property represents the state of the virtual filesystem after the application terminated. This property can be used to retrieve log files or other data aiding in diagnosing the error.

While in general the `runX` function returns a promise, there are two special cases. Calling `runX()` (with no arguments) does not run the application, but preloads and caches, in memory, all of the necessary resources. Calling `runX([...])` (with arguments) after that executes the application synchronously instead of returning a promise.
Calling `await runX()` (with no arguments) does not run the application, but ensures that all of the necessary resources are fetched and available for use. Once this is done, the additional overload, below, becomes available, which executes the application synchronously instead of returning a promise. This form should only be used in Web Workers and similar non-UI threads, in contexts where the use of an asynchronous API is infeasible.

```js
const filesOut = runX(args, filesIn, { ..., synchronously: true });
```

[Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

Expand Down
9 changes: 3 additions & 6 deletions lib/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@ export type RunOptions = {
stdin?: InputStream | null;
stdout?: OutputStream | null,
stderr?: OutputStream | null,
decodeASCII?: boolean
decodeASCII?: boolean,
synchronously?: boolean,
};

export class Application {
constructor(resources: () => Promise<any>, instantiate: any, argv0: string);

preload(): Promise<void>;

execute(args: string[], files?: Tree, options?: RunOptions): Tree;

run(args?: string[], files?: Tree, options?: RunOptions): Tree | Promise<Tree> | undefined;
run(args?: string[], files?: Tree, options?: RunOptions): Promise<Tree> | Tree | undefined;
}

export class Exit extends Error {
Expand Down
44 changes: 22 additions & 22 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,38 @@ export class Application {
this.argv0 = argv0;
}

async preload() {
if (this.resourceData === null)
this.resourceData = await this.resources().then(fetchResources);
}

// The `printLine` option is deprecated and not documented but still accepted for compatibility.
execute(args, files = {}, { stdin, stdout, stderr, decodeASCII = true, printLine } = {}) {
run(args, files = {}, options = {}) {
if (this.resourceData === null) {
if (options.synchronously)
throw new Error("Cannot run application synchronously unless resources are preloaded first");

return this.resources().then(fetchResources).then((resourceData) => {
this.resourceData = resourceData;
return this.run(args, files, options);
});
}

if (args === null)
return; // prefetch resources, but do not actually run

const environment = new Environment();
environment.args = [this.argv0].concat(args);
environment.root = directoryFromTree(files);
for (const [dirName, dirContents] of Object.entries(this.resourceData.filesystem))
environment.root.files[dirName] = directoryFromTree(dirContents);
const lineBufferedConsole = lineBuffered(printLine ?? console.log);
environment.stdin = stdin === undefined ? null : stdin;
environment.stdout = stdout === undefined ? lineBufferedConsole : stdout;
environment.stderr = stderr === undefined ? lineBufferedConsole : stderr;
const lineBufferedConsole = lineBuffered(options.printLine ?? console.log);
environment.stdin = options.stdin === undefined ? null : options.stdin;
environment.stdout = options.stdout === undefined ? lineBufferedConsole : options.stdout;
environment.stderr = options.stderr === undefined ? lineBufferedConsole : options.stderr;

const wasmCommand = this.instantiate(
(filename) => this.resourceData.modules[filename],
{ runtime: environment.exports },
// workaround for bytecodealliance/jco#374
(module, imports) => new WebAssembly.Instance(module, imports));
(module, imports) => options.synchronously
? new WebAssembly.Instance(module, imports)
: WebAssembly.instantiate(module, imports));
let error = null;
try {
wasmCommand.run.run();
Expand All @@ -81,22 +91,12 @@ export class Application {

for (const dirName of Object.keys(this.resourceData.filesystem))
delete environment.root.files[dirName];
files = directoryIntoTree(environment.root, { decodeASCII });
files = directoryIntoTree(environment.root, { decodeASCII: options.decodeASCII ?? true });
if (error !== null) {
error.files = files;
throw error;
} else {
return files;
}
}

run(args = null, files = {}, options = {}) {
if (this.resourceData === null)
return this.preload().then(_ => this.run(args, files, options));

if (args === null)
return; // prefetch resources, but do not actually run

return this.execute(args, files, options);
}
}

0 comments on commit d9987ad

Please sign in to comment.