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

feat: accept WebAssembly.Module, Response inputs #32

Merged
merged 1 commit into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"imports": {
"js-sdk:worker-url": "./src/worker-url.ts",
"js-sdk:response-to-module": "./src/polyfills/response-to-module.ts",
"js-sdk:minimatch": "./src/polyfills/deno-minimatch.ts",
"js-sdk:capabilities": "./src/polyfills/deno-capabilities.ts",
"js-sdk:wasi": "./src/polyfills/deno-wasi.ts",
Expand Down
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ build_node_cjs out='cjs' args='[]':
"minify": false,
"alias": {
"js-sdk:capabilities": "./src/polyfills/node-capabilities.ts",
"js-sdk:response-to-module": "./src/polyfills/response-to-module.ts",
"js-sdk:minimatch": "./src/polyfills/node-minimatch.ts",
"js-sdk:worker-url": "./dist/worker/node/worker-url.ts",
"js-sdk:fs": "node:fs/promises",
Expand Down Expand Up @@ -182,6 +183,7 @@ build_node_esm out='esm' args='[]':
"minify": false,
"alias": {
"js-sdk:capabilities": "./src/polyfills/node-capabilities.ts",
"js-sdk:response-to-module": "./src/polyfills/response-to-module.ts",
"js-sdk:minimatch": "./src/polyfills/node-minimatch.ts",
"js-sdk:worker-url": "./dist/worker/node/worker-url.ts",
"js-sdk:fs": "node:fs/promises",
Expand All @@ -202,6 +204,7 @@ build_bun out='bun' args='[]':
"minify": false,
"alias": {
"js-sdk:worker-url": "./src/polyfills/bun-worker-url.ts",
"js-sdk:response-to-module": "./src/polyfills/bun-response-to-module.ts",
"js-sdk:minimatch": "./src/polyfills/node-minimatch.ts",
"js-sdk:capabilities": "./src/polyfills/bun-capabilities.ts",
"js-sdk:fs": "node:fs/promises",
Expand All @@ -222,6 +225,7 @@ build_browser out='browser' args='[]':
"format": "esm",
"alias": {
"js-sdk:capabilities": "./src/polyfills/browser-capabilities.ts",
"js-sdk:response-to-module": "./src/polyfills/response-to-module.ts",
"js-sdk:minimatch": "./src/polyfills/node-minimatch.ts",
"node:worker_threads": "./src/polyfills/host-node-worker_threads.ts",
"js-sdk:fs": "./src/polyfills/browser-fs.ts",
Expand Down
4 changes: 2 additions & 2 deletions src/background-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ class HttpContext {
export async function createBackgroundPlugin(
opts: InternalConfig,
names: string[],
modules: ArrayBuffer[],
modules: WebAssembly.Module[],
): Promise<BackgroundPlugin> {
const worker = new Worker(WORKER_URL);
const context = new CallContext(SharedArrayBuffer, opts.logger, opts.config);
Expand Down Expand Up @@ -394,7 +394,7 @@ export async function createBackgroundPlugin(
});
});

worker.postMessage(message, modules);
worker.postMessage(message);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't transfer WebAssembly.Module instances between threads, unlike ArrayBuffers.

However, that's not necessarily a bad thing! The modules are structurally cloned between threads, which means we can send them to multiple threads in the future – which will be necessary when we go to implement pooling.

await onready;

return new BackgroundPlugin(worker, sharedData, opts, context);
Expand Down
48 changes: 23 additions & 25 deletions src/foreground-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import { loadWasi } from 'js-sdk:wasi';

export const EXTISM_ENV = 'extism:host/env';

type InstantiatedModule = { guestType: string; module: WebAssembly.Module; instance: WebAssembly.Instance };

export class ForegroundPlugin {
#context: CallContext;
#modules: { guestType: string; module: WebAssembly.WebAssemblyInstantiatedSource }[];
#modules: InstantiatedModule[];
#names: string[];
#active: boolean = false;

constructor(
context: CallContext,
names: string[],
modules: { guestType: string; module: WebAssembly.WebAssemblyInstantiatedSource }[],
) {
constructor(context: CallContext, names: string[], modules: InstantiatedModule[]) {
this.#context = context;
this.#names = names;
this.#modules = modules;
Expand All @@ -41,7 +39,7 @@ export class ForegroundPlugin {
? [this.lookupTarget(search[0]), search[1]]
: [
this.#modules.find((guest) => {
const exports = WebAssembly.Module.exports(guest.module.module);
const exports = WebAssembly.Module.exports(guest.module);
return exports.find((item) => {
return item.name === search[0] && item.kind === 'function';
});
Expand All @@ -53,7 +51,7 @@ export class ForegroundPlugin {
return false;
}

const func = target.module.instance.exports[name] as any;
const func = target.instance.exports[name] as any;

if (!func) {
return false;
Expand All @@ -74,7 +72,7 @@ export class ForegroundPlugin {
? [this.lookupTarget(search[0]), search[1]]
: [
this.#modules.find((guest) => {
const exports = WebAssembly.Module.exports(guest.module.module);
const exports = WebAssembly.Module.exports(guest.module);
return exports.find((item) => {
return item.name === search[0] && item.kind === 'function';
});
Expand All @@ -85,7 +83,7 @@ export class ForegroundPlugin {
if (!target) {
throw Error(`Plugin error: target "${search.join('" "')}" does not exist`);
}
const func = target.module.instance.exports[name] as any;
const func = target.instance.exports[name] as any;
if (!func) {
throw Error(`Plugin error: function "${search.join('" "')}" does not exist`);
}
Expand Down Expand Up @@ -124,7 +122,7 @@ export class ForegroundPlugin {
return output;
}

private lookupTarget(name: any): { guestType: string; module: WebAssembly.WebAssemblyInstantiatedSource } {
private lookupTarget(name: any): InstantiatedModule {
const target = String(name ?? '0');
const idx = this.#names.findIndex((xs) => xs === target);
if (idx === -1) {
Expand All @@ -134,15 +132,15 @@ export class ForegroundPlugin {
}

async getExports(name?: string): Promise<WebAssembly.ModuleExportDescriptor[]> {
return WebAssembly.Module.exports(this.lookupTarget(name).module.module) || [];
return WebAssembly.Module.exports(this.lookupTarget(name).module) || [];
}

async getImports(name?: string): Promise<WebAssembly.ModuleImportDescriptor[]> {
return WebAssembly.Module.imports(this.lookupTarget(name).module.module) || [];
return WebAssembly.Module.imports(this.lookupTarget(name).module) || [];
}

async getInstance(name?: string): Promise<WebAssembly.Instance> {
return this.lookupTarget(name).module.instance;
return this.lookupTarget(name).instance;
}

async close(): Promise<void> {
Expand All @@ -153,7 +151,7 @@ export class ForegroundPlugin {
export async function createForegroundPlugin(
opts: InternalConfig,
names: string[],
sources: ArrayBuffer[],
modules: WebAssembly.Module[],
context: CallContext = new CallContext(ArrayBuffer, opts.logger, opts.config),
): Promise<ForegroundPlugin> {
const wasi = opts.wasiEnabled ? await loadWasi(opts.allowedPaths) : null;
Expand All @@ -171,27 +169,27 @@ export async function createForegroundPlugin(
}
}

const modules = await Promise.all(
sources.map(async (source) => {
const module = await WebAssembly.instantiate(source, imports);
const instances = await Promise.all(
modules.map(async (module) => {
const instance = await WebAssembly.instantiate(module, imports);
if (wasi) {
await wasi?.initialize(module.instance);
await wasi?.initialize(instance);
}

const guestType = module.instance.exports.hs_init
const guestType = instance.exports.hs_init
? 'haskell'
: module.instance.exports._initialize
: instance.exports._initialize
? 'reactor'
: module.instance.exports._start
: instance.exports._start
? 'command'
: 'none';

const initRuntime: any = module.instance.exports.hs_init ? module.instance.exports.hs_init : () => {};
const initRuntime: any = instance.exports.hs_init ? instance.exports.hs_init : () => {};
initRuntime();

return { module, guestType };
return { module, instance, guestType };
}),
);

return new ForegroundPlugin(context, names, modules);
return new ForegroundPlugin(context, names, instances);
}
33 changes: 28 additions & 5 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,39 @@ export interface ManifestWasmPath {
}

/**
* The WASM to load as bytes, a path, or a url
* Represents a WASM module as a response
*/
export interface ManifestWasmResponse {
response: Response;
}

/**
* Represents a WASM module as a response
*/
export interface ManifestWasmModule {
module: WebAssembly.Module;
}

/**
* The WASM to load as bytes, a path, a fetch `Response`, a `WebAssembly.Module`, or a url
*
* @property name The name of the Wasm module. Used when disambiguating {@link Plugin#call | `Plugin#call`} targets when the
* plugin embeds multiple Wasm modules.
*
* @property hash The expected SHA-256 hash of the associated Wasm module data. {@link createPlugin} validates incoming Wasm against
* provided hashes. If running on Node v18, `node` must be invoked using the `--experimental-global-webcrypto` flag.
*
* ⚠️ `module` cannot be used in conjunction with `hash`: the Web Platform does not currently provide a way to get source
* bytes from a `WebAssembly.Module` in order to hash.
*
*/
export type ManifestWasm = (ManifestWasmUrl | ManifestWasmData | ManifestWasmPath) & {
export type ManifestWasm = (
| ManifestWasmUrl
| ManifestWasmData
| ManifestWasmPath
| ManifestWasmResponse
| ManifestWasmModule
) & {
name?: string | undefined;
hash?: string | undefined;
};
Expand Down Expand Up @@ -241,9 +264,9 @@ export interface Manifest {
/**
* Any type that can be converted into an Extism {@link Manifest}.
* - `object` instances that implement {@link Manifest} are validated.
* - `ArrayBuffer` instances are converted into {@link Manifest}s with a single {@link ManifestWasmData} member.
* - `ArrayBuffer` instances are converted into {@link Manifest}s with a single {@link ManifestUint8Array} member.
* - `URL` instances are fetched and their responses interpreted according to their `content-type` response header. `application/wasm` and `application/octet-stream` items
* are treated as {@link ManifestWasmData} items; `application/json` and `text/json` are treated as JSON-encoded {@link Manifest}s.
* are treated as {@link ManifestUint8Array} items; `application/json` and `text/json` are treated as JSON-encoded {@link Manifest}s.
* - `string` instances that start with `http://`, `https://`, or `file://` are treated as URLs.
* - `string` instances that start with `{` treated as JSON-encoded {@link Manifest}s.
* - All other `string` instances are treated as {@link ManifestWasmPath}.
Expand All @@ -266,7 +289,7 @@ export interface Manifest {
* @throws {@link TypeError} when `URL` parameters don't resolve to a known `content-type`
* @throws {@link TypeError} when the resulting {@link Manifest} does not contain a `wasm` member with valid {@link ManifestWasm} items.
*/
export type ManifestLike = Manifest | ArrayBuffer | string | URL;
export type ManifestLike = Manifest | Response | WebAssembly.Module | ArrayBuffer | string | URL;

export interface Capabilities {
/**
Expand Down
Loading