You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The compiler and VM follow "Writing a Compiler in Go" by Thorsten Ball, but with some OCaml-specific considerations.
81
+
82
+
### Why the code looks "imperative"
83
+
84
+
While OCaml excels at functional programming, the VM implementation uses mutation in a few places for performance:
85
+
86
+
-**Compiler**: Uses `Dynarray` (dynamic arrays) and `Buffer` for building bytecode, and mutable fields for tracking compilation state
87
+
-**VM**: Uses mutable arrays for the stack, globals, and call frames
88
+
89
+
This is intentional. A purely functional VM with immutable data structures would allocate more and be slower. The mutation is localized and doesn't leak into the rest of the codebase.
90
+
91
+
### Performance
92
+
93
+
Recursive Fibonacci benchmark (`fib(35)`):
94
+
95
+
| Implementation | Go | OCaml |
96
+
|----------------|------|-------|
97
+
| Tree-walking interpreter |~8.8s |~4.9s |
98
+
| Bytecode VM |~2.8s |~2.0s |
99
+
100
+
OCaml's tree-walker is ~1.8x faster than Go's thanks to efficient pattern matching and algebraic data types. The bytecode VM is ~29% faster than Go after optimization:
101
+
102
+
1.**`[@inline]` hints on hot functions** - The biggest win. OCaml's compiler is conservative about inlining; Go is more aggressive. Adding `[@inline]` to `push`, `pop`, `current_frame`, `execute_binary_op`, etc. gave ~16% speedup.
103
+
104
+
2.**`Bytes.unsafe_get` / `Array.unsafe_get`** - Skip bounds checking in the VM loop. Safe because we trust our own compiler's bytecode. ~3% speedup.
105
+
106
+
3.**`Obj.magic` for opcode dispatch** - Convert int to opcode variant without pattern matching through `of_int`. OCaml represents simple variants as integers internally, so this is safe. Preserves exhaustiveness checking in the main dispatch.
107
+
108
+
### Why OCaml's tree-walker is so fast
109
+
110
+
OCaml's interpreter is ~1.8x faster than Go's because:
111
+
- Pattern matching compiles to efficient jump tables
112
+
- Algebraic data types have no runtime type assertions
113
+
- The GC is optimized for functional allocation patterns
114
+
115
+
This means the VM has less relative speedup over the interpreter compared to Go, but in absolute terms both implementations are fast.
78
116
79
117
## TODO
80
118
@@ -99,3 +137,28 @@ Monkey supports closures and first class functions. It would be interesting to a
99
137
-[ ] pattern matching
100
138
101
139
We those in place, it would be a JS-looking language with a ML core 🤔
140
+
141
+
### Type Safety
142
+
143
+
Currently, out-of-bounds array access and missing hash keys return `null` (matching canonical Monkey). This is a footgun in dynamic languages.
0 commit comments