Skip to content
Merged
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
7 changes: 7 additions & 0 deletions changelog.d/8212-file-size-cap-split.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
**Split `gc/layout.rs` and `codegen/artifacts.rs` back under the 2000-line file cap** (#8212). #8204's header shrink pushed `crates/perry-runtime/src/gc/layout.rs` to 2110 lines and `crates/perry-codegen/src/codegen/artifacts.rs` — which had been sitting at exactly 2000 — to 2005, turning the required `lint` context red on `main` for every open PR. Pure code moves, no behaviour change:

- the typed-shape layout installation protocol (`TypedShapeProof`, `init_typed_shape_layout`, `install_typed_shape_layout_slow`, `js_gc_init_typed_shape_layout`, `js_gc_declare_typed_shape_layout`) moves from `gc/layout.rs` into the new `gc/layout/typed_shape.rs` (2110 → 1778), next to the existing `layout/slot_mask.rs` split; the two extern "C" entry points keep their `crate::gc::` paths via an explicit named re-export;
- `synthesized_ctor_param_count` moves from `codegen/artifacts.rs` into the new `codegen/ctor_arity.rs` (2005 → 1930);
- `scripts/shape_descriptor_census_baseline.json` is refreshed mechanically for the one `keys_array` callsite whose file path changed (`raw_member_files` 65 → 66, same site and count).

Validated as relocation-only: zero-warning rebuild of both crates; `perry-runtime --lib` 2522/0/4; full `perry-codegen --no-fail-fast` failure set byte-identical by name to `origin/main` (1493/9 both); `perry --bin perry` 987/0; all GC script gates green.
77 changes: 1 addition & 76 deletions crates/perry-codegen/src/codegen/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use super::closure::{
compile_closure, compile_typed_f64_closure, compile_typed_i1_closure,
compile_typed_i32_closure, compile_typed_string_closure,
};
use super::ctor_arity::synthesized_ctor_param_count;
use super::entry::compile_module_entry;
use super::helpers::{
function_body_returns_generator_object, sanitize, scoped_fn_name, unknown_func_wrapper_name,
Expand Down Expand Up @@ -96,82 +97,6 @@ pub(super) struct ModuleArtifactsCtx<'a> {
pub cross_module: &'a CrossModuleCtx,
}

/// The standalone-constructor arity Perry emits for `class`, accounting for the
/// JS spec default ctor `constructor(...args) { super(...args) }` that a class
/// with NO own constructor but WITH heritage inherits. Walks the ancestor chain
/// (local `class_table` + cross-module `imported_classes` ctor-param counts +
/// imported stubs) for the nearest ctor-bearing parent's user arity, mirroring
/// the `found_params` walk in the per-class ctor emission below.
///
/// This MUST agree with the synthesized standalone ctor's actual LLVM signature,
/// otherwise the runtime registers a different `total_params` than the function
/// declares and `replay_registered_class_constructor` forwards the wrong number
/// of args (Next.js wall 51: `class AppRouteRouteMatcher extends
/// _mod.RouteMatcher {}` emitted a 1-param forwarding ctor but registered it as
/// 0 params, so `new mod.AppRouteRouteMatcher(def)` dropped `def` before
/// `RouteMatcher(definition)` ran and every matcher's `this.definition` was a
/// garbage number).
fn synthesized_ctor_param_count(
class: &perry_hir::Class,
class_table: &HashMap<String, &perry_hir::Class>,
imported_class_stubs: &[perry_hir::Class],
imported_classes: &[super::opts::ImportedClass],
) -> usize {
if let Some(c) = class.constructor.as_ref() {
return c.params.len();
}
// A native parent (`extends Error` / `extends events.EventEmitter`) has its
// own native construction path that consumes the construction args directly;
// don't synthesize a forwarding ctor for it.
if class.native_extends.is_some() {
return 0;
}
// No heritage at all → nothing to forward.
if class.extends_name.is_none() && class.extends_expr.is_none() {
return 0;
}
// Walk ancestors for the nearest ctor-bearing parent's user arity.
let mut cur = class.extends_name.clone();
while let Some(pname) = cur {
let imported_ctor_params = imported_classes
.iter()
.find(|i| i.local_alias.as_deref().unwrap_or(&i.name) == pname.as_str())
.map(|ic| ic.constructor_param_count)
.unwrap_or(0);
if let Some(pclass) = class_table.get(pname.as_str()) {
if let Some(pctor) = &pclass.constructor {
return pctor.params.len();
}
if imported_ctor_params > 0 {
return imported_ctor_params;
}
cur = pclass.extends_name.clone();
} else if let Some(stub) = imported_class_stubs.iter().find(|c| c.name == pname) {
if imported_ctor_params > 0 {
return imported_ctor_params;
}
cur = stub.extends_name.clone();
} else {
break;
}
}
// The parent's exact ctor arity is genuinely unavailable here in some build
// modes: the auto-optimize / standalone path compiles each nested
// `node_modules` module with an EMPTY `imported_classes` list and resolves
// the cross-module parent purely as a runtime DYNAMIC parent
// (`extends_expr` + `js_register_class_parent_dynamic`), so it is absent
// from `class_table` and `imported_class_stubs` here. Without a forwarding
// signature the synthesized `super()` dropped every construction arg
// (Next.js wall 51: `class PagesRouteMatcher extends _mod.RouteMatcher {}`
// → `RouteMatcher(definition)` saw garbage → every matcher's
// `this.definition` was undefined). Forward a generous fixed band of
// positional params: the `new` site pads missing slots with `undefined`,
// and a parent ctor reading fewer params ignores the trailing `undefined`s,
// so over-declaring is correct for any (non-native) parent up to this band.
const UNRESOLVED_PARENT_FWD_ARITY: usize = 8;
UNRESOLVED_PARENT_FWD_ARITY
}

/// Emit the artifact tail: bodies, wrappers, namespace globals, entry
/// function, string pool. Mirrors the in-prelude execution order of
/// the original `compile_module`.
Expand Down
83 changes: 83 additions & 0 deletions crates/perry-codegen/src/codegen/ctor_arity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! Synthesized standalone-constructor arity for classes that inherit the JS
//! spec default ctor `constructor(...args) { super(...args) }`.
//!
//! Split out of `codegen/artifacts.rs` for the 2000-line file cap (#8204 took
//! it to 2005). Pure code move — no logic change.

use std::collections::HashMap;

/// The standalone-constructor arity Perry emits for `class`, accounting for the
/// JS spec default ctor `constructor(...args) { super(...args) }` that a class
/// with NO own constructor but WITH heritage inherits. Walks the ancestor chain
/// (local `class_table` + cross-module `imported_classes` ctor-param counts +
/// imported stubs) for the nearest ctor-bearing parent's user arity, mirroring
/// the `found_params` walk in the per-class ctor emission below.
///
/// This MUST agree with the synthesized standalone ctor's actual LLVM signature,
/// otherwise the runtime registers a different `total_params` than the function
/// declares and `replay_registered_class_constructor` forwards the wrong number
/// of args (Next.js wall 51: `class AppRouteRouteMatcher extends
/// _mod.RouteMatcher {}` emitted a 1-param forwarding ctor but registered it as
/// 0 params, so `new mod.AppRouteRouteMatcher(def)` dropped `def` before
/// `RouteMatcher(definition)` ran and every matcher's `this.definition` was a
/// garbage number).
pub(super) fn synthesized_ctor_param_count(
class: &perry_hir::Class,
class_table: &HashMap<String, &perry_hir::Class>,
imported_class_stubs: &[perry_hir::Class],
imported_classes: &[super::opts::ImportedClass],
) -> usize {
if let Some(c) = class.constructor.as_ref() {
return c.params.len();
}
// A native parent (`extends Error` / `extends events.EventEmitter`) has its
// own native construction path that consumes the construction args directly;
// don't synthesize a forwarding ctor for it.
if class.native_extends.is_some() {
return 0;
}
// No heritage at all → nothing to forward.
if class.extends_name.is_none() && class.extends_expr.is_none() {
return 0;
}
// Walk ancestors for the nearest ctor-bearing parent's user arity.
let mut cur = class.extends_name.clone();
while let Some(pname) = cur {
let imported_ctor_params = imported_classes
.iter()
.find(|i| i.local_alias.as_deref().unwrap_or(&i.name) == pname.as_str())
.map(|ic| ic.constructor_param_count)
.unwrap_or(0);
if let Some(pclass) = class_table.get(pname.as_str()) {
if let Some(pctor) = &pclass.constructor {
return pctor.params.len();
}
if imported_ctor_params > 0 {
return imported_ctor_params;
}
cur = pclass.extends_name.clone();
} else if let Some(stub) = imported_class_stubs.iter().find(|c| c.name == pname) {
if imported_ctor_params > 0 {
return imported_ctor_params;
}
cur = stub.extends_name.clone();
} else {
break;
}
}
// The parent's exact ctor arity is genuinely unavailable here in some build
// modes: the auto-optimize / standalone path compiles each nested
// `node_modules` module with an EMPTY `imported_classes` list and resolves
// the cross-module parent purely as a runtime DYNAMIC parent
// (`extends_expr` + `js_register_class_parent_dynamic`), so it is absent
// from `class_table` and `imported_class_stubs` here. Without a forwarding
// signature the synthesized `super()` dropped every construction arg
// (Next.js wall 51: `class PagesRouteMatcher extends _mod.RouteMatcher {}`
// → `RouteMatcher(definition)` saw garbage → every matcher's
// `this.definition` was undefined). Forward a generous fixed band of
// positional params: the `new` site pads missing slots with `undefined`,
// and a parent ctor reading fewer params ignores the trailing `undefined`s,
// so over-declaring is correct for any (non-native) parent up to this band.
const UNRESOLVED_PARENT_FWD_ARITY: usize = 8;
UNRESOLVED_PARENT_FWD_ARITY
}
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ mod artifacts;
mod boxed_locals;
mod closure;
mod closure_collect;
mod ctor_arity;
#[cfg(test)]
mod emission_order_tests;
mod entry;
Expand Down
Loading
Loading