Add function calls to vm_run_at. When we hit call $name:
1. Find the function in the table
2. Pop arguments from the stack (in reverse -- last arg was pushed last)
3. Save the current state: PC, locals, block depth, stack pointer
4. Set up new locals with the parameter values
5. Run the function body
6. Restore state
7. Push the return value
We need a call stack:
var vm_call_pc: [32]usize = undefined;
var vm_call_sp: [32]usize = undefined;
var vm_call_ln: [32][64][]const u8 = undefined;
var vm_call_lv: [32][64]i64 = undefined;
var vm_call_lc: [32]usize = undefined;
var vm_call_bd: [32]usize = undefined;
var vm_call_depth: usize = 0;
Add to vm_run_at, before the vm_exec_line fallthrough:
if (starts_with(line, "call $")) {
const fname: []const u8 = parse_name(line, 0);
// find function
var fi: usize = 0;
while (fi < vm_fn_count) {
if (streq(vm_fn_names[fi], fname)) { break; }
fi += 1;
}
if (fi < vm_fn_count) {
// pop args
var args: [8]i64 = undefined;
var ai: usize = vm_fn_param_count[fi];
while (ai > 0) {
ai -= 1;
args[ai] = vm_pop();
}
// save state
const cd: usize = vm_call_depth;
vm_call_pc[cd] = vm_pc + 1;
vm_call_sp[cd] = vm_sp;
vm_call_lc[cd] = vm_local_count;
vm_call_bd[cd] = vm_block_depth;
for (0..vm_local_count) |li| {
vm_call_ln[cd][li] = vm_local_names[li];
vm_call_lv[cd][li] = vm_local_values[li];
}
vm_call_depth += 1;
// set up params as locals
vm_local_count = 0;
for (0..vm_fn_param_count[fi]) |pi| {
vm_set_local(vm_fn_params[fi][pi], args[pi]);
}
// run function body
const result: i64 = vm_run_at(vm_fn_start[fi], vm_fn_end[fi]);
// restore state
vm_call_depth -= 1;
vm_sp = vm_call_sp[cd];
vm_local_count = vm_call_lc[cd];
vm_block_depth = vm_call_bd[cd];
for (0..vm_local_count) |li| {
vm_local_names[li] = vm_call_ln[cd][li];
vm_local_values[li] = vm_call_lv[cd][li];
}
// push result
vm_push(result);
vm_pc = vm_call_pc[cd];
continue;
}
vm_pc += 1;
continue;
}
This is the same save/restore pattern we used in the interpreter's call_fn. Save everything, set up new locals, run the body, restore, push the result. The VM's function call is structurally identical to the interpreter's -- different data, same idea.