fix gc closure shortening

This commit is contained in:
2026-02-20 23:07:47 -06:00
parent 26f63bccee
commit 34cb19c357
2 changed files with 178 additions and 0 deletions

View File

@@ -1598,6 +1598,8 @@ var streamline = function(ir, log) {
var found = false
var anc_remap = null
var old_slot = 0
var max_close = null
var needed = 0
var fi = 0
var i = 0
var j = 0
@@ -1703,6 +1705,8 @@ var streamline = function(ir, log) {
}
// Fix get/put parent_slot references using ancestor remap tables
// and track the max close slot per ancestor for nr_close_slots update
max_close = array(func_count + 1, -1)
fi = 0
while (fi < func_count) {
instrs = functions[fi].instructions
@@ -1725,6 +1729,9 @@ var streamline = function(ir, log) {
instr[2] = anc_remap[old_slot]
}
}
if (ancestor >= 0 && instr[2] > max_close[ancestor]) {
max_close[ancestor] = instr[2]
}
}
i = i + 1
}
@@ -1732,6 +1739,26 @@ var streamline = function(ir, log) {
fi = fi + 1
}
// Update nr_close_slots for functions whose close slots were remapped.
// Frame shortening keeps 1 + nr_args + nr_close_slots slots, so
// nr_close_slots must cover the highest-numbered close slot.
fi = 0
while (fi < func_count) {
if (max_close[fi] >= 0) {
needed = max_close[fi] - (functions[fi].nr_args != null ? functions[fi].nr_args : 0)
if (needed > functions[fi].nr_close_slots) {
functions[fi].nr_close_slots = needed
}
}
fi = fi + 1
}
if (max_close[func_count] >= 0 && ir.main != null) {
needed = max_close[func_count] - (ir.main.nr_args != null ? ir.main.nr_args : 0)
if (needed > ir.main.nr_close_slots) {
ir.main.nr_close_slots = needed
}
}
return null
}

View File

@@ -5539,6 +5539,157 @@ run("gc blob forward pointer chase", function() {
}
})
// ============================================================================
// GC CLOSURE FRAME SHORTENING
// Verify that closure-captured variables survive GC collection, particularly
// when the streamline optimizer remaps close slots to different positions.
// ============================================================================
var force_gc = function() {
var _g = 0
var _gx = null
for (_g = 0; _g < 200; _g = _g + 1) {
_gx = {a: _g, b: [1, 2, 3], c: "garbage"}
}
}
run("gc closure basic - captured function survives gc", function() {
var make = function() {
function helper() { return 42 }
var obj = { call() { return helper() } }
return obj
}
var obj = make()
force_gc()
assert_eq(obj.call(), 42, "captured function should survive GC")
})
run("gc closure - captured variable survives gc", function() {
var make = function() {
var val = 99
var obj = { get() { return val } }
return obj
}
var obj = make()
force_gc()
assert_eq(obj.get(), 99, "captured variable should survive GC")
})
run("gc closure - multiple captured variables survive gc", function() {
var make = function() {
var a = 10
var b = 20
var c = 30
var obj = {
sum() { return a + b + c }
}
return obj
}
var obj = make()
force_gc()
assert_eq(obj.sum(), 60, "all captured vars should survive GC")
})
run("gc closure - captured function and var survive gc", function() {
var make = function() {
function double(x) { return x * 2 }
var base = 5
var obj = { compute() { return double(base) } }
return obj
}
var obj = make()
force_gc()
assert_eq(obj.compute(), 10, "captured fn and var should survive GC")
})
run("gc closure - nested closure chain survives gc", function() {
var outer = function() {
var x = 7
var mid = function() {
var y = 3
var inner = function() { return x + y }
return inner
}
return mid()
}
var fn = outer()
force_gc()
assert_eq(fn(), 10, "nested closure chain should survive GC")
})
run("gc closure - multiple methods share captured frame", function() {
var make = function() {
var count = 0
function inc() { count = count + 1 }
function get() { return count }
var obj = {
increment() { inc() },
value() { return get() }
}
return obj
}
var obj = make()
obj.increment()
obj.increment()
force_gc()
obj.increment()
assert_eq(obj.value(), 3, "shared closure frame should survive GC")
})
run("gc closure - closure survives repeated gc cycles", function() {
var make = function() {
var val = 123
var obj = { get() { return val } }
return obj
}
var obj = make()
force_gc()
force_gc()
force_gc()
assert_eq(obj.get(), 123, "closure should survive repeated GC cycles")
})
run("gc closure - object literal method with temp slot reuse", function() {
var make = function() {
function helper() { return "ok" }
var temp = [1, 2, 3]
var unused = {x: temp}
var obj = { call() { return helper() } }
return obj
}
var obj = make()
force_gc()
assert_eq(obj.call(), "ok", "closure should work after temp slots discarded")
})
run("gc closure - closure array survives gc", function() {
var make = function() {
var items = [10, 20, 30]
var obj = {
first() { return items[0] },
last() { return items[2] }
}
return obj
}
var obj = make()
force_gc()
assert_eq(obj.first(), 10, "captured array first element")
assert_eq(obj.last(), 30, "captured array last element")
})
run("gc closure - factory pattern survives gc", function() {
var factory = function(name) {
function greet() { return "hello " + name }
var obj = { say() { return greet() } }
return obj
}
var a = factory("alice")
var b = factory("bob")
force_gc()
assert_eq(a.say(), "hello alice", "first factory closure")
assert_eq(b.say(), "hello bob", "second factory closure")
})
// ============================================================================
// SUMMARY
// ============================================================================