Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

half_check.pl

Half checks: a check whose object was checked before at another range tests only the bound that is not already known (docs/half.md).

An access check tests that the capability is not null, that the address is at or above lower and that its end is at or below upper. When an earlier check of the same object, through a pointer at a constant distance from the same base, dominates this one, it proved the capability not null and an address range of the object. If this access starts at or above where that range started, the lower test is known; if it ends at or below where that range ended, the upper test is, since an object's upper changes only when it is freed, which sets its free flag. The check becomes

TermTests
half_checkupper, Lo, Hi, Rights, Alignupper, then read-only for a write, and alignment
half_checklower, Lo, Hi, Rights, Alignlower (signed), the free flag and read-only for a write, and alignment

reading tmpQ1, P, Q1 the earlier proof. It traps exactly where the check would, at the same access and with the same report. Nothing moves, and no fact needs a kill list: the facts it uses last as long as the object does.

The pass is selected with --checks=half, after renewal. ir.pl's validator accepts each half check by its own rule (half_known/6), and half_refines/2 checks that the only change is checks replaced in place by half checks of the same pointer, range, rights and alignment.

modulehalf_check
useslists
usesassoc
usespairs
usesirsingle_definitions/3, guard_chain/3, pointer_distance/4
usescfgfunc_cfg/2, cfg_idoms/2, dom_intervals/3, dominates_by_interval/3
half_checks+M0, -Mdet

M is the checked module M0 with each heap check that an earlier heap check of the same object dominates, at a constant distance through one single-definition base, replaced by a half check of the bound that earlier check does not establish. Functions with a setjmp are unchanged. Every instruction keeps its index, so context sites are unchanged.

45half_checksmoduleItems0, moduleItems ←
maplisthalf_item, Items0, Items
48half_itemfuncN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
memberblock_, _, Is, _, Bs0
memberlet_, _, setjmp_, _, Is
→
Bs = Bs0
else
single_definitionsPs, Bs0, Once
func_cfgBs0, Cfg
cfg_idomsCfg, Idom
dom_intervalsCfg, Idom, Dom
half_sourcesBs0, Once, Dom, Sources
maplisthalf_blockOnce, Dom, Sources, Bs0, Bs
56half_itemItem, Item

half_sources(+Blocks, +Once, +Dom, -Sources): Sources maps each single-definition base name B to s(Pre, L, I, Q1, P1, Lo1, Hi1, K1) for each heap proof Q1 of P1 in a reachable block L at index I that proves P1+[Lo1, Hi1), where Q1 and P1 have one definition and P1 is B + K1 exactly (guard_chain/3). Pre is L's preorder number in the dominator tree (dom_intervals/3), so a block's dominators come before it, and dominance is an interval test, not a walk up the tree.

65half_sourcesBlocks, Once, Dom, Sources ←
findallB−sPre, L, I, Q1, P1, Lo1, Hi1, K1,
memberblockL, _, Is, _, Blocks
get_assocL, Dom, Pre−_
nth0I, Is, lettmpQ1, a, Op, Args
provesOp, Args, P1, Lo1, Hi1
get_assocQ1, Once, _
P1 = tmpN1
get_assocN1, Once, _
guard_chainOnce, P1, Chain
membertmpB−iK1, K1, Chain
get_assocB, Once, _
,
Pairs0
keysortPairs0, Pairs
group_pairs_by_keyPairs, Groups0
findallB−Ss,
memberB−Ss0, Groups0
msortSs0, Ss
,
Groups
list_to_assocGroups, Sources
proves+Op, +Args, -P, -Lo, -Hi

a heap proof Op of Args has proven P's [Lo, Hi): a check or check_ahead of its whole range, a guarded renewal or a hoisted check, which are their checks.

proves
83checkLo, Hi, _, _PPLoHi
84check_aheadLo, Hi, _, _, _PPLoHi
85renew_ifLo, Hi, _, _P, _PLoHi
86check_limitLo, Hi, _, _P, _PLoHi
88half_blockOnce, Dom, Sources, blockL, Phis, Is0, J, blockL, Phis, Is, J ←
get_assocL, Dom, _
→
half_instsIs0, 0, L, Once, Dom, Sources, Is
else
Is = Is0
94half_insts[], _, _, _, _, _, []
95half_instsInst0|Is0, I, L, Once, Dom, Sources, Inst|Is ←
Inst0 = lettmpQ, a, checkLo, Hi, Rights, Align, P
Is0 = _|_
guard_chainOnce, P, Chain
membertmpB−iK, K, Chain
get_assocB, Sources, Ss
members_, L1, I1, Q1, P1, Lo1, Hi1, K1, Ss
L1 ≡ L
→
I1 < I
else
known_boundK−K1, Lo, Hi, Lo1, Hi1, Bound
The validator's identity rule, whose walk is bounded: the pass
makes only half checks it accepts.
pointer_distanceOnce, P, P1, D
D−K−K1 mod 1 << 64 =:= 0
→
Inst = lettmpQ, a, half_checkBound, Lo, Hi, Rights, Align, tmpQ1, P
else
Inst = Inst0
I2 is I + 1
half_instsIs0, I2, L, Once, Dom, Sources, Is

known_bound(+Distance, +Lo, +Hi, +Lo1, +Hi1, -Bound): a check of [Lo, Hi) at P = P1 + Distance (modulo 2^64), after a passed check of [Lo1, Hi1) at P1, needs to test only Bound: lower when its end is at or below the earlier end (one test fewer for a write, whose flag test then covers free too), else upper when its start is at or above the earlier start. The distance is taken signed, and both differences must be far from 2^63, where an address could wrap.

120known_boundDistance, Lo, Hi, Lo1, Hi1, Bound ←
D is Distance + 9223372036854775808 mod 18446744073709551616−9223372036854775808
Start is D + Lo−Lo1
End is D + Hi−Hi1
Near = 4611686018427387904
absStart ≤ Near
absEnd ≤ Near
Hi−Lo ≤ Near
End ≤ 0
→
Bound = lower
Start ≥ 0
→
Bound = upper
half_refines+M0, +Msemidet

M is M0 except for heap checks replaced in place by half checks of the same pointer, range, rights and alignment. A half check is its check by definition (ir.pl); whether its source establishes the bound it leaves untested is the validator's question (valid_module/1).

135half_refinesmoduleItems0, moduleItems ←
maplistitem_refines, Items0, Items
138item_refinesfuncN, L, R, Ps, Bs0, funcN, L, R, Ps, Bs ←
!
maplistblock_refines, Bs0, Bs
140item_refinesItem, Item
142block_refinesblockL, Ph, Is0, J, blockL, Ph, Is, J ←
maplistinst_refines, Is0, Is
145inst_refinesInst, Inst ←
!
146inst_refinesletQ, a, checkLo, Hi, Rs, Al, P, letQ, a, half_checkBound, Lo, Hi, Rs, Al, tmp_, P ←
memberchkBound, upper, lower