Skip to content

Commit

Permalink
feat(ext/ffi): implement UnsafePointer and UnsafePointerView (denolan…
Browse files Browse the repository at this point in the history
  • Loading branch information
eliassjogreen authored Dec 15, 2021
1 parent 4d176b7 commit ee49cce
Show file tree
Hide file tree
Showing 9 changed files with 591 additions and 52 deletions.
80 changes: 77 additions & 3 deletions cli/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,90 @@ declare namespace Deno {
| "usize"
| "isize"
| "f32"
| "f64";
| "f64"
| "pointer";

/** A foreign function as defined by its parameter and result types */
export interface ForeignFunction {
parameters: (NativeType | "buffer")[];
parameters: NativeType[];
result: NativeType;
/** When true, function calls will run on a dedicated blocking thread and will return a Promise resolving to the `result`. */
nonblocking?: boolean;
}

type TypedArray =
| Int8Array
| Uint8Array
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Uint8ClampedArray
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array;

/** **UNSTABLE**: Unsafe and new API, beware!
*
* An unsafe pointer to a memory location for passing and returning pointers to and from the ffi
*/
export class UnsafePointer {
constructor(value: bigint);

value: bigint;

/**
* Return the direct memory pointer to the typed array in memory
*/
static of(typedArray: TypedArray): UnsafePointer;

/**
* Returns the value of the pointer which is useful in certain scenarios.
*/
valueOf(): bigint;
}

/** **UNSTABLE**: Unsafe and new API, beware!
*
* An unsafe pointer view to a memory location as specified by the `pointer`
* value. The `UnsafePointerView` API mimics the standard built in interface
* `DataView` for accessing the underlying types at an memory location
* (numbers, strings and raw bytes).
*/
export class UnsafePointerView {
constructor(pointer: UnsafePointer);

pointer: UnsafePointer;

/** Gets an unsigned 8-bit integer at the specified byte offset from the pointer. */
getUint8(offset?: number): number;
/** Gets a signed 8-bit integer at the specified byte offset from the pointer. */
getInt8(offset?: number): number;
/** Gets an unsigned 16-bit integer at the specified byte offset from the pointer. */
getUint16(offset?: number): number;
/** Gets a signed 16-bit integer at the specified byte offset from the pointer. */
getInt16(offset?: number): number;
/** Gets an unsigned 32-bit integer at the specified byte offset from the pointer. */
getUint32(offset?: number): number;
/** Gets a signed 32-bit integer at the specified byte offset from the pointer. */
getInt32(offset?: number): number;
/** Gets an unsigned 64-bit integer at the specified byte offset from the pointer. */
getBigUint64(offset?: number): bigint;
/** Gets a signed 64-bit integer at the specified byte offset from the pointer. */
getBigInt64(offset?: number): bigint;
/** Gets a signed 32-bit float at the specified byte offset from the pointer. */
getFloat32(offset?: number): number;
/** Gets a signed 64-bit float at the specified byte offset from the pointer. */
getFloat64(offset?: number): number;
/** Gets a C string (null terminated string) at the specified byte offset from the pointer. */
getCString(offset?: number): string;
/** Gets an ArrayBuffer of length `byteLength` at the specified byte offset from the pointer. */
getArrayBuffer(byteLength: number, offset?: number): ArrayBuffer;
/** Copies the memory of the pointer into a typed array. Length is determined from the typed array's `byteLength`. Also takes optional offset from the pointer. */
copyInto(destination: TypedArray, offset?: number): void;
}

/** A dynamic library resource */
export interface DynamicLibrary<S extends Record<string, ForeignFunction>> {
/** All of the registered symbols along with functions for calling them */
Expand All @@ -135,7 +209,7 @@ declare namespace Deno {
close(): void;
}

/** **UNSTABLE**: new API
/** **UNSTABLE**: Unsafe and new API, beware!
*
* Opens a dynamic library and registers symbols
*/
Expand Down
185 changes: 175 additions & 10 deletions ext/ffi/00_ffi.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,142 @@
const __bootstrap = window.__bootstrap;
const {
ArrayBuffer,
Uint8Array,
BigInt,
Number,
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;
}
}

class DynamicLibrary {
#rid;
symbols = {};
Expand All @@ -16,37 +151,67 @@

for (const symbol in symbols) {
const isNonBlocking = symbols[symbol].nonblocking;
const types = symbols[symbol].parameters;

this.symbols[symbol] = (...args) => {
const parameters = [];
const buffers = [];

for (const arg of args) {
if (
arg?.buffer instanceof ArrayBuffer &&
arg.byteLength !== undefined
) {
parameters.push(buffers.length);
buffers.push(arg);
for (let i = 0; i < types.length; i++) {
const type = types[i];
const arg = args[i];

if (type === "pointer") {
if (
arg?.buffer instanceof ArrayBuffer &&
arg.byteLength !== undefined
) {
parameters.push(buffers.length);
buffers.push(arg);
} else if (arg instanceof UnsafePointer) {
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);
}
}

if (isNonBlocking) {
return core.opAsync("op_ffi_call_nonblocking", {
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 {
return core.opSync("op_ffi_call", {
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;
}
};
}
Expand All @@ -63,5 +228,5 @@
return new DynamicLibrary(pathFromURL(path), symbols);
}

window.__bootstrap.ffi = { dlopen };
window.__bootstrap.ffi = { dlopen, UnsafePointer, UnsafePointerView };
})(this);
Loading

0 comments on commit ee49cce

Please sign in to comment.