Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING(ffi/unstable): Use BigInt representation in turbocall #23983

Merged
12 changes: 6 additions & 6 deletions cli/tsc/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ declare namespace Deno {
: T extends NativeU32Enum<infer U> ? U
: T extends NativeI32Enum<infer U> ? U
: number
: T extends NativeBigIntType ? number | bigint
: T extends NativeBigIntType ? bigint
: T extends NativeBooleanType ? boolean
: T extends NativePointerType
? T extends NativeTypedPointer<infer U> ? U | null : PointerValue
Expand Down Expand Up @@ -501,7 +501,7 @@ declare namespace Deno {
*/
export class UnsafePointer {
/** Create a pointer from a numeric value. This one is <i>really</i> dangerous! */
static create<T = unknown>(value: number | bigint): PointerValue<T>;
static create<T = unknown>(value: bigint): PointerValue<T>;
/** Returns `true` if the two pointers point to the same address. */
static equals<T = unknown>(a: PointerValue<T>, b: PointerValue<T>): boolean;
/** Return the direct memory pointer to the typed array in memory. */
Expand All @@ -514,7 +514,7 @@ declare namespace Deno {
offset: number,
): PointerValue<T>;
/** Get the numeric value of a pointer */
static value(value: PointerValue): number | bigint;
static value(value: PointerValue): bigint;
}

/** **UNSTABLE**: New API, yet to be vetted.
Expand Down Expand Up @@ -554,10 +554,10 @@ declare namespace Deno {
getInt32(offset?: number): number;
/** Gets an unsigned 64-bit integer at the specified byte offset from the
* pointer. */
getBigUint64(offset?: number): number | bigint;
getBigUint64(offset?: number): bigint;
/** Gets a signed 64-bit integer at the specified byte offset from the
* pointer. */
getBigInt64(offset?: number): number | bigint;
getBigInt64(offset?: number): bigint;
/** Gets a signed 32-bit float at the specified byte offset from the
* pointer. */
getFloat32(offset?: number): number;
Expand Down Expand Up @@ -816,7 +816,7 @@ declare namespace Deno {
* );
*
* // Call the symbol `add`
* const result = dylib.symbols.add(35, 34); // 69
* const result = dylib.symbols.add(35n, 34n); // 69n
*
* console.log(`Result from external addition of 35 and 34: ${result}`);
* ```
Expand Down
65 changes: 11 additions & 54 deletions ext/ffi/00_ffi.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,14 @@ const {
ArrayBufferPrototypeGetByteLength,
ArrayPrototypeMap,
ArrayPrototypeJoin,
BigInt,
DataViewPrototypeGetByteLength,
ObjectDefineProperty,
ObjectHasOwn,
ObjectPrototypeIsPrototypeOf,
NumberIsSafeInteger,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
TypeError,
Uint8Array,
Int32Array,
Uint32Array,
BigInt64Array,
BigUint64Array,
Function,
ReflectHas,
PromisePrototypeThen,
Expand All @@ -79,9 +74,6 @@ function getBufferSourceByteLength(source) {
}
return ArrayBufferPrototypeGetByteLength(source);
}
const U32_BUFFER = new Uint32Array(2);
const U64_BUFFER = new BigUint64Array(TypedArrayPrototypeGetBuffer(U32_BUFFER));
const I64_BUFFER = new BigInt64Array(TypedArrayPrototypeGetBuffer(U32_BUFFER));
class UnsafePointerView {
pointer;

Expand Down Expand Up @@ -139,21 +131,21 @@ class UnsafePointerView {
}

getBigUint64(offset = 0) {
op_ffi_read_u64(
return op_ffi_read_u64(
this.pointer,
offset,
U32_BUFFER,
// We return a BigInt, so the turbocall
// is forced to use BigInts everywhere.
BigInt(offset),
);
return U64_BUFFER[0];
}

getBigInt64(offset = 0) {
op_ffi_read_i64(
return op_ffi_read_i64(
this.pointer,
offset,
U32_BUFFER,
// We return a BigInt, so the turbocall
// is forced to use BigInts everywhere.
BigInt(offset),
);
return I64_BUFFER[0];
}

getFloat32(offset = 0) {
Expand Down Expand Up @@ -226,10 +218,6 @@ class UnsafePointerView {
}
}

const OUT_BUFFER = new Uint32Array(2);
const OUT_BUFFER_64 = new BigInt64Array(
TypedArrayPrototypeGetBuffer(OUT_BUFFER),
);
const POINTER_TO_BUFFER_WEAK_MAP = new SafeWeakMap();
class UnsafePointer {
static create(value) {
Expand Down Expand Up @@ -279,12 +267,7 @@ class UnsafePointer {
if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) {
value = value.pointer;
}
op_ffi_ptr_value(value, OUT_BUFFER);
const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1];
if (NumberIsSafeInteger(result)) {
return result;
}
return OUT_BUFFER_64[0];
return op_ffi_ptr_value(value);
}
}

Expand Down Expand Up @@ -342,11 +325,6 @@ class UnsafeFnPointer {
}
}

function isReturnedAsBigInt(type) {
return type === "u64" || type === "i64" ||
type === "usize" || type === "isize";
}

function isStruct(type) {
return typeof type === "object" && type !== null &&
typeof type.struct === "object";
Expand Down Expand Up @@ -517,7 +495,6 @@ class DynamicLibrary {
const structSize = isStructResult
? getTypeSizeAndAlignment(resultType)[0]
: 0;
const needsUnpacking = isReturnedAsBigInt(resultType);

const isNonBlocking = symbols[symbol].nonblocking;
if (isNonBlocking) {
Expand Down Expand Up @@ -553,27 +530,7 @@ class DynamicLibrary {
);
}

if (needsUnpacking && !isNonBlocking) {
const call = this.symbols[symbol];
const parameters = symbols[symbol].parameters;
const vi = new Int32Array(2);
const b = new BigInt64Array(TypedArrayPrototypeGetBuffer(vi));

const params = ArrayPrototypeJoin(
ArrayPrototypeMap(parameters, (_, index) => `p${index}`),
", ",
);
// Make sure V8 has no excuse to not optimize this function.
this.symbols[symbol] = new Function(
"vi",
"b",
"call",
`return function (${params}) {
call(${params}${parameters.length > 0 ? ", " : ""}vi);
return b[0];
}`,
)(vi, b, call);
} else if (isStructResult && !isNonBlocking) {
if (isStructResult && !isNonBlocking) {
const call = this.symbols[symbol];
const parameters = symbols[symbol].parameters;
const params = ArrayPrototypeJoin(
Expand Down
48 changes: 4 additions & 44 deletions ext/ffi/dlfcn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,6 @@ impl DynamicLibraryResource {
}
}

pub fn needs_unwrap(rv: &NativeType) -> bool {
matches!(
rv,
NativeType::I64 | NativeType::ISize | NativeType::U64 | NativeType::USize
)
}

fn is_i64(rv: &NativeType) -> bool {
matches!(rv, NativeType::I64 | NativeType::ISize)
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ForeignFunction {
Expand Down Expand Up @@ -242,10 +231,6 @@ fn make_sync_fn<'s>(
// SAFETY: The pointer will not be deallocated until the function is
// garbage collected.
let symbol = unsafe { &*(external.value() as *const Symbol) };
let needs_unwrap = match needs_unwrap(&symbol.result_type) {
true => Some(args.get(symbol.parameter_types.len() as i32)),
false => None,
};
let out_buffer = match symbol.result_type {
NativeType::Struct(_) => {
let argc = args.length();
Expand All @@ -261,35 +246,10 @@ fn make_sync_fn<'s>(
};
match crate::call::ffi_call_sync(scope, args, symbol, out_buffer) {
Ok(result) => {
match needs_unwrap {
Some(v) => {
let view: v8::Local<v8::ArrayBufferView> = v.try_into().unwrap();
let pointer =
view.buffer(scope).unwrap().data().unwrap().as_ptr() as *mut u8;

if is_i64(&symbol.result_type) {
// SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
// it points to a fixed continuous slice of bytes on the heap.
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<v8::BackingStore> is similar to Arc<[u8]>,
// it points to a fixed continuous slice of bytes on the heap.
let bs = unsafe { &mut *(pointer as *mut u64) };
// SAFETY: We checked that type == U64
let value = unsafe { result.u64_value };
*bs = value;
}
}
None => {
let result =
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
unsafe { result.to_v8(scope, symbol.result_type.clone()) };
rv.set(result);
}
}
let result =
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
unsafe { result.to_v8(scope, symbol.result_type.clone()) };
rv.set(result);
}
Err(err) => {
deno_core::_ops::throw_type_error(scope, err.to_string());
Expand Down
56 changes: 16 additions & 40 deletions ext/ffi/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,30 +112,19 @@ unsafe extern "C" fn noop_deleter_callback(
}

#[op2(fast)]
#[bigint]
pub fn op_ffi_ptr_value<FP>(
state: &mut OpState,
ptr: *mut c_void,
#[buffer] out: &mut [u32],
) -> Result<(), AnyError>
) -> Result<usize, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointer#value");
let permissions = state.borrow_mut::<FP>();
permissions.check_partial(None)?;

let outptr = out.as_ptr() as *mut usize;
let length = out.len();
assert!(
length >= (std::mem::size_of::<usize>() / std::mem::size_of::<u32>())
);
assert_eq!(outptr as usize % std::mem::size_of::<usize>(), 0);

// SAFETY: Out buffer was asserted to be at least large enough to hold a usize, and properly aligned.
let out = unsafe { &mut *outptr };
*out = ptr as usize;

Ok(())
Ok(ptr as usize)
}

#[op2]
Expand Down Expand Up @@ -398,12 +387,14 @@ where
}

#[op2(fast)]
#[bigint]
pub fn op_ffi_read_u64<FP>(
state: &mut OpState,
ptr: *mut c_void,
#[number] offset: isize,
#[buffer] out: &mut [u32],
) -> Result<(), AnyError>
// Note: The representation of 64-bit integers is function-wide. We cannot
// choose to take this parameter as a number while returning a bigint.
#[bigint] offset: isize,
) -> Result<u64, AnyError>
where
FP: FfiPermissions + 'static,
{
Expand All @@ -412,13 +403,6 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check_partial(None)?;

let outptr = out.as_mut_ptr() as *mut u64;

assert!(
out.len() >= (std::mem::size_of::<u64>() / std::mem::size_of::<u32>())
);
assert_eq!((outptr as usize % std::mem::size_of::<u64>()), 0);

if ptr.is_null() {
return Err(type_error("Invalid u64 pointer, pointer is null"));
}
Expand All @@ -427,33 +411,26 @@ where
// SAFETY: ptr and offset are user provided.
unsafe { ptr::read_unaligned::<u64>(ptr.offset(offset) as *const u64) };

// SAFETY: Length and alignment of out slice were asserted to be correct.
unsafe { *outptr = value };
Ok(())
Ok(value)
}

#[op2(fast)]
#[bigint]
pub fn op_ffi_read_i64<FP>(
state: &mut OpState,
ptr: *mut c_void,
#[number] offset: isize,
#[buffer] out: &mut [u32],
) -> Result<(), AnyError>
// Note: The representation of 64-bit integers is function-wide. We cannot
// choose to take this parameter as a number while returning a bigint.
#[bigint] offset: isize,
) -> Result<i64, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
check_unstable(state, "Deno.UnsafePointerView#getBigInt64");

let permissions = state.borrow_mut::<FP>();
permissions.check_partial(None)?;

let outptr = out.as_mut_ptr() as *mut i64;

assert!(
out.len() >= (std::mem::size_of::<i64>() / std::mem::size_of::<u32>())
);
assert_eq!((outptr as usize % std::mem::size_of::<i64>()), 0);

if ptr.is_null() {
return Err(type_error("Invalid i64 pointer, pointer is null"));
}
Expand All @@ -462,8 +439,7 @@ where
// SAFETY: ptr and offset are user provided.
unsafe { ptr::read_unaligned::<i64>(ptr.offset(offset) as *const i64) };
// SAFETY: Length and alignment of out slice were asserted to be correct.
unsafe { *outptr = value };
Ok(())
Ok(value)
}

#[op2(fast)]
Expand Down
Loading