schedule.pl
Scheduled checks (docs/schedule.md): a check of a struct pointer covers, early, the bytes that every path ahead of it will need, so that the later checks of the same object become renewals.
FilPizlonator's scheduleChecks moves bounds requirements backward to where every path will need them, and merges them into one range check. This pass does the part of that which pays in nanofil, in two steps that keep every check where it is:
1. **Widen.** A heap check of P, a pointer at an exact offset K from
a single-definition base B (ir.pl's guard_chain/3), whose access is followed on every path, before anything but the closed list crossable/1, a poll edge or an edge back, by accesses through B that reach B + H, becomes `check_ahead(Lo, H - K, Rights, Align, Hi)`. Only the upper end moves. Its trap comes earlier than the reference's only on executions on which the reference traps later.
2. **Renew at a distance.** Every check that a dominating check or
check_ahead of the same base covers, shifted by their distance, becomes a renew (check_reuse's renew_checks/3 with `any`), a flag test.
Facts
| Fact | Means |
|---|---|
B-H in need | every path from here, before a blocker, accesses bytes through base tmpB reaching B + H |
A block's exit facts are the meet of what its edges carry (a plain edge to a later block: that block's entry facts; any other edge: none), each base keeping its least H. Walking backward, an instruction that is not crossable/1 ends every fact, a definition of B ends B's fact, and an access raises the needs of its pointer's exact bases. The validator (ir.pl's ahead_justified/3) recomputes the same facts by its own code and accepts each check_ahead only if they justify it; schedule_refines/2 checks that nothing else changed.
The pass is selected with --checks=schedule, after reuse and renewal; it is opt-in, since it changes when a trap happens.
M is the checked module M0 with each heap check widened to a check_ahead over the bytes that every path ahead needs from its base, then each check that a dominating check or check_ahead covers at a distance renewed. Functions with a setjmp are unchanged.
repointed_item(+Item1, +Item2, -Item): a renew that renewal at distance 0 made reads a check's proof Q; when this pass renewed that check too, from Q1, the renew reads Q1 instead (its root, following such renewals), which covers it by transitivity: Q1's check covers Q's range shifted by their distance, and Q's covered the renew's.
Entries maps each walked block to its entry needs; Done collects the widened blocks, front first, as the walk goes from the last block.
Insts are the block's instructions (Acc holds those after them), each widened check rewritten; After are the needs after the first of RevInsts, AfterNext those after the instruction that follows it.
ahead_insts/8 asks.offsets are exact only modulo 2^64; count only those well inside the signed range (ir.pl's near_offset/1, by its own code).
crossable(+Inst): a check may be widened past Inst toward a later access: Inst cannot exit to the runtime, call, free, run a signal handler, fault otherwise than a Fil-C trap or loop. Pointer stores (ensure-aux may exit), calls, allocation, getters, atomics, setjmp, va_arg and division are not.
M is M0 except for heap checks, each at its own index with its own pointer, rights, alignment and lower end, that became a check_ahead of a wider range whose access's own range is the check's, or a renew of at least the check's range. This is identity, not safety or timing: the validator (ir.pl) accepts each check_ahead only where its early bytes are needed ahead, and each renew only where an earlier check covers it.