diff options
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/rt.zc | 218 |
1 files changed, 109 insertions, 109 deletions
diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc index a54d88c..ef5a5c6 100644 --- a/src/runtime/rt.zc +++ b/src/runtime/rt.zc @@ -1,33 +1,33 @@ namespace rt; include run; -//: ============================================================================ -//: rt.zc: the runtime half of the genome. memory map, i/o, arithmetic helpers, -//: the heap (size-class free lists) and the mark-sweep collector. -//: ============================================================================ +// ============================================================================ +// rt.zc: the runtime half of the genome. memory map, i/o, arithmetic helpers, +// the heap (size-class free lists) and the mark-sweep collector. +// ============================================================================ -//: how the runtime is laid out, for a reader coming in cold: -//: -//: memory map generated constants name the regions used by a build. the -//: compiler and runtime use the same names and values. -//: i/o, strings thin wrappers over the cpu's i/o opcodes, plus the two -//: string forms (char* strings and packed byte strings). -//: heap a buddy allocator with a 5 word header on every block. -//: new/free/recycle are built on it. every heap object has at -//: most one owner (the container that holds it), and freeing -//: an owner frees what it owns. -//: growable arrays T[?] handles, built on the heap. -//: mark-sweep a conservative collector. it is the safety net behind -//: free: anything the compiler's ownership analysis could not -//: free gets reclaimed here. -//: memory debug optional checks (use after free, double free, leaks). -//: syscalls file and environment access through one gate word. -//: -//: everything is a 64-bit word, and an address is a word index, not a byte -//: index. the cpu is a stack machine, so "asm { a @ . }" pushes a, fetches the -//: word at that address with @, then outputs it with . (see the vm reference). -//: -//: ---- memory map (words) ---- +// how the runtime is laid out, for a reader coming in cold: +// +// memory map generated constants name the regions used by a build. the +// compiler and runtime use the same names and values. +// i/o, strings thin wrappers over the cpu's i/o opcodes, plus the two +// string forms (char* strings and packed byte strings). +// heap a buddy allocator with a 5 word header on every block. +// new/free/recycle are built on it. every heap object has at +// most one owner (the container that holds it), and freeing +// an owner frees what it owns. +// growable arrays T[?] handles, built on the heap. +// mark-sweep a conservative collector. it is the safety net behind +// free: anything the compiler's ownership analysis could not +// free gets reclaimed here. +// memory debug optional checks (use after free, double free, leaks). +// syscalls file and environment access through one gate word. +// +// everything is a 64-bit word, and an address is a word index, not a byte +// index. the cpu is a stack machine, so "asm { a @ . }" pushes a, fetches the +// word at that address with @, then outputs it with . (see the vm reference). +// +// ---- memory map (words) ---- // src/tools/genmap writes the map values for each build between these markers. // keep the names and order stable so programs can use the same map interface @@ -51,7 +51,7 @@ const DSHI = 0x1D4FF0; const MAGIC = 0x2B7E1516; // @end-map -//: frames grow in stable segments so pointers to a caller's locals stay valid. +// frames grow in stable segments so pointers to a caller's locals stay valid. word frame_floor = FBOT, frame_top = FTOP, frame_node; int frame_chunk = FTOP - FBOT, frame_guard = (FTOP - FBOT) >> 2, frame_busy, gc_active; @@ -61,7 +61,7 @@ word return_low = RS0, return_top = MARK0 - 8, return_node; const int F_STACK = 1024; const int F_INTERNAL = 512; -//: ---- virtual enhancements ---- +// ---- virtual enhancements ---- // install(desc) passes a descriptor to the core through the % opcode. the core // reads it and installs the region it describes in the cpu, so the compiler can replace @@ -84,7 +84,7 @@ enhance ishl(a, b) micro "a = shl a b"; enhance lfa(k) micro "r1 = ld 589824; k = add r1 k"; // 1 when i is outside 0..n-1 (the unsigned compare also catches negative i), else 0. enhance outside(i, n) micro "i = ltu i n; i = xor i 1"; -//: ---- bit operations and float conversions ---- +// ---- bit operations and float conversions ---- // these use single machine instructions where the cpu has them. @@ -114,7 +114,7 @@ enhance zf2s(a) micro "a = f2s a"; // s2f, the software form used where the cpu has none. enhance zs2f(a) micro "a = s2f a"; -//: runtime code segments retain the addresses of earlier functions and globals. +// runtime code segments retain the addresses of earlier functions and globals. word code_blocks; // distinguish a raw function address from a heap closure's environment block. int callable_code(word f) { @@ -124,13 +124,13 @@ int callable_code(word f) { return 0; } -//: ---- i/o ---- +// ---- i/o ---- -//: output can be redirected. mode 1 drops what is written and mode 2 appends -//: it to a buffer; mode 3 counts characters without storing them (the server -//: uses these modes while it checks a document, -//: so a parse word that prints can't write into the protocol stream). -//: raw_c always reaches the terminal. +// output can be redirected. mode 1 drops what is written and mode 2 appends +// it to a buffer; mode 3 counts characters without storing them (the server +// uses these modes while it checks a document, +// so a parse word that prints can't write into the protocol stream). +// raw_c always reaches the terminal. int outmode; char* outbuf; int outn, outcap; word outgrow; // write one character to the terminal with no translation. @@ -185,12 +185,12 @@ int eq_w(char* a, char* b) { while (a[i] && a[i] == b[i]) i++; return a[i] == b[i]; } -//: ---- word helpers ---- +// ---- word helpers ---- -//: copy and fill by word. copy_w copies forward, so overlapping ranges are only -//: valid when dst is below src. a long run above the program area goes to the -//: host in one call (ops 18 and 19 of the syscall gate); a host that answers -1 -//: (a board, the js vm, a range that overlaps itself) gets the plain loop. +// copy and fill by word. copy_w copies forward, so overlapping ranges are only +// valid when dst is below src. a long run above the program area goes to the +// host in one call (ops 18 and 19 of the syscall gate); a host that answers -1 +// (a board, the js vm, a range that overlaps itself) gets the plain loop. const int SYS_FILL = 18, SYS_COPY = 19; // copy n words from src to dst. @@ -342,7 +342,7 @@ bstr n_to_s(int n) { return p; } -//: ---- arithmetic the hardware doesn't have ---- +// ---- arithmetic the hardware doesn't have ---- word[64] pow2; int zrem; @@ -380,42 +380,42 @@ int zmod(int a, int b) { // a shifted left by b bits, for b under 64. int zshl(int a, int b) { return a * pow2[b & 63]; } -//: ---- heap ---- +// ---- heap ---- -//: a buddy allocator. the heap is cut into aligned power-of-two chunks, every -//: block is a power of two words, and a freed block merges with its buddy -//: (the other half of the block they were split from) whenever that one is -//: free too, so freed space can be reused at any size. every block has a 5 -//: word header before the pointer the program sees: -//: p[-5] block size -//: p[-4] flags in the low 16 bits, size class in the next 5 -//: 0 free (check word cleared) or quarantined (check word still p ^ MAGIC) -//: 1 live 2 releasing 3 marked 4 quarantined but a word still points at it -//: bit 8 (value 8) is orthogonal: the object is shared. free is a no-op, -//: own and release do not follow it, and the sweep still collects it when -//: nothing points at it. bits 0..2 keep the meanings above. -//: p[-3] p ^ MAGIC p[-2] owner (the container that owns it, or 0) -//: p[-1] its type (a type record from new(T), 0 from new(n), -(n + 1) from o3's new_raw) -//: free blocks sit on a doubly linked list per class, linked through p[0], p[1]. -//: free quarantines: the block stays intact until a sweep finds nothing pointing -//: at it. recycle is the fast path for a pointer the program has already -//: dropped: it pushes the block onto its size-class list and does not walk the -//: heap. a two-word node is still 8 words (the smallest block); a three-word -//: node fits in 8 instead of 16. +// a buddy allocator. the heap is cut into aligned power-of-two chunks, every +// block is a power of two words, and a freed block merges with its buddy +// (the other half of the block they were split from) whenever that one is +// free too, so freed space can be reused at any size. every block has a 5 +// word header before the pointer the program sees: +// p[-5] block size +// p[-4] flags in the low 16 bits, size class in the next 5 +// 0 free (check word cleared) or quarantined (check word still p ^ MAGIC) +// 1 live 2 releasing 3 marked 4 quarantined but a word still points at it +// bit 8 (value 8) is orthogonal: the object is shared. free is a no-op, +// own and release do not follow it, and the sweep still collects it when +// nothing points at it. bits 0..2 keep the meanings above. +// p[-3] p ^ MAGIC p[-2] owner (the container that owns it, or 0) +// p[-1] its type (a type record from new(T), 0 from new(n), -(n + 1) from o3's new_raw) +// free blocks sit on a doubly linked list per class, linked through p[0], p[1]. +// free quarantines: the block stays intact until a sweep finds nothing pointing +// at it. recycle is the fast path for a pointer the program has already +// dropped: it pushes the block onto its size-class list and does not walk the +// heap. a two-word node is still 8 words (the smallest block); a three-word +// node fits in 8 instead of 16. const int HDR = 5; -//: p[-4] bit, orthogonal to live / mark / releasing. +// p[-4] bit, orthogonal to live / mark / releasing. const int F_SHARE = 8; -//: flags live in these bits; the size class is above them. +// flags live in these bits; the size class is above them. const int F_LOW = 65535; int hbump; word[32] freel; int gc_runs, gc_freed, gc_auto = 1; -//: block walks and new chunks stop here. +// block walks and new chunks stop here. int hstop; -//: while nonzero, balloc will not return a block overlapping this range. +// while nonzero, balloc will not return a block overlapping this range. int hold_lo, hold_hi; -//: words of memory the host has (0: the heap cannot grow); hcut: the heap was cut, so it must not grow. +// words of memory the host has (0: the heap cannot grow); hcut: the heap was cut, so it must not grow. int hmax, hcut; -//: memdebug bits, and the line the compiler stamps before new. +// memdebug bits, and the line the compiler stamps before new. int dbg, alloc_line; // the size of the top-level chunk holding heap offset off, within [0, hstop) @@ -563,7 +563,7 @@ word balloc(int k) { } return 0; } -//: ---- a heap that grows ---- +// ---- a heap that grows ---- // the heap is [HEAP_LO, hstop). on a map with HEAP_GROW the memory above // HEAP_HI belongs to the host (a big zeroed array nobody has touched yet), and @@ -613,7 +613,7 @@ int hfree() { } return n; } -//: end of the block bfree last made, for the sweep. +// end of the block bfree last made, for the sweep. int fend; // put block b back, merging it with its buddy as long as it can int bfree(word b) { @@ -650,10 +650,10 @@ int bdrop(word b) { // quarantined)? the check word p[-3] == p ^ MAGIC validates the pointer before // checking it against block metadata; a random value would have to match by chance. word isobj(word x) { return x >= HEAP_LO + HDR && x < hbump && x[-3] == (x ^ MAGIC) && (x[-4] & 1); } -//: the number of words to skip in p[0..n) before one that could be a pointer -//: to a block (see op 20 in core.c); n when none can be. a long payload of -//: plain numbers costs one host call instead of an isobj per word. a host that -//: does not have the op (-1) is asked once and then never again. +// the number of words to skip in p[0..n) before one that could be a pointer +// to a block (see op 20 in core.c); n when none can be. a long payload of +// plain numbers costs one host call instead of an isobj per word. a host that +// does not have the op (-1) is asked once and then never again. const int SYS_SCAN = 20; int scan_off; // walk the n words at p and mark every heap object they point to. @@ -956,7 +956,7 @@ word tget(word p, int i) { return p[i]; } -//: ---- growable arrays ---- +// ---- growable arrays ---- // a T[?] is a handle: [len, cap, items, element size]. items is an object the // handle owns; growing swaps it for a bigger one and moves its children over. @@ -1085,12 +1085,12 @@ int dlen(word h) { return h[0]; } // the capacity of a growable array: how many elements it can hold before it grows. int dcap(word h) { return h[1]; } -//: ---- mark-sweep ---- +// ---- mark-sweep ---- -//: conservative and non-moving. roots: the program area (globals live -//: between functions), the genome's globals, the compiler's input stack, -//: the live part of the frame stack and the live part of the data stack. -//: opcode 619 pushes that index. a word below it is not a root. +// conservative and non-moving. roots: the program area (globals live +// between functions), the genome's globals, the compiler's input stack, +// the live part of the frame stack and the live part of the data stack. +// opcode 619 pushes that index. a word below it is not a root. int ms_top, ms_over, gc_lo, gc_hi; word ms_buf = MARK0; int markcap = MARKN; @@ -1213,12 +1213,12 @@ int scrub() { return 0; } -//: ---- memory debug ---- +// ---- memory debug ---- -//: dbg bits: 1 header checks and allocation sites, 2 poison the payload on free -//: (the word 57005, which is 0xDEAD), 4 interior pointers keep a quarantined -//: block (the mark walks back at most 64 words to a real header). off unless -//: a program requests. sites are p ^ MAGIC so the collector does not treat them as pointers. +// dbg bits: 1 header checks and allocation sites, 2 poison the payload on free +// (the word 57005, which is 0xDEAD), 4 interior pointers keep a quarantined +// block (the mark walks back at most 64 words to a real header). off unless +// a program requests. sites are p ^ MAGIC so the collector does not treat them as pointers. const int NSITE = 64; const int POISON = 57005; @@ -1301,28 +1301,28 @@ int memreport() { return n; } -//: ---- host syscalls ---- +// ---- host syscalls ---- -//: a store of the op number to SYSGATE runs it. the three words after the gate -//: are the arguments, and the store leaves the result in the gate. -//: 1 open(path, 0 read | 1 write-create) 2 close(fd) -//: 3 read(fd, buf, n) 4 write(fd, buf, n) bytes, one per word -//: 5 getenv(name, buf, cap) 6 time() unix seconds -//: 21 argc() 22 argv(i, buf, cap) 23 envc() 24 envp(i, buf, cap) -//: 40 raw(block) any linux syscall: the block holds the number and six arguments. -//: the result is the kernel's, or -errno. see Host.raw in src/lib/std.zc +// a store of the op number to SYSGATE runs it. the three words after the gate +// are the arguments, and the store leaves the result in the gate. +// 1 open(path, 0 read | 1 write-create) 2 close(fd) +// 3 read(fd, buf, n) 4 write(fd, buf, n) bytes, one per word +// 5 getenv(name, buf, cap) 6 time() unix seconds +// 21 argc() 22 argv(i, buf, cap) 23 envc() 24 envp(i, buf, cap) +// 40 raw(block) any linux syscall: the block holds the number and six arguments. +// the result is the kernel's, or -errno. see Host.raw in src/lib/std.zc const int SYSGATE = ZFP + 2; const int SYS_OPEN = 1, SYS_CLOSE = 2, SYS_READ = 3, SYS_WRITE = 4, SYS_GETENV = 5, SYS_TIME = 6; -/* terminates the host process with the exit status supplied in argument a. */ +// terminates the host process with the exit status supplied in argument a. const int SYS_EXIT = 7; const int SYS_BOPEN = 10, SYS_BREAD = 11, SYS_BWRITE = 12, SYS_BGETENV = 13; const int SYS_RAW = 40; -//: the command line and the environment. argv holds the program path, which is -//: <eval> when the source came from stdin, so argc is 1 even with no arguments. -//: the string ops take an index and copy one entry across, one character per -//: word like SYS_GETENV: a zero buffer requests the length alone, so the caller -//: can size the string before it fetches it. src/lib/std.zc builds the arrays. +// the command line and the environment. argv holds the program path, which is +// <eval> when the source came from stdin, so argc is 1 even with no arguments. +// the string ops take an index and copy one entry across, one character per +// word like SYS_GETENV: a zero buffer requests the length alone, so the caller +// can size the string before it fetches it. src/lib/std.zc builds the arrays. const int SYS_ARGC = 21, SYS_ARG = 22, SYS_ENVC = 23, SYS_ENV = 24; // byte-string paths. the address is the first byte (addr_b of the word after // the length), not the string object. char* string syscalls are unchanged. @@ -1333,15 +1333,15 @@ int bread(int fd, bstr s, int n) { return syscall(SYS_BREAD, fd, addr_b(bstr_sto int bwrite(int fd, bstr s, int n) { return syscall(SYS_BWRITE, fd, addr_b(bstr_storage(s) + 1), n); } // copy the value of the environment variable name into dst, returning its length. int bgetenv(bstr name, bstr dst, int n) { return syscall(SYS_BGETENV, addr_b(bstr_storage(name) + 1), addr_b(bstr_storage(dst) + 1), n); } -//: ---- c abi ---- +// ---- c abi ---- -//: ops 14..17 reach native code (see the c abi section of core.c and extern in -//: compiler.zc). cfind(name) is 1 if a native symbol resolves (name is a byte -//: string), cload(path) opens a library so its symbols resolve, and -//: cbmake(entry, sig) is a native function pointer that runs the zc function at -//: entry (cfunc(f) in the compiler builds the signature). a native pointer -//: from cbmake only works while a native call is in progress, and not on the -//: arm64 jit, where it is 0. +// ops 14..17 reach native code (see the c abi section of core.c and extern in +// compiler.zc). cfind(name) is 1 if a native symbol resolves (name is a byte +// string), cload(path) opens a library so its symbols resolve, and +// cbmake(entry, sig) is a native function pointer that runs the zc function at +// entry (cfunc(f) in the compiler builds the signature). a native pointer +// from cbmake only works while a native call is in progress, and not on the +// arm64 jit, where it is 0. const int SYS_CCALL = 14, SYS_CFIND = 15, SYS_CBMAKE = 16, SYS_CLOAD = 17; // the address of the native symbol name in the loaded libraries, or 0 when it is not |
