summaryrefslogtreecommitdiff
path: root/ext/ffi/00_ffi.js
diff options
context:
space:
mode:
authorDj <43033058+DjDeveloperr@users.noreply.github.com>2023-01-07 19:58:10 -0800
committerGitHub <noreply@github.com>2023-01-08 09:28:10 +0530
commitad82918f56b215a428ebe7c533ee825e1152d1b4 (patch)
tree2669f9d8d419a88e9d58b12b98579e884aec6988 /ext/ffi/00_ffi.js
parent84ef26ac9b5f0e1199d77837cd97cb203baa8729 (diff)
feat(ext/ffi): structs by value (#15060)
Adds support for passing and returning structs as buffers to FFI. This does not implement fastapi support for structs. Needed for certain system APIs such as AppKit on macOS.
Diffstat (limited to 'ext/ffi/00_ffi.js')
-rw-r--r--ext/ffi/00_ffi.js157
1 files changed, 140 insertions, 17 deletions
diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js
index 66ac315b3..5ebd3f96c 100644
--- a/ext/ffi/00_ffi.js
+++ b/ext/ffi/00_ffi.js
@@ -14,12 +14,18 @@
Number,
NumberIsSafeInteger,
TypeError,
+ Uint8Array,
Int32Array,
Uint32Array,
BigInt64Array,
BigUint64Array,
Function,
ReflectHas,
+ PromisePrototypeThen,
+ MathMax,
+ MathCeil,
+ Map,
+ SafeArrayIterator,
} = window.__bootstrap.primordials;
const U32_BUFFER = new Uint32Array(2);
@@ -181,26 +187,55 @@
class UnsafeFnPointer {
pointer;
definition;
+ #structSize;
constructor(pointer, definition) {
this.pointer = pointer;
this.definition = definition;
+ this.#structSize = isStruct(definition.result)
+ ? getTypeSizeAndAlignment(definition.result)[0]
+ : null;
}
call(...parameters) {
if (this.definition.nonblocking) {
- return core.opAsync(
- "op_ffi_call_ptr_nonblocking",
- this.pointer,
- this.definition,
- parameters,
- );
+ if (this.#structSize === null) {
+ return core.opAsync(
+ "op_ffi_call_ptr_nonblocking",
+ this.pointer,
+ this.definition,
+ parameters,
+ );
+ } else {
+ const buffer = new Uint8Array(this.#structSize);
+ return PromisePrototypeThen(
+ core.opAsync(
+ "op_ffi_call_ptr_nonblocking",
+ this.pointer,
+ this.definition,
+ parameters,
+ buffer,
+ ),
+ () => buffer,
+ );
+ }
} else {
- return ops.op_ffi_call_ptr(
- this.pointer,
- this.definition,
- parameters,
- );
+ if (this.#structSize === null) {
+ return ops.op_ffi_call_ptr(
+ this.pointer,
+ this.definition,
+ parameters,
+ );
+ } else {
+ const buffer = new Uint8Array(this.#structSize);
+ ops.op_ffi_call_ptr(
+ this.pointer,
+ this.definition,
+ parameters,
+ buffer,
+ );
+ return buffer;
+ }
}
}
}
@@ -215,6 +250,60 @@
return type === "i64" || type === "isize";
}
+ function isStruct(type) {
+ return typeof type === "object" && type !== null &&
+ typeof type.struct === "object";
+ }
+
+ function getTypeSizeAndAlignment(type, cache = new Map()) {
+ if (isStruct(type)) {
+ const cached = cache.get(type);
+ if (cached !== undefined) {
+ if (cached === null) {
+ throw new TypeError("Recursive struct definition");
+ }
+ return cached;
+ }
+ cache.set(type, null);
+ let size = 0;
+ let alignment = 1;
+ for (const field of new SafeArrayIterator(type.struct)) {
+ const [fieldSize, fieldAlign] = getTypeSizeAndAlignment(field, cache);
+ alignment = MathMax(alignment, fieldAlign);
+ size = MathCeil(size / fieldAlign) * fieldAlign;
+ size += fieldSize;
+ }
+ size = MathCeil(size / alignment) * alignment;
+ cache.set(type, size);
+ return [size, alignment];
+ }
+
+ switch (type) {
+ case "bool":
+ case "u8":
+ case "i8":
+ return [1, 1];
+ case "u16":
+ case "i16":
+ return [2, 2];
+ case "u32":
+ case "i32":
+ case "f32":
+ return [4, 4];
+ case "u64":
+ case "i64":
+ case "f64":
+ case "pointer":
+ case "buffer":
+ case "function":
+ case "usize":
+ case "isize":
+ return [8, 8];
+ default:
+ throw new TypeError(`Unsupported type: ${type}`);
+ }
+ }
+
class UnsafeCallback {
#refcount;
// Internal promise only meant to keep Deno from exiting
@@ -306,6 +395,10 @@
continue;
}
const resultType = symbols[symbol].result;
+ const isStructResult = isStruct(resultType);
+ const structSize = isStructResult
+ ? getTypeSizeAndAlignment(resultType)[0]
+ : 0;
const needsUnpacking = isReturnedAsBigInt(resultType);
const isNonBlocking = symbols[symbol].nonblocking;
@@ -317,12 +410,27 @@
configurable: false,
enumerable: true,
value: (...parameters) => {
- return core.opAsync(
- "op_ffi_call_nonblocking",
- this.#rid,
- symbol,
- parameters,
- );
+ if (isStructResult) {
+ const buffer = new Uint8Array(structSize);
+ const ret = core.opAsync(
+ "op_ffi_call_nonblocking",
+ this.#rid,
+ symbol,
+ parameters,
+ buffer,
+ );
+ return PromisePrototypeThen(
+ ret,
+ () => buffer,
+ );
+ } else {
+ return core.opAsync(
+ "op_ffi_call_nonblocking",
+ this.#rid,
+ symbol,
+ parameters,
+ );
+ }
},
writable: false,
},
@@ -359,6 +467,21 @@
return b[0];
}`,
)(vi, vui, b, call, NumberIsSafeInteger, Number);
+ } else if (isStructResult && !isNonBlocking) {
+ const call = this.symbols[symbol];
+ const parameters = symbols[symbol].parameters;
+ const params = ArrayPrototypeJoin(
+ ArrayPrototypeMap(parameters, (_, index) => `p${index}`),
+ ", ",
+ );
+ this.symbols[symbol] = new Function(
+ "call",
+ `return function (${params}) {
+ const buffer = new Uint8Array(${structSize});
+ call(${params}${parameters.length > 0 ? ", " : ""}buffer);
+ return buffer;
+ }`,
+ )(call);
}
}
}