Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F7535577
test_gc_tco.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
6 KB
Referenced Files
None
Subscribers
None
test_gc_tco.js
View Options
// Stress test: GC compaction during tail-call optimization
// Verifies that tc.func, tc.closure_scope, and tc.args[] are properly
// rooted so GC compaction doesn't corrupt them mid-trampoline.
console
.
log
(
'=== GC + Tail Call Stress Test ===\n'
);
function
fmt
(
bytes
)
{
if
(
bytes
<
1024
)
return
bytes
+
' B'
;
if
(
bytes
<
1024
*
1024
)
return
(
bytes
/
1024
).
toFixed
(
2
)
+
' KB'
;
return
(
bytes
/
1024
/
1024
).
toFixed
(
2
)
+
' MB'
;
}
let
failures
=
0
;
function
assert
(
cond
,
msg
)
{
if
(
!
cond
)
{
console
.
log
(
'FAIL:'
,
msg
);
failures
++
;
}
}
function
inflateArena
()
{
let
junk
=
[];
for
(
let
i
=
0
;
i
<
40000
;
i
++
)
{
junk
.
push
({
idx
:
i
,
payload
:
'padding_string_'
+
i
+
'_extra_data_to_bulk_up_the_arena_size'
});
}
let
used
=
Ant
.
stats
().
arenaUsed
;
assert
(
used
>=
10
*
1024
*
1024
,
'arena should be >= 10MB, got '
+
fmt
(
used
));
console
.
log
(
' Arena inflated to'
,
fmt
(
used
));
return
junk
;
}
// ---------------------------------------------------------------------------
// Test 1: Tail recursion passing heap objects as args, GC between iterations
// ---------------------------------------------------------------------------
console
.
log
(
'Test 1: Tail-call args survive GC compaction'
);
let
_garbage
=
inflateArena
();
function
tailWithObjects
(
n
,
obj
)
{
if
(
n
<=
0
)
return
obj
;
return
tailWithObjects
(
n
-
1
,
{
value
:
obj
.
value
+
1
,
prev
:
obj
});
}
let
result1
=
tailWithObjects
(
200
,
{
value
:
0
,
prev
:
null
});
assert
(
result1
.
value
===
200
,
'final value should be 200, got '
+
result1
.
value
);
let
walk
=
result1
;
let
chainOk
=
true
;
for
(
let
i
=
200
;
i
>=
0
;
i
--
)
{
if
(
walk
.
value
!==
i
)
{
chainOk
=
false
;
break
;
}
walk
=
walk
.
prev
;
}
assert
(
chainOk
,
'object chain should be intact through all 200 links'
);
console
.
log
(
' Object args through tail calls: OK\n'
);
// ---------------------------------------------------------------------------
// Test 2: Mutual tail recursion with string args + GC pressure
// ---------------------------------------------------------------------------
console
.
log
(
'Test 2: Mutual tail recursion with string args + GC'
);
_garbage
=
inflateArena
();
function
pingStr
(
n
,
s
)
{
if
(
n
<=
0
)
return
s
;
return
pongStr
(
n
-
1
,
s
+
'p'
);
}
function
pongStr
(
n
,
s
)
{
if
(
n
<=
0
)
return
s
;
return
pingStr
(
n
-
1
,
s
+
'o'
);
}
let
result2
=
pingStr
(
100
,
'start:'
);
assert
(
result2
.
length
===
106
,
'string length should be 106, got '
+
result2
.
length
);
assert
(
result2
.
startsWith
(
'start:'
),
'should start with "start:"'
);
console
.
log
(
' Mutual tail recursion with strings: OK\n'
);
// ---------------------------------------------------------------------------
// Test 3: Closure scope survives GC during tail calls
// ---------------------------------------------------------------------------
console
.
log
(
'Test 3: Closure scope survives GC during tail calls'
);
_garbage
=
inflateArena
();
function
makeAccumulator
()
{
let
captured
=
{
sum
:
0
};
function
loop
(
n
)
{
if
(
n
<=
0
)
return
captured
;
captured
.
sum
+=
n
;
return
loop
(
n
-
1
);
}
return
loop
;
}
let
accResult
=
makeAccumulator
()(
500
);
assert
(
accResult
.
sum
===
(
500
*
501
)
/
2
,
'sum should be 125250, got '
+
accResult
.
sum
);
console
.
log
(
' Closure scope through tail calls: OK\n'
);
// ---------------------------------------------------------------------------
// Test 4: Multi-arg tail calls with mixed heap types + GC
// ---------------------------------------------------------------------------
console
.
log
(
'Test 4: Multi-arg tail calls with mixed types + GC'
);
_garbage
=
inflateArena
();
function
multiArg
(
n
,
arr
,
obj
,
str
)
{
if
(
n
<=
0
)
return
{
arr
,
obj
,
str
};
arr
.
push
(
n
);
return
multiArg
(
n
-
1
,
arr
,
{
v
:
obj
.
v
+
1
,
inner
:
obj
},
str
+
'x'
);
}
let
result4
=
multiArg
(
100
,
[],
{
v
:
0
,
inner
:
null
},
''
);
assert
(
result4
.
arr
.
length
===
100
,
'array should have 100 elements, got '
+
result4
.
arr
.
length
);
assert
(
result4
.
obj
.
v
===
100
,
'nested obj.v should be 100, got '
+
result4
.
obj
.
v
);
assert
(
result4
.
str
.
length
===
100
,
'string should have 100 chars, got '
+
result4
.
str
.
length
);
console
.
log
(
' Multi-arg mixed types: OK\n'
);
// ---------------------------------------------------------------------------
// Test 5: Deep tail recursion with GC every N iterations
// ---------------------------------------------------------------------------
console
.
log
(
'Test 5: Deep tail recursion (50k) with periodic GC'
);
_garbage
=
inflateArena
();
function
deepTail
(
n
,
acc
)
{
if
(
n
<=
0
)
return
acc
;
return
deepTail
(
n
-
1
,
acc
+
1
);
}
let
result5
=
deepTail
(
50000
,
0
);
assert
(
result5
===
50000
,
'deepTail should return 50000, got '
+
result5
);
console
.
log
(
' Deep tail recursion with periodic GC: OK\n'
);
// ---------------------------------------------------------------------------
// Test 6: Tail call where callee is a different function (not self-recursion)
// ---------------------------------------------------------------------------
console
.
log
(
'Test 6: Tail call to different functions + GC'
);
_garbage
=
inflateArena
();
function
step1
(
n
,
data
)
{
if
(
n
<=
0
)
return
data
;
data
.
a
++
;
return
step2
(
n
-
1
,
data
);
}
function
step2
(
n
,
data
)
{
if
(
n
<=
0
)
return
data
;
data
.
b
++
;
return
step3
(
n
-
1
,
data
);
}
function
step3
(
n
,
data
)
{
if
(
n
<=
0
)
return
data
;
data
.
c
++
;
return
step1
(
n
-
1
,
data
);
}
let
result6
=
step1
(
300
,
{
a
:
0
,
b
:
0
,
c
:
0
});
assert
(
result6
.
a
===
100
,
'a should be 100, got '
+
result6
.
a
);
assert
(
result6
.
b
===
100
,
'b should be 100, got '
+
result6
.
b
);
assert
(
result6
.
c
===
100
,
'c should be 100, got '
+
result6
.
c
);
console
.
log
(
' Cross-function tail calls: OK\n'
);
// ---------------------------------------------------------------------------
// Test 7: Array args allocated fresh each iteration + GC
// ---------------------------------------------------------------------------
console
.
log
(
'Test 7: Fresh array allocation per tail-call iteration + GC'
);
_garbage
=
inflateArena
();
function
freshArrays
(
n
,
results
)
{
if
(
n
<=
0
)
return
results
;
let
data
=
new
Array
(
50
);
for
(
let
i
=
0
;
i
<
50
;
i
++
)
data
[
i
]
=
n
;
results
.
push
(
data
);
return
freshArrays
(
n
-
1
,
results
);
}
let
result7
=
freshArrays
(
100
,
[]);
assert
(
result7
.
length
===
100
,
'should have 100 arrays, got '
+
result7
.
length
);
assert
(
result7
[
0
][
0
]
===
100
,
'first array should contain 100'
);
assert
(
result7
[
99
][
0
]
===
1
,
'last array should contain 1'
);
console
.
log
(
' Fresh array per iteration: OK\n'
);
// ---------------------------------------------------------------------------
// Summary
// ---------------------------------------------------------------------------
_garbage
=
null
;
console
.
log
(
'=== Summary ==='
);
if
(
failures
===
0
)
{
console
.
log
(
'All GC + TCO stress tests passed!'
);
}
else
{
console
.
log
(
'Failures:'
,
failures
);
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Jun 17, 12:07 PM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
543506
Default Alt Text
test_gc_tco.js (6 KB)
Attached To
Mode
rANT Ant
Attached
Detach File
Event Timeline
Log In to Comment