From b56b8c8a753442036ace3bb0f4403c952f18d408 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Wed, 22 Feb 2023 19:32:38 +0200 Subject: [PATCH] feat(ext/ffi): Replace pointer integers with v8::External objects (#16889) --- cli/tests/integration/run_tests.rs | 24 ++ cli/tests/testdata/run/ffi/unstable_ffi_10.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_11.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_12.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_13.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_14.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_15.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_16.js | 1 + .../testdata/run/ffi/unstable_ffi_16.js.out | 1 + cli/tests/testdata/run/ffi/unstable_ffi_17.js | 1 + .../testdata/run/ffi/unstable_ffi_17.js.out | 1 + cli/tests/testdata/run/ffi/unstable_ffi_18.js | 1 + .../testdata/run/ffi/unstable_ffi_18.js.out | 1 + cli/tests/testdata/run/ffi/unstable_ffi_19.js | 1 + .../testdata/run/ffi/unstable_ffi_19.js.out | 1 + cli/tests/testdata/run/ffi/unstable_ffi_2.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_3.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_5.js | 7 +- cli/tests/testdata/run/ffi/unstable_ffi_6.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_7.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_8.js | 2 +- cli/tests/testdata/run/ffi/unstable_ffi_9.js | 2 +- cli/tests/unit/ffi_test.ts | 8 +- cli/tsc/dts/lib.deno.unstable.d.ts | 43 +-- ext/ffi/00_ffi.js | 34 ++- ext/ffi/call.rs | 140 +++++----- ext/ffi/callback.rs | 70 ++--- ext/ffi/dlfcn.rs | 26 +- ext/ffi/ir.rs | 68 +---- ext/ffi/lib.rs | 5 + ext/ffi/repr.rs | 249 +++++++++++------- ext/ffi/static.rs | 11 +- ext/ffi/turbocall.rs | 14 +- ops/fast_call.rs | 6 +- ops/lib.rs | 38 +++ ops/optimizer.rs | 92 ++++++- ops/optimizer_tests/op_ffi_ptr_value.expected | 11 + ops/optimizer_tests/op_ffi_ptr_value.out | 127 +++++++++ ops/optimizer_tests/op_ffi_ptr_value.rs | 3 + serde_v8/error.rs | 1 + serde_v8/lib.rs | 1 + serde_v8/magic/external_pointer.rs | 56 ++++ serde_v8/magic/mod.rs | 2 + serde_v8/ser.rs | 12 +- test_ffi/tests/ffi_types.ts | 43 +-- test_ffi/tests/integration_tests.rs | 15 -- test_ffi/tests/test.js | 69 +++-- 47 files changed, 834 insertions(+), 373 deletions(-) create mode 100644 cli/tests/testdata/run/ffi/unstable_ffi_16.js create mode 100644 cli/tests/testdata/run/ffi/unstable_ffi_16.js.out create mode 100644 cli/tests/testdata/run/ffi/unstable_ffi_17.js create mode 100644 cli/tests/testdata/run/ffi/unstable_ffi_17.js.out create mode 100644 cli/tests/testdata/run/ffi/unstable_ffi_18.js create mode 100644 cli/tests/testdata/run/ffi/unstable_ffi_18.js.out create mode 100644 cli/tests/testdata/run/ffi/unstable_ffi_19.js create mode 100644 cli/tests/testdata/run/ffi/unstable_ffi_19.js.out create mode 100644 ops/optimizer_tests/op_ffi_ptr_value.expected create mode 100644 ops/optimizer_tests/op_ffi_ptr_value.out create mode 100644 ops/optimizer_tests/op_ffi_ptr_value.rs create mode 100644 serde_v8/magic/external_pointer.rs diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 6a436c318c..d42136f84f 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -2824,6 +2824,30 @@ itest!(unstable_ffi_15 { exit_code: 70, }); +itest!(unstable_ffi_16 { + args: "run run/ffi/unstable_ffi_16.js", + output: "run/ffi/unstable_ffi_16.js.out", + exit_code: 70, +}); + +itest!(unstable_ffi_17 { + args: "run run/ffi/unstable_ffi_17.js", + output: "run/ffi/unstable_ffi_17.js.out", + exit_code: 70, +}); + +itest!(unstable_ffi_18 { + args: "run run/ffi/unstable_ffi_18.js", + output: "run/ffi/unstable_ffi_18.js.out", + exit_code: 70, +}); + +itest!(unstable_ffi_19 { + args: "run run/ffi/unstable_ffi_19.js", + output: "run/ffi/unstable_ffi_19.js.out", + exit_code: 70, +}); + itest!(future_check2 { args: "run --check run/future_check.ts", output: "run/future_check2.out", diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_10.js b/cli/tests/testdata/run/ffi/unstable_ffi_10.js index da1c5b3a25..d291c6bbc8 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_10.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_10.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_i16(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_i16(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_11.js b/cli/tests/testdata/run/ffi/unstable_ffi_11.js index c2d9213b4f..fc00fac38e 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_11.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_11.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_u32(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_u32(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_12.js b/cli/tests/testdata/run/ffi/unstable_ffi_12.js index d3aaa7a71e..6f085115d5 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_12.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_12.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_i32(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_i32(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_13.js b/cli/tests/testdata/run/ffi/unstable_ffi_13.js index 859fbad60d..c3b5105db1 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_13.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_13.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_u64(0n, 0, new Uint32Array(2)); +Deno[Deno.internal].core.ops.op_ffi_read_u64(null, 0, new Uint32Array(2)); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_14.js b/cli/tests/testdata/run/ffi/unstable_ffi_14.js index 19f8a48c86..2d095c5d66 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_14.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_14.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_f32(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_f32(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_15.js b/cli/tests/testdata/run/ffi/unstable_ffi_15.js index 3a4c0252b9..a3cf2b0c5b 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_15.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_15.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_f64(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_f64(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_16.js b/cli/tests/testdata/run/ffi/unstable_ffi_16.js new file mode 100644 index 0000000000..2bf3759b36 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_16.js @@ -0,0 +1 @@ +Deno[Deno.internal].core.ops.op_ffi_ptr_value(null, new Uint32Array(2)); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_16.js.out b/cli/tests/testdata/run/ffi/unstable_ffi_16.js.out new file mode 100644 index 0000000000..d688707841 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_16.js.out @@ -0,0 +1 @@ +Unstable API 'Deno.UnsafePointer#value'. The --unstable flag must be provided. diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_17.js b/cli/tests/testdata/run/ffi/unstable_ffi_17.js new file mode 100644 index 0000000000..595727092a --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_17.js @@ -0,0 +1 @@ +Deno[Deno.internal].core.ops.op_ffi_get_buf(null, 0, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_17.js.out b/cli/tests/testdata/run/ffi/unstable_ffi_17.js.out new file mode 100644 index 0000000000..2949312436 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_17.js.out @@ -0,0 +1 @@ +Unstable API 'Deno.UnsafePointerView#getArrayBuffer'. The --unstable flag must be provided. diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_18.js b/cli/tests/testdata/run/ffi/unstable_ffi_18.js new file mode 100644 index 0000000000..d222a7b7d7 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_18.js @@ -0,0 +1 @@ +Deno[Deno.internal].core.ops.op_ffi_ptr_create(null); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_18.js.out b/cli/tests/testdata/run/ffi/unstable_ffi_18.js.out new file mode 100644 index 0000000000..6f7ea0d8f3 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_18.js.out @@ -0,0 +1 @@ +Unstable API 'Deno.UnsafePointer#create'. The --unstable flag must be provided. diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_19.js b/cli/tests/testdata/run/ffi/unstable_ffi_19.js new file mode 100644 index 0000000000..97d6500229 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_19.js @@ -0,0 +1 @@ +Deno[Deno.internal].core.ops.op_ffi_ptr_equals(null, null); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_19.js.out b/cli/tests/testdata/run/ffi/unstable_ffi_19.js.out new file mode 100644 index 0000000000..15a99b9ab8 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_19.js.out @@ -0,0 +1 @@ +Unstable API 'Deno.UnsafePointer#equals'. The --unstable flag must be provided. diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_2.js b/cli/tests/testdata/run/ffi/unstable_ffi_2.js index 4ed91a9c2b..c99b1e586d 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_2.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_2.js @@ -1,4 +1,4 @@ -Deno[Deno.internal].core.ops.op_ffi_call_ptr(0n, { +Deno[Deno.internal].core.ops.op_ffi_call_ptr(null, { name: null, parameters: [], result: "void", diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_3.js b/cli/tests/testdata/run/ffi/unstable_ffi_3.js index 284ecbc913..b59a264ead 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_3.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_3.js @@ -1,4 +1,4 @@ -Deno[Deno.internal].core.opAsync("op_ffi_call_ptr_nonblocking", 0n, { +Deno[Deno.internal].core.opAsync("op_ffi_call_ptr_nonblocking", null, { name: null, parameters: [], result: "void", diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_5.js b/cli/tests/testdata/run/ffi/unstable_ffi_5.js index 278c3c9d25..416c781752 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_5.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_5.js @@ -1 +1,6 @@ -Deno[Deno.internal].core.ops.op_ffi_buf_copy_into(0n, 0, new Uint8Array(0), 0); +Deno[Deno.internal].core.ops.op_ffi_buf_copy_into( + null, + 0, + new Uint8Array(0), + 0, +); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_6.js b/cli/tests/testdata/run/ffi/unstable_ffi_6.js index e6add70d62..7a079f5fb8 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_6.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_6.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_cstr_read(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_cstr_read(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_7.js b/cli/tests/testdata/run/ffi/unstable_ffi_7.js index 6ba28b3e33..1f9e5f0c05 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_7.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_7.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_u8(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_u8(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_8.js b/cli/tests/testdata/run/ffi/unstable_ffi_8.js index 2b0e0343bf..cbd0ec9eca 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_8.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_8.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_i8(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_i8(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_9.js b/cli/tests/testdata/run/ffi/unstable_ffi_9.js index 729a8584ed..9e8da12db1 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_9.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_9.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_u16(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_u16(null, 0); diff --git a/cli/tests/unit/ffi_test.ts b/cli/tests/unit/ffi_test.ts index 690b37afcb..65b257774d 100644 --- a/cli/tests/unit/ffi_test.ts +++ b/cli/tests/unit/ffi_test.ts @@ -29,7 +29,8 @@ Deno.test({ permissions: { ffi: false } }, function ffiPermissionDenied() { Deno.dlopen("/usr/lib/libc.so.6", {}); }, Deno.errors.PermissionDenied); const fnptr = new Deno.UnsafeFnPointer( - 0n, + // @ts-expect-error: Not NonNullable but null check is after premissions check. + null, { parameters: ["u32", "pointer"], result: "void", @@ -41,7 +42,10 @@ Deno.test({ permissions: { ffi: false } }, function ffiPermissionDenied() { assertThrows(() => { Deno.UnsafePointer.of(new Uint8Array(0)); }, Deno.errors.PermissionDenied); - const ptrView = new Deno.UnsafePointerView(0n); + const ptrView = new Deno.UnsafePointerView( + // @ts-expect-error: Not NonNullable but null check is after premissions check. + null, + ); assertThrows(() => { ptrView.copyInto(new Uint8Array(0)); }, Deno.errors.PermissionDenied); diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts index 0362b416df..a1584701f8 100644 --- a/cli/tsc/dts/lib.deno.unstable.d.ts +++ b/cli/tsc/dts/lib.deno.unstable.d.ts @@ -131,10 +131,10 @@ declare namespace Deno { */ type ToNativeTypeMap = & Record - & Record + & Record & Record - & Record - & Record + & Record + & Record & Record; /** **UNSTABLE**: New API, yet to be vetted. @@ -191,7 +191,7 @@ declare namespace Deno { */ type FromNativeTypeMap = & Record - & Record + & Record & Record & Record & Record @@ -340,6 +340,9 @@ declare namespace Deno { [K in keyof T]: StaticForeignSymbol; }; + const brand: unique symbol; + type PointerObject = { [brand]: unknown }; + /** **UNSTABLE**: New API, yet to be vetted. * * Pointer type depends on the architecture and actual pointer value. @@ -350,7 +353,7 @@ declare namespace Deno { * * @category FFI */ - export type PointerValue = number | bigint; + export type PointerValue = null | PointerObject; /** **UNSTABLE**: New API, yet to be vetted. * @@ -360,8 +363,16 @@ declare namespace Deno { * @category FFI */ export class UnsafePointer { + /** Create a pointer from a numeric value. This is one is really dangerous! */ + static create(value: number | bigint): PointerValue; + /** Returns `true` if the two pointers point to the same address. */ + static equals(a: PointerValue, b: PointerValue): boolean; /** Return the direct memory pointer to the typed array in memory. */ static of(value: Deno.UnsafeCallback | BufferSource): PointerValue; + /** Return a new pointer offset from the original by `offset` bytes. */ + static offset(value: NonNullable, offset: number): PointerValue + /** Get the numeric value of a pointer */ + static value(value: PointerValue): number | bigint; } /** **UNSTABLE**: New API, yet to be vetted. @@ -374,9 +385,9 @@ declare namespace Deno { * @category FFI */ export class UnsafePointerView { - constructor(pointer: PointerValue); + constructor(pointer: NonNullable); - pointer: PointerValue; + pointer: NonNullable; /** Gets a boolean at the specified byte offset from the pointer. */ getBool(offset?: number): boolean; @@ -400,29 +411,31 @@ declare namespace Deno { getInt32(offset?: number): number; /** Gets an unsigned 64-bit integer at the specified byte offset from the * pointer. */ - getBigUint64(offset?: number): PointerValue; + getBigUint64(offset?: number): number | bigint; /** Gets a signed 64-bit integer at the specified byte offset from the * pointer. */ - getBigInt64(offset?: number): PointerValue; + getBigInt64(offset?: number): 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 pointer at the specified byte offset from the pointer */ + getPointer(offset?: number): PointerValue; /** Gets a C string (`null` terminated string) at the specified byte offset * from the pointer. */ getCString(offset?: number): string; /** Gets a C string (`null` terminated string) at the specified byte offset * from the specified pointer. */ - static getCString(pointer: PointerValue, offset?: number): string; + static getCString(pointer: NonNullable, offset?: number): string; /** Gets an `ArrayBuffer` of length `byteLength` at the specified byte * offset from the pointer. */ getArrayBuffer(byteLength: number, offset?: number): ArrayBuffer; /** Gets an `ArrayBuffer` of length `byteLength` at the specified byte * offset from the specified pointer. */ static getArrayBuffer( - pointer: PointerValue, + pointer: NonNullable, byteLength: number, offset?: number, ): ArrayBuffer; @@ -438,7 +451,7 @@ declare namespace Deno { * * Also takes optional byte offset from the pointer. */ static copyInto( - pointer: PointerValue, + pointer: NonNullable, destination: BufferSource, offset?: number, ): void; @@ -453,11 +466,11 @@ declare namespace Deno { */ export class UnsafeFnPointer { /** The pointer to the function. */ - pointer: PointerValue; + pointer: NonNullable; /** The definition of the function. */ definition: Fn; - constructor(pointer: PointerValue, definition: Const); + constructor(pointer: NonNullable, definition: Const); /** Call the foreign function. */ call: FromForeignFunction; @@ -516,7 +529,7 @@ declare namespace Deno { ); /** The pointer to the unsafe callback. */ - pointer: PointerValue; + pointer: NonNullable; /** The definition of the unsafe callback. */ definition: Definition; /** The callback function. */ diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index 6864fd6385..24a0dc913f 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -118,6 +118,13 @@ class UnsafePointerView { ); } + getPointer(offset = 0) { + return ops.op_ffi_read_ptr( + this.pointer, + offset, + ); + } + getCString(offset = 0) { return ops.op_ffi_cstr_read( this.pointer, @@ -170,11 +177,33 @@ class UnsafePointerView { const OUT_BUFFER = new Uint32Array(2); const OUT_BUFFER_64 = new BigInt64Array(OUT_BUFFER.buffer); class UnsafePointer { + static create(value) { + return ops.op_ffi_ptr_create(value); + } + + static equals(a, b) { + if (a === null || b === null) { + return a === b; + } + return ops.op_ffi_ptr_equals(a, b); + } + static of(value) { if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) { return value.pointer; } - ops.op_ffi_ptr_of(value, OUT_BUFFER); + return ops.op_ffi_ptr_of(value); + } + + static offset(value, offset) { + return ops.op_ffi_ptr_offset(value, offset); + } + + static value(value) { + if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) { + value = value.pointer; + } + ops.op_ffi_ptr_value(value, OUT_BUFFER); const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1]; if (NumberIsSafeInteger(result)) { return result; @@ -240,8 +269,7 @@ class UnsafeFnPointer { } function isReturnedAsBigInt(type) { - return type === "buffer" || type === "pointer" || type === "function" || - type === "u64" || type === "i64" || + return type === "u64" || type === "i64" || type === "usize" || type === "isize"; } diff --git a/ext/ffi/call.rs b/ext/ffi/call.rs index 731460af94..8a9b393c39 100644 --- a/ext/ffi/call.rs +++ b/ext/ffi/call.rs @@ -14,9 +14,11 @@ use deno_core::error::AnyError; use deno_core::op; use deno_core::serde_json::Value; use deno_core::serde_v8; +use deno_core::serde_v8::ExternalPointer; use deno_core::v8; use deno_core::ResourceId; use libffi::middle::Arg; +use serde::Serialize; use std::cell::RefCell; use std::ffi::c_void; use std::future::Future; @@ -28,14 +30,13 @@ unsafe fn ffi_call_rtype_struct( fn_ptr: &libffi::middle::CodePtr, call_args: Vec, out_buffer: *mut u8, -) -> NativeValue { +) { libffi::raw::ffi_call( cif.as_raw_ptr(), Some(*fn_ptr.as_safe_fun()), out_buffer as *mut c_void, call_args.as_ptr() as *mut *mut c_void, ); - NativeValue { void_value: () } } // A one-off synchronous FFI call. @@ -174,16 +175,25 @@ where pointer: cif.call::<*mut c_void>(*fun_ptr, &call_args), } } - NativeType::Struct(_) => ffi_call_rtype_struct( - &symbol.cif, - &symbol.ptr, - call_args, - out_buffer.unwrap().0, - ), + NativeType::Struct(_) => NativeValue { + void_value: ffi_call_rtype_struct( + &symbol.cif, + &symbol.ptr, + call_args, + out_buffer.unwrap().0, + ), + }, }) } } +#[derive(Serialize)] +#[serde(untagged)] +pub enum FfiValue { + Value(Value), + External(ExternalPointer), +} + fn ffi_call( call_args: Vec, cif: &libffi::middle::Cif, @@ -191,7 +201,7 @@ fn ffi_call( parameter_types: &[NativeType], result_type: NativeType, out_buffer: Option, -) -> Result { +) -> Result { let call_args: Vec = call_args .iter() .enumerate() @@ -205,55 +215,57 @@ fn ffi_call( // types of symbol. unsafe { Ok(match result_type { - NativeType::Void => NativeValue { - void_value: cif.call::<()>(fun_ptr, &call_args), - }, - NativeType::Bool => NativeValue { - bool_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::U8 => NativeValue { - u8_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::I8 => NativeValue { - i8_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::U16 => NativeValue { - u16_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::I16 => NativeValue { - i16_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::U32 => NativeValue { - u32_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::I32 => NativeValue { - i32_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::U64 => NativeValue { - u64_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::I64 => NativeValue { - i64_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::USize => NativeValue { - usize_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::ISize => NativeValue { - isize_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::F32 => NativeValue { - f32_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::F64 => NativeValue { - f64_value: cif.call::(fun_ptr, &call_args), - }, + NativeType::Void => { + cif.call::<()>(fun_ptr, &call_args); + FfiValue::Value(Value::from(())) + } + NativeType::Bool => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::U8 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::I8 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::U16 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::I16 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::U32 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::I32 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::U64 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::I64 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::USize => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::ISize => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::F32 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::F64 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } NativeType::Pointer | NativeType::Function | NativeType::Buffer => { - NativeValue { - pointer: cif.call::<*mut c_void>(fun_ptr, &call_args), - } + FfiValue::External(ExternalPointer::from( + cif.call::<*mut c_void>(fun_ptr, &call_args), + )) } NativeType::Struct(_) => { - ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0) + ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0); + FfiValue::Value(Value::Null) } }) } @@ -263,11 +275,11 @@ fn ffi_call( pub fn op_ffi_call_ptr_nonblocking<'scope, FP>( scope: &mut v8::HandleScope<'scope>, state: Rc>, - pointer: usize, + pointer: *mut c_void, def: ForeignFunction, parameters: serde_v8::Value<'scope>, out_buffer: Option>, -) -> Result>, AnyError> +) -> Result>, AnyError> where FP: FfiPermissions + 'static, { @@ -280,7 +292,6 @@ where let symbol = PtrSymbol::new(pointer, &def)?; let call_args = ffi_parse_args(scope, parameters, &def.parameters)?; - let def_result = def.result.clone(); let out_buffer = out_buffer .map(|v| v8::Local::::try_from(v.v8_value).unwrap()); @@ -303,7 +314,7 @@ where .await .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??; // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. - Ok(unsafe { result.to_value(def_result) }) + Ok(result) }) } @@ -316,7 +327,8 @@ pub fn op_ffi_call_nonblocking<'scope>( symbol: String, parameters: serde_v8::Value<'scope>, out_buffer: Option>, -) -> Result> + 'static, AnyError> { +) -> Result> + 'static, AnyError> +{ let symbol = { let state = state.borrow(); let resource = state.resource_table.get::(rid)?; @@ -332,7 +344,6 @@ pub fn op_ffi_call_nonblocking<'scope>( .map(|v| v8::Local::::try_from(v.v8_value).unwrap()); let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer); - let result_type = symbol.result_type.clone(); let join_handle = tokio::task::spawn_blocking(move || { let Symbol { cif, @@ -356,7 +367,7 @@ pub fn op_ffi_call_nonblocking<'scope>( .await .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??; // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. - Ok(unsafe { result.to_value(result_type) }) + Ok(result) }) } @@ -364,11 +375,11 @@ pub fn op_ffi_call_nonblocking<'scope>( pub fn op_ffi_call_ptr( scope: &mut v8::HandleScope<'scope>, state: Rc>, - pointer: usize, + pointer: *mut c_void, def: ForeignFunction, parameters: serde_v8::Value<'scope>, out_buffer: Option>, -) -> Result, AnyError> +) -> Result where FP: FfiPermissions + 'static, { @@ -395,6 +406,5 @@ where out_buffer_ptr, )?; // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. - let result = unsafe { result.to_v8(scope, def.result) }; Ok(result) } diff --git a/ext/ffi/callback.rs b/ext/ffi/callback.rs index 1558d950ee..d608c54326 100644 --- a/ext/ffi/callback.rs +++ b/ext/ffi/callback.rs @@ -40,7 +40,10 @@ pub struct PtrSymbol { } impl PtrSymbol { - pub fn new(fn_ptr: usize, def: &ForeignFunction) -> Result { + pub fn new( + fn_ptr: *mut c_void, + def: &ForeignFunction, + ) -> Result { let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _); let cif = libffi::middle::Cif::new( def @@ -236,11 +239,11 @@ unsafe fn do_ffi_callback( } } NativeType::Pointer | NativeType::Buffer | NativeType::Function => { - let result = *((*val) as *const usize); - if result > MAX_SAFE_INTEGER as usize { - v8::BigInt::new_from_u64(scope, result as u64).into() + let result = *((*val) as *const *mut c_void); + if result.is_null() { + v8::null(scope).into() } else { - v8::Number::new(scope, result as f64).into() + v8::External::new(scope, result).into() } } NativeType::Struct(_) => { @@ -353,34 +356,43 @@ unsafe fn do_ffi_callback( }; *(result as *mut f64) = value; } - NativeType::Pointer | NativeType::Buffer | NativeType::Function => { - let pointer = if let Ok(value) = + NativeType::Buffer => { + let pointer: *mut u8 = if let Ok(value) = v8::Local::::try_from(value) { let byte_offset = value.byte_offset(); - let backing_store = value + let pointer = value .buffer(scope) .expect("Unable to deserialize result parameter.") - .get_backing_store(); - &backing_store[byte_offset..] as *const _ as *const u8 - } else if let Ok(value) = v8::Local::::try_from(value) { - value.u64_value().0 as usize as *const u8 + .data(); + if let Some(non_null) = pointer { + // SAFETY: Pointer is non-null, and V8 guarantees that the byte_offset + // is within the buffer backing store. + unsafe { non_null.as_ptr().add(byte_offset) as *mut u8 } + } else { + ptr::null_mut() + } } else if let Ok(value) = v8::Local::::try_from(value) { - let backing_store = value.get_backing_store(); - &backing_store[..] as *const _ as *const u8 - } else if let Ok(value) = v8::Local::::try_from(value) { - value.value() as usize as *const u8 - } else if value.is_null() { - ptr::null() + let pointer = value.data(); + if let Some(non_null) = pointer { + non_null.as_ptr() as *mut u8 + } else { + ptr::null_mut() + } } else { - // Fallthrough: Probably someone returned a number but this could - // also be eg. a string. This is essentially UB. - value - .integer_value(scope) - .expect("Unable to deserialize result parameter.") as usize - as *const u8 + ptr::null_mut() }; - *(result as *mut *const u8) = pointer; + *(result as *mut *mut u8) = pointer; + } + NativeType::Pointer | NativeType::Function => { + let pointer: *mut c_void = + if let Ok(external) = v8::Local::::try_from(value) { + external.value() + } else { + // TODO(@aapoalas): Start throwing errors into JS about invalid callback return values. + ptr::null_mut() + }; + *(result as *mut *mut c_void) = pointer; } NativeType::I8 => { let value = if let Ok(value) = v8::Local::::try_from(value) { @@ -591,7 +603,7 @@ where let closure = libffi::middle::Closure::new(cif, deno_ffi_callback, unsafe { info.as_ref().unwrap() }); - let ptr = *closure.code_ptr() as usize; + let ptr = *closure.code_ptr() as *mut c_void; let resource = UnsafeCallbackResource { cancel: CancelHandle::new_rc(), closure, @@ -600,11 +612,7 @@ where let rid = state.resource_table.add(resource); let rid_local = v8::Integer::new_from_unsigned(scope, rid); - let ptr_local: v8::Local = if ptr > MAX_SAFE_INTEGER as usize { - v8::BigInt::new_from_u64(scope, ptr as u64).into() - } else { - v8::Number::new(scope, ptr as f64).into() - }; + let ptr_local: v8::Local = v8::External::new(scope, ptr).into(); let array = v8::Array::new(scope, 2); array.set_index(scope, 0, rid_local.into()); array.set_index(scope, 1, ptr_local); diff --git a/ext/ffi/dlfcn.rs b/ext/ffi/dlfcn.rs index c2b1232464..cb5009de71 100644 --- a/ext/ffi/dlfcn.rs +++ b/ext/ffi/dlfcn.rs @@ -38,13 +38,13 @@ impl Resource for DynamicLibraryResource { } impl DynamicLibraryResource { - pub fn get_static(&self, symbol: String) -> Result<*const c_void, AnyError> { + pub fn get_static(&self, symbol: String) -> Result<*mut c_void, AnyError> { // By default, Err returned by this function does not tell // which symbol wasn't exported. So we'll modify the error // message to include the name of symbol. // // SAFETY: The obtained T symbol is the size of a pointer. - match unsafe { self.lib.symbol::<*const c_void>(&symbol) } { + match unsafe { self.lib.symbol::<*mut c_void>(&symbol) } { Ok(value) => Ok(Ok(value)), Err(err) => Err(generic_error(format!( "Failed to register symbol {symbol}: {err}" @@ -56,13 +56,7 @@ impl DynamicLibraryResource { pub fn needs_unwrap(rv: &NativeType) -> bool { matches!( rv, - NativeType::Function - | NativeType::Pointer - | NativeType::Buffer - | NativeType::I64 - | NativeType::ISize - | NativeType::U64 - | NativeType::USize + NativeType::I64 | NativeType::ISize | NativeType::U64 | NativeType::USize ) } @@ -257,26 +251,20 @@ fn make_sync_fn<'s>( match needs_unwrap { Some(v) => { let view: v8::Local = v.try_into().unwrap(); - let backing_store = - view.buffer(scope).unwrap().get_backing_store(); + let pointer = + view.buffer(scope).unwrap().data().unwrap().as_ptr() as *mut u8; if is_i64(&symbol.result_type) { // SAFETY: v8::SharedRef is similar to Arc<[u8]>, // it points to a fixed continuous slice of bytes on the heap. - let bs = unsafe { - &mut *(&backing_store[..] as *const _ as *mut [u8] - as *mut i64) - }; + let bs = unsafe { &mut *(pointer as *mut i64) }; // SAFETY: We already checked that type == I64 let value = unsafe { result.i64_value }; *bs = value; } else { // SAFETY: v8::SharedRef is similar to Arc<[u8]>, // it points to a fixed continuous slice of bytes on the heap. - let bs = unsafe { - &mut *(&backing_store[..] as *const _ as *mut [u8] - as *mut u64) - }; + let bs = unsafe { &mut *(pointer as *mut u64) }; // SAFETY: We checked that type == U64 let value = unsafe { result.u64_value }; *bs = value; diff --git a/ext/ffi/ir.rs b/ext/ffi/ir.rs index 80f727cd20..8ca96a280c 100644 --- a/ext/ffi/ir.rs +++ b/ext/ffi/ir.rs @@ -5,7 +5,6 @@ use crate::MAX_SAFE_INTEGER; use crate::MIN_SAFE_INTEGER; use deno_core::error::type_error; use deno_core::error::AnyError; -use deno_core::serde_json::Value; use deno_core::serde_v8; use deno_core::v8; use libffi::middle::Arg; @@ -80,33 +79,6 @@ impl NativeValue { } } - // SAFETY: native_type must correspond to the type of value represented by the union field - pub unsafe fn to_value(&self, native_type: NativeType) -> Value { - match native_type { - NativeType::Void => Value::Null, - NativeType::Bool => Value::from(self.bool_value), - NativeType::U8 => Value::from(self.u8_value), - NativeType::I8 => Value::from(self.i8_value), - NativeType::U16 => Value::from(self.u16_value), - NativeType::I16 => Value::from(self.i16_value), - NativeType::U32 => Value::from(self.u32_value), - NativeType::I32 => Value::from(self.i32_value), - NativeType::U64 => Value::from(self.u64_value), - NativeType::I64 => Value::from(self.i64_value), - NativeType::USize => Value::from(self.usize_value), - NativeType::ISize => Value::from(self.isize_value), - NativeType::F32 => Value::from(self.f32_value), - NativeType::F64 => Value::from(self.f64_value), - NativeType::Pointer | NativeType::Function | NativeType::Buffer => { - Value::from(self.pointer as usize) - } - NativeType::Struct(_) => { - // Return value is written to out_buffer - Value::Null - } - } - } - // SAFETY: native_type must correspond to the type of value represented by the union field #[inline] pub unsafe fn to_v8<'scope>( @@ -206,13 +178,11 @@ impl NativeValue { local_value.into() } NativeType::Pointer | NativeType::Buffer | NativeType::Function => { - let value = self.pointer as u64; - let local_value: v8::Local = - if value > MAX_SAFE_INTEGER as u64 { - v8::BigInt::new_from_u64(scope, value).into() - } else { - v8::Number::new(scope, value as f64).into() - }; + let local_value: v8::Local = if self.pointer.is_null() { + v8::null(scope).into() + } else { + v8::External::new(scope, self.pointer).into() + }; local_value.into() } NativeType::Struct(_) => { @@ -396,22 +366,16 @@ pub fn ffi_parse_f64_arg( #[inline] pub fn ffi_parse_pointer_arg( - scope: &mut v8::HandleScope, + _scope: &mut v8::HandleScope, arg: v8::Local, ) -> Result { - // Order of checking: - // 1. BigInt: Uncommon and not supported by Fast API, optimise this case. - // 2. Number: Common and supported by Fast API. - // 3. Null: Very uncommon / can be represented by a 0. - let pointer = if let Ok(value) = v8::Local::::try_from(arg) { - value.u64_value().0 as usize as *mut c_void - } else if let Ok(value) = v8::Local::::try_from(arg) { - value.integer_value(scope).unwrap() as usize as *mut c_void + let pointer = if let Ok(value) = v8::Local::::try_from(arg) { + value.value() } else if arg.is_null() { ptr::null_mut() } else { return Err(type_error( - "Invalid FFI pointer type, expected null, integer or BigInt", + "Invalid FFI pointer type, expected null, or External", )); }; Ok(NativeValue { pointer }) @@ -502,22 +466,16 @@ pub fn ffi_parse_struct_arg( #[inline] pub fn ffi_parse_function_arg( - scope: &mut v8::HandleScope, + _scope: &mut v8::HandleScope, arg: v8::Local, ) -> Result { - // Order of checking: - // 1. BigInt: Uncommon and not supported by Fast API, optimise this case. - // 2. Number: Common and supported by Fast API, optimise this case as second. - // 3. Null: Very uncommon / can be represented by a 0. - let pointer = if let Ok(value) = v8::Local::::try_from(arg) { - value.u64_value().0 as usize as *mut c_void - } else if let Ok(value) = v8::Local::::try_from(arg) { - value.integer_value(scope).unwrap() as usize as *mut c_void + let pointer = if let Ok(value) = v8::Local::::try_from(arg) { + value.value() } else if arg.is_null() { ptr::null_mut() } else { return Err(type_error( - "Invalid FFI function type, expected null, integer, or BigInt", + "Invalid FFI function type, expected null, or External", )); }; Ok(NativeValue { pointer }) diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index a14a28a83e..d49b662741 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -91,7 +91,11 @@ pub fn init(unstable: bool) -> Extension { op_ffi_call_nonblocking::decl(), op_ffi_call_ptr::decl::

(), op_ffi_call_ptr_nonblocking::decl::

(), + op_ffi_ptr_create::decl::

(), + op_ffi_ptr_equals::decl::

(), op_ffi_ptr_of::decl::

(), + op_ffi_ptr_offset::decl::

(), + op_ffi_ptr_value::decl::

(), op_ffi_get_buf::decl::

(), op_ffi_buf_copy_into::decl::

(), op_ffi_cstr_read::decl::

(), @@ -106,6 +110,7 @@ pub fn init(unstable: bool) -> Extension { op_ffi_read_i64::decl::

(), op_ffi_read_f32::decl::

(), op_ffi_read_f64::decl::

(), + op_ffi_read_ptr::decl::

(), op_ffi_unsafe_callback_create::decl::

(), op_ffi_unsafe_callback_ref::decl(), op_ffi_unsafe_callback_unref::decl(), diff --git a/ext/ffi/repr.rs b/ext/ffi/repr.rs index c21b2b0f18..4372b0f1bb 100644 --- a/ext/ffi/repr.rs +++ b/ext/ffi/repr.rs @@ -13,16 +13,90 @@ use std::ffi::c_void; use std::ffi::CStr; use std::ptr; +#[op(fast)] +fn op_ffi_ptr_create( + state: &mut deno_core::OpState, + ptr_number: usize, +) -> Result<*mut c_void, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointer#create"); + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(ptr_number as *mut c_void) +} + +#[op(fast)] +pub fn op_ffi_ptr_equals( + state: &mut deno_core::OpState, + a: *const c_void, + b: *const c_void, +) -> Result +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointer#equals"); + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(a == b) +} + #[op(fast)] pub fn op_ffi_ptr_of( state: &mut deno_core::OpState, buf: *const u8, +) -> Result<*mut c_void, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointer#of"); + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(buf as *mut c_void) +} + +#[op(fast)] +fn op_ffi_ptr_offset( + state: &mut deno_core::OpState, + ptr: *mut c_void, + offset: isize, +) -> Result<*mut c_void, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointer#offset"); + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + if ptr.is_null() { + return Err(type_error("Invalid pointer to offset, pointer is null")); + } + + // SAFETY: Pointer and offset are user provided. + Ok(unsafe { ptr.offset(offset) }) +} + +unsafe extern "C" fn noop_deleter_callback( + _data: *mut c_void, + _byte_length: usize, + _deleter_data: *mut c_void, +) { +} + +#[op(fast)] +fn op_ffi_ptr_value( + state: &mut deno_core::OpState, + ptr: *mut c_void, out: &mut [u32], ) -> Result<(), AnyError> where FP: FfiPermissions + 'static, { - check_unstable(state, "Deno.UnsafePointer#of"); + check_unstable(state, "Deno.UnsafePointer#value"); let permissions = state.borrow_mut::(); permissions.check(None)?; @@ -35,47 +109,35 @@ where // SAFETY: Out buffer was asserted to be at least large enough to hold a usize, and properly aligned. let out = unsafe { &mut *outptr }; - *out = buf as usize; + *out = ptr as usize; Ok(()) } -unsafe extern "C" fn noop_deleter_callback( - _data: *mut c_void, - _byte_length: usize, - _deleter_data: *mut c_void, -) { -} - #[op(v8)] pub fn op_ffi_get_buf( scope: &mut v8::HandleScope<'scope>, state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, len: usize, ) -> Result, AnyError> where FP: FfiPermissions + 'static, { - check_unstable(state, "Deno.UnsafePointerView#arrayBuffer"); + check_unstable(state, "Deno.UnsafePointerView#getArrayBuffer"); let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *mut c_void; - if ptr.is_null() { - return Err(type_error("Invalid FFI pointer value, got nullptr")); + return Err(type_error("Invalid ArrayBuffer pointer, pointer is null")); } - // SAFETY: Offset is user defined. - let ptr = unsafe { ptr.add(offset) }; - - // SAFETY: Trust the user to have provided a real pointer, and a valid matching size to it. Since this is a foreign pointer, we should not do any deletion. + // SAFETY: Trust the user to have provided a real pointer, offset, and a valid matching size to it. Since this is a foreign pointer, we should not do any deletion. let backing_store = unsafe { v8::ArrayBuffer::new_backing_store_from_ptr( - ptr, + ptr.offset(offset), len, noop_deleter_callback, std::ptr::null_mut(), @@ -90,8 +152,8 @@ where #[op(fast)] pub fn op_ffi_buf_copy_into( state: &mut deno_core::OpState, - src: usize, - offset: usize, + src: *mut c_void, + offset: isize, dst: &mut [u8], len: usize, ) -> Result<(), AnyError> @@ -103,19 +165,20 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - if dst.len() < len { + if src.is_null() { + Err(type_error("Invalid ArrayBuffer pointer, pointer is null")) + } else if dst.len() < len { Err(range_error( "Destination length is smaller than source length", )) } else { let src = src as *const c_void; - // SAFETY: Offset is user defined. - let src = unsafe { src.add(offset) as *const u8 }; - - // SAFETY: src is user defined. + // SAFETY: src and offset are user defined. // dest is properly aligned and is valid for writes of len * size_of::() bytes. - unsafe { ptr::copy::(src, dst.as_mut_ptr(), len) }; + unsafe { + ptr::copy::(src.offset(offset) as *const u8, dst.as_mut_ptr(), len) + }; Ok(()) } } @@ -124,8 +187,8 @@ where pub fn op_ffi_cstr_read( scope: &mut v8::HandleScope<'scope>, state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result, AnyError> where FP: FfiPermissions + 'static, @@ -135,17 +198,13 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid CString pointer, pointer is null")); } - // SAFETY: Offset is user defined. - let ptr = unsafe { ptr.add(offset) }; - - // SAFETY: Pointer is user provided. - let cstr = unsafe { CStr::from_ptr(ptr as *const c_char) }.to_bytes(); + let cstr = + // SAFETY: Pointer and offset are user provided. + unsafe { CStr::from_ptr(ptr.offset(offset) as *const c_char) }.to_bytes(); let value: v8::Local = v8::String::new_from_utf8(scope, cstr, v8::NewStringType::Normal) .ok_or_else(|| { @@ -158,8 +217,8 @@ where #[op(fast)] pub fn op_ffi_read_bool( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -169,21 +228,19 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid bool pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const bool) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const bool) }) } #[op(fast)] pub fn op_ffi_read_u8( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -193,21 +250,21 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid u8 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const u8) as u32 }) + Ok(unsafe { + ptr::read_unaligned::(ptr.offset(offset) as *const u8) as u32 + }) } #[op(fast)] pub fn op_ffi_read_i8( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -217,21 +274,21 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid i8 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const i8) as i32 }) + Ok(unsafe { + ptr::read_unaligned::(ptr.offset(offset) as *const i8) as i32 + }) } #[op(fast)] pub fn op_ffi_read_u16( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -241,23 +298,21 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid u16 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. Ok(unsafe { - ptr::read_unaligned::(ptr.add(offset) as *const u16) as u32 + ptr::read_unaligned::(ptr.offset(offset) as *const u16) as u32 }) } #[op(fast)] pub fn op_ffi_read_i16( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -267,23 +322,21 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid i16 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. Ok(unsafe { - ptr::read_unaligned::(ptr.add(offset) as *const i16) as i32 + ptr::read_unaligned::(ptr.offset(offset) as *const i16) as i32 }) } #[op(fast)] pub fn op_ffi_read_u32( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -293,21 +346,19 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid u32 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const u32) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const u32) }) } #[op(fast)] pub fn op_ffi_read_i32( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -317,21 +368,19 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid i32 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const i32) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const i32) }) } #[op] pub fn op_ffi_read_u64( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, out: &mut [u32], ) -> Result<(), AnyError> where @@ -349,15 +398,13 @@ where ); assert_eq!((outptr as usize % std::mem::size_of::()), 0); - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid u64 pointer, pointer is null")); } let value = // SAFETY: ptr and offset are user provided. - unsafe { ptr::read_unaligned::(ptr.add(offset) as *const u64) }; + unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const u64) }; // SAFETY: Length and alignment of out slice were asserted to be correct. unsafe { *outptr = value }; @@ -367,8 +414,8 @@ where #[op(fast)] pub fn op_ffi_read_i64( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, out: &mut [u32], ) -> Result<(), AnyError> where @@ -386,15 +433,13 @@ where ); assert_eq!((outptr as usize % std::mem::size_of::()), 0); - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid i64 pointer, pointer is null")); } let value = // SAFETY: ptr and offset are user provided. - unsafe { ptr::read_unaligned::(ptr.add(offset) as *const i64) }; + unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const i64) }; // SAFETY: Length and alignment of out slice were asserted to be correct. unsafe { *outptr = value }; Ok(()) @@ -403,8 +448,8 @@ where #[op(fast)] pub fn op_ffi_read_f32( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -414,21 +459,19 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid f32 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const f32) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const f32) }) } #[op(fast)] pub fn op_ffi_read_f64( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -438,12 +481,34 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid f64 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const f64) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const f64) }) +} + +#[op(fast)] +pub fn op_ffi_read_ptr( + state: &mut deno_core::OpState, + ptr: *mut c_void, + offset: isize, +) -> Result<*mut c_void, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointerView#getPointer"); + + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + if ptr.is_null() { + return Err(type_error("Invalid pointer pointer, pointer is null")); + } + + // SAFETY: ptr and offset are user provided. + Ok(unsafe { + ptr::read_unaligned::<*mut c_void>(ptr.offset(offset) as *const *mut c_void) + }) } diff --git a/ext/ffi/static.rs b/ext/ffi/static.rs index 9ea0d616d1..2f32f03fd1 100644 --- a/ext/ffi/static.rs +++ b/ext/ffi/static.rs @@ -10,6 +10,7 @@ use deno_core::op; use deno_core::serde_v8; use deno_core::v8; use deno_core::ResourceId; +use std::ffi::c_void; use std::ptr; #[op(v8)] @@ -134,13 +135,9 @@ pub fn op_ffi_get_static<'scope>( number.into() } NativeType::Pointer | NativeType::Function | NativeType::Buffer => { - let result = data_ptr as u64; - let integer: v8::Local = if result > MAX_SAFE_INTEGER as u64 { - v8::BigInt::new_from_u64(scope, result).into() - } else { - v8::Number::new(scope, result as f64).into() - }; - integer.into() + let external: v8::Local = + v8::External::new(scope, data_ptr as *mut c_void).into(); + external.into() } NativeType::Struct(_) => { return Err(type_error("Invalid FFI static type 'struct'")); diff --git a/ext/ffi/turbocall.rs b/ext/ffi/turbocall.rs index 1eb1655e15..3d01f00f58 100644 --- a/ext/ffi/turbocall.rs +++ b/ext/ffi/turbocall.rs @@ -48,6 +48,9 @@ pub(crate) fn make_template(sym: &Symbol, trampoline: &Trampoline) -> Template { let ret = if needs_unwrap(&sym.result_type) { params.push(fast_api::Type::TypedArray(fast_api::CType::Int32)); fast_api::Type::Void + } else if sym.result_type == NativeType::Buffer { + // Buffer can be used as a return type and converts differently than in parameters. + fast_api::Type::Pointer } else { fast_api::Type::from(&sym.result_type) }; @@ -71,9 +74,9 @@ impl Trampoline { } pub(crate) struct Template { - args: Box<[fast_api::Type]>, - ret: fast_api::CType, - symbol_ptr: *const c_void, + pub args: Box<[fast_api::Type]>, + pub ret: fast_api::CType, + pub symbol_ptr: *const c_void, } impl fast_api::FastFunction for Template { @@ -106,9 +109,8 @@ impl From<&NativeType> for fast_api::Type { NativeType::I64 => fast_api::Type::Int64, NativeType::U64 => fast_api::Type::Uint64, NativeType::ISize => fast_api::Type::Int64, - NativeType::USize | NativeType::Pointer | NativeType::Function => { - fast_api::Type::Uint64 - } + NativeType::USize => fast_api::Type::Uint64, + NativeType::Pointer | NativeType::Function => fast_api::Type::Pointer, NativeType::Buffer => fast_api::Type::TypedArray(fast_api::CType::Uint8), NativeType::Struct(_) => { fast_api::Type::TypedArray(fast_api::CType::Uint8) diff --git a/ops/fast_call.rs b/ops/fast_call.rs index fe6455f371..a7ca51d4fd 100644 --- a/ops/fast_call.rs +++ b/ops/fast_call.rs @@ -418,13 +418,14 @@ pub(crate) fn generate( fn q_fast_ty(v: &FastValue) -> Quote { match v { FastValue::Void => q!({ () }), + FastValue::Bool => q!({ bool }), FastValue::U32 => q!({ u32 }), FastValue::I32 => q!({ i32 }), FastValue::U64 => q!({ u64 }), FastValue::I64 => q!({ i64 }), FastValue::F32 => q!({ f32 }), FastValue::F64 => q!({ f64 }), - FastValue::Bool => q!({ bool }), + FastValue::Pointer => q!({ *mut ::std::ffi::c_void }), FastValue::V8Value => q!({ v8::Local }), FastValue::Uint8Array | FastValue::Uint32Array @@ -436,13 +437,14 @@ fn q_fast_ty(v: &FastValue) -> Quote { fn q_fast_ty_variant(v: &FastValue) -> Quote { match v { FastValue::Void => q!({ Void }), + FastValue::Bool => q!({ Bool }), FastValue::U32 => q!({ Uint32 }), FastValue::I32 => q!({ Int32 }), FastValue::U64 => q!({ Uint64 }), FastValue::I64 => q!({ Int64 }), FastValue::F32 => q!({ Float32 }), FastValue::F64 => q!({ Float64 }), - FastValue::Bool => q!({ Bool }), + FastValue::Pointer => q!({ Pointer }), FastValue::V8Value => q!({ V8Value }), FastValue::Uint8Array => q!({ TypedArray(CType::Uint8) }), FastValue::Uint32Array => q!({ TypedArray(CType::Uint32) }), diff --git a/ops/lib.rs b/ops/lib.rs index d8f28dd378..28cb3c320a 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -474,6 +474,13 @@ fn codegen_arg( let #ident = #blk; }; } + // Fast path for `*const c_void` and `*mut c_void` + if is_ptr_cvoid(&**ty) { + let blk = codegen_cvoid_ptr(core, idx); + return quote! { + let #ident = #blk; + }; + } // Otherwise deserialize it via serde_v8 quote! { let #ident = args.get(#idx as i32); @@ -560,6 +567,19 @@ fn codegen_u8_ptr(core: &TokenStream2, idx: usize) -> TokenStream2 { }} } +fn codegen_cvoid_ptr(core: &TokenStream2, idx: usize) -> TokenStream2 { + quote! {{ + let value = args.get(#idx as i32); + if value.is_null() { + std::ptr::null_mut() + } else if let Ok(b) = #core::v8::Local::<#core::v8::External>::try_from(value) { + b.value() + } else { + return #core::_ops::throw_type_error(scope, format!("Expected External at position {}", #idx)); + } + }} +} + fn codegen_u32_mut_slice(core: &TokenStream2, idx: usize) -> TokenStream2 { quote! { if let Ok(view) = #core::v8::Local::<#core::v8::Uint32Array>::try_from(args.get(#idx as i32)) { @@ -626,6 +646,15 @@ fn codegen_sync_ret( quote! { rv.set_uint32(result as u32); } + } else if is_ptr_cvoid(output) || is_ptr_cvoid_rv(output) { + quote! { + if result.is_null() { + // External canot contain a null pointer, null pointers are instead represented as null. + rv.set_null(); + } else { + rv.set(v8::External::new(scope, result as *mut ::std::ffi::c_void).into()); + } + } } else { quote! { match #core::serde_v8::to_v8(scope, result) { @@ -723,6 +752,15 @@ fn is_ptr_u8(ty: impl ToTokens) -> bool { tokens(ty) == "* const u8" } +fn is_ptr_cvoid(ty: impl ToTokens) -> bool { + tokens(&ty) == "* const c_void" || tokens(&ty) == "* mut c_void" +} + +fn is_ptr_cvoid_rv(ty: impl ToTokens) -> bool { + tokens(&ty).contains("Result < * const c_void") + || tokens(&ty).contains("Result < * mut c_void") +} + fn is_optional_fast_callback_option(ty: impl ToTokens) -> bool { tokens(&ty).contains("Option < & mut FastApiCallbackOptions") } diff --git a/ops/optimizer.rs b/ops/optimizer.rs index ae31755115..a3ccd51b3c 100644 --- a/ops/optimizer.rs +++ b/ops/optimizer.rs @@ -45,6 +45,7 @@ enum TransformKind { SliceU8(bool), SliceF64(bool), PtrU8, + PtrVoid, WasmMemory, } @@ -90,6 +91,13 @@ impl Transform { index, } } + + fn void_ptr(index: usize) -> Self { + Transform { + kind: TransformKind::PtrVoid, + index, + } + } } #[derive(Debug, PartialEq)] @@ -195,19 +203,25 @@ impl Transform { .as_ptr(); }) } + TransformKind::PtrVoid => { + *ty = parse_quote! { *mut ::std::ffi::c_void }; + + q!(Vars {}, {}) + } } } } fn get_fast_scalar(s: &str) -> Option { match s { + "bool" => Some(FastValue::Bool), "u32" => Some(FastValue::U32), "i32" => Some(FastValue::I32), "u64" => Some(FastValue::U64), "i64" => Some(FastValue::I64), "f32" => Some(FastValue::F32), "f64" => Some(FastValue::F64), - "bool" => Some(FastValue::Bool), + "* const c_void" | "* mut c_void" => Some(FastValue::Pointer), "ResourceId" => Some(FastValue::U32), _ => None, } @@ -226,13 +240,14 @@ fn can_return_fast(v: &FastValue) -> bool { #[derive(Debug, PartialEq, Clone)] pub(crate) enum FastValue { Void, + Bool, U32, I32, U64, I64, F32, F64, - Bool, + Pointer, V8Value, Uint8Array, Uint32Array, @@ -414,6 +429,31 @@ impl Optimizer { { self.fast_result = Some(FastValue::Void); } + Some(GenericArgument::Type(Type::Ptr(TypePtr { + mutability: Some(_), + elem, + .. + }))) => { + match &**elem { + Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) => { + // Is `T` a c_void? + let segment = single_segment(segments)?; + match segment { + PathSegment { ident, .. } if ident == "c_void" => { + self.fast_result = Some(FastValue::Pointer); + return Ok(()); + } + _ => { + return Err(BailoutReason::FastUnsupportedParamType) + } + } + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + } + } _ => return Err(BailoutReason::FastUnsupportedParamType), } } @@ -430,6 +470,29 @@ impl Optimizer { } }; } + Type::Ptr(TypePtr { + mutability: Some(_), + elem, + .. + }) => { + match &**elem { + Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) => { + // Is `T` a c_void? + let segment = single_segment(segments)?; + match segment { + PathSegment { ident, .. } if ident == "c_void" => { + self.fast_result = Some(FastValue::Pointer); + return Ok(()); + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + } + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + } + } _ => return Err(BailoutReason::FastUnsupportedParamType), }; @@ -684,6 +747,31 @@ impl Optimizer { } _ => return Err(BailoutReason::FastUnsupportedParamType), }, + // *const T + Type::Ptr(TypePtr { + elem, + mutability: Some(_), + .. + }) => match &**elem { + Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) => { + let segment = single_segment(segments)?; + match segment { + // Is `T` a c_void? + PathSegment { ident, .. } if ident == "c_void" => { + self.fast_parameters.push(FastValue::Pointer); + assert!(self + .transforms + .insert(index, Transform::void_ptr(index)) + .is_none()); + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + } + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + }, _ => return Err(BailoutReason::FastUnsupportedParamType), }, _ => return Err(BailoutReason::FastUnsupportedParamType), diff --git a/ops/optimizer_tests/op_ffi_ptr_value.expected b/ops/optimizer_tests/op_ffi_ptr_value.expected new file mode 100644 index 0000000000..00a28591c0 --- /dev/null +++ b/ops/optimizer_tests/op_ffi_ptr_value.expected @@ -0,0 +1,11 @@ +=== Optimizer Dump === +returns_result: false +has_ref_opstate: false +has_rc_opstate: false +has_fast_callback_option: false +needs_fast_callback_option: true +fast_result: Some(Void) +fast_parameters: [V8Value, Pointer, Uint32Array] +transforms: {0: Transform { kind: PtrVoid, index: 0 }, 1: Transform { kind: SliceU32(true), index: 1 }} +is_async: false +fast_compatible: true diff --git a/ops/optimizer_tests/op_ffi_ptr_value.out b/ops/optimizer_tests/op_ffi_ptr_value.out new file mode 100644 index 0000000000..bfbbd5ae75 --- /dev/null +++ b/ops/optimizer_tests/op_ffi_ptr_value.out @@ -0,0 +1,127 @@ +#[allow(non_camel_case_types)] +///Auto-generated by `deno_ops`, i.e: `#[op]` +/// +///Use `op_ffi_ptr_value::decl()` to get an op-declaration +///you can include in a `deno_core::Extension`. +pub struct op_ffi_ptr_value; +#[doc(hidden)] +impl op_ffi_ptr_value { + pub fn name() -> &'static str { + stringify!(op_ffi_ptr_value) + } + pub fn v8_fn_ptr<'scope>() -> deno_core::v8::FunctionCallback { + use deno_core::v8::MapFnTo; + Self::v8_func.map_fn_to() + } + pub fn decl<'scope>() -> deno_core::OpDecl { + deno_core::OpDecl { + name: Self::name(), + v8_fn_ptr: Self::v8_fn_ptr(), + enabled: true, + fast_fn: Some( + Box::new(op_ffi_ptr_value_fast { + _phantom: ::std::marker::PhantomData, + }), + ), + is_async: false, + is_unstable: false, + is_v8: false, + argc: 2usize, + } + } + #[inline] + #[allow(clippy::too_many_arguments)] + pub fn call(ptr: *mut c_void, out: &mut [u32]) {} + pub fn v8_func<'scope>( + scope: &mut deno_core::v8::HandleScope<'scope>, + args: deno_core::v8::FunctionCallbackArguments, + mut rv: deno_core::v8::ReturnValue, + ) { + let ctx = unsafe { + &*(deno_core::v8::Local::::cast(args.data()).value() + as *const deno_core::_ops::OpCtx) + }; + let arg_0 = { + let value = args.get(0usize as i32); + if value.is_null() { + std::ptr::null_mut() + } else if let Ok(b) + = deno_core::v8::Local::::try_from(value) { + b.value() + } else { + return deno_core::_ops::throw_type_error( + scope, + format!("Expected External at position {}", 0usize), + ); + } + }; + let arg_1 = if let Ok(view) + = deno_core::v8::Local::< + deno_core::v8::Uint32Array, + >::try_from(args.get(1usize as i32)) { + let (offset, len) = (view.byte_offset(), view.byte_length()); + let buffer = match view.buffer(scope) { + Some(v) => v, + None => { + return deno_core::_ops::throw_type_error( + scope, + format!("Expected Uint32Array at position {}", 1usize), + ); + } + }; + if let Some(data) = buffer.data() { + let store = data.cast::().as_ptr(); + unsafe { + ::std::slice::from_raw_parts_mut( + store.add(offset) as *mut u32, + len / 4, + ) + } + } else { + &mut [] + } + } else { + return deno_core::_ops::throw_type_error( + scope, + format!("Expected Uint32Array at position {}", 1usize), + ); + }; + let result = Self::call(arg_0, arg_1); + let op_state = ::std::cell::RefCell::borrow(&*ctx.state); + op_state.tracker.track_sync(ctx.id); + } +} +struct op_ffi_ptr_value_fast { + _phantom: ::std::marker::PhantomData<()>, +} +impl<'scope> deno_core::v8::fast_api::FastFunction for op_ffi_ptr_value_fast { + fn function(&self) -> *const ::std::ffi::c_void { + op_ffi_ptr_value_fast_fn as *const ::std::ffi::c_void + } + fn args(&self) -> &'static [deno_core::v8::fast_api::Type] { + use deno_core::v8::fast_api::Type::*; + use deno_core::v8::fast_api::CType; + &[V8Value, Pointer, TypedArray(CType::Uint32), CallbackOptions] + } + fn return_type(&self) -> deno_core::v8::fast_api::CType { + deno_core::v8::fast_api::CType::Void + } +} +fn op_ffi_ptr_value_fast_fn<'scope>( + _: deno_core::v8::Local, + ptr: *mut ::std::ffi::c_void, + out: *const deno_core::v8::fast_api::FastApiTypedArray, + fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions, +) -> () { + use deno_core::v8; + use deno_core::_ops; + let out = match unsafe { &*out }.get_storage_if_aligned() { + Some(v) => v, + None => { + unsafe { &mut *fast_api_callback_options }.fallback = true; + return Default::default(); + } + }; + let result = op_ffi_ptr_value::call(ptr, out); + result +} diff --git a/ops/optimizer_tests/op_ffi_ptr_value.rs b/ops/optimizer_tests/op_ffi_ptr_value.rs new file mode 100644 index 0000000000..4c3364507b --- /dev/null +++ b/ops/optimizer_tests/op_ffi_ptr_value.rs @@ -0,0 +1,3 @@ +pub fn op_ffi_ptr_value(ptr: *mut c_void, out: &mut [u32]) { + // ... +} diff --git a/serde_v8/error.rs b/serde_v8/error.rs index 2cd8eab655..145524abbe 100644 --- a/serde_v8/error.rs +++ b/serde_v8/error.rs @@ -22,6 +22,7 @@ pub enum Error { ExpectedObject, ExpectedBuffer, ExpectedDetachable, + ExpectedExternal, ExpectedUtf8, ExpectedLatin1, diff --git a/serde_v8/lib.rs b/serde_v8/lib.rs index c15ca715a0..b857acbe81 100644 --- a/serde_v8/lib.rs +++ b/serde_v8/lib.rs @@ -20,6 +20,7 @@ pub use magic::bytestring::ByteString; pub use magic::detached_buffer::DetachedBuffer; pub use magic::string_or_buffer::StringOrBuffer; pub use magic::u16string::U16String; +pub use magic::ExternalPointer; pub use magic::Global; pub use magic::Value; pub use ser::to_v8; diff --git a/serde_v8/magic/external_pointer.rs b/serde_v8/magic/external_pointer.rs new file mode 100644 index 0000000000..fca6028d67 --- /dev/null +++ b/serde_v8/magic/external_pointer.rs @@ -0,0 +1,56 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::ffi::c_void; + +use super::transl8::impl_magic; +use super::transl8::FromV8; +use super::transl8::ToV8; + +pub struct ExternalPointer(*mut c_void); + +// SAFETY: Nonblocking FFI is user controller and we must trust user to have it right. +unsafe impl Send for ExternalPointer {} +// SAFETY: Nonblocking FFI is user controller and we must trust user to have it right. +unsafe impl Sync for ExternalPointer {} + +impl_magic!(ExternalPointer); + +impl ToV8 for ExternalPointer { + fn to_v8<'a>( + &mut self, + scope: &mut v8::HandleScope<'a>, + ) -> Result, crate::Error> { + if self.0.is_null() { + Ok(v8::null(scope).into()) + } else { + Ok(v8::External::new(scope, self.0).into()) + } + } +} + +impl FromV8 for ExternalPointer { + fn from_v8( + _scope: &mut v8::HandleScope, + value: v8::Local, + ) -> Result { + if value.is_null() { + Ok(ExternalPointer(std::ptr::null_mut())) + } else if let Ok(external) = v8::Local::::try_from(value) { + Ok(ExternalPointer(external.value())) + } else { + Err(crate::Error::ExpectedExternal) + } + } +} + +impl From<*mut c_void> for ExternalPointer { + fn from(value: *mut c_void) -> Self { + ExternalPointer(value) + } +} + +impl From<*const c_void> for ExternalPointer { + fn from(value: *const c_void) -> Self { + ExternalPointer(value as *mut c_void) + } +} diff --git a/serde_v8/magic/mod.rs b/serde_v8/magic/mod.rs index fe45776728..f96e422b16 100644 --- a/serde_v8/magic/mod.rs +++ b/serde_v8/magic/mod.rs @@ -2,6 +2,7 @@ pub mod buffer; pub mod bytestring; pub mod detached_buffer; +mod external_pointer; mod global; pub(super) mod rawbytes; pub mod string_or_buffer; @@ -9,5 +10,6 @@ pub mod transl8; pub mod u16string; pub mod v8slice; mod value; +pub use external_pointer::ExternalPointer; pub use global::Global; pub use value::Value; diff --git a/serde_v8/ser.rs b/serde_v8/ser.rs index 834efa78a9..fa4cfecde7 100644 --- a/serde_v8/ser.rs +++ b/serde_v8/ser.rs @@ -16,6 +16,7 @@ use crate::magic::transl8::ToV8; use crate::magic::transl8::MAGIC_FIELD; use crate::ByteString; use crate::DetachedBuffer; +use crate::ExternalPointer; use crate::StringOrBuffer; use crate::U16String; use crate::ZeroCopyBuf; @@ -269,6 +270,7 @@ impl<'a, 'b, 'c, T: MagicType + ToV8> ser::SerializeStruct // Dispatches between magic and regular struct serializers pub enum StructSerializers<'a, 'b, 'c> { + ExternalPointer(MagicalSerializer<'a, 'b, 'c, magic::ExternalPointer>), Magic(MagicalSerializer<'a, 'b, 'c, magic::Value<'a>>), ZeroCopyBuf(MagicalSerializer<'a, 'b, 'c, ZeroCopyBuf>), MagicDetached(MagicalSerializer<'a, 'b, 'c, DetachedBuffer>), @@ -288,6 +290,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> { value: &T, ) -> Result<()> { match self { + StructSerializers::ExternalPointer(s) => s.serialize_field(key, value), StructSerializers::Magic(s) => s.serialize_field(key, value), StructSerializers::ZeroCopyBuf(s) => s.serialize_field(key, value), StructSerializers::MagicDetached(s) => s.serialize_field(key, value), @@ -302,6 +305,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> { fn end(self) -> JsResult<'a> { match self { + StructSerializers::ExternalPointer(s) => s.end(), StructSerializers::Magic(s) => s.end(), StructSerializers::ZeroCopyBuf(s) => s.end(), StructSerializers::MagicDetached(s) => s.end(), @@ -385,8 +389,8 @@ macro_rules! forward_to { }; } -const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1; -const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER; +pub(crate) const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1; +pub(crate) const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER; impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> { type Ok = v8::Local<'a, v8::Value>; @@ -564,6 +568,10 @@ impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> { len: usize, ) -> Result { match name { + magic::ExternalPointer::MAGIC_NAME => { + let m = MagicalSerializer::::new(self.scope); + Ok(StructSerializers::ExternalPointer(m)) + } ByteString::MAGIC_NAME => { let m = MagicalSerializer::::new(self.scope); Ok(StructSerializers::MagicByteString(m)) diff --git a/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts index d7c66203c3..c04c13d794 100644 --- a/test_ffi/tests/ffi_types.ts +++ b/test_ffi/tests/ffi_types.ts @@ -140,30 +140,31 @@ remote.symbols.method14(0); remote.symbols.method15("foo"); // @ts-expect-error: Invalid argument remote.symbols.method15(new Uint16Array(1)); -remote.symbols.method15(0n); +remote.symbols.method15(null); +remote.symbols.method15({} as Deno.PointerValue); const result = remote.symbols.method16(); // @ts-expect-error: Invalid argument let r_0: string = result; -let r_1: Deno.PointerValue = result; +let r_1: number | bigint = result; const result2 = remote.symbols.method17(); // @ts-expect-error: Invalid argument result2.then((_0: string) => {}); -result2.then((_1: Deno.PointerValue) => {}); +result2.then((_1: number | bigint) => {}); const result3 = remote.symbols.method18(); // @ts-expect-error: Invalid argument let r3_0: Deno.BufferSource = result3; -let r3_1: Deno.UnsafePointer = result3; +let r3_1: null | Deno.UnsafePointer = result3; const result4 = remote.symbols.method19(); // @ts-expect-error: Invalid argument result4.then((_0: Deno.BufferSource) => {}); -result4.then((_1: Deno.UnsafePointer) => {}); +result4.then((_1: null | Deno.UnsafePointer) => {}); const fnptr = new Deno.UnsafeFnPointer( - 0n, + {} as NonNullable, { parameters: ["u32", "pointer"], result: "void", @@ -210,7 +211,7 @@ const unsafe_callback_right1 = new Deno.UnsafeCallback( parameters: ["u8", "u32", "pointer"], result: "void", }, - (_1: number, _2: number, _3: Deno.PointerValue) => {}, + (_1: number, _2: number, _3: null | Deno.PointerValue) => {}, ); const unsafe_callback_right2 = new Deno.UnsafeCallback( { @@ -232,14 +233,14 @@ const unsafe_callback_right4 = new Deno.UnsafeCallback( parameters: ["u8", "u32", "pointer"], result: "u8", }, - (_1: number, _2: number, _3: Deno.PointerValue) => 3, + (_1: number, _2: number, _3: null | Deno.PointerValue) => 3, ); const unsafe_callback_right5 = new Deno.UnsafeCallback( { parameters: ["u8", "i32", "pointer"], result: "void", }, - (_1: number, _2: number, _3: Deno.PointerValue) => {}, + (_1: number, _2: number, _3: null | Deno.PointerValue) => {}, ); // @ts-expect-error: Must pass callback @@ -255,9 +256,9 @@ remote.symbols.method23(new Uint32Array(1)); remote.symbols.method23(new Uint8Array(1)); // @ts-expect-error: Cannot pass pointer values as buffer. -remote.symbols.method23(0); +remote.symbols.method23({}); // @ts-expect-error: Cannot pass pointer values as buffer. -remote.symbols.method23(0n); +remote.symbols.method23({}); remote.symbols.method23(null); // @ts-expect-error: Cannot pass number as bool. @@ -278,16 +279,16 @@ let r42_1: number = remote.symbols.method24(true); // @ts-expect-error: Invalid member type const static1_wrong: null = remote.symbols.static1; -const static1_right: Deno.PointerValue = remote.symbols.static1; +const static1_right: number | bigint = remote.symbols.static1; // @ts-expect-error: Invalid member type const static2_wrong: null = remote.symbols.static2; -const static2_right: Deno.UnsafePointer = remote.symbols.static2; +const static2_right: null | Deno.UnsafePointer = remote.symbols.static2; // @ts-expect-error: Invalid member type const static3_wrong: null = remote.symbols.static3; -const static3_right: Deno.PointerValue = remote.symbols.static3; +const static3_right: number | bigint = remote.symbols.static3; // @ts-expect-error: Invalid member type const static4_wrong: null = remote.symbols.static4; -const static4_right: Deno.PointerValue = remote.symbols.static4; +const static4_right: number | bigint = remote.symbols.static4; // @ts-expect-error: Invalid member type const static5_wrong: null = remote.symbols.static5; const static5_right: number = remote.symbols.static5; @@ -299,7 +300,7 @@ const static7_wrong: null = remote.symbols.static7; const static7_right: number = remote.symbols.static7; // @ts-expect-error: Invalid member type const static8_wrong: null = remote.symbols.static8; -const static8_right: Deno.PointerValue = remote.symbols.static8; +const static8_right: number | bigint = remote.symbols.static8; // @ts-expect-error: Invalid member type const static9_wrong: null = remote.symbols.static9; const static9_right: number = remote.symbols.static9; @@ -311,7 +312,7 @@ const static11_wrong: null = remote.symbols.static11; const static11_right: number = remote.symbols.static11; // @ts-expect-error: Invalid member type const static12_wrong: null = remote.symbols.static12; -const static12_right: Deno.PointerValue = remote.symbols.static12; +const static12_right: number | bigint = remote.symbols.static12; // @ts-expect-error: Invalid member type const static13_wrong: null = remote.symbols.static13; const static13_right: number = remote.symbols.static13; @@ -376,8 +377,8 @@ type __Tests__ = [ symbols: { pushBuf: ( buf: BufferSource | null, - ptr: Deno.PointerValue | null, - func: Deno.PointerValue | null, + ptr: Deno.PointerValue, + func: Deno.PointerValue, ) => Deno.PointerValue; }; close(): void; @@ -395,8 +396,8 @@ type __Tests__ = [ { symbols: { foo: ( - ...args: (Deno.PointerValue | null)[] - ) => Deno.PointerValue; + ...args: (number | Deno.PointerValue | null)[] + ) => number | bigint; }; close(): void; }, diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs index c213f82951..fdfb5d8958 100644 --- a/test_ffi/tests/integration_tests.rs +++ b/test_ffi/tests/integration_tests.rs @@ -97,12 +97,8 @@ fn basic() { -9007199254740992n\n\ 579.9119873046875\n\ 579.912\n\ - After sleep_blocking\n\ - true\n\ Before\n\ - true\n\ After\n\ - true\n\ logCallback\n\ 1 -1 2 -2 3 -3 4 -4 0.5 -0.5 1 2 3 4 5 6 7 8\n\ u8: 8\n\ @@ -119,23 +115,12 @@ fn basic() { 78\n\ STORED_FUNCTION cleared\n\ STORED_FUNCTION_2 cleared\n\ - Thread safe call counter: 0\n\ logCallback\n\ - Thread safe call counter: 1\n\ u8: 8\n\ - Static u32: 42\n\ - Static i64: -1242464576485\n\ - Static ptr: true\n\ - Static ptr value: 42\n\ Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }\n\ Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }\n\ Rect { x: 20.0, y: 20.0, w: 100.0, h: 200.0 }\n\ Mixed { u8: 3, f32: 12.515, rect: Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }, usize: 12456789, array: [8, 32] }\n\ - arrayBuffer.byteLength: 4\n\ - uint32Array.length: 1\n\ - uint32Array[0]: 42\n\ - uint32Array[0] after mutation: 55\n\ - Static ptr value after mutation: 55\n\ 2264956937\n\ 2264956937\n\ Correct number of resources\n"; diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js index 15900e72c0..788faa93e9 100644 --- a/test_ffi/tests/test.js +++ b/test_ffi/tests/test.js @@ -1,12 +1,15 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // deno-lint-ignore-file -// Run using cargo test or `--v8-options=--allow-natives-syntax` +// Run using cargo test or `--v8-flags=--allow-natives-syntax` -import { assertEquals, assertInstanceOf, assertNotEquals } from "https://deno.land/std@0.149.0/testing/asserts.ts"; import { assertThrows, assert, + assertNotEquals, + assertInstanceOf, + assertEquals, + assertFalse, } from "../../test_util/std/testing/asserts.ts"; const targetDir = Deno.execPath().replace(/[^\/\\]+$/, ""); @@ -368,8 +371,8 @@ assertEquals(isNullBufferDeopt(externalOneBuffer), false, "isNullBufferDeopt(ext // Due to ops macro using `Local->Data()` to get the pointer for the slice that is then used to get // the pointer of an ArrayBuffer / TypedArray, the same effect can be seen where a zero byte length buffer returns // a null pointer as its pointer value. -assertEquals(Deno.UnsafePointer.of(externalZeroBuffer), 0, "Deno.UnsafePointer.of(externalZeroBuffer) !== 0"); -assertNotEquals(Deno.UnsafePointer.of(externalOneBuffer), 0, "Deno.UnsafePointer.of(externalOneBuffer) !== 0"); +assertEquals(Deno.UnsafePointer.of(externalZeroBuffer), null, "Deno.UnsafePointer.of(externalZeroBuffer) !== null"); +assertNotEquals(Deno.UnsafePointer.of(externalOneBuffer), null, "Deno.UnsafePointer.of(externalOneBuffer) === null"); const addU32Ptr = dylib.symbols.get_add_u32_ptr(); const addU32 = new Deno.UnsafeFnPointer(addU32Ptr, { @@ -486,16 +489,15 @@ await promise; let start = performance.now(); dylib.symbols.sleep_blocking(100); -console.log("After sleep_blocking"); -console.log(performance.now() - start >= 100); +assert(performance.now() - start >= 100); start = performance.now(); const promise_2 = dylib.symbols.sleep_nonblocking(100).then(() => { console.log("After"); - console.log(performance.now() - start >= 100); + assert(performance.now() - start >= 100); }); console.log("Before"); -console.log(performance.now() - start < 100); +assert(performance.now() - start < 100); // Await to make sure `sleep_nonblocking` calls and logs before we proceed await promise_2; @@ -532,7 +534,7 @@ const returnU8Callback = new Deno.UnsafeCallback( ); const returnBufferCallback = new Deno.UnsafeCallback({ parameters: [], - result: "pointer", + result: "buffer", }, () => { return buffer; }); @@ -617,27 +619,29 @@ const addToFooCallback = new Deno.UnsafeCallback({ }, () => counter++); // Test thread safe callbacks -console.log("Thread safe call counter:", counter); +assertEquals(counter, 0); addToFooCallback.ref(); await dylib.symbols.call_fn_ptr_thread_safe(addToFooCallback.pointer); addToFooCallback.unref(); logCallback.ref(); await dylib.symbols.call_fn_ptr_thread_safe(logCallback.pointer); logCallback.unref(); -console.log("Thread safe call counter:", counter); +assertEquals(counter, 1); returnU8Callback.ref(); await dylib.symbols.call_fn_ptr_return_u8_thread_safe(returnU8Callback.pointer); // Purposefully do not unref returnU8Callback: Instead use it to test close() unrefing. // Test statics -console.log("Static u32:", dylib.symbols.static_u32); -console.log("Static i64:", dylib.symbols.static_i64); -console.log( - "Static ptr:", - typeof dylib.symbols.static_ptr === "number", +assertEquals(dylib.symbols.static_u32, 42); +assertEquals(dylib.symbols.static_i64, -1242464576485); +assert( + typeof dylib.symbols.static_ptr === "object" +); +assertEquals( + Object.keys(dylib.symbols.static_ptr).length, 0 ); const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr); -console.log("Static ptr value:", view.getUint32()); +assertEquals(view.getUint32(), 42); // Test struct returning const rect_sync = dylib.symbols.make_rect(10, 20, 100, 200); @@ -656,7 +660,7 @@ assertEquals(rect_async.length, 4 * 8); assertEquals(Array.from(new Float64Array(rect_async.buffer)), [10, 20, 100, 200]); // Test complex, mixed struct returning and passing -const mixedStruct = dylib.symbols.create_mixed(3, 12.515000343322754, rect_async, 12456789, new Uint32Array([8, 32])); +const mixedStruct = dylib.symbols.create_mixed(3, 12.515000343322754, rect_async, Deno.UnsafePointer.create(12456789), new Uint32Array([8, 32])); assertEquals(mixedStruct.length, 56); assertEquals(Array.from(mixedStruct.subarray(0, 4)), [3, 0, 0, 0]); assertEquals(new Float32Array(mixedStruct.buffer, 4, 1)[0], 12.515000343322754); @@ -681,12 +685,31 @@ cb.close(); const arrayBuffer = view.getArrayBuffer(4); const uint32Array = new Uint32Array(arrayBuffer); -console.log("arrayBuffer.byteLength:", arrayBuffer.byteLength); -console.log("uint32Array.length:", uint32Array.length); -console.log("uint32Array[0]:", uint32Array[0]); +assertEquals(arrayBuffer.byteLength, 4); +assertEquals(uint32Array.length, 1); +assertEquals(uint32Array[0], 42); uint32Array[0] = 55; // MUTATES! -console.log("uint32Array[0] after mutation:", uint32Array[0]); -console.log("Static ptr value after mutation:", view.getUint32()); +assertEquals(uint32Array[0], 55); +assertEquals(view.getUint32(), 55); + + +{ + // Test UnsafePointer APIs + assertEquals(Deno.UnsafePointer.create(0), null); + const createdPointer = Deno.UnsafePointer.create(1); + assertNotEquals(createdPointer, null); + assertEquals(typeof createdPointer, "object"); + assertEquals(Deno.UnsafePointer.value(null), 0); + assertEquals(Deno.UnsafePointer.value(createdPointer), 1); + assert(Deno.UnsafePointer.equals(null, null)); + assertFalse(Deno.UnsafePointer.equals(null, createdPointer)); + assertFalse(Deno.UnsafePointer.equals(Deno.UnsafePointer.create(2), createdPointer)); + // Do not allow offsetting from null, `create` function should be used instead. + assertThrows(() => Deno.UnsafePointer.offset(null, 5)); + const offsetPointer = Deno.UnsafePointer.offset(createdPointer, 5); + assertEquals(Deno.UnsafePointer.value(offsetPointer), 6); + assertEquals(Deno.UnsafePointer.offset(offsetPointer, -6), null); +} // Test non-UTF-8 characters