-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Background
All arithmetic operators (+, -, <, etc.) are compiled as Call instructions. Even after issue #107 (single-candidate binding collapse), a call to -(n, 1) still goes through the full call machinery: push callee, push args, Call(2) → resolve_callee → dispatch_call_with_memo → native closure → pop args, push result.
For integer-only hot paths like fib, this is significant overhead for what amounts to a - b.
Proposed fix
Add typed arithmetic opcodes to OpCode:
pub enum OpCode {
// ... existing ...
AddInt,
SubInt,
MulInt,
LtInt,
LtEqInt,
GtInt,
GtEqInt,
// etc.
}The VM handles these inline with no function call overhead:
OpCode::SubInt => {
let b = self.stack.pop().expect("stack underflow");
let a = self.stack.pop().expect("stack underflow");
match (a, b) {
(Value::Int(x), Value::Int(y)) => self.stack.push(Value::Int(x - y)),
(a, b) => return Err(VmError::new(
format!("SubInt: expected (Int, Int), got ({}, {})", a.static_type(), b.static_type()),
span,
)),
}
}The compiler emits these when both operand types are statically known to be Int at the call site.
Prerequisite: improved type inference
This optimisation is currently blocked because unannotated parameters like fn fib(n) are inferred as Any, so the compiler cannot confirm that n is an Int and cannot safely emit SubInt.
Type inference would need to propagate call-site types into function bodies — e.g. fib(26) implies n: Int on the first call, and recursive calls fib(n-1) with n-1: Int perpetuate that. This is either inter-procedural type inference or requires explicit type annotations from the user.
Once type inference is improved, the compiler can emit SubInt / LtInt etc. for the hot paths in fib, eliminating ~4 function-call dispatches per non-leaf frame.