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
2 changes: 1 addition & 1 deletion crates/perry-codegen-js/src/emit/stmts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ impl JsEmitter {
// (alloca slot+box for hoisted FnDecl ids). The JS backend has no
// equivalent — JS hoisting handles this for free in the V8 / JSC
// runtime. Emit nothing.
Stmt::PreallocateBoxes(_) => {}
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => {}
}
}
}
2 changes: 1 addition & 1 deletion crates/perry-codegen-wasm/src/emit/js_fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ impl WasmModuleEmitter {
// Issue #569: PreallocateBoxes is a perry-codegen-only directive
// — JS hoisting handles forward refs natively, so the wasm/JS
// backend has no equivalent to emit.
Stmt::PreallocateBoxes(_) => {}
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => {}
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen-wasm/src/emit/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ impl<'a> FuncEmitCtx<'a> {
// Issue #569: Wasm backend has no equivalent of LLVM's
// alloca'd box slot — JS hoisting in the host runtime handles
// forward refs natively. Emit nothing.
Stmt::PreallocateBoxes(_) => {}
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => {}
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen-wasm/src/emit/string_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ impl WasmModuleEmitter {
}
}
Stmt::Break | Stmt::Continue | Stmt::LabeledBreak(_) | Stmt::LabeledContinue(_) => {}
Stmt::PreallocateBoxes(_) => {}
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => {}
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/boxed_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ fn collect_prealloc_box_ids_in_stmts(stmts: &[perry_hir::Stmt], out: &mut HashSe
use perry_hir::Stmt;
for s in stmts {
match s {
Stmt::PreallocateBoxes(ids) => {
Stmt::PreallocateBoxes(ids) | Stmt::PreallocateTdzBoxes(ids) => {
for id in ids {
out.insert(*id);
}
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,7 @@ pub(super) fn compile_closure(
func_returns_class: &cross_module.func_returns_class,
boxed_vars: closure_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
tdz_boxes: std::collections::HashSet::new(),
compiler_private_async_i32_control_locals: &cross_module
.compiler_private_async_i32_control_locals,
compiler_private_async_i1_control_locals: &cross_module
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ pub(super) fn compile_module_entry(
func_returns_class: &cross_module.func_returns_class,
boxed_vars: main_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
tdz_boxes: std::collections::HashSet::new(),
compiler_private_async_i32_control_locals: &cross_module
.compiler_private_async_i32_control_locals,
compiler_private_async_i1_control_locals: &cross_module
Expand Down Expand Up @@ -1134,6 +1135,7 @@ pub(super) fn compile_module_entry(
func_returns_class: &cross_module.func_returns_class,
boxed_vars: init_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
tdz_boxes: std::collections::HashSet::new(),
compiler_private_async_i32_control_locals: &cross_module
.compiler_private_async_i32_control_locals,
compiler_private_async_i1_control_locals: &cross_module
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/codegen/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ pub(super) fn compile_function(
func_returns_class: &cross_module.func_returns_class,
boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
tdz_boxes: std::collections::HashSet::new(),
compiler_private_async_i32_control_locals: &cross_module
.compiler_private_async_i32_control_locals,
compiler_private_async_i1_control_locals: &cross_module
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/codegen/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ pub(super) fn compile_method(
func_returns_class: &cross_module.func_returns_class,
boxed_vars: method_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
tdz_boxes: std::collections::HashSet::new(),
compiler_private_async_i32_control_locals: &cross_module
.compiler_private_async_i32_control_locals,
compiler_private_async_i1_control_locals: &cross_module
Expand Down Expand Up @@ -1262,6 +1263,7 @@ pub(super) fn compile_static_method(
func_returns_class: &cross_module.func_returns_class,
boxed_vars: static_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
tdz_boxes: std::collections::HashSet::new(),
compiler_private_async_i32_control_locals: &cross_module
.compiler_private_async_i32_control_locals,
compiler_private_async_i1_control_locals: &cross_module
Expand Down
3 changes: 2 additions & 1 deletion crates/perry-codegen/src/collectors/escape_arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ fn collect_used_array_indices_in_stmts(
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => {}
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => {}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/collectors/escape_news.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ fn collect_used_new_fields_in_stmts(
);
}
Stmt::Break | Stmt::Continue | Stmt::LabeledBreak(_) | Stmt::LabeledContinue(_) => {}
Stmt::PreallocateBoxes(_) => {}
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => {}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/perry-codegen/src/collectors/escape_objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ fn collect_used_object_fields_in_stmts(
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => {}
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => {}
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions crates/perry-codegen/src/collectors/hir_facts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,8 @@ fn collect_owned_buffer_lets(
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::Throw(_)
| Stmt::PreallocateBoxes(_) => {}
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => {}
}
}
}
Expand Down Expand Up @@ -704,7 +705,8 @@ impl ArrayFactCollector {
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => {}
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => {}
}
}

Expand Down
6 changes: 4 additions & 2 deletions crates/perry-codegen/src/collectors/pointer_locals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ pub fn collect_pointer_typed_locals(
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => {}
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => {}
Stmt::If {
condition,
then_branch,
Expand Down Expand Up @@ -475,7 +476,8 @@ pub fn collect_pointer_typed_locals(
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => {}
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => {}
Stmt::If {
condition,
then_branch,
Expand Down
3 changes: 2 additions & 1 deletion crates/perry-codegen/src/collectors/scalar_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ fn stmt_writes_this_property(stmt: &Stmt, property: &str) -> bool {
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => false,
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => false,
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/perry-codegen/src/collectors/shadow_slots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ pub fn collect_declared_shadow_locals_in_stmt(
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::Throw(_)
| Stmt::PreallocateBoxes(_) => {}
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => {}
}
}
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/collectors/this_as_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ pub fn stmts_use_this_as_value(stmts: &[perry_hir::Stmt], fields: &HashSet<Strin
Stmt::Break | Stmt::Continue | Stmt::LabeledBreak(_) | Stmt::LabeledContinue(_) => {
false
}
Stmt::PreallocateBoxes(_) => false,
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => false,
};
if bad {
return true;
Expand Down
7 changes: 7 additions & 0 deletions crates/perry-codegen/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,13 @@ pub(crate) struct FnCtx<'a> {
/// pre-allocated box. The id is added to `boxed_vars` automatically
/// so subsequent `LocalGet`/`LocalSet`/`Update` go through the box.
pub prealloc_boxes: std::collections::HashSet<u32>,
/// LocalIds whose pre-allocated box was seeded with the TAG_TDZ sentinel
/// (Temporal Dead Zone) via `Stmt::PreallocateTdzBoxes` rather than
/// `undefined`. A read of one of these boxes before its `Stmt::Let` runs
/// throws a spec ReferenceError (enforced in the runtime `js_box_get_bits`
/// choke point). The `Stmt::Let` arm consults this set so a no-init
/// declaration (`let x;`) still clears the sentinel to `undefined`.
pub tdz_boxes: std::collections::HashSet<u32>,
/// Compiler-private async/generator control locals whose closure-shared
/// storage is a primitive heap cell instead of a generic JSValue box.
/// These ids are emitted by Perry's generator transform, not user source:
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/lower_call/closure_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ pub fn find_outer_writes_stmt(
}
}
Stmt::Labeled { body, .. } => find_outer_writes_stmt(body, inner_ids, out),
Stmt::PreallocateBoxes(_) => {}
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => {}
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/perry-codegen/src/lower_call/new_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ fn stmt_any(
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => false,
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => false,
}
}

Expand Down
5 changes: 5 additions & 0 deletions crates/perry-codegen/src/nanbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ pub const TAG_TRUE: u64 = 0x7FFC_0000_0000_0004;
/// invariant. Inline `IndexGet` paths emitted by the codegen must select-
/// rewrite this back to `TAG_UNDEFINED` so user code never sees the sentinel.
pub const TAG_HOLE: u64 = 0x7FFC_0000_0000_0010;
/// TDZ (Temporal Dead Zone) sentinel — see `perry-runtime::value::TAG_TDZ`.
/// A lexical `let`/`const`/`class` box is seeded with this at scope entry;
/// reading it before the declaration runs throws a spec ReferenceError.
pub const TAG_TDZ: u64 = 0x7FFC_0000_0000_0011;
pub const POINTER_TAG: u64 = 0x7FFD_0000_0000_0000;
pub const POINTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
pub const INT32_TAG: u64 = 0x7FFE_0000_0000_0000;
Expand All @@ -37,6 +41,7 @@ pub const TAG_NULL_I64: &str = "9222246136947933186";
pub const TAG_FALSE_I64: &str = "9222246136947933187";
pub const TAG_TRUE_I64: &str = "9222246136947933188";
pub const TAG_HOLE_I64: &str = "9222246136947933200";
pub const TAG_TDZ_I64: &str = "9222246136947933201";
pub const POINTER_TAG_I64: &str = "9222527611924643840";
pub const POINTER_MASK_I64: &str = "281474976710655";
pub const INT32_TAG_I64: &str = "9222809086901354496";
Expand Down
30 changes: 30 additions & 0 deletions crates/perry-codegen/src/stmt/let_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,27 @@ pub(crate) fn lower_let(
// no initializer (`var x;`) keeps the prior value, matching JS.
if ctx.locals.contains_key(&id) {
if let Some(init_expr) = init {
// The binding's OWN declaration ends its Temporal Dead Zone: the
// reused-slot write below (plain, unchecked) overwrites any TAG_TDZ
// sentinel with the real value.
ctx.tdz_boxes.remove(&id);
crate::expr::lower_expr(
ctx,
&perry_hir::Expr::LocalSet(id, Box::new(init_expr.clone())),
)?;
} else if ctx.tdz_boxes.remove(&id) {
// No-init reuse (`let x;`) of a TDZ-seeded box must still end the
// dead zone by clearing the sentinel to `undefined`; otherwise a
// later legitimate read of `x` would wrongly throw.
if let Some(slot) = ctx.locals.get(&id).cloned() {
let blk = ctx.block();
let bptr = blk.load(crate::types::I64, &slot);
let undef_bits = crate::nanbox::TAG_UNDEFINED_I64.to_string();
blk.call_void(
"js_box_set_bits",
&[(crate::types::I64, &bptr), (crate::types::I64, &undef_bits)],
);
}
}
return Ok(());
}
Expand Down Expand Up @@ -881,6 +898,19 @@ pub(crate) fn lower_let(
&[(crate::types::I64, &bptr), (I64, &init_bits)],
);
}
} else if ctx.tdz_boxes.contains(&id) {
// TDZ box with a no-init declaration (`let x;`): the box was
// seeded with TAG_TDZ at scope entry; running the declaration
// ends the dead zone by initializing the binding to
// `undefined`. Without this the sentinel would survive and a
// later legitimate read of `x` would wrongly throw.
let slot_clone = ctx.locals[&id].clone();
let bptr = ctx.block().load(I64, &slot_clone);
let undef_bits = crate::nanbox::TAG_UNDEFINED_I64.to_string();
ctx.block().call_void(
"js_box_set_bits",
&[(crate::types::I64, &bptr), (I64, &undef_bits)],
);
}
return Ok(());
}
Expand Down
14 changes: 9 additions & 5 deletions crates/perry-codegen/src/stmt/loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1258,7 +1258,7 @@ fn stmt_is_packed_f64_loop_safe(
Stmt::Labeled { body, .. } => {
stmt_is_packed_f64_loop_safe(ctx, body.as_ref(), arr_id, counter_id)
}
Stmt::PreallocateBoxes(_) => true,
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => true,
Stmt::Return(_)
| Stmt::Throw(_)
| Stmt::Break
Expand Down Expand Up @@ -2274,7 +2274,8 @@ fn collect_guarded_array_aliases_in_stmt(
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => false,
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => false,
Stmt::If {
condition,
then_branch,
Expand Down Expand Up @@ -2776,7 +2777,8 @@ fn stmt_mutates_local(stmt: &perry_hir::Stmt, local_id: u32) -> bool {
| Stmt::Continue
| Stmt::LabeledBreak(_)
| Stmt::LabeledContinue(_)
| Stmt::PreallocateBoxes(_) => false,
| Stmt::PreallocateBoxes(_)
| Stmt::PreallocateTdzBoxes(_) => false,
Stmt::If {
condition,
then_branch,
Expand Down Expand Up @@ -3163,7 +3165,9 @@ fn stmt_array_length_effect(
Stmt::Break | Stmt::Continue | Stmt::LabeledBreak(_) | Stmt::LabeledContinue(_) => {
LoopArrayLengthEffect::Preserves
}
Stmt::PreallocateBoxes(_) => LoopArrayLengthEffect::Preserves,
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => {
LoopArrayLengthEffect::Preserves
}
}
}

Expand Down Expand Up @@ -3587,7 +3591,7 @@ pub(crate) fn stmt_preserves_array_length(
aliases,
),
Stmt::Break | Stmt::Continue | Stmt::LabeledBreak(_) | Stmt::LabeledContinue(_) => true,
Stmt::PreallocateBoxes(_) => true,
Stmt::PreallocateBoxes(_) | Stmt::PreallocateTdzBoxes(_) => true,
}
}

Expand Down
Loading
Loading