a4dfc6f955
Currently, slow call path will always create a dangling pointer to replace a null pointer when called with eg. a `new Uint8Array()` parameter, which V8 initialises as a null pointer backed buffer. However, the fast call path will never change the pointer value and will thus expose a null pointer. Thus, it's possible that the pointer value that a native call sees coming from Deno changes between two sequential invocations of the same function with the exact same parameters. Since null pointers can be quite important, and `Uint8Array` is the chosen fast path for Deno FFI `"buffer"` parameters, I think it is fairly important that the null pointer be properly exposed to the native code. Thus this PR. ### `*mut c_void` While here, I also changed the type of our pointer values to `*mut c_void`. This is mainly due to JS buffers always being `*mut`, and because we offer a way to turn a pointer into a JS `ArrayBuffer` (`op_ffi_get_buf`) which is read-write. I'm not exactly sure which way we should really go here, we have pointers that are definitely mut but we also cannot assume all of our pointers are. So, do we go with the maxima or the minima? ### `optimisedCall(new Uint8Array())` V8 seems to have a bug where calling an optimised function with a newly created empty `Uint8Array` (no argument or 0) will not see the data pointer being null but instead it's some stable pointer, perhaps pointing to some internal null-backing-store. The pointer value is also an odd (not even) number, so it might specifically be a tagged pointer. This will probably be an issue for some users, if they try to use eg. `method(cstr("something"), new Uint8Array())` as a way to do a fast call to `method` with a null pointer as the second parameter. If instead of a `new Uint8Array()` the user instead uses some `const NULL = new Uint8Array()` where the `NULL` buffer has been passed to a slow call previously, then the fast call will properly see a null pointer. I'll take this up with some V8 engineers to see if this couldn't be fixed. |
||
---|---|---|
.. | ||
00_ffi.js | ||
Cargo.toml | ||
fast_call.rs | ||
lib.rs | ||
README.md |
deno_ffi
This crate implements dynamic library ffi.
Performance
Deno FFI calls have extremely low overhead (~1ns on M1 16GB RAM) and perform on par with native code. Deno leverages V8 fast api calls and JIT compiled bindings to achieve these high speeds.
Deno.dlopen
generates an optimized and a fallback path. Optimized paths are
triggered when V8 decides to optimize the function, hence call through the Fast
API. Fallback paths handle types like function callbacks and implement proper
error handling for unexpected types, that is not supported in Fast calls.
Optimized calls enter a JIT compiled function "trampoline" that translates Fast
API values directly for symbol calls. JIT compilation itself is super fast,
thanks to tinycc
. Currently, the optimized path is only supported on Linux and
MacOS.
To run benchmarks:
target/release/deno bench --allow-ffi --allow-read --unstable ./test_ffi/tests/bench.js