From 8fcd645baee24344eaa4169b16ca42b1f0af830c Mon Sep 17 00:00:00 2001 From: jhmaster2000 <32803471+jhmaster2000@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:45:27 -0300 Subject: [PATCH] bun-polyfills: refactor ffi module --- packages/bun-polyfills/src/modules/bun.ts | 2 +- packages/bun-polyfills/src/modules/ffi.ts | 474 +++++++++++----------- packages/bun-polyfills/src/repl.ts | 2 +- 3 files changed, 243 insertions(+), 235 deletions(-) diff --git a/packages/bun-polyfills/src/modules/bun.ts b/packages/bun-polyfills/src/modules/bun.ts index 4e111b781f..cdc37d51ee 100644 --- a/packages/bun-polyfills/src/modules/bun.ts +++ b/packages/bun-polyfills/src/modules/bun.ts @@ -40,7 +40,7 @@ export const main = path.resolve(process.cwd(), process.argv[1] ?? 'repl') satis //? These are automatically updated on build by tools/updateversions.ts, do not edit manually. export const version = '1.0.4' satisfies typeof Bun.version; -export const revision = 'a25bb42416fffaeef837b592bf81cad8b257aa48' satisfies typeof Bun.revision; +export const revision = '91f4ba534be9e23e6d4543738301af393848e954' satisfies typeof Bun.revision; export const gc = (globalThis.gc ? (() => (globalThis.gc!(), process.memoryUsage().heapUsed)) : (() => { const err = new Error('[bun-polyfills] Garbage collection polyfills are only available when Node.js is ran with the --expose-gc flag.'); diff --git a/packages/bun-polyfills/src/modules/ffi.ts b/packages/bun-polyfills/src/modules/ffi.ts index c21b483701..7fea544041 100644 --- a/packages/bun-polyfills/src/modules/ffi.ts +++ b/packages/bun-polyfills/src/modules/ffi.ts @@ -22,12 +22,7 @@ koffi.alias('cstring', 'uint8_t*'); koffi.alias('pointer', 'void*'); koffi.alias('ptr', 'void*'); -function bunffiTypeToKoffiType(type: bunffi.FFITypeOrString = 'void'): string { - if (typeof type === 'number') return ffi.FFIType[type]; - else return type; -} - -enum FFIType { +export enum FFIType { char = 0, i8 = 1, int8_t = 1, @@ -59,6 +54,12 @@ enum FFIType { u64_fast = 16, function = 17, }; +FFIType satisfies typeof bunffi.FFIType; + +function bunffiTypeToKoffiType(type: bunffi.FFITypeOrString = 'void'): string { + if (typeof type === 'number') return FFIType[type]; + else return type; +} /** * Koffi/Node.js don't seem to have a way to get the pointer address of a value, so we have to track them ourselves, @@ -67,240 +68,247 @@ enum FFIType { const ptrsToValues = new Map(); let fakePtr = 4; -const ffi = { - dlopen>>(name: string, symbols: Fns) { - const lib = koffi.load(name); - const outsyms = {} as bunffi.ConvertFns; - for (const [sym, def] of Object.entries(symbols) as [string, bunffi.FFIFunction][]) { - const returnType = bunffiTypeToKoffiType(def.returns); - const argTypes = def.args?.map(bunffiTypeToKoffiType) ?? []; - const rawfn = lib.func( - sym, - returnType, - argTypes, - ); - Reflect.set( - outsyms, - sym, - function(...args: any[]) { - args.forEach((arg, i) => { - if (typeof arg === 'number' && (argTypes[i] === 'ptr' || argTypes[i] === 'pointer')) { - const ptrVal = ptrsToValues.get(arg); - if (!ptrVal) throw new Error( - `Untracked pointer ${arg} in ffi function call ${sym}, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - args[i] = ptrVal; - } - }); - const rawret = rawfn(...args); - if (returnType === 'function' || returnType === 'pointer' || returnType === 'ptr') { - const ptrAddr = Number(koffi.address(rawret)); - ptrsToValues.set(ptrAddr, rawret); - return ptrAddr; +export const suffix = ( + process.platform === 'darwin' ? '.dylib' : + (process.platform === 'win32' ? '.dll' : '.so') +) satisfies typeof bunffi.suffix; + +export const dlopen = (>>(name: string, symbols: Fns) => { + const lib = koffi.load(name); + const outsyms = {} as bunffi.ConvertFns; + for (const [sym, def] of Object.entries(symbols) as [string, bunffi.FFIFunction][]) { + const returnType = bunffiTypeToKoffiType(def.returns); + const argTypes = def.args?.map(bunffiTypeToKoffiType) ?? []; + const rawfn = lib.func( + sym, + returnType, + argTypes, + ); + Reflect.set( + outsyms, + sym, + function (...args: any[]) { + args.forEach((arg, i) => { + if (typeof arg === 'number' && (argTypes[i] === 'ptr' || argTypes[i] === 'pointer')) { + const ptrVal = ptrsToValues.get(arg); + if (!ptrVal) throw new Error( + `Untracked pointer ${arg} in ffi function call ${sym}, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + args[i] = ptrVal; } - if (returnType === 'cstring') { - const ptrAddr = Number(koffi.address(rawret)); - ptrsToValues.set(ptrAddr, rawret); - return new ffi.CString(ptrAddr); - } - return rawret; + }); + const rawret = rawfn(...args); + if (returnType === 'function' || returnType === 'pointer' || returnType === 'ptr') { + const ptrAddr = Number(koffi.address(rawret)); + ptrsToValues.set(ptrAddr, rawret); + return ptrAddr; } - ); + if (returnType === 'cstring') { + const ptrAddr = Number(koffi.address(rawret)); + ptrsToValues.set(ptrAddr, rawret); + return new CString(ptrAddr); + } + return rawret; + } + ); + } + return { + close() { lib.unload(); }, + symbols: outsyms, + }; +}) satisfies typeof bunffi.dlopen; + +export const linkSymbols = (>>(symbols: Fns) => { + const linked = {} as bunffi.ConvertFns; + for (const [sym, def] of Object.entries(symbols) as [string, bunffi.FFIFunction][]) { + if (!def.ptr) throw new Error('ffi.linkSymbols requires a non-null pointer'); + Reflect.set(linked, sym, CFunction(def as typeof def & { ptr: bunffi.Pointer; })); + } + return { + close() { }, + symbols: linked, + }; +}) satisfies typeof bunffi.linkSymbols; + +export const viewSource = ((symsOrCb, isCb) => { + // Impossible to polyfill, but we preserve the important properties of the function: + // 1. Returns string if the 2nd argument is true, or an array of strings if it's false/unset. + // 2. The string array has the same length as there are keys in the given symbols object. + const stub = '/* [native code] */' as const; + return isCb ? stub : Object.keys(symsOrCb).map(() => stub) as any; // any cast to suppress type error due to non-overload syntax +}) satisfies typeof bunffi.viewSource; + +export const toBuffer = ((ptr, bOff, bLen) => { + const arraybuffer = toArrayBuffer(ptr, bOff, bLen); + return Buffer.from(arraybuffer); +}) satisfies typeof bunffi.toBuffer; + +//! Problem: these arraybuffer views are not mapped to the native memory, so they can't be used to modify the memory. +export const toArrayBuffer = ((ptr, byteOff?, byteLen?) => { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.toArrayBuffer, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return view as ArrayBuffer; // ? + if (util.types.isExternal(view)) { + if (byteLen === undefined) { + let bytes = [], byte, off = 0; + do { + byte = koffi.decode(view, off++, 'unsigned char[]', 1); + bytes.push(byte[0]); + } while (byte[0]); + bytes.pop(); + return new Uint8Array(bytes).buffer as ArrayBuffer; // ? + } else { + return koffi.decode(view, byteOff ?? 0, 'unsigned char[]', byteLen).buffer; } - return { - close() { lib.unload(); }, - symbols: outsyms, - }; - }, - linkSymbols>>(symbols: Fns) { - const linked = {} as bunffi.ConvertFns; - for (const [sym, def] of Object.entries(symbols) as [string, bunffi.FFIFunction][]) { - if (!def.ptr) throw new Error('ffi.linkSymbols requires a non-null pointer'); - Reflect.set(linked, sym, ffi.CFunction(def as typeof def & { ptr: bunffi.Pointer })); - } - return { - close() {}, - symbols: linked, - }; - }, - viewSource(symsOrCb, isCb) { - // Impossible to polyfill, but we preserve the important properties of the function: - // 1. Returns string if the 2nd argument is true, or an array of strings if it's false/unset. - // 2. The string array has the same length as there are keys in the given symbols object. - const stub = '/* [native code] */' as const; - return isCb ? stub : Object.keys(symsOrCb).map(() => stub) as any; // any cast to suppress type error due to non-overload syntax - }, - toBuffer(ptr, bOff, bLen) { - const arraybuffer = this.toArrayBuffer(ptr, bOff, bLen); - return Buffer.from(arraybuffer); - }, - //! Problem: these arraybuffer views are not mapped to the native memory, so they can't be used to modify the memory. - toArrayBuffer(ptr, byteOff?, byteLen?) { + } + if (byteOff === undefined) return (view as DataView).buffer; + return (view as DataView).buffer.slice(byteOff, byteOff + (byteLen ?? (view as DataView).byteLength)); +}) satisfies typeof bunffi.toArrayBuffer; + +export const ptr = ((view, byteOffset = 0) => { + const known = [...ptrsToValues.entries()].find(([_, v]) => v === view); + if (known) return known[0]; + const ptr = fakePtr; + fakePtr += (view.byteLength + 3) & ~0x3; + if (!byteOffset) { + ptrsToValues.set(ptr, view); + return ptr; + } else { + const view2 = new DataView( + (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) ? view : view.buffer, + byteOffset, view.byteLength + ); + ptrsToValues.set(ptr + byteOffset, view2); + return ptr + byteOffset; + } +}) satisfies typeof bunffi.ptr; + +export const CFunction = ((sym): CallableFunction & { close(): void; } => { + if (!sym.ptr) throw new Error('ffi.CFunction requires a non-null pointer'); + const fnName = `anonymous__${sym.ptr.toString(16).replaceAll('.', '_')}`; + const fnSig = koffi.proto(fnName, bunffiTypeToKoffiType(sym.returns), sym.args?.map(bunffiTypeToKoffiType) ?? []); + const fnPtr = ptrsToValues.get(sym.ptr); + if (!fnPtr) throw new Error( + `Untracked pointer ${sym.ptr} in ffi.CFunction, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + const fn = koffi.decode(fnPtr, fnSig); + fn.close = () => { }; + return fn; +}) satisfies typeof bunffi.CFunction; + +export class CString extends String implements bunffi.CString { + constructor(ptr: bunffi.Pointer, bOff?: number, bLen?: number) { + const buf = toBuffer(ptr, bOff, bLen); + const str = buf.toString('ascii'); + super(str); + this.ptr = ptr; + this.#buffer = buf.buffer as ArrayBuffer; // ? + } + close() { }; + ptr: bunffi.Pointer; + byteOffset?: number; + byteLength?: number; + #buffer: ArrayBuffer; + get arrayBuffer(): ArrayBuffer { return this.#buffer; }; +}; + +// TODO +export class JSCallback implements bunffi.JSCallback { + constructor(cb: (...args: any[]) => any, def: bunffi.FFIFunction) { } + readonly ptr!: bunffi.Pointer | null; + readonly threadsafe!: boolean; + close() { }; +}; + +export const read = { + f32(ptr, bOff = 0) { const view = ptrsToValues.get(ptr); if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.toArrayBuffer, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + `Untracked pointer ${ptr} in ffi.read.f32, this polyfill is limited to pointers obtained through the same instance of the ffi module.` ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return view as ArrayBuffer; // ? - if (util.types.isExternal(view)) { - if (byteLen === undefined) { - let bytes = [], byte, off = 0; - do { - byte = koffi.decode(view, off++, 'unsigned char[]', 1); - bytes.push(byte[0]); - } while (byte[0]); - bytes.pop(); - return new Uint8Array(bytes).buffer as ArrayBuffer; // ? - } else { - return koffi.decode(view, byteOff ?? 0, 'unsigned char[]', byteLen).buffer; - } - } - if (byteOff === undefined) return (view as DataView).buffer; - return (view as DataView).buffer.slice(byteOff, byteOff + (byteLen ?? (view as DataView).byteLength)); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getFloat32(bOff, LE); + return koffi.decode(view, bOff, 'f32'); }, - ptr(view, byteOffset = 0) { - const known = [...ptrsToValues.entries()].find(([_, v]) => v === view); - if (known) return known[0]; - const ptr = fakePtr; - fakePtr += (view.byteLength + 3) & ~0x3; - if (!byteOffset) { - ptrsToValues.set(ptr, view); - return ptr; - } else { - const view2 = new DataView( - (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) ? view : view.buffer, - byteOffset, view.byteLength - ); - ptrsToValues.set(ptr + byteOffset, view2); - return ptr + byteOffset; - } - }, - read: { - f32(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.f32, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getFloat32(bOff, LE); - return koffi.decode(view, bOff, 'f32'); - }, - f64(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.f64, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getFloat64(bOff, LE); - return koffi.decode(view, bOff, 'f64'); - }, - i8(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.i8, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getInt8(bOff); - return koffi.decode(view, bOff, 'i8'); - }, - i16(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.i16, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getInt16(bOff, LE); - return koffi.decode(view, bOff, 'i16'); - }, - i32(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.i32, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getInt32(bOff, LE); - return koffi.decode(view, bOff, 'i32'); - }, - i64(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.i64, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getBigInt64(bOff, LE); - return koffi.decode(view, bOff, 'i64'); - }, - intptr(ptr, bOff = 0) { - return this.i32(ptr, bOff); - }, - ptr(ptr, bOff = 0) { - const u64 = this.u64(ptr, bOff); - const masked = u64 & 0b11111111_11111111_11111111_11111111_11111111_11111111_00000111_00000000n; - return Number(masked); - }, - u8(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.u8, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getUint8(bOff); - return koffi.decode(view, bOff, 'u8'); - }, - u16(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.u16, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getUint16(bOff, LE); - return koffi.decode(view, bOff, 'u16'); - }, - u32(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.u32, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getUint32(bOff, LE); - return koffi.decode(view, bOff, 'u32'); - }, - u64(ptr, bOff = 0) { - const view = ptrsToValues.get(ptr); - if (!view) throw new Error( - `Untracked pointer ${ptr} in ffi.read.u64, this polyfill is limited to pointers obtained through the same instance of the ffi module.` - ); - if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getBigUint64(bOff, LE); - return koffi.decode(view, bOff, 'u64'); - }, - }, - suffix: - process.platform === 'darwin' ? '.dylib' : - (process.platform === 'win32' ? '.dll' : '.so'), - CString: class CString extends String implements bunffi.CString { - constructor(ptr: bunffi.Pointer, bOff?: number, bLen?: number) { - const buf = ffi.toBuffer(ptr, bOff, bLen); - const str = buf.toString('ascii'); - super(str); - this.ptr = ptr; - this.#buffer = buf.buffer as ArrayBuffer; // ? - } - close() {}; - ptr: bunffi.Pointer; - byteOffset?: number; - byteLength?: number; - #buffer: ArrayBuffer; - get arrayBuffer(): ArrayBuffer { return this.#buffer; }; - }, - CFunction(sym): CallableFunction & { close(): void; } { - if (!sym.ptr) throw new Error('ffi.CFunction requires a non-null pointer'); - const fnName = `anonymous__${sym.ptr.toString(16).replaceAll('.', '_')}` - const fnSig = koffi.proto(fnName, bunffiTypeToKoffiType(sym.returns), sym.args?.map(bunffiTypeToKoffiType) ?? []); - const fnPtr = ptrsToValues.get(sym.ptr); - if (!fnPtr) throw new Error( - `Untracked pointer ${sym.ptr} in ffi.CFunction, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + f64(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.f64, this polyfill is limited to pointers obtained through the same instance of the ffi module.` ); - const fn = koffi.decode(fnPtr, fnSig); - fn.close = () => {}; - return fn; + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getFloat64(bOff, LE); + return koffi.decode(view, bOff, 'f64'); }, - // TODO - JSCallback: class JSCallback implements bunffi.JSCallback { - constructor(cb: (...args: any[]) => any, def: bunffi.FFIFunction) {} - readonly ptr!: bunffi.Pointer | null; - readonly threadsafe!: boolean; - close() {}; + i8(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.i8, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getInt8(bOff); + return koffi.decode(view, bOff, 'i8'); }, - FFIType, -} satisfies typeof bunffi; -export default ffi; + i16(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.i16, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getInt16(bOff, LE); + return koffi.decode(view, bOff, 'i16'); + }, + i32(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.i32, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getInt32(bOff, LE); + return koffi.decode(view, bOff, 'i32'); + }, + i64(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.i64, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getBigInt64(bOff, LE); + return koffi.decode(view, bOff, 'i64'); + }, + intptr(ptr, bOff = 0) { + return this.i32(ptr, bOff); + }, + ptr(ptr, bOff = 0) { + const u64 = this.u64(ptr, bOff); + const masked = u64 & 0b11111111_11111111_11111111_11111111_11111111_11111111_00000111_00000000n; + return Number(masked); + }, + u8(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.u8, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getUint8(bOff); + return koffi.decode(view, bOff, 'u8'); + }, + u16(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.u16, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getUint16(bOff, LE); + return koffi.decode(view, bOff, 'u16'); + }, + u32(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.u32, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getUint32(bOff, LE); + return koffi.decode(view, bOff, 'u32'); + }, + u64(ptr, bOff = 0) { + const view = ptrsToValues.get(ptr); + if (!view) throw new Error( + `Untracked pointer ${ptr} in ffi.read.u64, this polyfill is limited to pointers obtained through the same instance of the ffi module.` + ); + if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return new DataView(view).getBigUint64(bOff, LE); + return koffi.decode(view, bOff, 'u64'); + }, +} satisfies typeof bunffi.read; diff --git a/packages/bun-polyfills/src/repl.ts b/packages/bun-polyfills/src/repl.ts index 2294e87713..da58f6d29b 100644 --- a/packages/bun-polyfills/src/repl.ts +++ b/packages/bun-polyfills/src/repl.ts @@ -26,4 +26,4 @@ globalThis.Bun = bun as typeof bun & { }; Reflect.set(globalThis, 'jsc', jsc); -Reflect.set(globalThis, 'ffi', ffi.default); +Reflect.set(globalThis, 'ffi', ffi);