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
23 changes: 17 additions & 6 deletions crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
// Issue #26: record the authoritative root→leaf init chain. `parent_chain`
// was pushed direct-parent-first, so reverse it (deepest ancestor first),
// then append the leaf class `c` (with its own fields, init exprs intact).
{
let chain: Vec<(String, Vec<perry_hir::ClassField>)> = {
let mut chain: Vec<(String, Vec<perry_hir::ClassField>)> =
parent_chain.iter().rev().cloned().collect();
chain.push((c.name.clone(), c.fields.clone()));
Expand All @@ -896,7 +896,8 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
.entry(alias.clone())
.or_insert_with(|| chain.clone());
}
}
chain
};
// Refs #486: register self-binding aliases (`_X` from `var X = class _X`)
// so the inline-alloc fast path at lower_call.rs:2532 finds the keys
// global when the class is referenced by its inner name. Without this,
Expand All @@ -908,7 +909,12 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
.entry(alias.clone())
.or_insert_with(|| global_name.clone());
}
let typed_layout = crate::typed_shape::class_typed_layout(&class_table, &c.name);
// Refs #5094: derive the GC raw-f64/pointer masks from the SAME
// prefix-disambiguated chain that built `packed_keys` above, so mask
// bits stay aligned with the actual slot layout when same-named
// cross-module parents exist (the name-keyed `class_typed_layout`
// walk picks whichever stub won the bare-name race).
let typed_layout = crate::typed_shape::class_typed_layout_from_chain(&chain);
class_field_counts_map.insert(c.name.clone(), total_field_count);
for alias in &c.aliases {
class_field_counts_map
Expand Down Expand Up @@ -999,18 +1005,23 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
packed_keys.push_str(&f.name);
packed_keys.push('\0');
}
let typed_layout = crate::typed_shape::class_typed_layout(&class_table, &c.name);
class_field_counts_map
.entry(c.name.clone())
.or_insert(total_field_count);
// Issue #26: authoritative root→leaf init chain for the imported class
// (prefix-disambiguated parents + this stub's own fields as the leaf).
{
// Refs #5094: the GC raw-f64/pointer masks derive from this same chain
// (not the name-keyed `class_typed_layout` walk) so mask bits stay
// aligned with the packed-keys slot layout under same-named
// cross-module parents.
let typed_layout = {
let mut chain: Vec<(String, Vec<perry_hir::ClassField>)> =
parent_chain.iter().rev().cloned().collect();
chain.push((c.name.clone(), c.fields.clone()));
let typed_layout = crate::typed_shape::class_typed_layout_from_chain(&chain);
class_init_chains_map.entry(c.name.clone()).or_insert(chain);
}
typed_layout
};
class_keys_init_data.push((
global_name,
packed_keys,
Expand Down
9 changes: 8 additions & 1 deletion crates/perry-codegen/src/lower_call/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,14 @@ fn emit_typed_shape_layout_init(ctx: &mut FnCtx<'_>, class_name: &str, obj_handl
let Some(keys_global_name) = ctx.class_keys_globals.get(class_name).cloned() else {
return;
};
let typed_layout = crate::typed_shape::class_typed_layout(ctx.classes, class_name);
// Refs #5094: prefer the prefix-disambiguated chain so slot/word counts
// agree with the mask globals emitted in compile_module (same-named
// cross-module parents mis-resolve in the name-keyed walk).
let typed_layout = ctx
.class_init_chains
.get(class_name)
.map(|chain| crate::typed_shape::class_typed_layout_from_chain(chain))
.unwrap_or_else(|| crate::typed_shape::class_typed_layout(ctx.classes, class_name));
let slot_count_str = typed_layout.slot_count.to_string();
let raw_mask_word_count_str = typed_layout.raw_f64_mask_words.len().to_string();
let pointer_mask_word_count_str = typed_layout.pointer_mask_words.len().to_string();
Expand Down
9 changes: 8 additions & 1 deletion crates/perry-codegen/src/lower_call/scalar_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,14 @@ fn emit_materialized_scalar_receiver_typed_shape_init(
let Some(keys_global_name) = ctx.class_keys_globals.get(class_name).cloned() else {
return;
};
let typed_layout = crate::typed_shape::class_typed_layout(ctx.classes, class_name);
// Refs #5094: prefer the prefix-disambiguated chain so slot/word counts
// agree with the mask globals emitted in compile_module (same-named
// cross-module parents mis-resolve in the name-keyed walk).
let typed_layout = ctx
.class_init_chains
.get(class_name)
.map(|chain| crate::typed_shape::class_typed_layout_from_chain(chain))
.unwrap_or_else(|| crate::typed_shape::class_typed_layout(ctx.classes, class_name));
let slot_count_str = typed_layout.slot_count.to_string();
let raw_mask_word_count_str = typed_layout.raw_f64_mask_words.len().to_string();
let pointer_mask_word_count_str = typed_layout.pointer_mask_words.len().to_string();
Expand Down
28 changes: 27 additions & 1 deletion crates/perry-codegen/src/typed_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,36 @@ pub(crate) fn class_typed_layout(
}
chain.reverse();

typed_layout_from_fields(chain.iter().flat_map(|class| class.fields.iter()))
}

/// Issue #26 / #321 (refs #5094): typed layout from an authoritative,
/// source-prefix-disambiguated root→leaf chain (`class_init_chains`). The
/// name-keyed walk in [`class_typed_layout`] mis-resolves same-named
/// cross-module parents (effect's `Type` in SchemaAST.ts vs ParseResult.ts),
/// which misaligns every mask bit after the wrong parent's field count. The
/// GC scanner reads these masks per slot, so a misaligned mask is only kept
/// from corrupting memory by the install-time backstop in
/// `js_gc_init_typed_shape_layout` (each raw-f64 slot is validated to hold a
/// plain double before the descriptor is promoted) — which also means
/// dup-named classes silently never get a typed descriptor, so the #5093
/// class-field fast path never engages for them. The chain is built in
/// `compile_module` by the SAME walk that emits the packed-keys global and
/// field count, so masks derived from it are consistent with the slot layout
/// instances actually get.
pub(crate) fn class_typed_layout_from_chain(
chain: &[(String, Vec<perry_hir::ClassField>)],
) -> TypedShapeLayout {
typed_layout_from_fields(chain.iter().flat_map(|(_, fields)| fields.iter()))
}

fn typed_layout_from_fields<'a>(
fields: impl Iterator<Item = &'a perry_hir::ClassField>,
) -> TypedShapeLayout {
let mut raw_f64_mask_words = Vec::new();
let mut pointer_mask_words = Vec::new();
let mut slot_count = 0u32;
for field in chain.iter().flat_map(|class| class.fields.iter()) {
for field in fields {
if field.key_expr.is_some() {
continue;
}
Expand Down
Loading