Progress / July 2026
Status Update
The month of July has been productive with a baseline rewrite soon to be ready ⚔️1 for a canary release. This month I have been rewriting the interpreter to be tail-called instead of directed-threaded ⚔️2 which has lead to some intriguing results. Additionally, I have started investigating the viability of directly compiled various functions to a baseline JIT on initial compilation.
Interpreter Rework
For some background, bytecode interpreters typically dispatch a sequence of instructions where a basic implementation would use a switch case:
// The program, a list of encoded instructions
let bytecode: List[Number] = [...];
// A simple decoder for instructions
let decode = fn (encoded: Number): Instruction => Todo();
// A basic handler for dispatching instructions using `match`
let dispatch = fn (offset: Number) {
// Decode the incoming instruction
let instruction = decode(bytecode.get(offset));
// Other languages could use `switch` statements
match (instruction.opcode()) {
Opcode.ADD: Todo(),
Opcode.SUB: Todo(),
*: panic "Unknown Opcode",
}
}
// Iterate over the available encoded instructions
for (offset in List.range(bytecode.size())) dispatch(offset);Previously ⚔️2, Talos implemented the interpreter using direct-threading. Although this solution has great performance, it could be relatively finicky to fine-tune since it relied on all cases being perfectly inlined ⚔️3 to maintain this performance. During the current rewrite, I decided to instead opt for a tail-call interpreter since this should come with similar benefits to the direct-thread approach whilst also simplifying compilation heuristics.
Note:
The match statement is currently unimplemented in v0.1.2 and will be available when v0.2.0 is released.
Baseline JIT Compilation
Many modern dynamic programming languages implement some form a JIT within their compilation/optimization process. For Talos, I wanted to make this JIT opt-in through a specialized attribute:
#[Internal.Optimize]
let foo = fn => 42;Since this is only a baseline JIT, it should be utilized sparingly to improve performance. This is because the current downside to this implementation is that the outputs require storage space with simple functions requiring a minimum of ~256 bytes ⚔️4. Statistics of jitted functions is availble through the hidden --dump-assembly flag.
Upcoming Work
After work on the baseline JIT has been completed, the next goal is to prepare a canary release for Talos from the work that has been implemented thus far. This canary release will also include the foundations for the match statement.
Footnotes
⚔️ 1 — Currently GitHub CI integration needs to be revised for releases.
⚔️ 2 — Only up to v0.1.2 was a direct-thread interpreter used.
⚔️ 3 — Direct-thread interpreters work best with small functions as compiler heuristics may remove the indirect-jump if a function is too large effectively reversing the performance gain.
⚔️ 4 — Functions that return constants/strings resolve to much smaller values (eg: around 16-36 bytes), whilst method calls can be much larger (around 200+ bytes).