diff options
| author | 2026-10-12 08:46:23 +0330 | |
|---|---|---|
| committer | 2026-10-12 08:46:23 +0330 | |
| commit | ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch) | |
| tree | 70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /docs/history/29-strings/notes.md | |
a
Diffstat (limited to 'docs/history/29-strings/notes.md')
| -rw-r--r-- | docs/history/29-strings/notes.md | 21 |
1 files changed, 21 insertions, 0 deletions
diff --git a/docs/history/29-strings/notes.md b/docs/history/29-strings/notes.md new file mode 100644 index 0000000..9d96087 --- /dev/null +++ b/docs/history/29-strings/notes.md @@ -0,0 +1,21 @@ +# 29 — byte strings + +word strings stay one character per word, 0 terminated. `"hi"` did not change, and neither did ops 1 to 5. a new literal, `b"..."`, is a packed string on the byte addressing from stage 28. the `b` has to sit against the quote; `b "hi"` is still a name plus a word string. + +the layout is the same for a literal in the code and an object on the heap. word 0 is the length, not counting the trailing 0. the bytes start at the next word, eight per word, little-endian, and a 0 byte follows the last character. when the length is a multiple of 8, including 0, that 0 occupies a word of its own. a literal is a jump over the data, the length as a raw word, the packed words, then a literal of the length word's address. `snew(n)` is `new(1 + ((n + 8) >> 3))`, which is one length word plus enough words for the bytes and the trailing 0. + +the runtime names are `slen`, `sbyte`, `snew`, `scat`, `sslice`, `scmp`, `seqs`, `slt`, `sle`, `bputs`, `ston` and `ntos`. `sbyte` returns 0 outside the length. `sslice` takes an exclusive end and clamps. `scmp` returns -1, 0 or 1 over unsigned bytes. `ntos` of the minimum word copies the word-string `"-9223372036854775808"` into a byte string. heap results are fresh owned objects, so the analyzer requires a `let`. they are not shared yet. + +the resident compiler and `zc0` both track which value is a byte string. on the resident compiler that is bit 6 of the local's tag. `len` of one calls `slen`. `s[i]` calls `sbyte` and is not a place to store. `s[i:j]` calls `sslice`. `+` and the six comparisons require a byte string on both sides; one side is an error. `puts` of one byte string calls `bputs`. `++` and `+=` clear the bit. + +ops 10 to 13 are the packed versions of open, read, write and getenv. the pointer they take is `baddr(s + 1)`, the payload, not the object. the hosted core, the linux microcore port and the virt-board port implement them. the stub port still returns -1. the RAM file store on the boards is unchanged, so `tests/bstr_io.zc` stays hosted. `lib/std.zc` keeps `atoi`, `itoa`, `strcat`, `starts`, `env` and `writefile`, and adds `batoi`, `bitoa`, `bstarts`, `benv` and `bwritefile`. + +the 1m map stores type records in the last 16384 words of the heap, and the type list is the first words of that range. holding those words as a buddy block does not survive `types_init`, which writes the list over the header, and the next large `new` was then handed the type records. the self-host died at `let pow2[64]` with `program too big` because `tword`'s size word had become the source text `= 0;`. the compiler now cuts the allocator off at `R_TY` after `heap_init` and leaves it cut. the sweep stops at the same line, so it does not walk a header that is really the type list. `tests/buddy.zc` still prints `131072 262144 1 0`: the bottom chunk is untouched, and the 20000 small blocks fragment it the same way. the small map keeps its type area above the heap, so the cut does not apply there. + +`tests/strings.zc` prints `hi! 3 3 104 i! 1 1 1 -42 42 -42 11 1 -9223372036854775808 0 1 3 10 1 1 1 W ab`. `examples/strings.zc` prints `hello, zc` and `9 42`. the fixpoint image is 100011 words, 800088 bytes. the stage-0 image of the same sources is 96068 words. the run-only reservation is 14336 words in a 36864-word machine; `tests/bytes.zc` is the largest of those images at 13191 words, and hello is 12568. the small compiler's stage-0 image is 96067 words against a reservation of 97280, in a 180224-word machine. + +`tests/bench_strings.zc` is a run-only image. one repetition scans the 36-character literal 200 times, concatenates a `!`, and slices `[1:8]`. the checksum is 674542. `bench_strings.sh` times that without the resident compiler. on this host: interpreter 5.499 ms, microcore 5.22 ms, arm64 jit under qemu-user 3.171 ms, llvm 0.235 ms. the llvm slope is the compiled loop; the first repetition pays to compile it. + +`test.sh` after this stage: 350 passed, 0 failed. the twenty-three new checks are the string line, `examples/strings.zc`, and `tests/bstr_io.zc` on the interpreter, the microcore, the arm64 jit and the llvm backend; the string line and the example on the small microcore and on `core.c` at that size; the run-only image of both; both virt boards running `tests/strings.zc` and compiling it; and one llvm run with `ZYHOT=1`. `tests/bstr_io.zc` is hosted only. the fixpoint is the same 100011 words on the interpreter, the microcore, the jit and llvm. + +what is still open: heap strings are uniquely owned, not shared, so a structure cannot point at one substring from two places without copying. there is no mutation through `s[i]`. `ston` stops at the first non-digit and does not say so. comparison is unsigned bytes, not a character set. part 3 is the collector that can share these objects. |
