# 47 — the limits come off: a heap that grows, native calls the optimizer sees, sized types, more micro-ops **a heap that grows.** the machine now has two sizes. PW is the end of the fixed part (code, compiler tables, genome, stacks) and MW is the host's total memory, 0x8000000 words by default. the heap starts at PW with 0x60000 words and grows at run time: when `new_raw` finds no block big enough it calls `heap_grow`, which appends an aligned piece (a few times the request, at least 0x40000 words, never past MW) through the same free code the buddy merge uses. the host's array is static and untouched until the heap reaches into it, so a small program costs what it did before. `heap_reset` (the compiler cutting the allocator off below its type area) holds growth off for that process, and the language server's fixed map has growth switched off. `tools/genmap` emits PW, CODE_END and HEAP_GROW; a build can choose its total with `-DMW=`. memory facts come from `syscall(9, k)`. **host-assisted bulk work.** filling, copying and scanning a block were loops in zc. ops 18 (fill), 19 (copy) and 20 (does the block hold a heap pointer, for `release`) do each in one call on hosts that know them, and answer -1 elsewhere, in which case the zc loop runs. freeing a large block stopped being the slow part of the grow test. **native calls.** up to 15 arguments (registers first, then the c stack), opcode 620 lets the lifter see a native call whose callee and signature are constants, so an o3 region keeps the call in the generated code: a call instruction on arm64, a direct call to the symbol's address on llvm, which means llvm can inline across it and turns the common libm functions on doubles into its own intrinsics. the dispatcher round trip is gone for those. **sized integers.** `i8 u8 i16 u16 i32 u32`. a scalar is a word that is cut to size at every store; arrays and pointers of them are packed (8, 4 or 2 to a word, little endian) with the byte ops, indexing yields a "packed twin" whose lvalue is a byte address. pointer arithmetic stays in words and `sizeof` counts words. no new vm opcodes: the byte ops from stage 28 already lift in o3, so packed access gets the optimizer and both jits for free. not done: packed struct fields, growable `[]u8`, taking the address of one element. **f32.** the same machinery with a float: a variable holds a value rounded to single precision, an array or pointer of f32 is packed two to a word. the rounding is two new micro-ops. **micro-ops.** `ctz popcnt bswap rotl rotr` and `f2s s2f` join the set. the interpreter, the microcore and the javascript vm (integers only) evaluate them; arm64 uses rbit+clz, the vector popcount, rev, rorv and fcvt; llvm uses `cttz ctpop bswap fshl fshr` and fptrunc/fpext. `rt.zc` wraps them as enhancements (`ctz(x)`, `popcnt(x)`, ...). a bug worth remembering: the interpreter's enhancement runner treated every op number at or above `U_LD8` as a byte op, so a new op past the end of the list ran as nothing. it is bounded now. **what this stage did not do.** the invalidation of translated code is still coarse (a store to a covered word rescans every block and an install flushes all), o3 calls still leave a region through the dispatcher, there is no automatic tiering, and the windows scripts and the bare metal runs were not re-run after the map change. they are listed under "open ends" in the readme.