Skip to content

Commit 097ae7d

Browse files
feat(runtime): Prevent stack overflow while collecting large lists (#2248)
Co-authored-by: Oscar Spencer <oscar.spen@gmail.com>
1 parent c549fac commit 097ae7d

File tree

3 files changed

+50
-40
lines changed

3 files changed

+50
-40
lines changed

compiler/test/suites/basic_functionality.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,6 @@ describe("basic functionality", ({test, testSkip}) => {
377377
~config_fn=smallestFileConfig,
378378
"smallest_grain_program",
379379
"",
380-
6540,
380+
6494,
381381
);
382382
});

compiler/test/suites/gc.re

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe("garbage collection", ({test, testSkip}) => {
139139
"OK\n",
140140
);
141141
assertRunGC(
142-
"long_lists",
142+
"medium_lists",
143143
350,
144144
{|
145145
from "list" include List
@@ -168,6 +168,20 @@ describe("garbage collection", ({test, testSkip}) => {
168168
|},
169169
"true\n",
170170
);
171+
assertRun(
172+
"large_lists",
173+
{|
174+
let rec make_list = (n, acc) => {
175+
if (n == 0) {
176+
acc
177+
} else {
178+
make_list(n - 1, [1, ...acc])
179+
}
180+
}
181+
assert make_list(500_000, []) != []
182+
|},
183+
"",
184+
);
171185
assertFileRun("memory_grow1", "memoryGrow", "1000000000000\n");
172186
assertMemoryLimitedFileRun(
173187
"loop_memory_reclaim",

stdlib/runtime/gc.gr

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ from "runtime/unsafe/panic" include Panic
2525
from "runtime/unsafe/wasmi32" include WasmI32
2626
use WasmI32.{ (+), (-), (*), (&), (==), (!=) }
2727

28+
primitive (!) = "@not"
2829
primitive (&&) = "@and"
2930
primitive (||) = "@or"
3031
primitive ignore = "@ignore"
@@ -84,24 +85,37 @@ let rec decRef = (userPtr: WasmI32, ignoreZeros: Bool) => {
8485
// }
8586

8687
if (WasmI32.eqz(refCount)) {
87-
if (ignoreZeros) {
88-
userPtr
89-
} else {
88+
if (!ignoreZeros) {
9089
throwDecRefError()
9190
}
9291
} else {
9392
let refCount = refCount - 1n
9493
setRefCount(userPtr, refCount)
9594

9695
if (WasmI32.eqz(refCount)) {
97-
decRefChildren(userPtr)
96+
/*
97+
* Note: We call free before decRefChildren to allow for a tail call.
98+
* This is okay because no allocations occur while we traverse the
99+
* structure and free does not mangle the data.
100+
*/
98101
free(userPtr)
102+
decRefChildren(userPtr)
99103
}
100-
101-
userPtr
102104
}
103-
} else {
104-
userPtr
105+
}
106+
}
107+
and decRefChildrenHelp = (
108+
userPtr: WasmI32,
109+
arityOffset: WasmI32,
110+
offset: WasmI32,
111+
) => {
112+
let arity = WasmI32.load(userPtr, arityOffset)
113+
if (arity != 0n) {
114+
let maxOffset = (arity - 1n) * 4n
115+
for (let mut i = 0n; WasmI32.ltU(i, maxOffset); i += 4n) {
116+
decRef(WasmI32.load(userPtr + i, offset), false)
117+
}
118+
decRef(WasmI32.load(userPtr + maxOffset, offset), false)
105119
}
106120
}
107121
and decRefChildren = (userPtr: WasmI32) => {
@@ -110,43 +124,25 @@ and decRefChildren = (userPtr: WasmI32) => {
110124
let tag = WasmI32.load(userPtr, 4n)
111125
if (userPtr == Tags._GRAIN_RATIONAL_BOXED_NUM_TAG) {
112126
// decRef underlying BigInts
113-
ignore(decRef(WasmI32.load(userPtr, 8n), false))
114-
ignore(decRef(WasmI32.load(userPtr, 12n), false))
127+
decRef(WasmI32.load(userPtr, 8n), false)
128+
decRef(WasmI32.load(userPtr, 12n), false)
115129
}
116130
},
117131
t when t == Tags._GRAIN_ADT_HEAP_TAG => {
118-
let arity = WasmI32.load(userPtr, 16n)
119-
let maxOffset = arity * 4n
120-
for (let mut i = 0n; WasmI32.ltU(i, maxOffset); i += 4n) {
121-
ignore(decRef(WasmI32.load(userPtr + i, 20n), false))
122-
}
132+
decRefChildrenHelp(userPtr, 16n, 20n)
123133
},
124-
t when t == Tags._GRAIN_RECORD_HEAP_TAG => {
125-
let arity = WasmI32.load(userPtr, 12n)
126-
let maxOffset = arity * 4n
127-
for (let mut i = 0n; WasmI32.ltU(i, maxOffset); i += 4n) {
128-
ignore(decRef(WasmI32.load(userPtr + i, 16n), false))
129-
}
134+
t when t == Tags._GRAIN_RECORD_HEAP_TAG || t == Tags._GRAIN_LAMBDA_HEAP_TAG => {
135+
decRefChildrenHelp(userPtr, 12n, 16n)
130136
},
131137
t when t == Tags._GRAIN_ARRAY_HEAP_TAG || t == Tags._GRAIN_TUPLE_HEAP_TAG => {
132-
let arity = WasmI32.load(userPtr, 4n)
133-
let maxOffset = arity * 4n
134-
for (let mut i = 0n; WasmI32.ltU(i, maxOffset); i += 4n) {
135-
ignore(decRef(WasmI32.load(userPtr + i, 8n), false))
136-
}
137-
},
138-
t when t == Tags._GRAIN_LAMBDA_HEAP_TAG => {
139-
let arity = WasmI32.load(userPtr, 12n)
140-
let maxOffset = arity * 4n
141-
for (let mut i = 0n; WasmI32.ltU(i, maxOffset); i += 4n) {
142-
ignore(decRef(WasmI32.load(userPtr + i, 16n), false))
143-
}
144-
},
145-
_ => {
146-
// No travelsal necessary for other tags
147-
void
138+
decRefChildrenHelp(userPtr, 4n, 8n)
148139
},
140+
// No traversal necessary for other tags
141+
_ => void,
149142
}
150143
}
151144

152-
provide let decRef = userPtr => decRef(userPtr, false)
145+
provide let decRef = userPtr => {
146+
decRef(userPtr, false)
147+
userPtr
148+
}

0 commit comments

Comments
 (0)