Skip to content

Commit 5017c5e

Browse files
committed
Add inline_stack to Compiler to track functions being inlined
Add contains_call_to_any helper to detect calls to any function in a list Check for mutual recursion before inlining: if the function body calls any function in the inline_stack, skip inlining to avoid infinite expansion Add test_mutual_recursion_tail_call test
1 parent 2ffe8f1 commit 5017c5e

File tree

2 files changed

+48
-4
lines changed

2 files changed

+48
-4
lines changed

src/compiler.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pub struct Compiler {
6363
self_name: Option<String>,
6464
/// Number of parameters of the current function (for loop optimization)
6565
self_arity: usize,
66+
/// Stack of functions currently being inlined (for mutual recursion detection)
67+
inline_stack: Vec<String>,
6668
}
6769

6870
/// Check if an expression is pure (no side effects)
@@ -207,6 +209,30 @@ fn contains_call_to(expr: &Value, name: &str) -> bool {
207209
false
208210
}
209211

212+
/// Check if expression contains a call to any function in the given list (mutual recursion check)
213+
fn contains_call_to_any(expr: &Value, names: &[String]) -> bool {
214+
if names.is_empty() {
215+
return false;
216+
}
217+
if let Some(items) = expr.as_list() {
218+
if !items.is_empty() {
219+
// Check if this is a call to any of the functions
220+
if let Some(first) = items[0].as_symbol() {
221+
if names.iter().any(|n| n == first) {
222+
return true;
223+
}
224+
}
225+
// Recursively check all sub-expressions
226+
for item in items.iter() {
227+
if contains_call_to_any(item, names) {
228+
return true;
229+
}
230+
}
231+
}
232+
}
233+
false
234+
}
235+
210236
/// Check if a function body is small enough to inline.
211237
/// We inline if the body is a single expression that is:
212238
/// - A simple call to another function (thin wrapper)
@@ -443,6 +469,7 @@ impl Compiler {
443469
inline_candidates: InlineCandidates::new(),
444470
self_name: None,
445471
self_arity: 0,
472+
inline_stack: Vec::new(),
446473
}
447474
}
448475

@@ -1094,11 +1121,18 @@ impl Compiler {
10941121
if args.len() == fn_def.params.len() {
10951122
// Check: function is small enough to inline
10961123
if is_small_body(&fn_def.body) {
1097-
// Check: function doesn't call itself (non-recursive)
1124+
// Check: function doesn't call itself (direct recursion)
10981125
if !contains_call_to(&fn_def.body, op) {
1099-
// Inline: substitute parameters with arguments and compile
1100-
let inlined = substitute(&fn_def.body, &fn_def.params, args);
1101-
return self.compile_expr(&inlined, dest, tail_pos);
1126+
// Check: function doesn't call any function in the inline stack (mutual recursion)
1127+
if !contains_call_to_any(&fn_def.body, &self.inline_stack) {
1128+
// Inline: substitute parameters with arguments and compile
1129+
let inlined = substitute(&fn_def.body, &fn_def.params, args);
1130+
// Track that we're inlining this function (for mutual recursion detection)
1131+
self.inline_stack.push(op.to_string());
1132+
let result = self.compile_expr(&inlined, dest, tail_pos);
1133+
self.inline_stack.pop();
1134+
return result;
1135+
}
11021136
}
11031137
}
11041138
}

src/vm.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,16 @@ mod tests {
16871687
assert_eq!(result, Value::Int(50005000));
16881688
}
16891689

1690+
#[test]
1691+
fn test_mutual_recursion_tail_call() {
1692+
// Mutual recursion with tail calls - should not stack overflow
1693+
let result = vm_eval("(do
1694+
(def is-even (fn (n) (if (= n 0) true (is-odd (- n 1)))))
1695+
(def is-odd (fn (n) (if (= n 0) false (is-even (- n 1)))))
1696+
(is-even 100))").unwrap();
1697+
assert_eq!(result, Value::Bool(true));
1698+
}
1699+
16901700
#[test]
16911701
fn test_specialized_car_cdr() {
16921702
// Test specialized car/cdr opcodes with cons cells

0 commit comments

Comments
 (0)