ReefVM/tests/tail-call.test.ts
2025-10-05 19:07:28 -07:00

230 lines
6.7 KiB
TypeScript

import { test, expect } from "bun:test"
import { VM } from "#vm"
import { OpCode } from "#opcode"
import { toValue } from "#value"
test("TAIL_CALL - basic tail recursive countdown", async () => {
// Tail recursive function that counts down to 0
// function countdown(n) {
// if (n === 0) return "done"
// return countdown(n - 1) // tail call
// }
const vm = new VM({
instructions: [
// 0: Setup - create function and call it
{ op: OpCode.MAKE_FUNCTION, operand: 0 },
{ op: OpCode.STORE, operand: 'countdown' },
{ op: OpCode.LOAD, operand: 'countdown' },
{ op: OpCode.PUSH, operand: 1 }, // start with 5
{ op: OpCode.CALL, operand: 1 },
{ op: OpCode.HALT },
// 6: Function body
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 2 }, // 0
{ op: OpCode.EQ },
{ op: OpCode.JUMP_IF_FALSE, operand: 2 }, // if n !== 0, jump to recursive case
{ op: OpCode.PUSH, operand: 3 }, // return "done"
{ op: OpCode.RETURN },
// 12: Recursive case (tail call)
{ op: OpCode.LOAD, operand: 'countdown' },
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 4 }, // 1
{ op: OpCode.SUB },
{ op: OpCode.TAIL_CALL, operand: 1 } // tail call with n-1
],
constants: [
{
type: 'function_def',
params: ['n'],
defaults: {},
body: 6,
variadic: false,
kwargs: false
},
toValue(5),
toValue(0),
toValue('done'),
toValue(1)
]
})
const result = await vm.run()
expect(result).toEqual({ type: 'string', value: 'done' })
})
test("TAIL_CALL - tail recursive sum with accumulator", async () => {
// function sum(n, acc = 0) {
// if (n === 0) return acc
// return sum(n - 1, acc + n) // tail call
// }
const vm = new VM({
instructions: [
// 0: Setup
{ op: OpCode.MAKE_FUNCTION, operand: 0 },
{ op: OpCode.STORE, operand: 'sum' },
{ op: OpCode.LOAD, operand: 'sum' },
{ op: OpCode.PUSH, operand: 1 }, // n = 10
{ op: OpCode.PUSH, operand: 2 }, // acc = 0
{ op: OpCode.CALL, operand: 2 },
{ op: OpCode.HALT },
// 7: Function body
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 2 }, // 0
{ op: OpCode.EQ },
{ op: OpCode.JUMP_IF_FALSE, operand: 2 },
{ op: OpCode.LOAD, operand: 'acc' },
{ op: OpCode.RETURN },
// 13: Recursive case
{ op: OpCode.LOAD, operand: 'sum' },
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 3 }, // 1
{ op: OpCode.SUB },
{ op: OpCode.LOAD, operand: 'acc' },
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.ADD },
{ op: OpCode.TAIL_CALL, operand: 2 }
],
constants: [
{
type: 'function_def',
params: ['n', 'acc'],
defaults: {},
body: 7,
variadic: false,
kwargs: false
},
toValue(10),
toValue(0),
toValue(1)
]
})
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 55 }) // sum of 1..10
})
test("TAIL_CALL - doesn't overflow stack with deep recursion", async () => {
// This would overflow the stack with regular CALL
// but should work fine with TAIL_CALL
const vm = new VM({
instructions: [
// 0: Setup
{ op: OpCode.MAKE_FUNCTION, operand: 0 },
{ op: OpCode.STORE, operand: 'deep' },
{ op: OpCode.LOAD, operand: 'deep' },
{ op: OpCode.PUSH, operand: 1 }, // 10000 iterations
{ op: OpCode.CALL, operand: 1 },
{ op: OpCode.HALT },
// 6: Function body
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 2 }, // 0
{ op: OpCode.LTE },
{ op: OpCode.JUMP_IF_FALSE, operand: 2 },
{ op: OpCode.PUSH, operand: 3 }, // "success"
{ op: OpCode.RETURN },
// 12: Tail recursive case
{ op: OpCode.LOAD, operand: 'deep' },
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 4 }, // 1
{ op: OpCode.SUB },
{ op: OpCode.TAIL_CALL, operand: 1 }
],
constants: [
{
type: 'function_def',
params: ['n'],
defaults: {},
body: 6,
variadic: false,
kwargs: false
},
toValue(10000), // This would overflow with regular recursion
toValue(0),
toValue('success'),
toValue(1)
]
})
const result = await vm.run()
expect(result).toEqual({ type: 'string', value: 'success' })
})
test("TAIL_CALL - tail call to different function", async () => {
// TAIL_CALL can call a different function (mutual recursion)
// function even(n) { return n === 0 ? true : odd(n - 1) }
// function odd(n) { return n === 0 ? false : even(n - 1) }
const vm = new VM({
instructions: [
// 0: Setup both functions
{ op: OpCode.MAKE_FUNCTION, operand: 0 }, // even
{ op: OpCode.STORE, operand: 'even' },
{ op: OpCode.MAKE_FUNCTION, operand: 1 }, // odd
{ op: OpCode.STORE, operand: 'odd' },
{ op: OpCode.LOAD, operand: 'even' },
{ op: OpCode.PUSH, operand: 2 }, // test with 7
{ op: OpCode.CALL, operand: 1 },
{ op: OpCode.HALT },
// 8: even function body
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 3 }, // 0
{ op: OpCode.EQ },
{ op: OpCode.JUMP_IF_FALSE, operand: 2 },
{ op: OpCode.PUSH, operand: 4 }, // true
{ op: OpCode.RETURN },
// Tail call to odd
{ op: OpCode.LOAD, operand: 'odd' },
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 5 }, // 1
{ op: OpCode.SUB },
{ op: OpCode.TAIL_CALL, operand: 1 },
// 19: odd function body
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 3 }, // 0
{ op: OpCode.EQ },
{ op: OpCode.JUMP_IF_FALSE, operand: 2 },
{ op: OpCode.PUSH, operand: 6 }, // false
{ op: OpCode.RETURN },
// Tail call to even
{ op: OpCode.LOAD, operand: 'even' },
{ op: OpCode.LOAD, operand: 'n' },
{ op: OpCode.PUSH, operand: 5 }, // 1
{ op: OpCode.SUB },
{ op: OpCode.TAIL_CALL, operand: 1 }
],
constants: [
{
type: 'function_def',
params: ['n'],
defaults: {},
body: 8, // even body
variadic: false,
kwargs: false
},
{
type: 'function_def',
params: ['n'],
defaults: {},
body: 19, // odd body
variadic: false,
kwargs: false
},
toValue(7),
toValue(0),
toValue(true),
toValue(1),
toValue(false)
]
})
const result = await vm.run()
expect(result).toEqual({ type: 'boolean', value: false }) // 7 is odd
})