Skip to content

Commit

Permalink
add support for version when creating WASI (nodejs/node#46469)
Browse files Browse the repository at this point in the history
  • Loading branch information
toyobayashi committed Feb 26, 2023
1 parent 7152924 commit 2311766
Show file tree
Hide file tree
Showing 17 changed files with 127 additions and 135 deletions.
128 changes: 77 additions & 51 deletions src/wasi/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { WASI as _WASI } from './preview1'
import { WASI as WASIPreview1 } from './preview1'
import type { Preopen } from './preview1'
import type { exitcode } from './types'

Expand All @@ -7,7 +7,8 @@ import {
validateArray,
validateBoolean,
validateFunction,
validateUndefined
validateUndefined,
validateString
} from './util'
import type { IFs, IFsPromises } from './fs'
import type { Asyncify } from '../asyncify'
Expand All @@ -18,6 +19,7 @@ const kExitCode = Symbol('kExitCode')
const kSetMemory = Symbol('kSetMemory')
const kStarted = Symbol('kStarted')
const kInstance = Symbol('kInstance')
const kBindingName = Symbol('kBindingName')

function setupInstance (self: WASI, instance: WebAssembly.Instance): void {
validateObject(instance, 'instance')
Expand All @@ -29,6 +31,7 @@ function setupInstance (self: WASI, instance: WebAssembly.Instance): void {

/** @public */
export interface WASIOptions {
version?: 'unstable' | 'preview1'
args?: string[] | undefined
env?: Record<string, string> | undefined
preopens?: Record<string, string> | undefined
Expand Down Expand Up @@ -68,17 +71,35 @@ export interface AsyncWASIOptions extends WASIOptions {
asyncify?: Asyncify
}

// /** @public */
// export type WasiSnapshotPreview1 = Omit<_WASI, '_setMemory'>

function validateOptions (options: WASIOptions & { fs?: IFs | { promises: IFsPromises } }): {
function validateOptions (this: WASI, options: WASIOptions & { fs?: IFs | { promises: IFsPromises } }): {
args: string[]
env: string[]
preopens: Preopen[]
stdio: readonly [0, 1, 2]
_WASI: any
} {
validateObject(options, 'options')

let _WASI: any
if (options.version !== undefined) {
validateString(options.version, 'options.version')
switch (options.version) {
case 'unstable':
_WASI = WASIPreview1
this[kBindingName] = 'wasi_unstable'
break
case 'preview1':
_WASI = WASIPreview1
this[kBindingName] = 'wasi_snapshot_preview1'
break
default:
throw new TypeError(`unsupported WASI version "${options.version as string}"`)
}
} else {
_WASI = WASIPreview1
this[kBindingName] = 'wasi_snapshot_preview1'
}

if (options.args !== undefined) {
validateArray(options.args, 'options.args')
}
Expand Down Expand Up @@ -145,11 +166,12 @@ function validateOptions (options: WASIOptions & { fs?: IFs | { promises: IFsPro
args,
env,
preopens,
stdio
stdio,
_WASI
}
}

function initWASI (this: WASI, setMemory: (m: WebAssembly.Memory) => void, wrap: _WASI): void {
function initWASI (this: WASI, setMemory: (m: WebAssembly.Memory) => void, wrap: any): void {
this[kSetMemory] = setMemory;
(this as any).wasiImport = wrap
this[kStarted] = false
Expand Down Expand Up @@ -189,57 +211,18 @@ export class WASI {
private [kStarted]!: boolean
private [kExitCode]!: number
private [kInstance]: WebAssembly.Instance | undefined
private [kBindingName]!: string

public readonly wasiImport!: Record<string, any>

static createSync (options: SyncWASIOptions = kEmptyObject): WASI {
return new WASI(options)
}

static async createAsync (options: AsyncWASIOptions = kEmptyObject): Promise<WASI> {
const {
args,
env,
preopens,
stdio
} = validateOptions(options)

if (options.asyncify !== undefined) {
validateObject(options.asyncify, 'options.asyncify')
validateFunction(options.asyncify.wrapImportFunction, 'options.asyncify.wrapImportFunction')
}

const wrap = await _WASI.createAsync(
args,
env,
preopens,
stdio,
options.fs,
options.print,
options.printErr,
options.asyncify
)

const setMemory = wrap._setMemory!
delete wrap._setMemory
const _this = Object.create(WASI.prototype)
initWASI.call(_this,
setMemory,
wrap
)

if (options.returnOnExit) { wrap.proc_exit = wasiReturnOnProcExit.bind(_this) }

return _this
}

public constructor (options: SyncWASIOptions = kEmptyObject) {
const {
args,
env,
preopens,
stdio
} = validateOptions(options)
stdio,
_WASI
} = validateOptions.call(this, options)

const wrap = _WASI.createSync(
args,
Expand Down Expand Up @@ -311,10 +294,53 @@ export class WASI {
return (_initialize as () => any)()
}
}

getImportObject (): Record<string, Record<string, any>> {
return { [this[kBindingName]]: this.wasiImport }
}
}

function wasiReturnOnProcExit (this: WASI, rval: exitcode): exitcode {
this[kExitCode] = rval
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw kExitCode
}

/** @public */
export async function createAsyncWASI (options: AsyncWASIOptions = kEmptyObject): Promise<WASI> {
const _this = Object.create(WASI.prototype)
const {
args,
env,
preopens,
stdio,
_WASI
} = validateOptions.call(_this, options)

if (options.asyncify !== undefined) {
validateObject(options.asyncify, 'options.asyncify')
validateFunction(options.asyncify.wrapImportFunction, 'options.asyncify.wrapImportFunction')
}

const wrap = await _WASI.createAsync(
args,
env,
preopens,
stdio,
options.fs,
options.print,
options.printErr,
options.asyncify
)

const setMemory = wrap._setMemory!
delete wrap._setMemory
initWASI.call(_this,
setMemory,
wrap
)

if (options.returnOnExit) { wrap.proc_exit = wasiReturnOnProcExit.bind(_this) }

return _this
}
6 changes: 2 additions & 4 deletions test/abort/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

describe('abort', function () {
it('should throw RuntimeError', async function () {
const wasi = wasmUtil.WASI.createSync({
const wasi = new wasmUtil.WASI({
returnOnExit: true
})
const { instance } = await wasmUtil.load('/test/abort/abort.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/abort/abort.wasm', wasi.getImportObject())

assertThrow(() => {
wasi.start(instance)
Expand Down
12 changes: 4 additions & 8 deletions test/assert/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,21 @@ describe('assert', function () {


it('assert false', async function () {
const wasi = wasmUtil.WASI.createSync({
const wasi = new wasmUtil.WASI({
returnOnExit: true
})
const { instance } = await wasmUtil.load('/test/assert/assert_false.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/assert/assert_false.wasm', wasi.getImportObject())

assertThrow(() => {
wasi.start(instance)
}, WebAssembly.RuntimeError, /unreachable/)
})

it('assert true', async function () {
const wasi = wasmUtil.WASI.createSync({
const wasi = new wasmUtil.WASI({
returnOnExit: true
})
const { instance } = await wasmUtil.load('/test/assert/assert_true.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/assert/assert_true.wasm', wasi.getImportObject())

wasi.start(instance)
})
Expand Down
4 changes: 2 additions & 2 deletions test/asyncify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

describe('asyncify', function () {
it('sleep 200ms', async function () {
const wasi = wasmUtil.WASI.createSync({
const wasi = new wasmUtil.WASI({
returnOnExit: true
})

Expand All @@ -16,7 +16,7 @@ describe('asyncify', function () {
})
})
},
wasi_snapshot_preview1: wasi.wasiImport
...wasi.getImportObject()
}
const bytes = await (await fetch('/test/asyncify/asyncify.wasm')).arrayBuffer()
const { instance } = await WebAssembly.instantiate(bytes, imports)
Expand Down
6 changes: 2 additions & 4 deletions test/clock/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

describe('clock', function () {
it('clock', async function () {
const wasi = wasmUtil.WASI.createSync({
const wasi = new wasmUtil.WASI({
returnOnExit: true
})
const { instance } = await wasmUtil.load('/test/clock/clock.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/clock/clock.wasm', wasi.getImportObject())

wasi.start(instance)
})
Expand Down
18 changes: 6 additions & 12 deletions test/directory/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ describe('directory', function () {
const vol = memfs.Volume.fromJSON({
'/fopen-directory-parent-directory.dir': null
})
const wasi = wasmUtil.WASI.createSync({
const wasi = new wasmUtil.WASI({
returnOnExit: true,
preopens: {
'fopen-directory-parent-directory.dir': 'fopen-directory-parent-directory.dir'
},
fs: memfs.createFsFromVolume(vol)
})
const { instance } = await wasmUtil.load('/test/directory/directory.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/directory/directory.wasm', wasi.getImportObject())

wasi.start(instance)
})
Expand All @@ -25,17 +23,15 @@ describe('directory', function () {
'/fopen-directory-parent-directory.dir': null
})
const asyncify = new wasmUtil.Asyncify()
const wasi = await wasmUtil.WASI.createAsync({
const wasi = await wasmUtil.createAsyncWASI({
returnOnExit: true,
preopens: {
'fopen-directory-parent-directory.dir': 'fopen-directory-parent-directory.dir'
},
fs: memfs.createFsFromVolume(vol),
asyncify: asyncify
})
const { instance } = await wasmUtil.load('/test/directory/directory_asyncify.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/directory/directory_asyncify.wasm', wasi.getImportObject())
const wrappedInstance = asyncify.init(instance.exports.memory, instance, {
wrapExports: ['_start']
})
Expand All @@ -47,16 +43,14 @@ describe('directory', function () {
const vol = memfs.Volume.fromJSON({
'/fopen-directory-parent-directory.dir': null
})
const wasi = await wasmUtil.WASI.createAsync({
const wasi = await wasmUtil.createAsyncWASI({
returnOnExit: true,
preopens: {
'fopen-directory-parent-directory.dir': 'fopen-directory-parent-directory.dir'
},
fs: memfs.createFsFromVolume(vol)
})
const { instance } = await wasmUtil.load('/test/directory/directory_jspi.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/directory/directory_jspi.wasm', wasi.getImportObject())
const wrappedInstance = { exports: wasmUtil.wrapExports(instance.exports, ['_start']) }

await wasi.start(wrappedInstance)
Expand Down
12 changes: 4 additions & 8 deletions test/exit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,20 @@ describe('exit', function () {


it('exit failure', async function () {
const wasi = wasmUtil.WASI.createSync({
const wasi = new wasmUtil.WASI({
returnOnExit: true
})
const { instance } = await wasmUtil.load('/test/exit/exit_failure.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/exit/exit_failure.wasm', wasi.getImportObject())

const code = wasi.start(instance)
assert(code === 1)
})

it('exit success', async function () {
const wasi = wasmUtil.WASI.createSync({
const wasi = new wasmUtil.WASI({
returnOnExit: true
})
const { instance } = await wasmUtil.load('/test/exit/exit_success.wasm', {
wasi_snapshot_preview1: wasi.wasiImport
})
const { instance } = await wasmUtil.load('/test/exit/exit_success.wasm', wasi.getImportObject())

const code = wasi.start(instance)
assert(code === 0)
Expand Down
Loading

0 comments on commit 2311766

Please sign in to comment.