1. Every instruction results in functional call overhead, which is something that tail calls (with protect_none) avoid.
2. Because you are using closures the next instruction has to be heap allocated. I don’t know what closures look like in Go, but there will involve some indirection.
3. Every iteration of the loop requires a bounds check on ip.
Now, none of these may matter for SQL because as the blog post said, the instructions are quite large. And it already shows huge improvements over the AST interpreter. I am just curious how the switch would benchmark.
On an unrelated note. Using closures to write an interpreter brought to mind this talk https://m.youtube.com/watch?v=V8dnIw3amLA&list=PLFTr8ChfQg9t...
Each instruction is an heap allocated closure, but it's allocated at “compile” time (when you generate the instruction from the AST), not at runtime (every time you run the instruction): by then they're (preallocated) free standing functions that receive the VM.
Every iteration of the loop in a big switch bytecode interpreter also requires a bounds checks.
I was listening to an episode of Latent Space and they were interviewing Bret Taylor of Google Maps fame.
He made an interesting point, that you could just ask the AI to code in Rust instead of say Python and you would get the memory safety benefits by just compiling.