deno.land / x / deno@v1.28.2 / test_ffi / tests / test.js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.// deno-lint-ignore-file
// Run using cargo test or `--v8-options=--allow-natives-syntax`
import { assertEquals } from "https://deno.land/std@0.149.0/testing/asserts.ts";import { assertThrows, assert,} from "../../test_util/std/testing/asserts.ts";
const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");const [libPrefix, libSuffix] = { darwin: ["lib", "dylib"], linux: ["lib", "so"], windows: ["", "dll"],}[Deno.build.os];const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
const resourcesPre = Deno.resources();
// dlopen shouldn't panicassertThrows(() => { Deno.dlopen("cli/src/main.rs", {});});
assertThrows( () => { Deno.dlopen(libPath, { non_existent_symbol: { parameters: [], result: "void", }, }); }, Error, "Failed to register symbol non_existent_symbol",);
const dylib = Deno.dlopen(libPath, { "printSomething": { name: "print_something", parameters: [], result: "void", }, "print_buffer": { parameters: ["buffer", "usize"], result: "void" }, "print_pointer": { name: "print_buffer", parameters: ["pointer", "usize"], result: "void" }, "print_buffer2": { parameters: ["buffer", "usize", "buffer", "usize"], result: "void", }, "return_buffer": { parameters: [], result: "buffer" }, "is_null_ptr": { parameters: ["pointer"], result: "u8" }, "add_u32": { parameters: ["u32", "u32"], result: "u32" }, "add_i32": { parameters: ["i32", "i32"], result: "i32" }, "add_u64": { parameters: ["u64", "u64"], result: "u64" }, "add_i64": { parameters: ["i64", "i64"], result: "i64" }, "add_usize": { parameters: ["usize", "usize"], result: "usize" }, "add_usize_fast": { parameters: ["usize", "usize"], result: "u32" }, "add_isize": { parameters: ["isize", "isize"], result: "isize" }, "add_f32": { parameters: ["f32", "f32"], result: "f32" }, "add_f64": { parameters: ["f64", "f64"], result: "f64" }, "and": { parameters: ["bool", "bool"], result: "bool" }, "add_u32_nonblocking": { name: "add_u32", parameters: ["u32", "u32"], result: "u32", nonblocking: true, }, "add_i32_nonblocking": { name: "add_i32", parameters: ["i32", "i32"], result: "i32", nonblocking: true, }, "add_u64_nonblocking": { name: "add_u64", parameters: ["u64", "u64"], result: "u64", nonblocking: true, }, "add_i64_nonblocking": { name: "add_i64", parameters: ["i64", "i64"], result: "i64", nonblocking: true, }, "add_usize_nonblocking": { name: "add_usize", parameters: ["usize", "usize"], result: "usize", nonblocking: true, }, "add_isize_nonblocking": { name: "add_isize", parameters: ["isize", "isize"], result: "isize", nonblocking: true, }, "add_f32_nonblocking": { name: "add_f32", parameters: ["f32", "f32"], result: "f32", nonblocking: true, }, "add_f64_nonblocking": { name: "add_f64", parameters: ["f64", "f64"], result: "f64", nonblocking: true, }, "fill_buffer": { parameters: ["u8", "buffer", "usize"], result: "void" }, "sleep_nonblocking": { name: "sleep_blocking", parameters: ["u64"], result: "void", nonblocking: true, }, "sleep_blocking": { parameters: ["u64"], result: "void" }, "nonblocking_buffer": { parameters: ["buffer", "usize"], result: "void", nonblocking: true, }, "get_add_u32_ptr": { parameters: [], result: "pointer", }, "get_sleep_blocking_ptr": { parameters: [], result: "pointer", }, // Callback function call_fn_ptr: { parameters: ["function"], result: "void", }, call_fn_ptr_thread_safe: { name: "call_fn_ptr", parameters: ["function"], result: "void", nonblocking: true, }, call_fn_ptr_many_parameters: { parameters: ["function"], result: "void", }, call_fn_ptr_return_u8: { parameters: ["function"], result: "void", }, call_fn_ptr_return_u8_thread_safe: { name: "call_fn_ptr_return_u8", parameters: ["function"], result: "void", }, call_fn_ptr_return_buffer: { parameters: ["function"], result: "void", }, store_function: { parameters: ["function"], result: "void", }, store_function_2: { parameters: ["function"], result: "void", }, call_stored_function: { parameters: [], result: "void", callback: true, }, call_stored_function_2: { parameters: ["u8"], result: "void", callback: true, }, log_many_parameters: { parameters: ["u8", "u16", "u32", "u64", "f64", "f32", "i64", "i32", "i16", "i8", "isize", "usize", "f64", "f32", "f64", "f32", "f64", "f32", "f64"], result: "void", }, cast_u8_u32: { parameters: ["u8"], result: "u32", }, cast_u32_u8: { parameters: ["u32"], result: "u8", }, add_many_u16: { parameters: ["u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16"], result: "u16", }, // Statics "static_u32": { type: "u32", }, "static_i64": { type: "i64", }, "static_ptr": { type: "pointer", }, /** * Invalid UTF-8 characters, buffer of length 14 */ "static_char": { type: "pointer", }, "hash": { parameters: ["buffer", "u32"], result: "u32" },});const { symbols } = dylib;
symbols.printSomething();const buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);const buffer2 = new Uint8Array([9, 10]);dylib.symbols.print_buffer(buffer, buffer.length);// Test subarraysconst subarray = buffer.subarray(3);dylib.symbols.print_buffer(subarray, subarray.length - 2);dylib.symbols.print_buffer2(buffer, buffer.length, buffer2, buffer2.length);
const { return_buffer } = symbols;function returnBuffer() { return return_buffer(); };
%PrepareFunctionForOptimization(returnBuffer);returnBuffer();%OptimizeFunctionOnNextCall(returnBuffer);const ptr0 = returnBuffer();assertIsOptimized(returnBuffer);
dylib.symbols.print_pointer(ptr0, 8);const ptrView = new Deno.UnsafePointerView(ptr0);const into = new Uint8Array(6);const into2 = new Uint8Array(3);const into2ptr = Deno.UnsafePointer.of(into2);const into2ptrView = new Deno.UnsafePointerView(into2ptr);const into3 = new Uint8Array(3);ptrView.copyInto(into);console.log([...into]);ptrView.copyInto(into2, 3);console.log([...into2]);into2ptrView.copyInto(into3);console.log([...into3]);const string = new Uint8Array([ ...new TextEncoder().encode("Hello from pointer!"), 0,]);const stringPtr = Deno.UnsafePointer.of(string);const stringPtrview = new Deno.UnsafePointerView(stringPtr);console.log(stringPtrview.getCString());console.log(stringPtrview.getCString(11));console.log(Boolean(dylib.symbols.is_null_ptr(ptr0)));console.log(Boolean(dylib.symbols.is_null_ptr(null)));console.log(Boolean(dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(into))));const emptyBuffer = new BigUint64Array(0);console.log(Boolean(dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(emptyBuffer))));const emptySlice = into.subarray(6);console.log(Boolean(dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(emptySlice))));
const addU32Ptr = dylib.symbols.get_add_u32_ptr();const addU32 = new Deno.UnsafeFnPointer(addU32Ptr, { parameters: ["u32", "u32"], result: "u32",});console.log(addU32.call(123, 456));
const sleepBlockingPtr = dylib.symbols.get_sleep_blocking_ptr();const sleepNonBlocking = new Deno.UnsafeFnPointer(sleepBlockingPtr, { nonblocking: true, parameters: ["u64"], result: "void",});const before = performance.now();await sleepNonBlocking.call(100);console.log(performance.now() - before >= 100);
const { add_u32, add_usize_fast } = symbols;function addU32Fast(a, b) { return add_u32(a, b);};testOptimized(addU32Fast, () => addU32Fast(123, 456));
function addU64Fast(a, b) { return add_usize_fast(a, b); };testOptimized(addU64Fast, () => addU64Fast(2, 3));
console.log(dylib.symbols.add_i32(123, 456));console.log(dylib.symbols.add_u64(0xffffffffn, 0xffffffffn));console.log(dylib.symbols.add_i64(-0xffffffffn, -0xffffffffn));console.log(dylib.symbols.add_usize(0xffffffffn, 0xffffffffn));console.log(dylib.symbols.add_isize(-0xffffffffn, -0xffffffffn));console.log(dylib.symbols.add_u64(Number.MAX_SAFE_INTEGER, 1));console.log(dylib.symbols.add_i64(Number.MAX_SAFE_INTEGER, 1));console.log(dylib.symbols.add_i64(Number.MIN_SAFE_INTEGER, -1));console.log(dylib.symbols.add_usize(Number.MAX_SAFE_INTEGER, 1));console.log(dylib.symbols.add_isize(Number.MAX_SAFE_INTEGER, 1));console.log(dylib.symbols.add_isize(Number.MIN_SAFE_INTEGER, -1));console.log(dylib.symbols.add_f32(123.123, 456.789));console.log(dylib.symbols.add_f64(123.123, 456.789));console.log(dylib.symbols.and(true, true));console.log(dylib.symbols.and(true, false));
function addF32Fast(a, b) { return dylib.symbols.add_f32(a, b);};testOptimized(addF32Fast, () => addF32Fast(123.123, 456.789));
function addF64Fast(a, b) { return dylib.symbols.add_f64(a, b);};testOptimized(addF64Fast, () => addF64Fast(123.123, 456.789));
// Test adders as nonblocking callsconsole.log(await dylib.symbols.add_i32_nonblocking(123, 456));console.log(await dylib.symbols.add_u64_nonblocking(0xffffffffn, 0xffffffffn));console.log( await dylib.symbols.add_i64_nonblocking(-0xffffffffn, -0xffffffffn),);console.log( await dylib.symbols.add_usize_nonblocking(0xffffffffn, 0xffffffffn),);console.log( await dylib.symbols.add_isize_nonblocking(-0xffffffffn, -0xffffffffn),);console.log(await dylib.symbols.add_u64_nonblocking(Number.MAX_SAFE_INTEGER, 1));console.log(await dylib.symbols.add_i64_nonblocking(Number.MAX_SAFE_INTEGER, 1));console.log(await dylib.symbols.add_i64_nonblocking(Number.MIN_SAFE_INTEGER, -1));console.log(await dylib.symbols.add_usize_nonblocking(Number.MAX_SAFE_INTEGER, 1));console.log(await dylib.symbols.add_isize_nonblocking(Number.MAX_SAFE_INTEGER, 1));console.log(await dylib.symbols.add_isize_nonblocking(Number.MIN_SAFE_INTEGER, -1));console.log(await dylib.symbols.add_f32_nonblocking(123.123, 456.789));console.log(await dylib.symbols.add_f64_nonblocking(123.123, 456.789));
// test mutating sync calls
function test_fill_buffer(fillValue, arr) { let buf = new Uint8Array(arr); dylib.symbols.fill_buffer(fillValue, buf, buf.length); for (let i = 0; i < buf.length; i++) { if (buf[i] !== fillValue) { throw new Error(`Found '${buf[i]}' in buffer, expected '${fillValue}'.`); } }}
test_fill_buffer(0, [2, 3, 4]);test_fill_buffer(5, [2, 7, 3, 2, 1]);
// Test non blocking calls
function deferred() { let methods; const promise = new Promise((resolve, reject) => { methods = { async resolve(value) { await value; resolve(value); }, reject(reason) { reject(reason); }, }; }); return Object.assign(promise, methods);}
const promise = deferred();const buffer3 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);dylib.symbols.nonblocking_buffer(buffer3, buffer3.length).then(() => { promise.resolve();});await promise;
let start = performance.now();dylib.symbols.sleep_blocking(100);console.log("After sleep_blocking");console.log(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);});console.log("Before");console.log(performance.now() - start < 100);
// Await to make sure `sleep_nonblocking` calls and logs before we proceedawait promise_2;
// Test calls with callback parametersconst logCallback = new Deno.UnsafeCallback( { parameters: [], result: "void" }, () => console.log("logCallback"),);const logManyParametersCallback = new Deno.UnsafeCallback({ parameters: [ "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "f32", "f64", "pointer", ], result: "void",}, (u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, pointer) => { const view = new Deno.UnsafePointerView(pointer); const copy_buffer = new Uint8Array(8); view.copyInto(copy_buffer); console.log(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, ...copy_buffer);});const returnU8Callback = new Deno.UnsafeCallback( { parameters: [], result: "u8" }, () => 8,);const returnBufferCallback = new Deno.UnsafeCallback({ parameters: [], result: "pointer",}, () => { return buffer;});const add10Callback = new Deno.UnsafeCallback({ parameters: ["u8"], result: "u8",}, (value) => value + 10);const throwCallback = new Deno.UnsafeCallback({ parameters: [], result: "void",}, () => { throw new TypeError("hi");});
assertThrows( () => { dylib.symbols.call_fn_ptr(throwCallback.pointer); }, TypeError, "hi",);
const { call_stored_function } = dylib.symbols;
dylib.symbols.call_fn_ptr(logCallback.pointer);dylib.symbols.call_fn_ptr_many_parameters(logManyParametersCallback.pointer);dylib.symbols.call_fn_ptr_return_u8(returnU8Callback.pointer);dylib.symbols.call_fn_ptr_return_buffer(returnBufferCallback.pointer);dylib.symbols.store_function(logCallback.pointer);call_stored_function();dylib.symbols.store_function_2(add10Callback.pointer);dylib.symbols.call_stored_function_2(20);
function logManyParametersFast(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s) { return symbols.log_many_parameters(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s);};testOptimized( logManyParametersFast, () => logManyParametersFast( 255, 65535, 4294967295, 4294967296, 123.456, 789.876, -1, -2, -3, -4, -1000, 1000, 12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910 ));
// Some ABIs rely on the convention to zero/sign-extend arguments by the caller to optimize the callee function.// If the trampoline did not zero/sign-extend arguments, this would return 256 instead of the expected 0 (in optimized builds)function castU8U32Fast(x) { return symbols.cast_u8_u32(x); };testOptimized(castU8U32Fast, () => castU8U32Fast(256));
// Some ABIs rely on the convention to expect garbage in the bits beyond the size of the return value to optimize the callee function.// If the trampoline did not zero/sign-extend the return value, this would return 256 instead of the expected 0 (in optimized builds)function castU32U8Fast(x) { return symbols.cast_u32_u8(x); };testOptimized(castU32U8Fast, () => castU32U8Fast(256));
// Generally the trampoline tail-calls into the FFI function, but in certain cases (e.g. when returning 8 or 16 bit integers)// the tail call is not possible and a new stack frame must be created. We need enough parameters to have some on the stackfunction addManyU16Fast(a, b, c, d, e, f, g, h, i, j, k, l, m) { return symbols.add_many_u16(a, b, c, d, e, f, g, h, i, j, k, l, m);};// N.B. V8 does not currently follow Aarch64 Apple's calling convention.// The current implementation of the JIT trampoline follows the V8 incorrect calling convention. This test covers the use-case// and is expected to fail once Deno uses a V8 version with the bug fixed.// The V8 bug is being tracked in https://bugs.chromium.org/p/v8/issues/detail?id=13171testOptimized(addManyU16Fast, () => addManyU16Fast(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
const nestedCallback = new Deno.UnsafeCallback( { parameters: [], result: "void" }, () => { dylib.symbols.call_stored_function_2(10); },);dylib.symbols.store_function(nestedCallback.pointer);
dylib.symbols.store_function(null);dylib.symbols.store_function_2(null);
let counter = 0;const addToFooCallback = new Deno.UnsafeCallback({ parameters: [], result: "void",}, () => counter++);
// Test thread safe callbacksconsole.log("Thread safe call counter:", counter);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);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 staticsconsole.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",);const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr);console.log("Static ptr value:", view.getUint32());
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]);uint32Array[0] = 55; // MUTATES!console.log("uint32Array[0] after mutation:", uint32Array[0]);console.log("Static ptr value after mutation:", view.getUint32());
// Test non-UTF-8 characters
const charView = new Deno.UnsafePointerView(dylib.symbols.static_char);
const charArrayBuffer = charView.getArrayBuffer(14);const uint8Array = new Uint8Array(charArrayBuffer);assertEquals([...uint8Array], [ 0xC0, 0xC1, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00]);
try { assertThrows(() => charView.getCString(), TypeError, "Invalid CString pointer, not valid UTF-8");} catch (_err) { console.log("Invalid UTF-8 characters to `v8::String`:", charView.getCString());}
const bytes = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);function hash() { return dylib.symbols.hash(bytes, bytes.byteLength); };
testOptimized(hash, () => hash());
(function cleanup() { dylib.close(); throwCallback.close(); logCallback.close(); logManyParametersCallback.close(); returnU8Callback.close(); returnBufferCallback.close(); add10Callback.close(); nestedCallback.close(); addToFooCallback.close();
const resourcesPost = Deno.resources();
const preStr = JSON.stringify(resourcesPre, null, 2); const postStr = JSON.stringify(resourcesPost, null, 2); if (preStr !== postStr) { throw new Error( `Difference in open resources before dlopen and after closing:Before: ${preStr}After: ${postStr}`, ); }
console.log("Correct number of resources");})();
function assertIsOptimized(fn) { const status = %GetOptimizationStatus(fn); assert(status & (1 << 4), `expected ${fn.name} to be optimized, but wasn't`);}
function testOptimized(fn, callback) { %PrepareFunctionForOptimization(fn); const r1 = callback(); if (r1 !== undefined) { console.log(r1); } %OptimizeFunctionOnNextCall(fn); const r2 = callback(); if (r2 !== undefined) { console.log(r2); } assertIsOptimized(fn);}
Version Info