nx_bck_elide.nx
buildroot/runtime/nx_bck_elide.nx
about
nx_bck_elide.nx -- LN7 SOUND BOUNDS-CHECK ELISION (bck_elide_dominated).
WHAT THIS IS. The consumer nx_opt.nx's own VRA header has NAMED and never built since
v0.0.1: "opt_bounds_check_elim: drop array bounds checks when index is provably within
range". This is that pass, and it is deliberately NOT the range-based version that comment
imagines -- it is the DOMINANCE version, which needs no range lattice and whose soundness
argument is short enough to be checked by eye.
THE THEOREM. emit_bounds_check_v (nx_parse.nx) emits, for one access:
b: c1 = idx >=s 0 ; br_cond c1 -> lo, fail
lo: c2 = idx <s len ; br_cond c2 -> ok, fail
fail: write(2,msg) ; exit_group(71) ; br ok
ok: <the access>
If a site S1 and a site S2 carry the SAME (idx value id, len constant) and S1's `ok` block
DOMINATES S2's entry block, then S2's comparisons can only ever answer true, so making S2's
two branches unconditional changes no observable behaviour. Why the facts still hold at S2:
values here are SSA, so an id names one immutable quantity for the whole function; dominance
says EVERY path from entry to S2 runs through S1's ok; and S1's ok is only entered with the
facts established (see the four structural preconditions below). Nothing about the distance
between the two sites, or what sits between them, can weaken that -- there is no store that
can retarget an SSA id.
WRONG IN THE DIRECTION OF KEEPING THE CHECK, BY CONSTRUCTION. Every predicate below is a
REASON TO ELIDE; the default is to keep. An unrecognised shape, an unreadable value, a
truncated CFG, a length that is not a constant -- all fall through to "keep the check". An
unsound elision is a silent memory-safety hole: it deletes the check that would have caught a
real overrun, and nothing anywhere reports it. So this file has no "probably" cases.
THE FOUR STRUCTURAL PRECONDITIONS a site must meet before it may JUSTIFY an elision -- each
one closes a way for `ok` to be entered WITHOUT the facts:
(1) `lo` has exactly ONE predecessor, and it is `b`. Otherwise control could enter lo from
elsewhere and take the true edge to ok having proven `idx < len` but never `idx >= 0`.
(2) every predecessor of `ok` is `lo` or `fail`. Any third edge into ok reaches it with no
facts at all. A pred count above the pred slots the struct actually has means the edge
list is TRUNCATED and cannot be enumerated -- that is a refusal, not a pass.
(3) `fail` provably does not return: it must contain a syscall whose number operand is the
exit_group constant. This is the precondition it would be easiest to assume instead of
dependencies 3 imports · 1 importers
imports: nx_types.nxnx_ir.nxnx_dom_fn.nx
imported by: nx_opt.nx
structs
| none |
consts
| 84 | const BCK_PRED_SLOTS: i64 = 3 |
| 88 | const BCK_O_LO: i64 = 0 |
| 89 | const BCK_O_OK: i64 = 1 |
| 90 | const BCK_O_IDX: i64 = 2 |
| 91 | const BCK_O_LEN: i64 = 3 |
| 92 | const BCK_SCRATCH_BYTES: i64 = 64 |
| 101 | const BCK_R_NOT_BRCOND: i64 = 0 |
| 102 | const BCK_R_GE_NOT_FOUND: i64 = 1 |
| 103 | const BCK_R_NOT_GE: i64 = 2 |
| 104 | const BCK_R_NOT_ZERO: i64 = 3 |
| 105 | const BCK_R_NO_LO: i64 = 4 |
| 106 | const BCK_R_LO_PREDS: i64 = 5 |
| 107 | const BCK_R_LO_NOT_BRCOND: i64 = 6 |
| 108 | const BCK_R_FAIL_MISMATCH: i64 = 7 |
| 109 | const BCK_R_LT_NOT_FOUND: i64 = 8 |
| 110 | const BCK_R_NOT_LT: i64 = 9 |
| 111 | const BCK_R_IDX_MISMATCH: i64 = 10 |
| 112 | const BCK_R_NO_FAIL: i64 = 11 |
| 113 | const BCK_R_FAIL_NOT_TERM: i64 = 12 |
| 114 | const BCK_R_NO_OK: i64 = 13 |
| 115 | const BCK_R_OK_PREDS: i64 = 14 |
| 116 | const BCK_R_MATCHED: i64 = 15 |
| 118 | const BCK_R_N: i64 = 26 |
| 612 | const BCK_IV_MAX_CONST: i64 = 1152921504606846976 // 2^60: guard bound + increment stays far below 2^63 |
| 613 | const BCK_R_IV_LOAD_NOT_LOCAL: i64 = 16 |
| 614 | const BCK_R_IV_NOT_LOAD: i64 = 17 |
| 615 | const BCK_R_IV_NOT_ALLOCA: i64 = 18 |
| 616 | const BCK_R_IV_ESCAPES: i64 = 19 |
| 617 | const BCK_R_IV_NO_INIT: i64 = 20 |
| 618 | const BCK_R_IV_BAD_STORE: i64 = 21 |
| 619 | const BCK_R_IV_INC_UNGUARDED: i64 = 22 |
| 620 | const BCK_R_IV_NO_GUARD: i64 = 23 |
| 621 | const BCK_R_IV_STORE_ON_PATH: i64 = 24 |
| 622 | const BCK_R_IV_MATCHED: i64 = 25 |
| 623 | const BCK_IV_OPERAND_SLOTS: i64 = 24 |
functions
| 139 | func nx_bckelide_set(v: i64, exit_group_nr: i64) -> i64 |
| 144 | func bck_elide_live() -> i64 { return g_bckelide_live } called by 1: opt_bounds_check_elim |
| 145 | func bck_exitgrp() -> i64 { return g_bck_exitgrp } called by 1: opt_bounds_check_elim |
| 146 | func bck_stat_sites() -> i64 { return g_bck_sites } |
| 147 | func bck_stat_elided() -> i64 { return g_bck_elided } |
| 148 | func bck_stat_ref_nolen() -> i64 { return g_bck_ref_nolen } |
| 149 | func bck_stat_ref_nodom() -> i64 { return g_bck_ref_nodom } |
| 150 | func bck_stat_decl_preds() -> i64 { return g_bck_decl_preds } |
| 155 | func bck_reasons() -> *i64 |
| 164 | func bck_reason_bump(code: i64) -> i64 |
| 174 | func bck_eputs(s: *u8) -> i64 |
| 180 | func bck_eputn(v: i64) -> i64 called by 1: bck_ekv |
| 195 | func bck_ekv(label: *u8, v: i64) -> i64 { bck_eputs(label); bck_eputn(v); return 0 } |
| 199 | func bck_ereason(label: *u8, code: i64) -> i64 |
| 208 | func bck_block_index_by_id(f: *Function, id: i64) -> i64 |
| 220 | func bck_def_in_block(b: *BasicBlock, vid: i64) -> *Instr |
| 236 | func bck_def_of(f: *Function, b: *BasicBlock, vid: i64) -> *Instr |
| 251 | func bck_val_is_const(f: *Function, v: i64, want: i64) -> i64 |
| 263 | func bck_const_of(f: *Function, v: i64, flag: *i64) -> i64 |
| 275 | func bck_fail_is_terminal(f: *Function, fb: *BasicBlock, exit_group_nr: i64) -> i64 |
| 289 | func bck_ok_preds_clean(ok: *BasicBlock, lo: *BasicBlock, fl: *BasicBlock) -> i64 called by 1: bck_match_site |
| 304 | func bck_lo_preds_clean(lo: *BasicBlock, b: *BasicBlock) -> i64 called by 1: bck_match_site |
| 315 | func bck_decline(code: i64, tally: i64) -> i64 |
| 320 | func bck_match_site(f: *Function, bi: i64, exit_group_nr: i64, out: *i64, tally: i64) -> i64 |
| 379 | func bck_rewrite_site(f: *Function, b_i: i64, lo_i: i64, ok_i: i64) -> i64 |
| 406 | func bck_report(fn_blocks: i64, cand_here: i64, sites_here: i64, elided_here: i64) -> i64 |
| 445 | func bck_elide_dominated(f: *Function, info: *DomInfoFn, exit_group_nr: i64) -> i64 |
| 627 | func bck_stat_iv_sites() -> i64 { return g_bck_iv_sites } |
| 628 | func bck_stat_iv_elided() -> i64 { return g_bck_iv_elided } |
| 632 | func bck_iv_opk(inst: *Instr, k: i64) -> i64 called by 1: bck_iv_noescape |
| 661 | func bck_iv_def(f: *Function, v: i64) -> *Instr called by 4: bck_iv_is_load_ofbck_iv_store_kindbck_iv_init_okbck_iv_check calls 2: bck_def_ofblock_at |
| 668 | func bck_iv_noescape(f: *Function, aid: i64) -> i64 |
| 703 | func bck_iv_small_const(f: *Function, v: i64, out: *i64) -> i64 |
| 714 | func bck_iv_is_load_of(f: *Function, v: i64, aid: i64) -> i64 |
| 724 | func bck_iv_store_kind(f: *Function, st: *Instr, aid: i64) -> i64 |
| 736 | func bck_iv_init_ok(f: *Function, aid: i64) -> i64 |
| 753 | func bck_iv_block_has_store(b: *BasicBlock, aid: i64) -> i64 called by 1: bck_iv_store_free |
| 762 | func bck_iv_push_succ(f: *Function, s: *BasicBlock, t_i: i64, mark: *u8, stack: *i64, sp: *i64) -> i64 |
| 775 | func bck_iv_mark_fwd(f: *Function, t_i: i64, mark: *u8, stack: *i64) -> i64 |
| 793 | func bck_iv_push_pred(f: *Function, p: *BasicBlock, t_i: i64, mark: *u8, stack: *i64, sp: *i64) -> i64 |
| 806 | func bck_iv_mark_bwd(f: *Function, t_i: i64, dst_i: i64, mark: *u8, stack: *i64) -> i64 |
| 826 | func bck_iv_store_free(f: *Function, t_i: i64, dst_i: i64, aid: i64, stop: *Instr, fwd: *u8, bwd: *u8, stack: *i64) -> i64 called by 1: bck_iv_check calls 5: block_atbck_iv_block_has_storebck_iv_mark_fwdbck_iv_mark_bwddf_block_index |
| 859 | func bck_iv_find_guard(f: *Function, info: *DomInfoFn, aid: i64, limit: i64, dst: *BasicBlock) -> i64 |
| 892 | func bck_iv_check(f: *Function, info: *DomInfoFn, b: *BasicBlock, bi: i64, ld: *Instr, lc: i64, fwd: *u8, bwd: *u8, stack: *i64) -> i64 called by 1: bck_elide_induction calls 7: bck_iv_defbck_iv_noescapebck_iv_init_okblock_atbck_iv_store_kindbck_iv_find_guard+1 |
| 925 | func bck_iv_report(fn_blocks: i64, cand: i64, elided: i64) -> i64 |
| 951 | func bck_elide_induction(f: *Function, info: *DomInfoFn, exit_group_nr: i64) -> i64 called by 1: opt_bounds_check_elim calls 8: block_atbck_match_sitebck_const_ofbck_def_in_blockbck_iv_checkbck_reason_bump+2 |