aboutsummaryrefslogtreecommitdiff
path: root/docs/history/15-o3/notes.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/history/15-o3/notes.md')
-rw-r--r--docs/history/15-o3/notes.md49
1 files changed, 49 insertions, 0 deletions
diff --git a/docs/history/15-o3/notes.md b/docs/history/15-o3/notes.md
new file mode 100644
index 0000000..c18d50b
--- /dev/null
+++ b/docs/history/15-o3/notes.md
@@ -0,0 +1,49 @@
+# o3, growable arrays, c-style return types
+
+code/ next to this file is the tree at the end of this stage.
+
+## message 15
+
+> write some really cool examples for this language
+
+> no, wdym building a fresh vm?? just pocs 😭 we already have a compiler and everything
+
+the examples (mandel, bf, dump, tree, lang, staged, quine, life). "building a fresh vm" in my progress note only meant compiling core.c into a binary to run them. no snapshot of its own: they're all in this stage's code.
+
+## message 16
+
+> add opt keywords, for example `optim` (or o3, i prefer o3 as the keyword) will cause any vars in a scope to not assign heap type metadata, compiles with opt level o3 and is immediately compiled down to native, and when used with variables they must be always typed and it reduces the perf cost for accessing or modifying them, etc etc. add a `fixed` keyword which says that an array has a fixed size and cannot be resized, etc
+
+answers: functions, blocks and single vars; a real optimizing tier; give up type stamps, zero-filling, the address of o3 vars, stack and frame checks; c-like return types instead of `->`; growable arrays too.
+
+## working notes
+
+order of work: return syntax first (it touches every test), then arrays, then o3 in the compiler, then the core.
+
+**return types.** `topitem` reads a type and a name, then decides between function and global by whether `(` follows. `fn name(` stays the untyped form. generics moved to a `<T>` prefix, because `T first<T>(...)` would need the type parameter before it's declared. fn types became `int(int, int)`: `ptype()` takes a postfix parameter list after any type. the prescan looks for `NAME (` ... `)` followed by `{` or `;`, carrying a pending `.u`.
+
+**arrays.** a `[]T` is a 4-word handle `[len, cap, items, element size]` that owns `items`. growing copies into a new items object and moves the children's owner fields over. `fixed` is a flag on the same type record (TY_DYN, `t[3]`), so `fixed []T` and `[]T` are different hash-consed types and compat allows fixed <- growable but not the reverse.
+
+**o3, compiler side.** the region is the code range plus the frame slots of its typed scalar locals. a function region starts after the prologue's parameter stores, at a `jmp` to the next word, so the cpu enters it through dispatch. it ends where the epilogue starts, so `return` is a jump out. block regions are queued and installed when the function ends, since a `return` inside the block is still a hole until the epilogue exists. ltag bit 16 marks o3 variables (the tag itself is the low 4 bits).
+
+first bug: `t = t + a[j]` with the bounds check in the middle. the address of `t` was pushed before the right-hand side, crossed the check's branch on the memory stack, and the store in the next block went to the frame instead of the register. fixed in the compiler: assignment to an o3 variable in o3 code drops the address, evaluates the value, and only then pushes the address. compound assignment the same way. the lifter also refuses any block that would leave a register variable's address on the stack, so the same mistake can't come back silently.
+
+**o3, core side.** blocks start at the region's start, at literal jump targets, after a `jz` and after a call. the leader pass walks from the start, so inline strings (jumped over) never get lifted. per block, the lifter runs the vm code symbolically: a value table keyed by stack offset from the entry top, lazily loading entry words (SLD), and `fp + k` is tracked so loads and stores to promoted slots become variable reads and writes. what's left on the stack at the end, minus words still holding what they held at entry, gets stored. var 0 is the frame pointer, read once at entry. it never changes inside a region, since a store to the frame pointer word ends the block.
+
+arm64: vars in w0..w7, temps from w8..w14 and w26..w28 (x26..x28 now saved by the enter stub), x15..x17 scratch, constants have no register. the end of a block does the stack stores, moves the condition to w17, adjusts x20, and resolves the var moves as a parallel move (a cycle parks one value in w16). exits spill the vars to the frame and go through x_disp, so the stack checks still run between regions and normal code. stores check the coverage byte and call the hook with x0..x15 saved on the native stack.
+
+llvm: vars as allocas, one basic block per region block, a switch on the entry index, O3 pipeline. most of the speed comes from mem2reg and llvm's loop passes once there's no stack traffic.
+
+second bug, not o3's: the region's coverage byte made a stray store visible. a store of 10 into address 16 of the first function, every time a growable array was freed. `release` set the container's flags to 0 (free) before releasing its children, and a child whose buddy was the container then merged into it and unlinked it from a free list it wasn't on, writing through its data words. the fix is a third state (2, going) during release. the bug had been there since the buddy allocator; lists just rarely had a child adjacent to its parent.
+
+**literal specialization.** with `lfa(k)` as an enhancement for single o3 variables in normal code, the plain jit emitted `li k; push; pop; li ZFP; ld with range check; add`, no better than the 4 vm ops. so the jit now looks for literals feeding an enhancement's last inputs, re-runs the optimizer with them as constants, and folds constant addresses and small addends into instructions. `x / 7` in ordinary code got the multiply-high sequence from this too.
+
+**numbers** (bench.sh, ms per run, start-up removed by taking the slope between 1 and n runs):
+
+```
+interp collatz 309.63 -> 209.87 sieve 45.58 -> 15.95 digits 191.32 -> 92.29 fib 6.70 -> 5.50
+llvm collatz 27.69 -> 3.48 sieve 17.32 -> 0.17 digits 24.72 -> 1.60 fib 1.82 -> 0.84
+arm64 (qemu) collatz 327.33 -> 16.78 sieve 90.23 -> 3.85 digits 147.67 -> 3.46 fib 22.62 -> 5.73
+```
+
+left for later: direct calls between regions, picking promoted vars by use count, caching a fixed array's length and items, cross-block optimization in the core's own optimizer, o3 inside images.