Refactor stack allocator to reuse freed slots
Introduce `free_slots` in `StackAllocator` to keep track of physical slots that have been freed. When mapping a new virtual slot, the allocator first attempts to reuse a slot from the `free_slots` vector before allocating a new one. This change also includes a new function `collect_scope_locals` that identifies non-captured local variables defined within a block. After these expressions are lowered, the allocator reclaims the physical slots associated with these locals, making them available for reuse by subsequent blocks. A new test `test_slot_reuse_non_overlapping_scopes` is added to verify that distinct scopes correctly reuse stack slots, ensuring optimal stack usage.
This commit is contained in:
@@ -180,3 +180,39 @@ fn test_reproduce_inlining_slot_clash_crash() {
|
||||
let res = env.run_script(source);
|
||||
assert!(res.is_ok(), "Inlining slot clash triggered: {:?}", res.err());
|
||||
}
|
||||
|
||||
// ── Slot reuse ────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_slot_reuse_non_overlapping_scopes() {
|
||||
let mut env = Environment::new();
|
||||
env.optimization = true;
|
||||
|
||||
// Two sequential inner blocks each bind a mutable local.
|
||||
// assign prevents the optimizer from inlining the variables away,
|
||||
// so both slots survive into the lowering pass.
|
||||
// x lives only in the first block; once that block ends its slot is free.
|
||||
// y is defined in the second block and must reuse x's slot.
|
||||
// Expected result: the function returns 3 (y = 2+1).
|
||||
let source = r#"
|
||||
(fn []
|
||||
(do
|
||||
(do (def x 1) (assign x (+ x 1)) x)
|
||||
(do (def y 2) (assign y (+ y 1)) y)))
|
||||
"#;
|
||||
|
||||
// Correctness: calling the function must still produce the right value.
|
||||
let result = env
|
||||
.run_script("((fn [] (do (do (def x 1) (assign x (+ x 1)) x) (do (def y 2) (assign y (+ y 1)) y))))")
|
||||
.unwrap();
|
||||
assert_eq!(format!("{}", result), "3");
|
||||
|
||||
// Stack efficiency: with slot reuse the Lambda's stack_size must be 1,
|
||||
// not 2. The dump format is "stack_size: N" in the Lambda metadata line.
|
||||
let dump = env.dump_ast(source).unwrap();
|
||||
assert!(
|
||||
dump.contains("stack_size: 1"),
|
||||
"Non-overlapping scopes must reuse the same slot (expected stack_size: 1). Dump:\n{}",
|
||||
dump
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user