mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
Reland "perf(core): generate inlined wrappers for async ops" (#16455)
Reland https://github.com/denoland/deno/pull/16428
This commit is contained in:
parent
d9e425a947
commit
e18950284f
10 changed files with 266 additions and 112 deletions
|
@ -63,7 +63,6 @@ pub fn bench_js_sync_with(
|
||||||
|
|
||||||
let code = v8::String::new(scope, looped_src.as_ref()).unwrap();
|
let code = v8::String::new(scope, looped_src.as_ref()).unwrap();
|
||||||
let script = v8::Script::compile(scope, code, None).unwrap();
|
let script = v8::Script::compile(scope, code, None).unwrap();
|
||||||
|
|
||||||
// Run once if profiling, otherwise regular bench loop
|
// Run once if profiling, otherwise regular bench loop
|
||||||
if is_profiling() {
|
if is_profiling() {
|
||||||
script.run(scope).unwrap();
|
script.run(scope).unwrap();
|
||||||
|
@ -102,7 +101,9 @@ pub fn bench_js_async_with(
|
||||||
};
|
};
|
||||||
let looped = loop_code(inner_iters, src);
|
let looped = loop_code(inner_iters, src);
|
||||||
let src = looped.as_ref();
|
let src = looped.as_ref();
|
||||||
|
runtime
|
||||||
|
.execute_script("init", "Deno.core.initializeAsyncOps();")
|
||||||
|
.unwrap();
|
||||||
if is_profiling() {
|
if is_profiling() {
|
||||||
for _ in 0..opts.profiling_outer {
|
for _ in 0..opts.profiling_outer {
|
||||||
tokio_runtime.block_on(inner_async(src, &mut runtime));
|
tokio_runtime.block_on(inner_async(src, &mut runtime));
|
||||||
|
|
|
@ -5,7 +5,7 @@ let [total, count] = typeof Deno !== "undefined"
|
||||||
: [process.argv[2], process.argv[3]];
|
: [process.argv[2], process.argv[3]];
|
||||||
|
|
||||||
total = total ? parseInt(total, 0) : 50;
|
total = total ? parseInt(total, 0) : 50;
|
||||||
count = count ? parseInt(count, 10) : 100000;
|
count = count ? parseInt(count, 10) : 1000000;
|
||||||
|
|
||||||
async function bench(fun) {
|
async function bench(fun) {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
@ -16,4 +16,5 @@ async function bench(fun) {
|
||||||
if (--total) queueMicrotask(() => bench(fun));
|
if (--total) queueMicrotask(() => bench(fun));
|
||||||
}
|
}
|
||||||
|
|
||||||
bench(() => Deno.core.opAsync("op_void_async"));
|
const { ops } = Deno.core;
|
||||||
|
bench(() => ops.op_void_async());
|
||||||
|
|
|
@ -84,6 +84,6 @@ Deno.test(function opNamesMatch() {
|
||||||
// @ts-ignore: Deno.core allowed
|
// @ts-ignore: Deno.core allowed
|
||||||
Deno.core.opNames().sort(),
|
Deno.core.opNames().sort(),
|
||||||
// @ts-ignore: Deno.core allowed
|
// @ts-ignore: Deno.core allowed
|
||||||
Object.keys(Deno.core.ops).sort(),
|
Object.keys(Deno.core.ops).sort().filter((name) => name !== "asyncOpsInfo"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
SymbolFor,
|
SymbolFor,
|
||||||
setQueueMicrotask,
|
setQueueMicrotask,
|
||||||
} = window.__bootstrap.primordials;
|
} = window.__bootstrap.primordials;
|
||||||
const ops = window.Deno.core.ops;
|
const { ops } = window.Deno.core;
|
||||||
|
|
||||||
const errorMap = {};
|
const errorMap = {};
|
||||||
// Builtin v8 / JS errors
|
// Builtin v8 / JS errors
|
||||||
|
@ -159,21 +159,63 @@
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function opAsync(opName, ...args) {
|
function rollPromiseId() {
|
||||||
const promiseId = nextPromiseId++;
|
return nextPromiseId++;
|
||||||
let p = setPromise(promiseId);
|
}
|
||||||
|
|
||||||
|
// Generate async op wrappers. See core/bindings.rs
|
||||||
|
function initializeAsyncOps() {
|
||||||
|
function genAsyncOp(op, name, args) {
|
||||||
|
return new Function(
|
||||||
|
"setPromise",
|
||||||
|
"getPromise",
|
||||||
|
"promiseIdSymbol",
|
||||||
|
"rollPromiseId",
|
||||||
|
"handleOpCallTracing",
|
||||||
|
"op",
|
||||||
|
"unwrapOpResult",
|
||||||
|
"PromisePrototypeThen",
|
||||||
|
`
|
||||||
|
return function ${name}(${args}) {
|
||||||
|
const id = rollPromiseId();
|
||||||
|
let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult);
|
||||||
try {
|
try {
|
||||||
ops[opName](promiseId, ...args);
|
op(id, ${args});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Cleanup the just-created promise
|
// Cleanup the just-created promise
|
||||||
getPromise(promiseId);
|
getPromise(id);
|
||||||
// Rethrow the error
|
// Rethrow the error
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
p = PromisePrototypeThen(p, unwrapOpResult);
|
handleOpCallTracing("${name}", id, promise);
|
||||||
|
promise[promiseIdSymbol] = id;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)(
|
||||||
|
setPromise,
|
||||||
|
getPromise,
|
||||||
|
promiseIdSymbol,
|
||||||
|
rollPromiseId,
|
||||||
|
handleOpCallTracing,
|
||||||
|
op,
|
||||||
|
unwrapOpResult,
|
||||||
|
PromisePrototypeThen,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// { <name>: <argc>, ... }
|
||||||
|
for (const ele of Object.entries(ops.asyncOpsInfo())) {
|
||||||
|
if (!ele) continue;
|
||||||
|
const [name, argc] = ele;
|
||||||
|
const op = ops[name];
|
||||||
|
const args = Array.from({ length: argc }, (_, i) => `arg${i}`).join(", ");
|
||||||
|
ops[name] = genAsyncOp(op, name, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpCallTracing(opName, promiseId, p) {
|
||||||
if (opCallTracingEnabled) {
|
if (opCallTracingEnabled) {
|
||||||
// Capture a stack trace by creating a new `Error` object. We remove the
|
|
||||||
// first 6 characters (the `Error\n` prefix) to get just the stack trace.
|
|
||||||
const stack = StringPrototypeSlice(new Error().stack, 6);
|
const stack = StringPrototypeSlice(new Error().stack, 6);
|
||||||
MapPrototypeSet(opCallTraces, promiseId, { opName, stack });
|
MapPrototypeSet(opCallTraces, promiseId, { opName, stack });
|
||||||
p = PromisePrototypeFinally(
|
p = PromisePrototypeFinally(
|
||||||
|
@ -181,9 +223,10 @@
|
||||||
() => MapPrototypeDelete(opCallTraces, promiseId),
|
() => MapPrototypeDelete(opCallTraces, promiseId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Save the id on the promise so it can later be ref'ed or unref'ed
|
}
|
||||||
p[promiseIdSymbol] = promiseId;
|
|
||||||
return p;
|
function opAsync(opName, ...args) {
|
||||||
|
return ops[opName](...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
function refOp(promiseId) {
|
function refOp(promiseId) {
|
||||||
|
@ -303,6 +346,7 @@
|
||||||
// Extra Deno.core.* exports
|
// Extra Deno.core.* exports
|
||||||
const core = ObjectAssign(globalThis.Deno.core, {
|
const core = ObjectAssign(globalThis.Deno.core, {
|
||||||
opAsync,
|
opAsync,
|
||||||
|
initializeAsyncOps,
|
||||||
resources,
|
resources,
|
||||||
metrics,
|
metrics,
|
||||||
registerErrorBuilder,
|
registerErrorBuilder,
|
||||||
|
@ -322,11 +366,11 @@
|
||||||
setPromiseHooks,
|
setPromiseHooks,
|
||||||
close: (rid) => ops.op_close(rid),
|
close: (rid) => ops.op_close(rid),
|
||||||
tryClose: (rid) => ops.op_try_close(rid),
|
tryClose: (rid) => ops.op_try_close(rid),
|
||||||
read: opAsync.bind(null, "op_read"),
|
read: (rid, buffer) => ops.op_read(rid, buffer),
|
||||||
readAll: opAsync.bind(null, "op_read_all"),
|
readAll: (rid) => ops.op_read_all(rid),
|
||||||
write: opAsync.bind(null, "op_write"),
|
write: (rid, buffer) => ops.op_write(rid, buffer),
|
||||||
writeAll: opAsync.bind(null, "op_write_all"),
|
writeAll: (rid, buffer) => ops.op_write_all(rid, buffer),
|
||||||
shutdown: opAsync.bind(null, "op_shutdown"),
|
shutdown: (rid) => ops.op_shutdown(rid),
|
||||||
print: (msg, isErr) => ops.op_print(msg, isErr),
|
print: (msg, isErr) => ops.op_print(msg, isErr),
|
||||||
setMacrotaskCallback: (fn) => ops.op_set_macrotask_callback(fn),
|
setMacrotaskCallback: (fn) => ops.op_set_macrotask_callback(fn),
|
||||||
setNextTickCallback: (fn) => ops.op_set_next_tick_callback(fn),
|
setNextTickCallback: (fn) => ops.op_set_next_tick_callback(fn),
|
||||||
|
|
|
@ -97,6 +97,7 @@ pub fn initialize_context<'s>(
|
||||||
scope: &mut v8::HandleScope<'s, ()>,
|
scope: &mut v8::HandleScope<'s, ()>,
|
||||||
op_ctxs: &[OpCtx],
|
op_ctxs: &[OpCtx],
|
||||||
snapshot_loaded: bool,
|
snapshot_loaded: bool,
|
||||||
|
will_snapshot: bool,
|
||||||
) -> v8::Local<'s, v8::Context> {
|
) -> v8::Local<'s, v8::Context> {
|
||||||
let scope = &mut v8::EscapableHandleScope::new(scope);
|
let scope = &mut v8::EscapableHandleScope::new(scope);
|
||||||
|
|
||||||
|
@ -112,7 +113,9 @@ pub fn initialize_context<'s>(
|
||||||
let ops_obj = JsRuntime::grab_global::<v8::Object>(scope, "Deno.core.ops")
|
let ops_obj = JsRuntime::grab_global::<v8::Object>(scope, "Deno.core.ops")
|
||||||
.expect("Deno.core.ops to exist");
|
.expect("Deno.core.ops to exist");
|
||||||
initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded);
|
initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded);
|
||||||
|
if !will_snapshot {
|
||||||
|
initialize_async_ops_info(scope, ops_obj, op_ctxs);
|
||||||
|
}
|
||||||
return scope.escape(context);
|
return scope.escape(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +127,10 @@ pub fn initialize_context<'s>(
|
||||||
|
|
||||||
// Bind functions to Deno.core.ops.*
|
// Bind functions to Deno.core.ops.*
|
||||||
let ops_obj = JsRuntime::ensure_objs(scope, global, "Deno.core.ops").unwrap();
|
let ops_obj = JsRuntime::ensure_objs(scope, global, "Deno.core.ops").unwrap();
|
||||||
|
if !will_snapshot {
|
||||||
initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded);
|
initialize_async_ops_info(scope, ops_obj, op_ctxs);
|
||||||
|
}
|
||||||
|
initialize_ops(scope, ops_obj, op_ctxs, !will_snapshot);
|
||||||
scope.escape(context)
|
scope.escape(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,3 +591,84 @@ pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
|
||||||
let exception = v8::Exception::type_error(scope, message);
|
let exception = v8::Exception::type_error(scope, message);
|
||||||
scope.throw_exception(exception);
|
scope.throw_exception(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AsyncOpsInfo {
|
||||||
|
ptr: *const OpCtx,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> IntoIterator for &'s AsyncOpsInfo {
|
||||||
|
type Item = &'s OpCtx;
|
||||||
|
type IntoIter = AsyncOpsInfoIterator<'s>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
AsyncOpsInfoIterator {
|
||||||
|
// SAFETY: OpCtx slice is valid for the lifetime of the Isolate
|
||||||
|
info: unsafe { std::slice::from_raw_parts(self.ptr, self.len) },
|
||||||
|
index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AsyncOpsInfoIterator<'s> {
|
||||||
|
info: &'s [OpCtx],
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Iterator for AsyncOpsInfoIterator<'s> {
|
||||||
|
type Item = &'s OpCtx;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
match self.info.get(self.index) {
|
||||||
|
Some(ctx) if ctx.decl.is_async => {
|
||||||
|
self.index += 1;
|
||||||
|
return Some(ctx);
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
self.index += 1;
|
||||||
|
}
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn async_ops_info(
|
||||||
|
scope: &mut v8::HandleScope,
|
||||||
|
args: v8::FunctionCallbackArguments,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
let async_op_names = v8::Object::new(scope);
|
||||||
|
let external: v8::Local<v8::External> = args.data().try_into().unwrap();
|
||||||
|
let info: &AsyncOpsInfo =
|
||||||
|
// SAFETY: external is guaranteed to be a valid pointer to AsyncOpsInfo
|
||||||
|
unsafe { &*(external.value() as *const AsyncOpsInfo) };
|
||||||
|
for ctx in info {
|
||||||
|
let name = v8::String::new(scope, ctx.decl.name).unwrap();
|
||||||
|
let argc = v8::Integer::new(scope, ctx.decl.argc as i32);
|
||||||
|
async_op_names.set(scope, name.into(), argc.into());
|
||||||
|
}
|
||||||
|
rv.set(async_op_names.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize_async_ops_info(
|
||||||
|
scope: &mut v8::HandleScope,
|
||||||
|
ops_obj: v8::Local<v8::Object>,
|
||||||
|
op_ctxs: &[OpCtx],
|
||||||
|
) {
|
||||||
|
let key = v8::String::new(scope, "asyncOpsInfo").unwrap();
|
||||||
|
let external = v8::External::new(
|
||||||
|
scope,
|
||||||
|
Box::into_raw(Box::new(AsyncOpsInfo {
|
||||||
|
ptr: op_ctxs as *const [OpCtx] as _,
|
||||||
|
len: op_ctxs.len(),
|
||||||
|
})) as *mut c_void,
|
||||||
|
);
|
||||||
|
let val = v8::Function::builder(async_ops_info)
|
||||||
|
.data(external.into())
|
||||||
|
.build(scope)
|
||||||
|
.unwrap();
|
||||||
|
val.set_name(key);
|
||||||
|
ops_obj.set(scope, key.into(), val.into());
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
// This is not a real HTTP server. We read blindly one time into 'requestBuf',
|
// This is not a real HTTP server. We read blindly one time into 'requestBuf',
|
||||||
// then write this fixed 'responseBuf'. The point of this benchmark is to
|
// then write this fixed 'responseBuf'. The point of this benchmark is to
|
||||||
// exercise the event loop in a simple yet semi-realistic way.
|
// exercise the event loop in a simple yet semi-realistic way.
|
||||||
|
Deno.core.initializeAsyncOps();
|
||||||
|
|
||||||
const requestBuf = new Uint8Array(64 * 1024);
|
const requestBuf = new Uint8Array(64 * 1024);
|
||||||
const responseBuf = new Uint8Array(
|
const responseBuf = new Uint8Array(
|
||||||
"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n"
|
"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n"
|
||||||
|
@ -16,7 +18,7 @@ function listen() {
|
||||||
|
|
||||||
/** Accepts a connection, returns rid. */
|
/** Accepts a connection, returns rid. */
|
||||||
function accept(serverRid) {
|
function accept(serverRid) {
|
||||||
return Deno.core.opAsync("op_accept", serverRid);
|
return Deno.core.ops.op_accept(serverRid);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function serve(rid) {
|
async function serve(rid) {
|
||||||
|
|
|
@ -16,6 +16,9 @@ pub struct OpDecl {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub is_async: bool,
|
pub is_async: bool,
|
||||||
pub is_unstable: bool,
|
pub is_unstable: bool,
|
||||||
|
/// V8 argument count. Used as an optimization
|
||||||
|
/// hint by `core.initalizeAsyncOps`.
|
||||||
|
pub argc: usize,
|
||||||
pub is_v8: bool,
|
pub is_v8: bool,
|
||||||
pub fast_fn: Option<Box<dyn FastFunction>>,
|
pub fast_fn: Option<Box<dyn FastFunction>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -384,7 +384,8 @@ impl JsRuntime {
|
||||||
isolate_ptr.read()
|
isolate_ptr.read()
|
||||||
};
|
};
|
||||||
let scope = &mut v8::HandleScope::new(&mut isolate);
|
let scope = &mut v8::HandleScope::new(&mut isolate);
|
||||||
let context = bindings::initialize_context(scope, &op_ctxs, false);
|
let context =
|
||||||
|
bindings::initialize_context(scope, &op_ctxs, false, true);
|
||||||
global_context = v8::Global::new(scope, context);
|
global_context = v8::Global::new(scope, context);
|
||||||
scope.set_default_context(context);
|
scope.set_default_context(context);
|
||||||
}
|
}
|
||||||
|
@ -422,7 +423,7 @@ impl JsRuntime {
|
||||||
};
|
};
|
||||||
let scope = &mut v8::HandleScope::new(&mut isolate);
|
let scope = &mut v8::HandleScope::new(&mut isolate);
|
||||||
let context =
|
let context =
|
||||||
bindings::initialize_context(scope, &op_ctxs, snapshot_loaded);
|
bindings::initialize_context(scope, &op_ctxs, snapshot_loaded, false);
|
||||||
|
|
||||||
global_context = v8::Global::new(scope, context);
|
global_context = v8::Global::new(scope, context);
|
||||||
}
|
}
|
||||||
|
@ -550,6 +551,7 @@ impl JsRuntime {
|
||||||
scope,
|
scope,
|
||||||
&self.state.borrow().op_ctxs,
|
&self.state.borrow().op_ctxs,
|
||||||
self.built_from_snapshot,
|
self.built_from_snapshot,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
JsRealm::new(v8::Global::new(scope, context))
|
JsRealm::new(v8::Global::new(scope, context))
|
||||||
};
|
};
|
||||||
|
@ -2243,6 +2245,7 @@ pub mod tests {
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
Async,
|
Async,
|
||||||
|
AsyncDeferred,
|
||||||
AsyncZeroCopy(bool),
|
AsyncZeroCopy(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2251,20 +2254,28 @@ pub mod tests {
|
||||||
dispatch_count: Arc<AtomicUsize>,
|
dispatch_count: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op(deferred)]
|
#[op]
|
||||||
async fn op_test(
|
async fn op_test(
|
||||||
rc_op_state: Rc<RefCell<OpState>>,
|
rc_op_state: Rc<RefCell<OpState>>,
|
||||||
control: u8,
|
control: u8,
|
||||||
buf: Option<ZeroCopyBuf>,
|
buf: Option<ZeroCopyBuf>,
|
||||||
) -> Result<u8, AnyError> {
|
) -> Result<u8, AnyError> {
|
||||||
|
#![allow(clippy::await_holding_refcell_ref)] // False positive.
|
||||||
let op_state_ = rc_op_state.borrow();
|
let op_state_ = rc_op_state.borrow();
|
||||||
let test_state = op_state_.borrow::<TestState>();
|
let test_state = op_state_.borrow::<TestState>();
|
||||||
test_state.dispatch_count.fetch_add(1, Ordering::Relaxed);
|
test_state.dispatch_count.fetch_add(1, Ordering::Relaxed);
|
||||||
match test_state.mode {
|
let mode = test_state.mode;
|
||||||
|
drop(op_state_);
|
||||||
|
match mode {
|
||||||
Mode::Async => {
|
Mode::Async => {
|
||||||
assert_eq!(control, 42);
|
assert_eq!(control, 42);
|
||||||
Ok(43)
|
Ok(43)
|
||||||
}
|
}
|
||||||
|
Mode::AsyncDeferred => {
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
assert_eq!(control, 42);
|
||||||
|
Ok(43)
|
||||||
|
}
|
||||||
Mode::AsyncZeroCopy(has_buffer) => {
|
Mode::AsyncZeroCopy(has_buffer) => {
|
||||||
assert_eq!(buf.is_some(), has_buffer);
|
assert_eq!(buf.is_some(), has_buffer);
|
||||||
if let Some(buf) = buf {
|
if let Some(buf) = buf {
|
||||||
|
@ -2314,14 +2325,15 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ref_unref_ops() {
|
fn test_ref_unref_ops() {
|
||||||
let (mut runtime, _dispatch_count) = setup(Mode::Async);
|
let (mut runtime, _dispatch_count) = setup(Mode::AsyncDeferred);
|
||||||
runtime
|
runtime
|
||||||
.execute_script(
|
.execute_script(
|
||||||
"filename.js",
|
"filename.js",
|
||||||
r#"
|
r#"
|
||||||
|
Deno.core.initializeAsyncOps();
|
||||||
var promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
|
var promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
|
||||||
var p1 = Deno.core.opAsync("op_test", 42);
|
var p1 = Deno.core.ops.op_test(42);
|
||||||
var p2 = Deno.core.opAsync("op_test", 42);
|
var p2 = Deno.core.ops.op_test(42);
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -2374,6 +2386,7 @@ pub mod tests {
|
||||||
"filename.js",
|
"filename.js",
|
||||||
r#"
|
r#"
|
||||||
let control = 42;
|
let control = 42;
|
||||||
|
Deno.core.initializeAsyncOps();
|
||||||
Deno.core.opAsync("op_test", control);
|
Deno.core.opAsync("op_test", control);
|
||||||
async function main() {
|
async function main() {
|
||||||
Deno.core.opAsync("op_test", control);
|
Deno.core.opAsync("op_test", control);
|
||||||
|
@ -2392,6 +2405,7 @@ pub mod tests {
|
||||||
.execute_script(
|
.execute_script(
|
||||||
"filename.js",
|
"filename.js",
|
||||||
r#"
|
r#"
|
||||||
|
Deno.core.initializeAsyncOps();
|
||||||
const p = Deno.core.opAsync("op_test", 42);
|
const p = Deno.core.opAsync("op_test", 42);
|
||||||
if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) {
|
if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) {
|
||||||
throw new Error("missing id on returned promise");
|
throw new Error("missing id on returned promise");
|
||||||
|
@ -2408,6 +2422,7 @@ pub mod tests {
|
||||||
.execute_script(
|
.execute_script(
|
||||||
"filename.js",
|
"filename.js",
|
||||||
r#"
|
r#"
|
||||||
|
Deno.core.initializeAsyncOps();
|
||||||
Deno.core.opAsync("op_test");
|
Deno.core.opAsync("op_test");
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
|
@ -2422,6 +2437,7 @@ pub mod tests {
|
||||||
.execute_script(
|
.execute_script(
|
||||||
"filename.js",
|
"filename.js",
|
||||||
r#"
|
r#"
|
||||||
|
Deno.core.initializeAsyncOps();
|
||||||
let zero_copy_a = new Uint8Array([0]);
|
let zero_copy_a = new Uint8Array([0]);
|
||||||
Deno.core.opAsync("op_test", null, zero_copy_a);
|
Deno.core.opAsync("op_test", null, zero_copy_a);
|
||||||
"#,
|
"#,
|
||||||
|
@ -3021,7 +3037,6 @@ pub mod tests {
|
||||||
function main() {
|
function main() {
|
||||||
console.log("asdf);
|
console.log("asdf);
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
@ -3041,18 +3056,16 @@ function assert(cond) {
|
||||||
throw Error("assert");
|
throw Error("assert");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let expected_error = r#"Error: assert
|
let expected_error = r#"Error: assert
|
||||||
at assert (error_stack.js:4:11)
|
at assert (error_stack.js:4:11)
|
||||||
at main (error_stack.js:9:3)
|
at main (error_stack.js:8:3)
|
||||||
at error_stack.js:12:1"#;
|
at error_stack.js:10:1"#;
|
||||||
assert_eq!(result.unwrap_err().to_string(), expected_error);
|
assert_eq!(result.unwrap_err().to_string(), expected_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3070,7 +3083,6 @@ main();
|
||||||
throw new Error("async");
|
throw new Error("async");
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await p;
|
await p;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -3083,7 +3095,7 @@ main();
|
||||||
let expected_error = r#"Error: async
|
let expected_error = r#"Error: async
|
||||||
at error_async_stack.js:5:13
|
at error_async_stack.js:5:13
|
||||||
at async error_async_stack.js:4:5
|
at async error_async_stack.js:4:5
|
||||||
at async error_async_stack.js:10:5"#;
|
at async error_async_stack.js:9:5"#;
|
||||||
|
|
||||||
match runtime.poll_event_loop(cx, false) {
|
match runtime.poll_event_loop(cx, false) {
|
||||||
Poll::Ready(Err(e)) => {
|
Poll::Ready(Err(e)) => {
|
||||||
|
@ -3138,6 +3150,7 @@ if (errMessage !== "higher-level sync error: original sync error") {
|
||||||
.execute_script(
|
.execute_script(
|
||||||
"test_error_context_async.js",
|
"test_error_context_async.js",
|
||||||
r#"
|
r#"
|
||||||
|
Deno.core.initializeAsyncOps();
|
||||||
(async () => {
|
(async () => {
|
||||||
let errMessage;
|
let errMessage;
|
||||||
try {
|
try {
|
||||||
|
@ -3176,7 +3189,6 @@ function assertEquals(a, b) {
|
||||||
const sab = new SharedArrayBuffer(16);
|
const sab = new SharedArrayBuffer(16);
|
||||||
const i32a = new Int32Array(sab);
|
const i32a = new Int32Array(sab);
|
||||||
globalThis.resolved = false;
|
globalThis.resolved = false;
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
const result = Atomics.waitAsync(i32a, 0, 0);
|
const result = Atomics.waitAsync(i32a, 0, 0);
|
||||||
result.value.then(
|
result.value.then(
|
||||||
|
@ -3184,7 +3196,6 @@ globalThis.resolved = false;
|
||||||
() => { assertUnreachable();
|
() => { assertUnreachable();
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const notify_return_value = Atomics.notify(i32a, 0, 1);
|
const notify_return_value = Atomics.notify(i32a, 0, 1);
|
||||||
assertEquals(1, notify_return_value);
|
assertEquals(1, notify_return_value);
|
||||||
"#,
|
"#,
|
||||||
|
@ -3294,7 +3305,7 @@ assertEquals(1, notify_return_value);
|
||||||
runtime
|
runtime
|
||||||
.execute_script(
|
.execute_script(
|
||||||
"op_async_borrow.js",
|
"op_async_borrow.js",
|
||||||
"Deno.core.opAsync('op_async_borrow')",
|
"Deno.core.initializeAsyncOps(); Deno.core.ops.op_async_borrow()",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
runtime.run_event_loop(false).await.unwrap();
|
runtime.run_event_loop(false).await.unwrap();
|
||||||
|
@ -3368,7 +3379,8 @@ Deno.core.ops.op_sync_serialize_object_with_numbers_as_keys({
|
||||||
.execute_script(
|
.execute_script(
|
||||||
"op_async_serialize_object_with_numbers_as_keys.js",
|
"op_async_serialize_object_with_numbers_as_keys.js",
|
||||||
r#"
|
r#"
|
||||||
Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
Deno.core.initializeAsyncOps();
|
||||||
|
Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
|
||||||
lines: {
|
lines: {
|
||||||
100: {
|
100: {
|
||||||
unit: "m"
|
unit: "m"
|
||||||
|
@ -3406,6 +3418,7 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
||||||
.execute_script(
|
.execute_script(
|
||||||
"macrotasks_and_nextticks.js",
|
"macrotasks_and_nextticks.js",
|
||||||
r#"
|
r#"
|
||||||
|
Deno.core.initializeAsyncOps();
|
||||||
(async function () {
|
(async function () {
|
||||||
const results = [];
|
const results = [];
|
||||||
Deno.core.ops.op_set_macrotask_callback(() => {
|
Deno.core.ops.op_set_macrotask_callback(() => {
|
||||||
|
@ -3416,7 +3429,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
||||||
results.push("nextTick");
|
results.push("nextTick");
|
||||||
Deno.core.ops.op_set_has_tick_scheduled(false);
|
Deno.core.ops.op_set_has_tick_scheduled(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.core.ops.op_set_has_tick_scheduled(true);
|
Deno.core.ops.op_set_has_tick_scheduled(true);
|
||||||
await Deno.core.opAsync('op_async_sleep');
|
await Deno.core.opAsync('op_async_sleep');
|
||||||
if (results[0] != "nextTick") {
|
if (results[0] != "nextTick") {
|
||||||
|
@ -3627,7 +3639,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
||||||
Deno.core.ops.op_store_pending_promise_exception(promise);
|
Deno.core.ops.op_store_pending_promise_exception(promise);
|
||||||
Deno.core.ops.op_promise_reject();
|
Deno.core.ops.op_promise_reject();
|
||||||
});
|
});
|
||||||
|
|
||||||
new Promise((_, reject) => reject(Error("reject")));
|
new Promise((_, reject) => reject(Error("reject")));
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
|
@ -3645,7 +3656,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
||||||
prev(...args);
|
prev(...args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
new Promise((_, reject) => reject(Error("reject")));
|
new Promise((_, reject) => reject(Error("reject")));
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
|
@ -3695,7 +3705,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
||||||
Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => {
|
Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => {
|
||||||
Deno.core.ops.op_promise_reject();
|
Deno.core.ops.op_promise_reject();
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new Error('top level throw');
|
throw new Error('top level throw');
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
@ -3826,8 +3835,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
||||||
const a1b = a1.subarray(0, 3);
|
const a1b = a1.subarray(0, 3);
|
||||||
const a2 = new Uint8Array([5,10,15]);
|
const a2 = new Uint8Array([5,10,15]);
|
||||||
const a2b = a2.subarray(0, 3);
|
const a2b = a2.subarray(0, 3);
|
||||||
|
|
||||||
|
|
||||||
if (!(a1.length > 0 && a1b.length > 0)) {
|
if (!(a1.length > 0 && a1b.length > 0)) {
|
||||||
throw new Error("a1 & a1b should have a length");
|
throw new Error("a1 & a1b should have a length");
|
||||||
}
|
}
|
||||||
|
@ -3838,7 +3845,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
||||||
if (a1.length > 0 || a1b.length > 0) {
|
if (a1.length > 0 || a1b.length > 0) {
|
||||||
throw new Error("expecting a1 & a1b to be detached");
|
throw new Error("expecting a1 & a1b to be detached");
|
||||||
}
|
}
|
||||||
|
|
||||||
const a3 = Deno.core.ops.op_boomerang(a2b);
|
const a3 = Deno.core.ops.op_boomerang(a2b);
|
||||||
if (a3.byteLength != 3) {
|
if (a3.byteLength != 3) {
|
||||||
throw new Error(`Expected a3.byteLength === 3, got ${a3.byteLength}`);
|
throw new Error(`Expected a3.byteLength === 3, got ${a3.byteLength}`);
|
||||||
|
@ -3849,7 +3855,6 @@ Deno.core.opAsync('op_async_serialize_object_with_numbers_as_keys', {
|
||||||
if (a2.byteLength > 0 || a2b.byteLength > 0) {
|
if (a2.byteLength > 0 || a2b.byteLength > 0) {
|
||||||
throw new Error("expecting a2 & a2b to be detached, a3 re-attached");
|
throw new Error("expecting a2 & a2b to be detached, a3 re-attached");
|
||||||
}
|
}
|
||||||
|
|
||||||
const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 });
|
const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 });
|
||||||
const w32 = new Uint32Array(wmem.buffer);
|
const w32 = new Uint32Array(wmem.buffer);
|
||||||
w32[0] = 1; w32[1] = 2; w32[2] = 3;
|
w32[0] = 1; w32[1] = 2; w32[2] = 3;
|
||||||
|
|
28
ops/lib.rs
28
ops/lib.rs
|
@ -118,7 +118,7 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let (has_fallible_fast_call, fast_impl, fast_field) =
|
let (has_fallible_fast_call, fast_impl, fast_field) =
|
||||||
codegen_fast_impl(&core, &func, name, is_async, must_be_fast);
|
codegen_fast_impl(&core, &func, name, is_async, must_be_fast);
|
||||||
|
|
||||||
let v8_body = if is_async {
|
let (v8_body, argc) = if is_async {
|
||||||
codegen_v8_async(&core, &func, margs, asyncness, deferred)
|
codegen_v8_async(&core, &func, margs, asyncness, deferred)
|
||||||
} else {
|
} else {
|
||||||
codegen_v8_sync(&core, &func, margs, has_fallible_fast_call)
|
codegen_v8_sync(&core, &func, margs, has_fallible_fast_call)
|
||||||
|
@ -154,6 +154,7 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
is_async: #is_async,
|
is_async: #is_async,
|
||||||
is_unstable: #is_unstable,
|
is_unstable: #is_unstable,
|
||||||
is_v8: #is_v8,
|
is_v8: #is_v8,
|
||||||
|
argc: #argc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +182,7 @@ fn codegen_v8_async(
|
||||||
margs: MacroArgs,
|
margs: MacroArgs,
|
||||||
asyncness: bool,
|
asyncness: bool,
|
||||||
deferred: bool,
|
deferred: bool,
|
||||||
) -> TokenStream2 {
|
) -> (TokenStream2, usize) {
|
||||||
let MacroArgs { is_v8, .. } = margs;
|
let MacroArgs { is_v8, .. } = margs;
|
||||||
let special_args = f
|
let special_args = f
|
||||||
.sig
|
.sig
|
||||||
|
@ -194,7 +195,7 @@ fn codegen_v8_async(
|
||||||
let rust_i0 = special_args.len();
|
let rust_i0 = special_args.len();
|
||||||
let args_head = special_args.into_iter().collect::<TokenStream2>();
|
let args_head = special_args.into_iter().collect::<TokenStream2>();
|
||||||
|
|
||||||
let (arg_decls, args_tail) = codegen_args(core, f, rust_i0, 1);
|
let (arg_decls, args_tail, argc) = codegen_args(core, f, rust_i0, 1);
|
||||||
let type_params = exclude_lifetime_params(&f.sig.generics.params);
|
let type_params = exclude_lifetime_params(&f.sig.generics.params);
|
||||||
|
|
||||||
let (pre_result, mut result_fut) = match asyncness {
|
let (pre_result, mut result_fut) = match asyncness {
|
||||||
|
@ -225,6 +226,7 @@ fn codegen_v8_async(
|
||||||
false => quote! { let result = Ok(result); },
|
false => quote! { let result = Ok(result); },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(
|
||||||
quote! {
|
quote! {
|
||||||
use #core::futures::FutureExt;
|
use #core::futures::FutureExt;
|
||||||
// SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime
|
// SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime
|
||||||
|
@ -262,7 +264,9 @@ fn codegen_v8_async(
|
||||||
#result_wrapper
|
#result_wrapper
|
||||||
(promise_id, op_id, #core::_ops::to_op_result(get_class, result))
|
(promise_id, op_id, #core::_ops::to_op_result(get_class, result))
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
argc,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scope_arg(arg: &FnArg) -> Option<TokenStream2> {
|
fn scope_arg(arg: &FnArg) -> Option<TokenStream2> {
|
||||||
|
@ -516,7 +520,7 @@ fn codegen_v8_sync(
|
||||||
f: &syn::ItemFn,
|
f: &syn::ItemFn,
|
||||||
margs: MacroArgs,
|
margs: MacroArgs,
|
||||||
has_fallible_fast_call: bool,
|
has_fallible_fast_call: bool,
|
||||||
) -> TokenStream2 {
|
) -> (TokenStream2, usize) {
|
||||||
let MacroArgs { is_v8, .. } = margs;
|
let MacroArgs { is_v8, .. } = margs;
|
||||||
let special_args = f
|
let special_args = f
|
||||||
.sig
|
.sig
|
||||||
|
@ -528,7 +532,7 @@ fn codegen_v8_sync(
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let rust_i0 = special_args.len();
|
let rust_i0 = special_args.len();
|
||||||
let args_head = special_args.into_iter().collect::<TokenStream2>();
|
let args_head = special_args.into_iter().collect::<TokenStream2>();
|
||||||
let (arg_decls, args_tail) = codegen_args(core, f, rust_i0, 0);
|
let (arg_decls, args_tail, argc) = codegen_args(core, f, rust_i0, 0);
|
||||||
let ret = codegen_sync_ret(core, &f.sig.output);
|
let ret = codegen_sync_ret(core, &f.sig.output);
|
||||||
let type_params = exclude_lifetime_params(&f.sig.generics.params);
|
let type_params = exclude_lifetime_params(&f.sig.generics.params);
|
||||||
|
|
||||||
|
@ -547,6 +551,7 @@ fn codegen_v8_sync(
|
||||||
quote! {}
|
quote! {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(
|
||||||
quote! {
|
quote! {
|
||||||
// SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime
|
// SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime
|
||||||
let ctx = unsafe {
|
let ctx = unsafe {
|
||||||
|
@ -564,7 +569,9 @@ fn codegen_v8_sync(
|
||||||
op_state.tracker.track_sync(ctx.id);
|
op_state.tracker.track_sync(ctx.id);
|
||||||
|
|
||||||
#ret
|
#ret
|
||||||
}
|
},
|
||||||
|
argc,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FastApiSyn {
|
struct FastApiSyn {
|
||||||
|
@ -803,12 +810,15 @@ fn is_fast_scalar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (full declarations, idents, v8 argument count)
|
||||||
|
type ArgumentDecl = (TokenStream2, TokenStream2, usize);
|
||||||
|
|
||||||
fn codegen_args(
|
fn codegen_args(
|
||||||
core: &TokenStream2,
|
core: &TokenStream2,
|
||||||
f: &syn::ItemFn,
|
f: &syn::ItemFn,
|
||||||
rust_i0: usize, // Index of first generic arg in rust
|
rust_i0: usize, // Index of first generic arg in rust
|
||||||
v8_i0: usize, // Index of first generic arg in v8/js
|
v8_i0: usize, // Index of first generic arg in v8/js
|
||||||
) -> (TokenStream2, TokenStream2) {
|
) -> ArgumentDecl {
|
||||||
let inputs = &f.sig.inputs.iter().skip(rust_i0).enumerate();
|
let inputs = &f.sig.inputs.iter().skip(rust_i0).enumerate();
|
||||||
let ident_seq: TokenStream2 = inputs
|
let ident_seq: TokenStream2 = inputs
|
||||||
.clone()
|
.clone()
|
||||||
|
@ -823,7 +833,7 @@ fn codegen_args(
|
||||||
codegen_arg(core, arg, format!("arg_{i}").as_ref(), v8_i0 + i)
|
codegen_arg(core, arg, format!("arg_{i}").as_ref(), v8_i0 + i)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(decls, ident_seq)
|
(decls, ident_seq, inputs.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn codegen_arg(
|
fn codegen_arg(
|
||||||
|
|
|
@ -692,6 +692,7 @@ delete Intl.v8BreakIterator;
|
||||||
throw new Error("Worker runtime already bootstrapped");
|
throw new Error("Worker runtime already bootstrapped");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core.initializeAsyncOps();
|
||||||
performance.setTimeOrigin(DateNow());
|
performance.setTimeOrigin(DateNow());
|
||||||
net.setup(runtimeOptions.unstableFlag);
|
net.setup(runtimeOptions.unstableFlag);
|
||||||
|
|
||||||
|
@ -791,6 +792,7 @@ delete Intl.v8BreakIterator;
|
||||||
throw new Error("Worker runtime already bootstrapped");
|
throw new Error("Worker runtime already bootstrapped");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core.initializeAsyncOps();
|
||||||
performance.setTimeOrigin(DateNow());
|
performance.setTimeOrigin(DateNow());
|
||||||
net.setup(runtimeOptions.unstableFlag);
|
net.setup(runtimeOptions.unstableFlag);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue