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.
Background
All arithmetic operators (
+,-,<, etc.) are compiled asCallinstructions. 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 toa - b.Proposed fix
Add typed arithmetic opcodes to
OpCode:The VM handles these inline with no function call overhead:
The compiler emits these when both operand types are statically known to be
Intat the call site.Prerequisite: improved type inference
This optimisation is currently blocked because unannotated parameters like
fn fib(n)are inferred asAny, so the compiler cannot confirm thatnis anIntand cannot safely emitSubInt.Type inference would need to propagate call-site types into function bodies — e.g.
fib(26)impliesn: Inton the first call, and recursive callsfib(n-1)withn-1: Intperpetuate 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/LtIntetc. for the hot paths infib, eliminating ~4 function-call dispatches per non-leaf frame.