Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog.d/8671-async-hooks-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### Fixed

- Complete the `node:async_hooks` parity tracker across all 194 fixtures:
hook mutation and lifecycle ordering, Promise/resource identity and trigger
chains, `AsyncResource` and `EventEmitterAsyncResource` subclasses,
`AsyncLocalStorage` propagation, and provider lifecycles for timers, files,
DNS, crypto, zlib, processes, signals, workers, streams, net, HTTP(S), TLS,
readline, event iterators, ESM, fetch, and UDP now match Node.
2 changes: 2 additions & 0 deletions crates/perry-api-manifest/src/entries/part_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ pub(crate) const API_MANIFEST_PART_4: &[ApiEntry] = &[
method("http", "ref", true, Some("HttpServer")),
method("http", "unref", true, Some("HttpServer")),
method("http", "on", true, Some("IncomingMessage")),
method("http", "once", true, Some("IncomingMessage")),
method("http", "addListener", true, Some("IncomingMessage")),
method("http", "pause", true, Some("IncomingMessage")),
method("http", "resume", true, Some("IncomingMessage")),
Expand All @@ -856,6 +857,7 @@ pub(crate) const API_MANIFEST_PART_4: &[ApiEntry] = &[
// method, so the manifest-consistency drift guard requires a row
// here even though the test collapses class_filter variants.
method("http", "setTimeout", true, Some("ClientRequest")),
method("http", "once", true, Some("ClientRequest")),
method("http", "listenerCount", true, Some("ClientRequest")),
method("http", "setHeader", true, Some("ClientRequest")),
method("http", "getHeader", true, Some("ClientRequest")),
Expand Down
11 changes: 8 additions & 3 deletions crates/perry-codegen/src/expr/calls/crypto_misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,18 @@ pub(crate) fn arm_crypto_prime(
unreachable!()
};
let first_box = lower_expr(ctx, &args[0])?;
let options_box = if args.len() >= 2 {
let is_async = matches!(property, "generatePrime" | "checkPrime");
// The callback forms are `(value, callback)` or
// `(value, options, callback)`. Treating the second argument as options
// unconditionally accidentally routed the common two-argument form to
// the synchronous implementation and returned the generated value.
let options_box = if args.len() >= 2 && (!is_async || args.len() >= 3) {
lower_expr(ctx, &args[1])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
let callback_box = if matches!(property, "generatePrime" | "checkPrime") && args.len() >= 3 {
Some(lower_expr(ctx, &args[2])?)
let callback_box = if is_async && args.len() >= 2 {
Some(lower_expr(ctx, &args[args.len().min(3) - 1])?)
} else {
None
};
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/expr/env_clones.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
let blk = ctx.block();
let raw = blk.call(
I64,
"js_net_create_server",
"js_ext_net_create_server",
&[(I64, &options_i64), (I64, &listener_i64)],
);
Ok(nanbox_pointer_inline(blk, &raw))
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/expr/instance_misc1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// Promise values are raw promise allocations, not ObjectHeader
// instances with a class_id field.
"Promise" => 0xFFFF0027u32,
"AsyncLocalStorage" => 0xFFFF0078u32,
"AsyncResource" => 0xFFFF0079u32,
// WHATWG fetch types. Like Blob/streams these are pointer-tagged
// small-int handles; the runtime resolves them via the stdlib
// fetch kind-probe (`res instanceof Response`, etc.).
Expand Down
3 changes: 2 additions & 1 deletion crates/perry-codegen/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ pub(crate) use write_barrier::{
emit_root_heap_word_store_on_block, emit_root_nanbox_store_on_block, emit_write_barrier,
emit_write_barrier_slot_generation_tested, emit_write_barrier_slot_on_block,
emit_write_barrier_slot_value_and_generation_tested, lower_array_super_init,
lower_event_emitter_subclass_init, lower_node_stream_super_init, lower_stream_super_init,
lower_event_emitter_async_resource_subclass_init, lower_event_emitter_subclass_init,
lower_node_stream_super_init, lower_stream_super_init,
};

// Issue #1098 phase 3: the `FnCtx` definition stays in this trunk, but its
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-codegen/src/expr/property_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
if receiver_class_is_proven && is_net_native_method_value(&class_name, property) {
return lower_class_method_bind(ctx, object, property);
}
if receiver_class_is_proven
&& class_name == "AsyncResource"
&& matches!(
property.as_str(),
"asyncId" | "triggerAsyncId" | "emitDestroy" | "runInAsyncScope" | "bind"
)
{
return lower_runtime_property_get_by_name(ctx, object, property);
}
if class_has_computed_runtime_members(ctx, &class_name) {
return lower_runtime_property_get_by_name(ctx, object, property);
}
Expand Down
143 changes: 141 additions & 2 deletions crates/perry-codegen/src/expr/this_super_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ use perry_hir::Expr;

use crate::lower_call::{bind_inline_constructor_params, restore_inline_constructor_scope};
use crate::nanbox::{double_literal, POINTER_MASK_I64};
use crate::rooting::{self, Repr};
use crate::types::{DOUBLE, I1, I32, I64, PTR};

use super::{
lower_array_super_init, lower_event_emitter_subclass_init, lower_expr,
lower_node_stream_super_init, lower_stream_super_init, nanbox_pointer_inline, FnCtx,
lower_array_super_init, lower_event_emitter_async_resource_subclass_init,
lower_event_emitter_subclass_init, lower_expr, lower_node_stream_super_init,
lower_stream_super_init, nanbox_pointer_inline, FnCtx,
};

/// Enter one derived constructor's `super()` binding scope.
Expand Down Expand Up @@ -256,6 +258,67 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)),
};
let async_parent = ctx
.classes
.get(&current_class_name)
.and_then(|class| class.extends_name.clone());
if matches!(
async_parent.as_deref(),
Some("EventEmitterAsyncResource" | "AsyncLocalStorage" | "AsyncResource")
) {
let undef = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let zero_idx = "0".to_string();
let one_idx = "1".to_string();
let first =
ctx.block()
.call(DOUBLE, "js_array_get_f64", &[(I64, &arr), (I32, &zero_idx)]);
let second =
ctx.block()
.call(DOUBLE, "js_array_get_f64", &[(I64, &arr), (I32, &one_idx)]);
rooting::with_rooted_group(ctx, 3, |ctx, group| {
let this_root = group.adopt_emitted(ctx, Repr::Boxed, &this_box, true);
let first_root = group.adopt_emitted(ctx, Repr::Boxed, &first, true);
let second_root = group.adopt_emitted(ctx, Repr::Boxed, &second, true);
let this_box = group.reread_emitted(ctx, this_root);
match async_parent.as_deref() {
Some("EventEmitterAsyncResource") => {
let options = group.reread_emitted(ctx, first_root);
lower_event_emitter_async_resource_subclass_init(
ctx, &this_box, &options,
);
}
Some("AsyncLocalStorage") => {
ctx.block().call(
DOUBLE,
"js_async_local_storage_subclass_init",
&[(DOUBLE, &this_box)],
);
}
Some("AsyncResource") => {
let type_value = group.reread_emitted(ctx, first_root);
let options = group.reread_emitted(ctx, second_root);
ctx.block().call(
DOUBLE,
"js_async_resource_subclass_init",
&[
(DOUBLE, &this_box),
(DOUBLE, &type_value),
(DOUBLE, &options),
],
);
}
_ => unreachable!(),
}
bind_derived_this_after_super(ctx);
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
Ok(undef.clone())
})?;
return Ok(undef);
}
// `class X extends Map | Set` with a spread super (`super(...args)`,
// e.g. NestJS's `ModulesContainer`'s `super(...arguments)`) — install
// the hidden collection backing from the (possibly spread) args
Expand Down Expand Up @@ -826,6 +889,82 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
)?;
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
}
if parent_name.as_str() == "EventEmitterAsyncResource" {
let operands: Vec<_> = super_args.iter().collect();
return rooting::with_operands_rooted(ctx, &operands, |ctx, lowered| {
let options = lowered.first().cloned().unwrap_or_else(|| {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
});
let this_box = match ctx.this_stack.last().cloned() {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
}
};
lower_event_emitter_async_resource_subclass_init(
ctx, &this_box, &options,
);
bind_derived_this_after_super(ctx);
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)))
});
}
if parent_name.as_str() == "AsyncLocalStorage" {
for arg in super_args {
let _ = lower_expr(ctx, arg)?;
}
let this_box = match ctx.this_stack.last().cloned() {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)),
};
ctx.block().call(
DOUBLE,
"js_async_local_storage_subclass_init",
&[(DOUBLE, &this_box)],
);
bind_derived_this_after_super(ctx);
let current_class_name =
ctx.class_stack.last().cloned().unwrap_or_default();
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
}
if parent_name.as_str() == "AsyncResource" {
let undef = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let operands: Vec<_> = super_args.iter().collect();
return rooting::with_operands_rooted(ctx, &operands, |ctx, lowered| {
let type_value =
lowered.first().cloned().unwrap_or_else(|| undef.clone());
let options = lowered.get(1).cloned().unwrap_or_else(|| undef.clone());
let this_box = match ctx.this_stack.last().cloned() {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => undef.clone(),
};
ctx.block().call(
DOUBLE,
"js_async_resource_subclass_init",
&[
(DOUBLE, &this_box),
(DOUBLE, &type_value),
(DOUBLE, &options),
],
);
bind_derived_this_after_super(ctx);
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)))
});
}
// `class X extends Request` / `extends Response`:
// `super(input, init)` allocates the underlying native
// Web-Fetch handle and stashes its id on `this` under
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-codegen/src/expr/write_barrier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1081,3 +1081,15 @@ pub(crate) fn lower_event_emitter_subclass_init(ctx: &mut FnCtx<'_>, this_box: &
&[(DOUBLE, this_box)],
);
}

pub(crate) fn lower_event_emitter_async_resource_subclass_init(
ctx: &mut FnCtx<'_>,
this_box: &str,
options_box: &str,
) {
ctx.block().call(
DOUBLE,
"js_event_emitter_async_resource_subclass_init",
&[(DOUBLE, this_box), (DOUBLE, options_box)],
);
}
Loading
Loading