# 37 — calls an o3 function calling another o3 function was leaving the region, running the prologue, and coming back through the epilogue. the return also called `fatten` whenever the type had a pointer. both are boundary work. a call that stays inside o3 is not a boundary. the region descriptor now carries the frame size, the arity, and the prologue address. a bare o3 block stores -1 for that address, so a real function whose prologue is at word 0 still gets registered. the caller builds the frame, copies the arguments, and enters the callee's region at its first block. the callee's prologue and epilogue do not run. a return to the caller puts the frame pointer back. `fatten` runs only when the value actually crosses into code that is not o3: the call site in ordinary code, an argument passed to an ordinary function (`free` and `recycle` still excepted), a store out of an o3 variable, and the end of an o3 block. `tests/o3call.zc` checks the split. `inner` returns a raw header (`p[-1] < 0`), and the same function called from `main` comes back fat. on the interpreter and on llvm, that entry is a direct call into the region. a callee that is ordinary bytecode is pushed on the vm return stack and run by the same driver that runs everything else, so a hot `new` stays compiled. on the arm64 jit the o3 callee is a `blr` to its entry stub. the return address lives in a small link stack, not in a trip out to c and back through `x_enter`. the fixpoint is 105293 words. the stage-0 image is 101152, still inside the 102400-word small reservation. the run-only image did not change. the suite is 426 passed, 0 failed: interpreter, microcore, soft-float, the small and run-only maps, both virt boards, the arm64 jit and llvm. ## sieve the inner loops do not call. milliseconds per count, median of five slopes. the before column is `history/36-alloc`. | path | before | after | | --- | ---: | ---: | | gcc -O3 -march=native, word flags | 0.149 | 0.148 | | gcc -O3 -march=native -flto, byte flags | 0.081 | 0.081 | | zc interpreter, plain, bounds-checked | 214.2 | 202.3 | | zc interpreter, o3, bounds-checked | 22.89 | 21.66 | | zc interpreter, o3, raw pointer | 14.63 | 14.35 | | zc llvm, ZYHOT=1, o3 raw pointer | 0.233 | 0.249 | | zc llvm, ZYHOT=1, o3 bounds-checked | 0.626 | 0.745 | | zc arm64 jit, qemu-user, o3 raw pointer | 2.066 | 2.103 | ## trees milliseconds per tree of depth 12. same kernels as stage 36. gcc on this run was 0.091 for malloc and 0.0143 for the arena. | path | before | after | | --- | ---: | ---: | | gcc -O3 -march=native, malloc | 0.091 | 0.091 | | gcc -O3 -march=native -flto, arena | 0.0143 | 0.0143 | | zc interpreter, plain new/free | 76.37 | 72.59 | | zc interpreter, o3 new/free | 175.3 | 162.1 | | zc interpreter, o3 arena | 2.111 | 1.426 | | zc llvm, ZYHOT=1, plain new/free | 6.91 | 7.78 | | zc llvm, ZYHOT=1, o3 new/free | 15.73 | 16.43 | | zc llvm, ZYHOT=1, o3 arena | 0.270 | 0.195 | | zc arm64 jit, qemu-user, plain new/free | 44.85 | 43.58 | | zc arm64 jit, qemu-user, o3 arena | 1.554 | 0.624 | the arena kernel is the one this change is aimed at: recursive o3 calls and no allocator. it dropped from 2.111 ms to 1.426 on the interpreter, from 0.270 ms to 0.195 on llvm, and from 1.554 ms to 0.624 on the arm64 jit under qemu. the o3 `new` / `free` tree is still slower than the plain one (162 ms against 73, and 16.4 ms against 7.8). `fatten` and `own` are still calls on every child store.