aboutsummaryrefslogtreecommitdiff
path: root/docs/history/38-bounds/notes.md
blob: 698d15be73c4d58418cc35e96089f7aac8d926b7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 38 — bounds

the o3 index of a `fixed []T` compared the index with `outside` and left that index on the data stack across the branch. the lifter reloaded it from a stack slot on the way back, on every in-bounds store. when the index is a local, both sides of the branch now load it from the variable. the failure path is the only spill, and it only exists to hand `oob` its two arguments.

a `for` whose test is `i < b`, whose step is `i++` or `i += 1`, and whose `b` holds `len` of that fixed array, drops the check. `i` has to be known non-negative, and the body must not have stored `i`, `b`, or the array yet. `i * i < n` is not that test, and `j += i` is not a step of one: a product can wrap negative, so the inner sieve loop keeps the check. `i = -1` keeps it. `f[i + 1]` keeps it, because that index is not the induction variable. the same proof applies to plain bytecode, which then computes the address without calling `dat`. `tests/bounds.zc` is the sum and a small sieve. `tests/bounds_lo.zc` and `tests/bounds_hi.zc` are the two checks that have to stay.

the fixpoint is 109545 words. the stage-0 image is 105232, and the small machine's copy is 105231, so that reservation moved from 102400 to 110592. the small machine is now 192512 words. the run-only image did not change. the suite is 438 passed, 0 failed: interpreter, microcore, soft-float, the small and run-only maps, both virt boards, the arm64 jit and llvm.

## sieve

milliseconds per count, median of five slopes. the before column is `history/37-call`. the raw pointer does not check, so it is the control.

| path | before | after |
| --- | ---: | ---: |
| gcc -O3 -march=native, word flags | 0.148 | 0.148 |
| gcc -O3 -march=native -flto, byte flags | 0.081 | 0.080 |
| zc interpreter, plain, bounds-checked | 202.3 | 144.2 |
| zc interpreter, o3, bounds-checked | 21.66 | 18.42 |
| zc interpreter, o3, raw pointer | 14.35 | 14.22 |
| zc llvm, ZYHOT=1, o3 raw pointer | 0.249 | 0.254 |
| zc llvm, ZYHOT=1, o3 bounds-checked | 0.745 | 0.430 |
| zc arm64 jit, qemu-user, o3 raw pointer | 2.103 | 2.080 |

the fill and the count are `i = 2; i < n; i++` with `n = len(f)`, so those checks are gone. the inner `j = i * i; j < n; j += i` still checks, and the index stays in its variable across that check. on llvm the checked sieve went from 0.745 ms to 0.430 ms. the raw pointer is 0.254 ms. the gap that is left is the check itself, and the empty blocks the loop still splits into.

## trees

milliseconds per tree of depth 12. gcc on this run was 0.091 for malloc and 0.0143 for the arena. these kernels do not index a `fixed []T` in a proved loop. a dump of their four o3 regions is the same blocks, at the same addresses, as the previous compiler.

| 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 | 72.59 | 72.73 |
| zc interpreter, o3 new/free | 162.1 | 162.0 |
| zc interpreter, o3 arena | 1.426 | 1.429 |
| zc llvm, ZYHOT=1, plain new/free | 7.78 | 7.32 |
| zc llvm, ZYHOT=1, o3 new/free | 16.43 | 18.31 |
| zc llvm, ZYHOT=1, o3 arena | 0.195 | 0.184 |
| zc arm64 jit, qemu-user, plain new/free | 43.58 | 44.21 |
| zc arm64 jit, qemu-user, o3 arena | 0.624 | 0.838 |

the interpreter arena stayed at 1.43 ms. the arm64 arena moved under qemu, from 0.624 ms to 0.838 ms, on code this pass did not change.