From a25bb42416fffaeef837b592bf81cad8b257aa48 Mon Sep 17 00:00:00 2001 From: jhmaster2000 <32803471+jhmaster2000@users.noreply.github.com> Date: Mon, 9 Oct 2023 21:57:26 -0300 Subject: [PATCH] more bun:ffi polyfill progress --- packages/bun-polyfills/src/modules/bun.ts | 2 +- packages/bun-polyfills/src/modules/ffi.ts | 159 +++++++++++++++++++--- 2 files changed, 139 insertions(+), 22 deletions(-) diff --git a/packages/bun-polyfills/src/modules/bun.ts b/packages/bun-polyfills/src/modules/bun.ts index fed93a07f5..2e5d1211c6 100644 --- a/packages/bun-polyfills/src/modules/bun.ts +++ b/packages/bun-polyfills/src/modules/bun.ts @@ -34,7 +34,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 = 'b510da04d1a1467d628124732295325752433e72' satisfies typeof Bun.revision; +export const revision = '3ee6aa803b12d393996b72b5f30c023bf6e1759d' 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 4329c6ec6a..c21b483701 100644 --- a/packages/bun-polyfills/src/modules/ffi.ts +++ b/packages/bun-polyfills/src/modules/ffi.ts @@ -1,6 +1,9 @@ +import { endianness } from 'node:os'; import util from 'node:util'; import koffi from 'koffi'; -import bunffi from 'bun:ffi'; +import type bunffi from 'bun:ffi'; + +const LE = endianness() === 'LE'; koffi.alias('f32', 'float'); koffi.alias('f64', 'double'); @@ -15,7 +18,7 @@ koffi.alias('u64', 'uint64_t'); koffi.alias('usize', 'uint64_t'); koffi.alias('callback', 'void*'); koffi.alias('function', 'void*'); -koffi.alias('cstring', 'void*'); +koffi.alias('cstring', 'uint8_t*'); koffi.alias('pointer', 'void*'); koffi.alias('ptr', 'void*'); @@ -95,6 +98,11 @@ const ffi = { ptrsToValues.set(ptrAddr, rawret); return ptrAddr; } + if (returnType === 'cstring') { + const ptrAddr = Number(koffi.address(rawret)); + ptrsToValues.set(ptrAddr, rawret); + return new ffi.CString(ptrAddr); + } return rawret; } ); @@ -104,8 +112,16 @@ const ffi = { symbols: outsyms, }; }, - linkSymbols(lib) { - return this.dlopen('', lib); // TODO + 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: @@ -114,7 +130,11 @@ const ffi = { 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) { return Buffer.alloc(0); }, // TODO + 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?) { const view = ptrsToValues.get(ptr); if (!view) throw new Error( @@ -122,13 +142,17 @@ const ffi = { ); if (view instanceof ArrayBuffer || view instanceof SharedArrayBuffer) return view as ArrayBuffer; // ? if (util.types.isExternal(view)) { - 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; // ? + 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)); @@ -150,20 +174,113 @@ const ffi = { return ptr + byteOffset; } }, - read: 0 as any, // TODO + 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'), - // TODO CString: class CString extends String implements bunffi.CString { - constructor(str: bunffi.Pointer, bOff?: number, bLen?: number) { + 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; + close() {}; + ptr: bunffi.Pointer; byteOffset?: number; byteLength?: number; - get arrayBuffer(): ArrayBuffer { return new ArrayBuffer(0); }; + #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'); @@ -174,15 +291,15 @@ const ffi = { `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 = () => koffi.unregister(fn); + fn.close = () => {}; return fn; }, // TODO JSCallback: class JSCallback implements bunffi.JSCallback { - constructor(cb: (...args: any[]) => any, def: bunffi.FFIFunction) { } + constructor(cb: (...args: any[]) => any, def: bunffi.FFIFunction) {} readonly ptr!: bunffi.Pointer | null; readonly threadsafe!: boolean; - close() { }; + close() {}; }, FFIType, } satisfies typeof bunffi;