Skip to content

Commit 86c6e12

Browse files
authored
fix(compiler): Ensure refcounts are maintained when tail calls use arguments multiple times (#1993)
1 parent f5e934f commit 86c6e12

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

compiler/src/codegen/garbage_collection.re

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,33 @@ let rec analyze_usage = instrs => {
9191
};
9292
};
9393

94+
let process_tail_call_arg = imm => {
95+
switch (imm.immediate_desc) {
96+
| MImmBinding(binding) =>
97+
switch (BindMap.find(binding, usage_map^)) {
98+
| Some(imm) => imm.immediate_analyses.last_usage = TailCallLast
99+
| None => ()
100+
}
101+
| MImmConst(_)
102+
| MImmTrap
103+
| MIncRef(_) => ()
104+
};
105+
};
106+
94107
let rec process_instr = instr => {
95108
switch (instr.instr_desc) {
96109
| MImmediate(imm) => process_imm(imm)
97110
| MCallRaw({args}) => List.iter(process_imm, args)
98111
| MCallKnown({closure: func, args})
112+
| MCallIndirect({func, args}) =>
113+
process_imm(func);
114+
List.iter(process_imm, args);
99115
| MReturnCallKnown({closure: func, args})
100-
| MCallIndirect({func, args})
101116
| MReturnCallIndirect({func, args}) =>
102117
process_imm(func);
103118
List.iter(process_imm, args);
119+
process_tail_call_arg(func);
120+
List.iter(process_tail_call_arg, args);
104121
| MError(e, args) => List.iter(process_imm, args)
105122
| MAllocate(alloc) =>
106123
switch (alloc) {
@@ -211,6 +228,12 @@ let is_last_usage = imm =>
211228
| _ => false
212229
};
213230

231+
let is_last_tail_call_usage = imm =>
232+
switch (imm.immediate_analyses.last_usage) {
233+
| TailCallLast => true
234+
| _ => false
235+
};
236+
214237
type bind_state =
215238
| Alive
216239
| Dead;
@@ -275,7 +298,8 @@ let rec apply_gc = (~level, ~loop_context, ~implicit_return=false, instrs) => {
275298
},
276299
};
277300

278-
let handle_imm = (~non_gc_instr=false, ~is_return=false, imm) => {
301+
let handle_imm =
302+
(~non_gc_instr=false, ~is_return=false, ~is_tail=false, imm) => {
279303
switch (imm.immediate_desc) {
280304
| MImmBinding((MArgBind(_) | MLocalBind(_) | MClosureBind(_)) as binding) =>
281305
let alloc =
@@ -314,10 +338,12 @@ let rec apply_gc = (~level, ~loop_context, ~implicit_return=false, instrs) => {
314338
);
315339
switch (alloc) {
316340
| Unmanaged(_) => imm
317-
| Managed when non_gc_instr || is_return => imm
341+
| Managed when non_gc_instr || is_return || is_tail => imm
318342
| Managed => incref(imm)
319343
};
320344
};
345+
} else if (is_last_tail_call_usage(imm)) {
346+
imm;
321347
} else {
322348
switch (alloc) {
323349
| Managed when non_gc_instr || is_return => imm
@@ -352,8 +378,8 @@ let rec apply_gc = (~level, ~loop_context, ~implicit_return=false, instrs) => {
352378
// tail calls will use arguments directly without the need to incref
353379
MReturnCallKnown({
354380
...data,
355-
closure: handle_imm(~is_return=true, closure),
356-
args: List.map(handle_imm(~is_return=true), args),
381+
closure: handle_imm(~is_tail=true, closure),
382+
args: List.map(handle_imm(~is_tail=true), args),
357383
})
358384
| MCallIndirect({func, args} as data) =>
359385
MCallIndirect({
@@ -365,8 +391,8 @@ let rec apply_gc = (~level, ~loop_context, ~implicit_return=false, instrs) => {
365391
// tail calls will use arguments directly without the need to incref
366392
MReturnCallIndirect({
367393
...data,
368-
func: handle_imm(~is_return=true, func),
369-
args: List.map(handle_imm(~is_return=true), args),
394+
func: handle_imm(~is_tail=true, func),
395+
args: List.map(handle_imm(~is_tail=true), args),
370396
})
371397
| MError(e, args) => MError(e, List.map(handle_imm, args))
372398
| MAllocate(alloc) =>

compiler/src/codegen/mashtree.re

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ and immediate_analyses = {mutable last_usage}
334334

335335
and last_usage =
336336
| Last
337+
| TailCallLast
337338
| NotLast
338339
| Unknown;
339340

compiler/test/suites/gc.re

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,12 @@ describe("garbage collection", ({test, testSkip}) => {
188188
print("4")|},
189189
"4\n",
190190
);
191+
assertRun(
192+
"no_tailcall_double_decref",
193+
{|
194+
let isNaN = x => x != x
195+
print(isNaN(NaN))
196+
|},
197+
"true\n",
198+
);
191199
});

0 commit comments

Comments
 (0)