summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2023-04-30 10:50:24 +0200
committerGitHub <noreply@github.com>2023-04-30 08:50:24 +0000
commitbb1f5e4262940a966e6314f57a4267514911d262 (patch)
tree0b5b870e34fca10daf8e664eb4214e5e756daf53 /core
parent9c8ebce3dcc784f1a6ecd29d5fe0b3d35256ab82 (diff)
perf(core): async op pseudo-codegen and performance work (#18887)
Performance: ``` async_ops.js: 760k -> 1030k (!) async_ops_deferred.js: 730k -> 770k Deno.serve bench: 118k -> 124k WS test w/ third_party/prebuilt/mac/load_test 100 localhost 8000 0 0: unchanged Startup time: approx 0.5ms slower (13.7 -> 14.2ms) ```
Diffstat (limited to 'core')
-rw-r--r--core/01_core.js465
-rw-r--r--core/bindings.js49
-rw-r--r--core/bindings.rs212
-rw-r--r--core/extensions.rs1
-rw-r--r--core/lib.deno_core.d.ts8
-rw-r--r--core/ops_builtin.rs18
-rw-r--r--core/runtime.rs30
7 files changed, 590 insertions, 193 deletions
diff --git a/core/01_core.js b/core/01_core.js
index a8bdeb2a8..3972dec33 100644
--- a/core/01_core.js
+++ b/core/01_core.js
@@ -16,11 +16,15 @@
ObjectAssign,
ObjectFreeze,
ObjectFromEntries,
+ ObjectKeys,
Promise,
+ PromiseReject,
+ PromiseResolve,
PromisePrototypeThen,
RangeError,
ReferenceError,
ReflectHas,
+ ReflectApply,
SafeArrayIterator,
SafeMap,
SafePromisePrototypeFinally,
@@ -32,7 +36,7 @@
TypeError,
URIError,
} = window.__bootstrap.primordials;
- const { ops } = window.Deno.core;
+ const { ops, asyncOps } = window.Deno.core;
const build = {
target: "unknown",
@@ -85,6 +89,17 @@
return opCallTracingEnabled;
}
+ function movePromise(promiseId) {
+ const idx = promiseId % RING_SIZE;
+ // Move old promise from ring to map
+ const oldPromise = promiseRing[idx];
+ if (oldPromise !== NO_PROMISE) {
+ const oldPromiseId = promiseId - RING_SIZE;
+ MapPrototypeSet(promiseMap, oldPromiseId, oldPromise);
+ }
+ return promiseRing[idx] = NO_PROMISE;
+ }
+
function setPromise(promiseId) {
const idx = promiseId % RING_SIZE;
// Move old promise from ring to map
@@ -208,7 +223,29 @@
return error;
}
- function unwrapOpResult(res) {
+ function unwrapOpError(hideFunction) {
+ return (res) => {
+ // .$err_class_name is a special key that should only exist on errors
+ const className = res?.$err_class_name;
+ if (!className) {
+ return res;
+ }
+
+ const errorBuilder = errorMap[className];
+ const err = errorBuilder ? errorBuilder(res.message) : new Error(
+ `Unregistered error class: "${className}"\n ${res.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
+ );
+ // Set .code if error was a known OS error, see error_codes.rs
+ if (res.code) {
+ err.code = res.code;
+ }
+ // Strip unwrapOpResult() and errorBuilder() calls from stack trace
+ ErrorCaptureStackTrace(err, hideFunction);
+ throw err;
+ };
+ }
+
+ function unwrapOpResultNewPromise(id, res, hideFunction) {
// .$err_class_name is a special key that should only exist on errors
if (res?.$err_class_name) {
const className = res.$err_class_name;
@@ -221,59 +258,359 @@
err.code = res.code;
}
// Strip unwrapOpResult() and errorBuilder() calls from stack trace
- ErrorCaptureStackTrace(err, unwrapOpResult);
- throw err;
+ ErrorCaptureStackTrace(err, hideFunction);
+ return PromiseReject(err);
}
- return res;
+ const promise = PromiseResolve(res);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ }
+
+ /*
+Basic codegen.
+
+TODO(mmastrac): automate this (handlebars?)
+
+let s = "";
+const vars = "abcdefghijklm";
+for (let i = 0; i < 10; i++) {
+ let args = "";
+ for (let j = 0; j < i; j++) {
+ args += `${vars[j]},`;
+ }
+ s += `
+ case ${i}:
+ fn = function async_op_${i}(${args}) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, ${args});
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_${i});
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_${i});
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(setPromise(id), unwrapOpError(eventLoopTick));
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+ `;
+}
+ */
+
+ // This function is called once per async stub
+ function asyncStub(opName, args) {
+ setUpAsyncStub(opName);
+ return ReflectApply(ops[opName], undefined, args);
+ }
+
+ function setUpAsyncStub(opName) {
+ const originalOp = asyncOps[opName];
+ let fn;
+ // The body of this switch statement can be generated using the script above.
+ switch (originalOp.length - 1) {
+ case 0:
+ fn = function async_op_0() {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_0);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_0);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 1:
+ fn = function async_op_1(a) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_1);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_1);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 2:
+ fn = function async_op_2(a, b) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a, b);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_2);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_2);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 3:
+ fn = function async_op_3(a, b, c) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a, b, c);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_3);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_3);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 4:
+ fn = function async_op_4(a, b, c, d) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a, b, c, d);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_4);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_4);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 5:
+ fn = function async_op_5(a, b, c, d, e) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a, b, c, d, e);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_5);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_5);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 6:
+ fn = function async_op_6(a, b, c, d, e, f) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a, b, c, d, e, f);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_6);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_6);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 7:
+ fn = function async_op_7(a, b, c, d, e, f, g) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a, b, c, d, e, f, g);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_7);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_7);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 8:
+ fn = function async_op_8(a, b, c, d, e, f, g, h) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a, b, c, d, e, f, g, h);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_8);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_8);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ case 9:
+ fn = function async_op_9(a, b, c, d, e, f, g, h, i) {
+ const id = nextPromiseId++;
+ try {
+ const maybeResult = originalOp(id, a, b, c, d, e, f, g, h, i);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, async_op_9);
+ }
+ } catch (err) {
+ movePromise(id);
+ ErrorCaptureStackTrace(err, async_op_9);
+ return PromiseReject(err);
+ }
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
+ promise = handleOpCallTracing(opName, id, promise);
+ promise[promiseIdSymbol] = id;
+ return promise;
+ };
+ break;
+
+ default:
+ throw new Error(
+ `Too many arguments for async op codegen (length of ${opName} was ${
+ originalOp.length - 1
+ })`,
+ );
+ }
+ return (ops[opName] = fn);
}
function opAsync2(name, arg0, arg1) {
const id = nextPromiseId++;
- let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult);
- let maybeResult;
try {
- maybeResult = ops[name](id, arg0, arg1);
+ const maybeResult = asyncOps[name](id, arg0, arg1);
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, opAsync2);
+ }
} catch (err) {
- // Cleanup the just-created promise
- getPromise(id);
- if (!ReflectHas(ops, name)) {
- throw new TypeError(`${name} is not a registered op`);
+ movePromise(id);
+ if (!ReflectHas(asyncOps, name)) {
+ return PromiseReject(new TypeError(`${name} is not a registered op`));
}
- // Rethrow the error
- throw err;
+ ErrorCaptureStackTrace(err, opAsync2);
+ return PromiseReject(err);
}
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
promise = handleOpCallTracing(name, id, promise);
promise[promiseIdSymbol] = id;
- if (typeof maybeResult !== "undefined") {
- const promise = getPromise(id);
- promise.resolve(maybeResult);
- }
-
return promise;
}
function opAsync(name, ...args) {
const id = nextPromiseId++;
- let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult);
- let maybeResult;
try {
- maybeResult = ops[name](id, ...new SafeArrayIterator(args));
+ const maybeResult = asyncOps[name](id, ...new SafeArrayIterator(args));
+ if (maybeResult !== undefined) {
+ movePromise(id);
+ return unwrapOpResultNewPromise(id, maybeResult, opAsync);
+ }
} catch (err) {
- // Cleanup the just-created promise
- getPromise(id);
- if (!ReflectHas(ops, name)) {
- throw new TypeError(`${name} is not a registered op`);
+ movePromise(id);
+ if (!ReflectHas(asyncOps, name)) {
+ return PromiseReject(new TypeError(`${name} is not a registered op`));
}
- // Rethrow the error
- throw err;
+ ErrorCaptureStackTrace(err, opAsync);
+ return PromiseReject(err);
}
+ let promise = PromisePrototypeThen(
+ setPromise(id),
+ unwrapOpError(eventLoopTick),
+ );
promise = handleOpCallTracing(name, id, promise);
promise[promiseIdSymbol] = id;
- if (typeof maybeResult !== "undefined") {
- const promise = getPromise(id);
- promise.resolve(maybeResult);
- }
-
return promise;
}
@@ -439,8 +776,52 @@
);
}
+ // Eagerly initialize ops for snapshot purposes
+ for (const opName of new SafeArrayIterator(ObjectKeys(asyncOps))) {
+ setUpAsyncStub(opName);
+ }
+
+ function generateAsyncOpHandler(/* opNames... */) {
+ const fastOps = {};
+ for (const opName of new SafeArrayIterator(arguments)) {
+ if (ops[opName] === undefined) {
+ throw new Error(`Unknown or disabled op '${opName}'`);
+ }
+ if (asyncOps[opName] !== undefined) {
+ fastOps[opName] = setUpAsyncStub(opName);
+ } else {
+ fastOps[opName] = ops[opName];
+ }
+ }
+ return fastOps;
+ }
+
+ const {
+ op_close: close,
+ op_try_close: tryClose,
+ op_read: read,
+ op_read_all: readAll,
+ op_write: write,
+ op_write_all: writeAll,
+ op_read_sync: readSync,
+ op_write_sync: writeSync,
+ op_shutdown: shutdown,
+ } = generateAsyncOpHandler(
+ "op_close",
+ "op_try_close",
+ "op_read",
+ "op_read_all",
+ "op_write",
+ "op_write_all",
+ "op_read_sync",
+ "op_write_sync",
+ "op_shutdown",
+ );
+
// Extra Deno.core.* exports
const core = ObjectAssign(globalThis.Deno.core, {
+ asyncStub,
+ generateAsyncOpHandler,
opAsync,
opAsync2,
resources,
@@ -460,15 +841,15 @@
unrefOp,
setReportExceptionCallback,
setPromiseHooks,
- close: (rid) => ops.op_close(rid),
- tryClose: (rid) => ops.op_try_close(rid),
- read: opAsync.bind(null, "op_read"),
- readAll: opAsync.bind(null, "op_read_all"),
- write: opAsync.bind(null, "op_write"),
- writeAll: opAsync.bind(null, "op_write_all"),
- readSync: (rid, buffer) => ops.op_read_sync(rid, buffer),
- writeSync: (rid, buffer) => ops.op_write_sync(rid, buffer),
- shutdown: opAsync.bind(null, "op_shutdown"),
+ close,
+ tryClose,
+ read,
+ readAll,
+ write,
+ writeAll,
+ readSync,
+ writeSync,
+ shutdown,
print: (msg, isErr) => ops.op_print(msg, isErr),
setMacrotaskCallback,
setNextTickCallback,
diff --git a/core/bindings.js b/core/bindings.js
new file mode 100644
index 000000000..c7d7af30c
--- /dev/null
+++ b/core/bindings.js
@@ -0,0 +1,49 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+if (!globalThis.Deno) {
+ globalThis.Deno = {
+ core: {
+ ops: {},
+ asyncOps: {},
+ },
+ };
+}
+
+Deno.__op__console = function (callConsole, console) {
+ Deno.core.callConsole = callConsole;
+ Deno.core.console = console;
+};
+
+Deno.__op__registerOp = function (isAsync, op, opName) {
+ const core = Deno.core;
+ if (isAsync) {
+ if (core.ops[opName] !== undefined) {
+ return;
+ }
+ core.asyncOps[opName] = op;
+ core.ops[opName] = function (...args) {
+ if (this !== core.ops) {
+ // deno-lint-ignore prefer-primordials
+ throw new Error(
+ "An async stub cannot be separated from Deno.core.ops. Use ???",
+ );
+ }
+ return core.asyncStub(opName, args);
+ };
+ } else {
+ core.ops[opName] = op;
+ }
+};
+
+Deno.__op__unregisterOp = function (isAsync, opName) {
+ if (isAsync) {
+ delete Deno.core.asyncOps[opName];
+ }
+ delete Deno.core.ops[opName];
+};
+
+Deno.__op__cleanup = function () {
+ delete Deno.__op__console;
+ delete Deno.__op__registerOp;
+ delete Deno.__op__unregisterOp;
+ delete Deno.__op__cleanup;
+};
diff --git a/core/bindings.rs b/core/bindings.rs
index 95e78b6cd..2d9c91461 100644
--- a/core/bindings.rs
+++ b/core/bindings.rs
@@ -1,9 +1,9 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use log::debug;
+use std::fmt::Write;
use std::option::Option;
use std::os::raw::c_void;
-
-use log::debug;
use v8::MapFnTo;
use crate::error::is_instance_of_error;
@@ -98,6 +98,23 @@ pub fn module_origin<'a>(
)
}
+fn get<'s, T>(
+ scope: &mut v8::HandleScope<'s>,
+ from: v8::Local<v8::Object>,
+ key: &'static [u8],
+ path: &'static str,
+) -> T
+where
+ v8::Local<'s, v8::Value>: TryInto<T>,
+{
+ let key = v8::String::new_external_onebyte_static(scope, key).unwrap();
+ from
+ .get(scope, key.into())
+ .unwrap_or_else(|| panic!("{path} exists"))
+ .try_into()
+ .unwrap_or_else(|_| panic!("unable to convert"))
+}
+
pub(crate) fn initialize_context<'s>(
scope: &mut v8::HandleScope<'s, ()>,
op_ctxs: &[OpCtx],
@@ -108,135 +125,92 @@ pub(crate) fn initialize_context<'s>(
let scope = &mut v8::ContextScope::new(scope, context);
- let deno_str =
- v8::String::new_external_onebyte_static(scope, b"Deno").unwrap();
- let core_str =
- v8::String::new_external_onebyte_static(scope, b"core").unwrap();
- let ops_str = v8::String::new_external_onebyte_static(scope, b"ops").unwrap();
-
- let ops_obj = if snapshot_options.loaded() {
- // Snapshot already registered `Deno.core.ops` but
- // extensions may provide ops that aren't part of the snapshot.
- // Grab the Deno.core.ops object & init it
- let deno_obj: v8::Local<v8::Object> = global
- .get(scope, deno_str.into())
- .unwrap()
- .try_into()
- .unwrap();
- let core_obj: v8::Local<v8::Object> = deno_obj
- .get(scope, core_str.into())
- .unwrap()
- .try_into()
- .unwrap();
- let ops_obj: v8::Local<v8::Object> = core_obj
- .get(scope, ops_str.into())
- .expect("Deno.core.ops to exist")
- .try_into()
- .unwrap();
- ops_obj
- } else {
- // globalThis.Deno = { core: { } };
- let deno_obj = v8::Object::new(scope);
- global.set(scope, deno_str.into(), deno_obj.into());
-
- let core_obj = v8::Object::new(scope);
- deno_obj.set(scope, core_str.into(), core_obj.into());
+ let mut codegen = String::with_capacity(op_ctxs.len() * 200);
+ codegen.push_str(include_str!("bindings.js"));
+ _ = writeln!(
+ codegen,
+ "Deno.__op__ = function(opFns, callConsole, console) {{"
+ );
+ if !snapshot_options.loaded() {
+ _ = writeln!(codegen, "Deno.__op__console(callConsole, console);");
+ }
+ for op_ctx in op_ctxs {
+ if op_ctx.decl.enabled {
+ // If we're loading from a snapshot, we can skip registration for most ops
+ if matches!(snapshot_options, SnapshotOptions::Load)
+ && !op_ctx.decl.force_registration
+ {
+ continue;
+ }
+ _ = writeln!(
+ codegen,
+ "Deno.__op__registerOp({}, opFns[{}], \"{}\");",
+ op_ctx.decl.is_async, op_ctx.id, op_ctx.decl.name
+ );
+ } else {
+ _ = writeln!(
+ codegen,
+ "Deno.__op__unregisterOp({}, \"{}\");",
+ op_ctx.decl.is_async, op_ctx.decl.name
+ );
+ }
+ }
+ codegen.push_str("Deno.__op__cleanup();");
+ _ = writeln!(codegen, "}}");
+ let script = v8::String::new_from_one_byte(
+ scope,
+ codegen.as_bytes(),
+ v8::NewStringType::Normal,
+ )
+ .unwrap();
+ let script = v8::Script::compile(scope, script, None).unwrap();
+ script.run(scope);
+
+ let deno = get(scope, global, b"Deno", "Deno");
+ let op_fn: v8::Local<v8::Function> =
+ get(scope, deno, b"__op__", "Deno.__op__");
+ let recv = v8::undefined(scope);
+ let op_fns = v8::Array::new(scope, op_ctxs.len() as i32);
+ for op_ctx in op_ctxs {
+ let op_fn = op_ctx_function(scope, op_ctx);
+ op_fns.set_index(scope, op_ctx.id as u32, op_fn.into());
+ }
+ if snapshot_options.loaded() {
+ op_fn.call(scope, recv.into(), &[op_fns.into()]);
+ } else {
// Bind functions to Deno.core.*
- set_func(scope, core_obj, "callConsole", call_console);
+ let call_console_fn = v8::Function::new(scope, call_console).unwrap();
// Bind v8 console object to Deno.core.console
let extra_binding_obj = context.get_extras_binding_object(scope);
- let console_str =
- v8::String::new_external_onebyte_static(scope, b"console").unwrap();
- let console_obj = extra_binding_obj.get(scope, console_str.into()).unwrap();
- core_obj.set(scope, console_str.into(), console_obj);
-
- // Bind functions to Deno.core.ops.*
- let ops_obj = v8::Object::new(scope);
- core_obj.set(scope, ops_str.into(), ops_obj.into());
- ops_obj
- };
+ let console_obj: v8::Local<v8::Object> = get(
+ scope,
+ extra_binding_obj,
+ b"console",
+ "ExtrasBindingObject.console",
+ );
- if matches!(snapshot_options, SnapshotOptions::Load) {
- // Only register ops that have `force_registration` flag set to true,
- // the remaining ones should already be in the snapshot. Ignore ops that
- // are disabled.
- for op_ctx in op_ctxs {
- if op_ctx.decl.enabled {
- if op_ctx.decl.force_registration {
- add_op_to_deno_core_ops(scope, ops_obj, op_ctx);
- }
- } else {
- delete_op_from_deno_core_ops(scope, ops_obj, op_ctx)
- }
- }
- } else if matches!(snapshot_options, SnapshotOptions::CreateFromExisting) {
- // Register all enabled ops, probing for which ones are already registered.
- for op_ctx in op_ctxs {
- let key = v8::String::new_external_onebyte_static(
- scope,
- op_ctx.decl.name.as_bytes(),
- )
- .unwrap();
-
- if op_ctx.decl.enabled {
- if ops_obj.get(scope, key.into()).is_some() {
- continue;
- }
- add_op_to_deno_core_ops(scope, ops_obj, op_ctx);
- } else {
- delete_op_from_deno_core_ops(scope, ops_obj, op_ctx)
- }
- }
- } else {
- // In other cases register all ops enabled unconditionally.
- for op_ctx in op_ctxs {
- if op_ctx.decl.enabled {
- add_op_to_deno_core_ops(scope, ops_obj, op_ctx);
- }
- }
+ op_fn.call(
+ scope,
+ recv.into(),
+ &[op_fns.into(), call_console_fn.into(), console_obj.into()],
+ );
}
context
}
-fn set_func(
- scope: &mut v8::HandleScope<'_>,
- obj: v8::Local<v8::Object>,
- name: &'static str,
- callback: impl v8::MapFnTo<v8::FunctionCallback>,
-) {
- let key =
- v8::String::new_external_onebyte_static(scope, name.as_bytes()).unwrap();
- let val = v8::Function::new(scope, callback).unwrap();
- val.set_name(key);
- obj.set(scope, key.into(), val.into());
-}
-
-fn delete_op_from_deno_core_ops(
- scope: &mut v8::HandleScope<'_>,
- obj: v8::Local<v8::Object>,
- op_ctx: &OpCtx,
-) {
- let key =
- v8::String::new_external_onebyte_static(scope, op_ctx.decl.name.as_bytes())
- .unwrap();
- obj.delete(scope, key.into());
-}
-
-fn add_op_to_deno_core_ops(
- scope: &mut v8::HandleScope<'_>,
- obj: v8::Local<v8::Object>,
+fn op_ctx_function<'s>(
+ scope: &mut v8::HandleScope<'s>,
op_ctx: &OpCtx,
-) {
+) -> v8::Local<'s, v8::Function> {
let op_ctx_ptr = op_ctx as *const OpCtx as *const c_void;
- let key =
- v8::String::new_external_onebyte_static(scope, op_ctx.decl.name.as_bytes())
- .unwrap();
let external = v8::External::new(scope, op_ctx_ptr as *mut c_void);
- let builder = v8::FunctionTemplate::builder_raw(op_ctx.decl.v8_fn_ptr)
- .data(external.into());
+ let builder: v8::FunctionBuilder<v8::FunctionTemplate> =
+ v8::FunctionTemplate::builder_raw(op_ctx.decl.v8_fn_ptr)
+ .data(external.into())
+ .length(op_ctx.decl.arg_count as i32);
let templ = if let Some(fast_function) = &op_ctx.decl.fast_fn {
builder.build_fast(
@@ -249,9 +223,7 @@ fn add_op_to_deno_core_ops(
} else {
builder.build(scope)
};
- let val = templ.get_function(scope).unwrap();
- val.set_name(key);
- obj.set(scope, key.into(), val.into());
+ templ.get_function(scope).unwrap()
}
pub extern "C" fn wasm_async_resolve_promise_callback(
diff --git a/core/extensions.rs b/core/extensions.rs
index a0f99c92b..a8b52eb3b 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -73,6 +73,7 @@ pub struct OpDecl {
pub is_unstable: bool,
pub is_v8: bool,
pub force_registration: bool,
+ pub arg_count: u8,
pub fast_fn: Option<FastFunction>,
}
diff --git a/core/lib.deno_core.d.ts b/core/lib.deno_core.d.ts
index 7f3ea2a19..fc7865829 100644
--- a/core/lib.deno_core.d.ts
+++ b/core/lib.deno_core.d.ts
@@ -23,11 +23,17 @@ declare namespace Deno {
/**
* List of all registered ops, in the form of a map that maps op
- * name to internal numerical op id.
+ * name to function.
*/
const ops: Record<string, (...args: unknown[]) => any>;
/**
+ * List of all registered async ops, in the form of a map that maps op
+ * name to function.
+ */
+ const asyncOps: Record<string, (...args: unknown[]) => any>;
+
+ /**
* Retrieve a list of all open resources, in the form of a map that maps
* resource id to the resource name.
*/
diff --git a/core/ops_builtin.rs b/core/ops_builtin.rs
index 0c071a918..70f478acd 100644
--- a/core/ops_builtin.rs
+++ b/core/ops_builtin.rs
@@ -27,9 +27,12 @@ crate::extension!(
op_wasm_streaming_feed,
op_wasm_streaming_set_url,
op_void_sync,
+ op_error_async,
+ op_error_async_deferred,
op_void_async,
op_void_async_deferred,
op_add,
+ op_add_async,
// TODO(@AaronO): track IO metrics for builtin streams
op_read,
op_read_all,
@@ -96,12 +99,27 @@ fn op_add(a: i32, b: i32) -> i32 {
a + b
}
+#[op]
+pub async fn op_add_async(a: i32, b: i32) -> i32 {
+ a + b
+}
+
#[op(fast)]
pub fn op_void_sync() {}
#[op]
pub async fn op_void_async() {}
+#[op]
+pub async fn op_error_async() -> Result<(), Error> {
+ Err(Error::msg("error"))
+}
+
+#[op(deferred)]
+pub async fn op_error_async_deferred() -> Result<(), Error> {
+ Err(Error::msg("error"))
+}
+
#[op(deferred)]
pub async fn op_void_async_deferred() {}
diff --git a/core/runtime.rs b/core/runtime.rs
index d88ddccac..e6c365e42 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -3738,21 +3738,6 @@ assertEquals(1, notify_return_value);
}
#[test]
- fn test_core_js_stack_frame() {
- let mut runtime = JsRuntime::new(RuntimeOptions::default());
- // Call non-existent op so we get error from `core.js`
- let error = runtime
- .execute_script_static(
- "core_js_stack_frame.js",
- "Deno.core.opAsync('non_existent');",
- )
- .unwrap_err();
- let error_string = error.to_string();
- // Test that the script specifier is a URL: `ext:<repo-relative path>`.
- assert!(error_string.contains("ext:core/01_core.js"));
- }
-
- #[test]
fn test_v8_platform() {
let options = RuntimeOptions {
v8_platform: Some(v8::new_default_platform(0, false).make_shared()),
@@ -4721,21 +4706,6 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
.is_ok());
}
- #[test]
- fn test_non_existent_async_op_error() {
- // Verify that "resizable ArrayBuffer" is disabled
- let mut runtime = JsRuntime::new(Default::default());
- let err = runtime
- .execute_script_static(
- "test_rab.js",
- r#"Deno.core.opAsync("this_op_doesnt_exist");"#,
- )
- .unwrap_err();
- assert!(err
- .to_string()
- .contains("this_op_doesnt_exist is not a registered op"));
- }
-
#[tokio::test]
async fn cant_load_internal_module_when_snapshot_is_loaded_and_not_snapshotting(
) {