deno/ext/ffi/00_ffi.js

339 lines
7.9 KiB
JavaScript

// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => {
const core = window.Deno.core;
const __bootstrap = window.__bootstrap;
const {
ArrayBufferPrototype,
Uint8Array,
BigInt,
Number,
ObjectDefineProperty,
ObjectPrototypeIsPrototypeOf,
TypeError,
} = window.__bootstrap.primordials;
function unpackU64([hi, lo]) {
return BigInt(hi) << 32n | BigInt(lo);
}
function packU64(value) {
return [Number(value >> 32n), Number(value & 0xFFFFFFFFn)];
}
function unpackI64([hi, lo]) {
const u64 = unpackU64([hi, lo]);
return u64 >> 63n ? u64 - 0x10000000000000000n : u64;
}
class UnsafePointerView {
pointer;
constructor(pointer) {
this.pointer = pointer;
}
getUint8(offset = 0) {
return core.opSync(
"op_ffi_read_u8",
packU64(this.pointer.value + BigInt(offset)),
);
}
getInt8(offset = 0) {
return core.opSync(
"op_ffi_read_i8",
packU64(this.pointer.value + BigInt(offset)),
);
}
getUint16(offset = 0) {
return core.opSync(
"op_ffi_read_u16",
packU64(this.pointer.value + BigInt(offset)),
);
}
getInt16(offset = 0) {
return core.opSync(
"op_ffi_read_i16",
packU64(this.pointer.value + BigInt(offset)),
);
}
getUint32(offset = 0) {
return core.opSync(
"op_ffi_read_u32",
packU64(this.pointer.value + BigInt(offset)),
);
}
getInt32(offset = 0) {
return core.opSync(
"op_ffi_read_i32",
packU64(this.pointer.value + BigInt(offset)),
);
}
getBigUint64(offset = 0) {
return unpackU64(core.opSync(
"op_ffi_read_u64",
packU64(this.pointer.value + BigInt(offset)),
));
}
getBigInt64(offset = 0) {
return unpackI64(core.opSync(
"op_ffi_read_u64",
packU64(this.pointer.value + BigInt(offset)),
));
}
getFloat32(offset = 0) {
return core.opSync(
"op_ffi_read_f32",
packU64(this.pointer.value + BigInt(offset)),
);
}
getFloat64(offset = 0) {
return core.opSync(
"op_ffi_read_f64",
packU64(this.pointer.value + BigInt(offset)),
);
}
getCString(offset = 0) {
return core.opSync(
"op_ffi_cstr_read",
packU64(this.pointer.value + BigInt(offset)),
);
}
getArrayBuffer(byteLength, offset = 0) {
const uint8array = new Uint8Array(byteLength);
this.copyInto(uint8array, offset);
return uint8array.buffer;
}
copyInto(destination, offset = 0) {
core.opSync("op_ffi_buf_copy_into", [
packU64(this.pointer.value + BigInt(offset)),
destination,
destination.byteLength,
]);
}
}
class UnsafePointer {
value;
constructor(value) {
this.value = value;
}
static of(typedArray) {
return new UnsafePointer(
unpackU64(core.opSync("op_ffi_ptr_of", typedArray)),
);
}
valueOf() {
return this.value;
}
}
const UnsafePointerPrototype = UnsafePointer.prototype;
function prepareArgs(types, args) {
const parameters = [];
const buffers = [];
for (let i = 0; i < types.length; i++) {
const type = types[i];
const arg = args[i];
if (type === "pointer") {
if (
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, arg?.buffer) &&
arg.byteLength !== undefined
) {
parameters.push(buffers.length);
buffers.push(arg);
} else if (ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, arg)) {
parameters.push(packU64(arg.value));
buffers.push(undefined);
} else if (arg === null) {
parameters.push(null);
buffers.push(undefined);
} else {
throw new TypeError(
"Invalid ffi arg value, expected TypedArray, UnsafePointer or null",
);
}
} else {
parameters.push(arg);
}
}
return { parameters, buffers };
}
class UnsafeFnPointer {
pointer;
definition;
constructor(pointer, definition) {
this.pointer = pointer;
this.definition = definition;
}
call(...args) {
const { parameters, buffers } = prepareArgs(
this.definition.parameters,
args,
);
if (this.definition.nonblocking) {
const promise = core.opAsync("op_ffi_call_ptr_nonblocking", {
pointer: packU64(this.pointer.value),
def: this.definition,
parameters,
buffers,
});
if (this.definition.result === "pointer") {
return promise.then((value) => new UnsafePointer(unpackU64(value)));
}
return promise;
} else {
const result = core.opSync("op_ffi_call_ptr", {
pointer: packU64(this.pointer.value),
def: this.definition,
parameters,
buffers,
});
if (this.definition.result === "pointer") {
return new UnsafePointer(unpackU64(result));
}
return result;
}
}
}
class DynamicLibrary {
#rid;
symbols = {};
constructor(path, symbols) {
this.#rid = core.opSync("op_ffi_load", { path, symbols });
for (const symbol in symbols) {
if ("type" in symbols[symbol]) {
const type = symbols[symbol].type;
if (type === "void") {
throw new TypeError(
"Foreign symbol of type 'void' is not supported.",
);
}
const name = symbols[symbol].name || symbol;
let value = core.opSync(
"op_ffi_get_static",
{
rid: this.#rid,
name,
type,
},
);
if (type === "pointer" || type === "u64") {
value = unpackU64(value);
if (type === "pointer") {
value = new UnsafePointer(value);
}
} else if (type === "i64") {
value = unpackI64(value);
}
ObjectDefineProperty(
this.symbols,
symbol,
{
configurable: false,
enumerable: true,
value,
writable: false,
},
);
continue;
}
const isNonBlocking = symbols[symbol].nonblocking;
const types = symbols[symbol].parameters;
const fn = (...args) => {
const { parameters, buffers } = prepareArgs(types, args);
if (isNonBlocking) {
const promise = core.opAsync("op_ffi_call_nonblocking", {
rid: this.#rid,
symbol,
parameters,
buffers,
});
if (symbols[symbol].result === "pointer") {
return promise.then((value) =>
new UnsafePointer(unpackU64(value))
);
}
return promise;
} else {
const result = core.opSync("op_ffi_call", {
rid: this.#rid,
symbol,
parameters,
buffers,
});
if (symbols[symbol].result === "pointer") {
return new UnsafePointer(unpackU64(result));
}
return result;
}
};
ObjectDefineProperty(
this.symbols,
symbol,
{
configurable: false,
enumerable: true,
value: fn,
writable: false,
},
);
}
}
close() {
core.close(this.#rid);
}
}
function dlopen(path, symbols) {
// URL support is progressively enhanced by util in `runtime/js`.
const pathFromURL = __bootstrap.util.pathFromURL ?? ((p) => p);
return new DynamicLibrary(pathFromURL(path), symbols);
}
window.__bootstrap.ffi = {
dlopen,
UnsafePointer,
UnsafePointerView,
UnsafeFnPointer,
};
})(this);