#[op2]
Expand description
A macro designed to provide an extremely fast V8->Rust interface layer.
op2
#[op2]
is the in-progress replacement for #[op]
.
Strings
String
s in Rust are always UTF-8. String
s in v8, however, are either
two-byte UTF-16 or one-byte Latin-1. One-byte Latin-1 strings are not
byte-compatible with UTF-8, as characters with the index 128-255 require two
bytes to encode in UTF-8.
Because of this, String
s in op
s always require a copy (at least) to ensure
that we are not incorrectly passing Latin-1 data to methods that expect a UTF-8
string. At this time there is no way to avoid this copy, though the op
code
does attempt to avoid any allocations where possible by making use of a stack
buffer.
async
calls
Asynchronous calls are supported in two forms:
async fn op_xyz(/* ... */) -> X {}
and
fn op_xyz(/* ... */) -> impl Future<Output = X> {}
These are desugared to a function that adds a hidden promise_id
argument, and
returns Option<X>
instead. Deno will eagerly poll the op, and if it is
immediately ready, the function will return Some(X)
. If the op is not ready,
the function will return None
and the future will be handled by Deno’s pending
op system.
fn op_xyz(promise_id: i32, /* ... */) -> Option<X> {}
Eager async
calls: async
By default, async
functions are eagerly polled, which reduces the latency of
the call dramatically if the async function is ready to return a value
immediately.
async(lazy)
async
calls may be marked as lazy
, which allows the runtime to defer polling
the op until a later time. The submission of an async(lazy)
op might be
faster, but the latency will be higher for ops that would have been ready on the
first poll.
NOTE: You may need to use this to get the maximum performance out of a set of async tasks, but it should only be used alongside careful benchmarking. In some cases it will allow for higher throughput at the expense of latency.
Lazy async
calls may be fastcalls, though the resolution will still happen
on a slow path.
async(deferred)
async
calls may also be marked as deferred
, which will allow the runtime to
poll the op immediately, but any results that are ready are deferred until a
later run of the event loop.
NOTE: This is almost certainly not what you want to use and should only be used if you really know what you are doing.
Lazy async(deferred)
calls may be fastcalls, though the resolution will
still happen on a slow path.
fastcalls
op2
requires fastcall-compatible ops to be annotated with fast
. If you wish
to avoid fastcalls for some reason (this is unlikely), you can specify nofast
instead.
You may also choose an alternate op function to use as the fastcall equivalent
to a slow function. In this case, you can specify fast(op_XYZ)
. The other op
must be decorated with #[op2(fast)]
, and does not need to be registered. When
v8 optimized the slow function to a fastcall, it will switch the implementation
over if the parameters are compatible. This is useful for a function that takes
any buffer type in the slow path and wishes to use the very fast typed u8
buffer for the fast path.
Parameters
Rust | Fastcall | v8 | |
---|---|---|---|
| ✅ | Bool | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | SMI is internally represented as a signed integer, but unsigned `#[smi]` types will be bit-converted to unsigned values for the Rust call. JavaScript code will continue to see signed integers. |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | Uint32, Int32, Number, BigInt | |
| ✅ | String | Fastcall available only if string is Latin-1. Will always create an allocated, UTF-8 copy of the String data. |
| ✅ | String | Fastcall available only if string is Latin-1. Will create an owned `String` copy of the String data if it doesn't fit on the stack. Will never allocate in a fastcall, but will copy Latin-1 -> UTF-8. |
| ✅ | String | Fastcall available only if string is Latin-1. Will create a `Cow::Owned` copy of the String data if it doesn't fit on the stack. Will always be `Cow::Borrowed` in a fastcall, but will copy Latin-1 -> UTF-8. |
| ✅ | String | Fastest `String`-type method. If the string is not Latin-1, will throw a TypeError. |
| ✅ | any | |
| ✅ | String | |
| ✅ | Object | |
| ✅ | Function | |
| ✅ | ... | |
| ✅ | any | |
| ✅ | String | |
| ✅ | Object | |
| ✅ | Function | |
| ✅ | ... | |
| any | ⚠️ Slower than `v8::Local`. | |
| String | ⚠️ Slower than `v8::Local`. | |
| Object | ⚠️ Slower than `v8::Local`. | |
| Function | ⚠️ Slower than `v8::Local`. | |
| ... | ⚠️ Slower than `v8::Local`. | |
| any | ⚠️ May be slow. | |
| any | ⚠️ May be slow. | |
| ✅ | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. |
| ✅ | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. |
| ✅ | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. |
| ✅ | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. |
| ✅ | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. |
| ✅ | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. |
| ✅ | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. |
| ✅ | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. |
| ✅ | ArrayBuffer (resizable=true,false) | Safe, but forces a copy. |
| ✅ | ArrayBuffer (resizable=true,false) | Safe, but forces a copy. |
| ✅ | ArrayBuffer (resizable=true,false) | Safe, but forces a copy. |
| ✅ | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. |
| ✅ | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. |
| ✅ | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. |
| ✅ | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. |
| ✅ | UInt8Array (resizable=true,false) | Safe, but forces a copy. |
| ✅ | UInt8Array (resizable=true,false) | Safe, but forces a copy. |
| ✅ | UInt8Array (resizable=true,false) | Safe, but forces a copy. |
| ✅ | UInt32Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. |
| ✅ | UInt32Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. |
| ✅ | UInt32Array (resizable=true,false) | Safe, but forces a copy. |
| ✅ | UInt32Array (resizable=true,false) | Safe, but forces a copy. |
| ✅ | ArrayBufferView (resizable=false) | ⚠️ JS may modify the contents of slices obtained from buffer. |
| ArrayBufferView (resizable=true,false) | Safe. | |
| ✅ | External | |
| ✅ | External | |
| ✅ | ||
| ✅ | ||
| ✅ | ||
| ✅ | Extracts an object from `OpState`. | |
| ✅ | Extracts an object from `OpState`. | |
| ✅ | Only usable in `deno_core`. | |
| ✅ | Only usable in `deno_core`. | |
| ✅ | Only usable in `deno_core`. | |
| ✅ | ⚠️ Extremely dangerous, may crash if you don't use `nofast` depending on what you do. |
Return Values
Rust | Fastcall | Async | v8 | |
---|---|---|---|---|
| ✅ | |||
| ✅ | |||
| ✅ | |||
| ✅ | |||
| ✅ | |||
| ✅ | |||
| ✅ | |||
| ✅ | SMI is internally represented as a signed integer, but unsigned `#[smi]` types will be bit-converted to unsigned values for the Rust call. JavaScript code will continue to see signed integers. | ||
| ✅ | |||
| ✅ | |||
| ✅ | |||
| ✅ | |||
| ✅ | Result must fit within `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER` | ||
| ✅ | Result must fit within `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER` | ||
| ✅ | Result must fit within `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER` | ||
| ✅ | Result must fit within `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER` | ||
| ✅ | |||
| ✅ | |||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ✅ | |||
| ✅ | |||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
|