diff options
Diffstat (limited to 'src/bun.js/ffi.exports.js')
-rw-r--r-- | src/bun.js/ffi.exports.js | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/src/bun.js/ffi.exports.js b/src/bun.js/ffi.exports.js new file mode 100644 index 000000000..1617ffc52 --- /dev/null +++ b/src/bun.js/ffi.exports.js @@ -0,0 +1,320 @@ +// --- FFIType --- + +export const ptr = globalThis.Bun.FFI.ptr; +export const toBuffer = globalThis.Bun.FFI.toBuffer; +export const toArrayBuffer = globalThis.Bun.FFI.toArrayBuffer; +export const viewSource = globalThis.Bun.FFI.viewSource; + +const BunCString = globalThis.Bun.FFI.CString; +const nativeLinkSymbols = globalThis.Bun.FFI.linkSymbols; + +export class CString extends String { + constructor(ptr, byteOffset, byteLength) { + super( + ptr + ? typeof byteLength === "number" && Number.isSafeInteger(byteLength) + ? new BunCString(ptr, byteOffset || 0, byteLength) + : new BunCString(ptr) + : "" + ); + this.ptr = typeof ptr === "number" ? ptr : 0; + if (typeof byteOffset !== "undefined") { + this.byteOffset = byteOffset; + } + if (typeof byteLength !== "undefined") { + this.byteLength = byteLength; + } + } + + ptr; + byteOffset; + byteLength; + #cachedArrayBuffer; + + get arrayBuffer() { + if (this.#cachedArrayBuffer) { + return this.#cachedArrayBuffer; + } + + if (!this.ptr) { + return (this.#cachedArrayBuffer = new ArrayBuffer(0)); + } + + return (this.#cachedArrayBuffer = toArrayBuffer( + this.ptr, + this.byteOffset, + this.byteLength + )); + } +} +Object.defineProperty(globalThis, "__GlobalBunCString", { + value: CString, + enumerable: false, + configurable: false, +}); + +const ffiWrappers = new Array(16); +var char = (val) => val | 0; +ffiWrappers.fill(char); +ffiWrappers[FFIType.uint8_t] = function uint8(val) { + return val < 0 ? 0 : val >= 255 ? 255 : val | 0; +}; +ffiWrappers[FFIType.int16_t] = function int16(val) { + return val <= -8192 ? -8192 : val >= 8192 ? 8192 : val | 0; +}; +ffiWrappers[FFIType.uint16_t] = function uint16(val) { + return val <= 0 ? 0 : val >= 16384 ? 16384 : val | 0; +}; +ffiWrappers[FFIType.int32_t] = function int32(val) { + return val | 0; +}; +// we never want to return NaN +ffiWrappers[FFIType.uint32_t] = function uint32(val) { + return val <= 0 ? 0 : val >= 0xffffffff ? 0xffffffff : +val || 0; +}; +ffiWrappers[FFIType.i64_fast] = function int64(val) { + if (typeof val === "bigint") { + if (val < BigInt(Number.MAX_VALUE)) { + return Number(val).valueOf() || 0; + } + + return val; + } + + return !val ? 0 : +val || 0; +}; + +ffiWrappers[FFIType.u64_fast] = function u64_fast(val) { + if (typeof val === "bigint") { + if (val < BigInt(Number.MAX_VALUE) && val > 0) { + return Number(val).valueOf() || 0; + } + + return val; + } + + return !val ? 0 : +val || 0; +}; + +ffiWrappers[FFIType.int64_t] = function int64(val) { + if (typeof val === "bigint") { + return val; + } + + if (typeof val === "number") { + return BigInt(val || 0); + } + + return BigInt(+val || 0); +}; + +ffiWrappers[FFIType.uint64_t] = function uint64(val) { + if (typeof val === "bigint") { + return val; + } + + if (typeof val === "number") { + return val <= 0 ? BigInt(0) : BigInt(val || 0); + } + + return BigInt(+val || 0); +}; + +ffiWrappers[FFIType.u64_fast] = function u64_fast(val) { + if (typeof val === "bigint") { + return val < BigInt(Number.MAX_VALUE) + ? val <= BigInt(0) + ? 0 + : Number(val) + : val; + } + + return typeof val === "number" ? (val <= 0 ? 0 : +val || 0) : +val || 0; +}; + +ffiWrappers[FFIType.uint16_t] = function uint16(val) { + const ret = (typeof val === "bigint" ? Number(val) : val) | 0; + return ret <= 0 ? 0 : ret > 0xffff ? 0xffff : ret; +}; + +ffiWrappers[FFIType.double] = function double(val) { + if (typeof val === "bigint") { + if (val.valueOf() < BigInt(Number.MAX_VALUE)) { + return ( + Math.abs(Number(val).valueOf()) + 0.00000000000001 - 0.00000000000001 + ); + } + } + + if (!val) { + return 0 + 0.00000000000001 - 0.00000000000001; + } + + return val + 0.00000000000001 - 0.00000000000001; +}; + +ffiWrappers[FFIType.float] = ffiWrappers[10] = function float(val) { + return Math.fround(val); +}; +ffiWrappers[FFIType.bool] = function bool(val) { + return !!val; +}; + +ffiWrappers[FFIType.cstring] = ffiWrappers[FFIType.pointer] = function pointer( + val +) { + if (typeof val === "number") return val; + if (!val) { + return 0; + } + + if (ArrayBuffer.isView(val) || val instanceof ArrayBuffer) { + return ptr(val); + } + + if (typeof val === "string") { + throw new TypeError( + "To convert a string to a pointer, encode it as a buffer" + ); + } + + throw new TypeError(`Unable to convert ${val} to a pointer`); +}; + +function cstringReturnType(val) { + return new __GlobalBunCString(val); +} + +function FFIBuilder(params, returnType, functionToCall, name) { + const hasReturnType = + typeof FFIType[returnType] === "number" && + FFIType[returnType] !== FFIType.void; + var paramNames = new Array(params.length); + var args = new Array(params.length); + for (let i = 0; i < params.length; i++) { + paramNames[i] = `p${i}`; + const wrapper = ffiWrappers[FFIType[params[i]]]; + if (wrapper) { + // doing this inline benchmarked about 4x faster than referencing + args[i] = `(${wrapper.toString()})(p${i})`; + } else { + throw new TypeError( + `Unsupported type ${params[i]}. Must be one of: ${Object.keys(FFIType) + .sort() + .join(", ")}` + ); + } + } + + var code = `functionToCall(${args.join(", ")})`; + if (hasReturnType) { + if (FFIType[returnType] === FFIType.cstring) { + code = `return (${cstringReturnType.toString()})(${code})`; + } else { + code = `return ${code}`; + } + } + + var func = new Function("functionToCall", ...paramNames, code); + Object.defineProperty(func, "name", { + value: name, + }); + const wrap = (...args) => func(functionToCall, ...args); + wrap.native = functionToCall; + + return wrap; +} + +const nativeDLOpen = globalThis.Bun.FFI.dlopen; +const nativeCallback = globalThis.Bun.FFI.callback; +export const native = { + dlopen: nativeDLOpen, + callback: nativeCallback, +}; + +export function dlopen(path, options) { + const result = nativeDLOpen(path, options); + + for (let key in result.symbols) { + var symbol = result.symbols[key]; + if ( + options[key]?.args?.length || + FFIType[options[key]?.returns] === FFIType.cstring + ) { + result.symbols[key] = FFIBuilder( + options[key].args ?? [], + options[key].returns ?? FFIType.void, + symbol, + // in stacktraces: + // instead of + // "/usr/lib/sqlite3.so" + // we want + // "sqlite3_get_version() - sqlit3.so" + path.includes("/") + ? `${key} (${path.split("/").pop()})` + : `${key} (${path})` + ); + } else { + // consistentcy + result.symbols[key].native = result.symbols[key]; + } + } + + return result; +} + +export function linkSymbols(options) { + const result = nativeLinkSymbols(options); + + for (let key in result.symbols) { + var symbol = result.symbols[key]; + if ( + options[key]?.args?.length || + FFIType[options[key]?.returns] === FFIType.cstring + ) { + result.symbols[key] = FFIBuilder( + options[key].args ?? [], + options[key].returns ?? FFIType.void, + symbol, + key + ); + } else { + // consistentcy + result.symbols[key].native = result.symbols[key]; + } + } + + return result; +} + +var cFunctionI = 0; +var cFunctionRegistry; +function onCloseCFunction(close) { + close(); +} +export function CFunction(options) { + const identifier = `CFunction${cFunctionI++}`; + var result = linkSymbols({ + [identifier]: options, + }); + var hasClosed = false; + var close = result.close; + result.symbols[identifier].close = () => { + if (hasClosed || !close) return; + hasClosed = true; + close(); + close = undefined; + }; + + cFunctionRegistry ||= new FinalizationRegistry(onCloseCFunction); + cFunctionRegistry.register( + result.symbols[identifier], + result.symbols[identifier].close + ); + + return result.symbols[identifier]; +} + +export function callback(options, cb) { + return nativeCallback(options, cb); +} |