1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

fix(core): remove async op inlining optimization (#17899)

Runtime generation of async op wrappers contributed to increased startup
time and core became unusable with
`--disallow-code-generation-from-strings` flag. The optimization only
affects very small microbenchmarks so this revert will not cause any
regressions.
This commit is contained in:
Divy Srivastava 2023-02-24 01:20:15 +05:30 committed by GitHub
parent 4773d07974
commit da781280b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 42 additions and 192 deletions

View file

@ -104,9 +104,6 @@ pub fn bench_js_async_with(
};
let looped = loop_code(inner_iters, src);
let src = looped.as_ref();
runtime
.execute_script("init", "Deno.core.initializeAsyncOps();")
.unwrap();
if is_profiling() {
for _ in 0..opts.profiling_outer {
tokio_runtime.block_on(inner_async(src, &mut runtime));

View file

@ -10,24 +10,19 @@
TypeError,
URIError,
Array,
ArrayFrom,
ArrayPrototypeFill,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypeMap,
ErrorCaptureStackTrace,
Function,
Promise,
ObjectAssign,
ObjectFromEntries,
ObjectPrototypeHasOwnProperty,
Map,
MapPrototypeGet,
MapPrototypeHas,
MapPrototypeDelete,
MapPrototypeSet,
PromisePrototypeThen,
ReflectApply,
SafePromisePrototypeFinally,
StringPrototypeSlice,
SymbolFor,
@ -175,61 +170,20 @@
return nextPromiseId++;
}
// 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 {
op(id, ${args});
} catch (err) {
// Cleanup the just-created promise
getPromise(id);
// Rethrow the error
throw err;
}
promise = handleOpCallTracing("${name}", id, promise);
promise[promiseIdSymbol] = id;
return promise;
}
`,
)(
setPromise,
getPromise,
promiseIdSymbol,
rollPromiseId,
handleOpCallTracing,
op,
unwrapOpResult,
PromisePrototypeThen,
);
}
// { <name>: <argc>, ... }
const info = ops.asyncOpsInfo();
for (const name in info) {
if (!ObjectPrototypeHasOwnProperty(info, name)) {
continue;
}
const argc = info[name];
const op = ops[name];
const args = ArrayPrototypeJoin(
ArrayFrom({ length: argc }, (_, i) => `arg${i}`),
", ",
);
ops[name] = genAsyncOp(op, name, args);
function opAsync(name, ...args) {
const id = rollPromiseId();
let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult);
try {
ops[name](id, ...args);
} catch (err) {
// Cleanup the just-created promise
getPromise(id);
// Rethrow the error
throw err;
}
promise = handleOpCallTracing(name, id, promise);
promise[promiseIdSymbol] = id;
return promise;
}
function handleOpCallTracing(opName, promiseId, p) {
@ -245,10 +199,6 @@
}
}
function opAsync(opName, ...args) {
return ReflectApply(ops[opName], ops, args);
}
function refOp(promiseId) {
if (!hasPromise(promiseId)) {
return;
@ -401,7 +351,6 @@
// Extra Deno.core.* exports
const core = ObjectAssign(globalThis.Deno.core, {
opAsync,
initializeAsyncOps,
resources,
metrics,
registerErrorBuilder,
@ -421,11 +370,11 @@
setPromiseHooks,
close: (rid) => ops.op_close(rid),
tryClose: (rid) => ops.op_try_close(rid),
read: (rid, buffer) => ops.op_read(rid, buffer),
readAll: (rid) => ops.op_read_all(rid),
write: (rid, buffer) => ops.op_write(rid, buffer),
writeAll: (rid, buffer) => ops.op_write_all(rid, buffer),
shutdown: (rid) => ops.op_shutdown(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"),
shutdown: opAsync.bind(null, "op_shutdown"),
print: (msg, isErr) => ops.op_print(msg, isErr),
setMacrotaskCallback: (fn) => ops.op_set_macrotask_callback(fn),
setNextTickCallback: (fn) => ops.op_set_next_tick_callback(fn),

View file

@ -135,9 +135,6 @@ pub fn initialize_context<'s>(
.try_into()
.unwrap();
initialize_ops(scope, ops_obj, op_ctxs, snapshot_options);
if snapshot_options != SnapshotOptions::CreateFromExisting {
initialize_async_ops_info(scope, ops_obj, op_ctxs);
}
return scope.escape(context);
}
@ -161,9 +158,6 @@ pub fn initialize_context<'s>(
let ops_obj = v8::Object::new(scope);
core_obj.set(scope, ops_str.into(), ops_obj.into());
if !snapshot_options.will_snapshot() {
initialize_async_ops_info(scope, ops_obj, op_ctxs);
}
initialize_ops(scope, ops_obj, op_ctxs, snapshot_options);
scope.escape(context)
}
@ -657,84 +651,3 @@ pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
let exception = v8::Exception::type_error(scope, message);
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());
}

View file

@ -2,9 +2,8 @@
// 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
// exercise the event loop in a simple yet semi-realistic way.
Deno.core.initializeAsyncOps();
const { ops } = Deno.core;
const { ops, opAsync } = Deno.core;
const requestBuf = new Uint8Array(64 * 1024);
const responseBuf = new Uint8Array(
@ -20,11 +19,11 @@ function listen() {
/** Accepts a connection, returns rid. */
function accept(serverRid) {
return ops.op_accept(serverRid);
return opAsync("op_accept", serverRid);
}
function read(serverRid, buf) {
return ops.op_read_socket(serverRid, buf);
return opAsync("op_read_socket", serverRid, buf);
}
async function serve(rid) {

View file

@ -322,12 +322,6 @@ impl SnapshotOptions {
SnapshotOptions::Load | SnapshotOptions::CreateFromExisting
)
}
pub fn will_snapshot(&self) -> bool {
matches!(
self,
SnapshotOptions::Create | SnapshotOptions::CreateFromExisting
)
}
fn from_bools(snapshot_loaded: bool, will_snapshot: bool) -> Self {
match (snapshot_loaded, will_snapshot) {
@ -2923,10 +2917,10 @@ pub mod tests {
.execute_script(
"filename.js",
r#"
Deno.core.initializeAsyncOps();
var promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
var p1 = Deno.core.ops.op_test(42);
var p2 = Deno.core.ops.op_test(42);
var p1 = Deno.core.opAsync("op_test", 42);
var p2 = Deno.core.opAsync("op_test", 42);
"#,
)
.unwrap();
@ -2979,7 +2973,7 @@ pub mod tests {
"filename.js",
r#"
let control = 42;
Deno.core.initializeAsyncOps();
Deno.core.opAsync("op_test", control);
async function main() {
Deno.core.opAsync("op_test", control);
@ -2998,7 +2992,7 @@ pub mod tests {
.execute_script(
"filename.js",
r#"
Deno.core.initializeAsyncOps();
const p = Deno.core.opAsync("op_test", 42);
if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) {
throw new Error("missing id on returned promise");
@ -3015,7 +3009,7 @@ pub mod tests {
.execute_script(
"filename.js",
r#"
Deno.core.initializeAsyncOps();
Deno.core.opAsync("op_test");
"#,
)
@ -3030,7 +3024,7 @@ pub mod tests {
.execute_script(
"filename.js",
r#"
Deno.core.initializeAsyncOps();
let zero_copy_a = new Uint8Array([0]);
Deno.core.opAsync("op_test", null, zero_copy_a);
"#,
@ -3904,7 +3898,7 @@ if (errMessage !== "higher-level sync error: original sync error") {
.execute_script(
"test_error_context_async.js",
r#"
Deno.core.initializeAsyncOps();
(async () => {
let errMessage;
try {
@ -4059,7 +4053,7 @@ assertEquals(1, notify_return_value);
runtime
.execute_script(
"op_async_borrow.js",
"Deno.core.initializeAsyncOps(); Deno.core.ops.op_async_borrow()",
"Deno.core.opAsync(\"op_async_borrow\")",
)
.unwrap();
runtime.run_event_loop(false).await.unwrap();
@ -4133,8 +4127,8 @@ Deno.core.ops.op_sync_serialize_object_with_numbers_as_keys({
.execute_script(
"op_async_serialize_object_with_numbers_as_keys.js",
r#"
Deno.core.initializeAsyncOps();
Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
lines: {
100: {
unit: "m"
@ -4172,7 +4166,7 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
.execute_script(
"macrotasks_and_nextticks.js",
r#"
Deno.core.initializeAsyncOps();
(async function () {
const results = [];
Deno.core.ops.op_set_macrotask_callback(() => {
@ -4440,12 +4434,12 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
"",
&format!(
r#"
Deno.core.initializeAsyncOps();
globalThis.rejectValue = undefined;
Deno.core.setPromiseRejectCallback((_type, _promise, reason) => {{
globalThis.rejectValue = `{realm_name}/${{reason}}`;
}});
Deno.core.ops.op_void_async().then(() => Promise.reject({number}));
Deno.core.opAsync("op_void_async").then(() => Promise.reject({number}));
"#
),
)
@ -4876,12 +4870,12 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
runtime.v8_isolate(),
"",
r#"
Deno.core.initializeAsyncOps();
(async function () {
const buf = await Deno.core.ops.op_test(false);
const buf = await Deno.core.opAsync("op_test", false);
let err;
try {
await Deno.core.ops.op_test(true);
await Deno.core.opAsync("op_test", true);
} catch(e) {
err = e;
}
@ -4930,8 +4924,8 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
runtime.v8_isolate(),
"",
r#"
Deno.core.initializeAsyncOps();
var promise = Deno.core.ops.op_pending();
var promise = Deno.core.opAsync("op_pending");
"#,
)
.unwrap();
@ -4940,8 +4934,8 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
runtime.v8_isolate(),
"",
r#"
Deno.core.initializeAsyncOps();
var promise = Deno.core.ops.op_pending();
var promise = Deno.core.opAsync("op_pending");
"#,
)
.unwrap();

View file

@ -390,7 +390,6 @@ function bootstrapMainRuntime(runtimeOptions) {
throw new Error("Worker runtime already bootstrapped");
}
core.initializeAsyncOps();
performance.setTimeOrigin(DateNow());
globalThis_ = globalThis;
@ -523,7 +522,6 @@ function bootstrapWorkerRuntime(
throw new Error("Worker runtime already bootstrapped");
}
core.initializeAsyncOps();
performance.setTimeOrigin(DateNow());
globalThis_ = globalThis;