summaryrefslogtreecommitdiff
path: root/notes
diff options
context:
space:
mode:
authorTaylan Kammer <taylan.kammer@gmail.com>2026-06-26 00:08:22 +0200
committerTaylan Kammer <taylan.kammer@gmail.com>2026-06-26 00:22:11 +0200
commit9151c8bb6f401bf9939765b0daa01b680a25a473 (patch)
tree7086440e0fe5ebd3873c9abbdfb06778b949c567 /notes
parentaaaa9d9eb290991b9b6d732c375472c891ddaf29 (diff)
fixup
Diffstat (limited to 'notes')
-rw-r--r--notes/260625-optimize.md51
1 files changed, 33 insertions, 18 deletions
diff --git a/notes/260625-optimize.md b/notes/260625-optimize.md
index d43ddc6..cdb9644 100644
--- a/notes/260625-optimize.md
+++ b/notes/260625-optimize.md
@@ -2,6 +2,8 @@
<!--TOC-->
+_2026 June_
+
I might be deluded, but I think it should be possible to make a tree
walking AST interpreter pretty much as efficient as a bytecode VM, if
we're smart enough about the AST representation and a well-selected
@@ -9,7 +11,7 @@ set of optimizations performed on it.
Here's what I came up with so far.
-## Abandon linked lists (cons cells / pairs)
+## No linked lists
Using traditional cons cells to represent code forms as linked lists
means you're paying a memory overhead of 50% to store lists. That's
@@ -26,14 +28,19 @@ AST walker will be going through the list one way or another, so it
doesn't really matter; having the elements always start at zero is
simpler.)
-## Tight packing of short arrays
+## Tight packing
To further ensure minimal memory footprint of AST nodes, we make sure
that they're allocated tightly in blocks, without any padding between
them. See `src/zisp/gc/ListPool.zig` for how I've implemented this.
-Cache locality and memory density should now be perfect.
+Cache locality and memory density should now be perfect:
+
+[ListPool.zig](https://git.tkammer.de/zisp/tree/src/zisp/gc/ListPool.zig)
+
+(I like to refactor code and move around source files a lot, so if
+this link becomes a 404 and I forget to update, I'm sorry.)
-## Help the prefetcher (ABANDONED)
+## Help the prefetcher?
Memory prefetching refers to a modern CPU feature in which the CPU
notices that you're accessing memory addresses sequentially, and
@@ -53,7 +60,7 @@ call that's not inlined is encountered.
So yeah, scrap that. Hopefully it makes no difference.
-## Quoted data directly in the AST
+## Quoted data in AST
This may just be a stupid micro-optimization, because data that could
be confused for code to evaluate (lists and identifiers) but is meant
@@ -132,14 +139,14 @@ I think I like that. There could still be a way for other modules to
bindings of that module into local bindings within the importing
module, thus making the constants eligible for folding anyway!
-## Lexical closure references by flat index
+## Lexicals by flat index
Now we're getting to the hot stuff.
Zisp won't allow capturing mutable variables by reference in closures;
you'll need to copy the variable's current value into a non-mutable
variable if you want to capture it. (There's precedent for this in
-other languages, like Java; nothing newly invented here.)
+other languages, like Java; nothing new invented here.)
You can still simply capture a reference to a mutable heap object,
like a one-element array aka "box" object type, so you're not really
@@ -229,7 +236,7 @@ to replace "local variable reference" objects with their values.
Those "local variable reference" objects can also just be flat array
indexes, thanks to other optimizations...
-## Eager stack frame allocation
+## Eager stack frame alloc
This may just be a micro-optimization; I've not thought about it that
much yet. But basically, the idea is to walk the body of a lambda,
@@ -253,7 +260,7 @@ Oh, and function parameters are of course just the first few entries
in this (conceptual) locals array (which may just be a stack pointer)
and get populated at the call-site with the passed arguments.
-## Lexical and local reference representation
+## Lexical / local references
Above, I've said that lexical and local variable references literally
become `lexicals[i]` and `locals[i]`, but that was a bit of a lie.
@@ -280,7 +287,7 @@ Realistically, you don't need 48 bits for a local or lexical variable
index. Even 16 bits would be more than enough. So, there's a bit of
waste here compared to what we could do with bytecode.
-## VM opcode in expression head
+## AST walker opcodes
I've already explained this above as part of the lexical references
optimization:
@@ -309,14 +316,22 @@ There's no question that bytecode would be more compact than this AST
despite all the optimizations. Wasting 56 bits on an unused opcode
payload here, 32 bits on an overly large locals index there, and the
generally pointer-rich structure of the tree... These will add up,
-and bytecode could fix all that. But the beauty of this strategy is
-that you literally just work directly on the data structure returned
-by the parser, doing only in-place mutations and a small number of
-auxiliary allocations. Even after optimizations, the AST should be
-almost trivial to serialize in a pretty-printed format to get either
-some nice insights into what the optimizer did, or, if allocation of
-debug metadata was enabled, it may even be possible to turn it back
-into original source form.
+and bytecode could fix all that. Further, the "almost everything is
+NaN-packed" strategy means we need frequent shifting and bit-masking
+to get some of the actual "opcodes" like lexical and local variable
+references. I'm not sure how impactful all this is likely to be on
+performance, but it seems obvious that there should be a measurable
+difference. Maybe up to 2-3x slower execution? I'd actually be
+content with that, because...
+
+The beauty of this strategy is that you directly work on the AST data
+structure returned by the parser, doing only in-place mutations and a
+small number of auxiliary allocations. Even after optimizations, the
+AST should be almost trivial to serialize in a pretty-printed format
+to get either some nice insights into what the optimizer did, or, if
+allocation of debug metadata was enabled, it may even be possible to
+turn it back into original source form sans comments, whitespace, and
+a few other minor one-way transforms on the way from text to AST.
So, the low latency and the REPL/debug experience will be impeccable.
Not to mention the incredible simplicity of it all: We don't need a