summary_nul-strings.md 2.1 KB

NUL-terminated string model (self-hosting unblocker)

Tag v3-nul-strings. Main suite 111/111.

Why

The compiler's own sources (and gm2) assume NUL-terminated strings: helpers like SymTab.Assign/Equal scan WHILE s[i] # CHR(0). V3 stored [count, bytes] with no terminator and trapped when indexing past the count, so those scans aborted. Minimal repro:

WHILE (i < HIGH(d)) AND (s[i] # CHR(0)) DO ... END;   (* s[n] out of bounds *)

Change: every CHAR array reserves a terminator slot

  • Model: a CHAR array of count n has n data bytes plus one zero byte at index n; indexed access is allowed for 0 .. n (the terminator is readable/writable). LEN/HIGH still report n/n-1.
  • Emission: ArrBodyItems emits z (n+1) for CHAR arrays; string literals get a trailing b 0; ArrObjSize/HeapSize add 1; a heap/local array's terminator is zeroed in InitHeap.
  • Bounds: Design's index handlers use ArrayHi+1 (fixed) or OpenHiChar = count (open) for CHAR-element arrays (SymTab.IsCharArray).
  • Shim: m2write/m2fputs/m2cstr derive length by scanning (strnlen, bounded by the capacity); m2arg copies + NUL-terminates and keeps the capacity in desc[0] (it no longer overwrites it with the length — that destroyed fixed-buffer capacity).
  • stdlib Strings: rewritten NUL-based (Length scans; Assign/ Concat/Delete/Append copy by scanned length bounded by HIGH(dst) and NUL-terminate).

Result

  • Suite 111/111; the small NEW + inline-array-field repro now runs.
  • The self-compiled compiler now runs: it opens its input (self- hosted FileIO + shim) and starts parsing.
  • It then segfaults in the scanner's Reset (Parse → Reset) — the next runtime bug to chase.

Note

desc[0] is now a capacity for arrays (and the literal length for string literals); string length is derived by scanning. m2setlen is effectively unused.

Files

runtime/syslib/shim.c, stdlib/strings.mod, compiler/src/{M2.atg,SymTab.def,SymTab.mod,QbeGen.def,QbeGen.mod,M2.lst}.