Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

opt.pl

The middle end's entry points for the driver, and the comparison the interpreter uses to validate it.

optimize/3 runs the passes at a level, or an explicit ordered subset:

o0M0 → M
o1M0 → promote → copy → local_storage → M
o1Inline, SimplifyM0 → inline → promote → copy → simplify → local_storage → M

optimization_passes/2 names that sequence. optimize_pass/3 runs one member, so the playground can time it and show its actual output without implementing another optimizer. passespromote, copy, for example, leaves local storage off. Ordering stays fixed: local storage introduces a vocabulary the earlier passes do not consume.

refines/2 relates the outcome of a program to the outcome of its optimized version. The driver's difftest and the unit tests validate the optimized IR, rather than repeating that work on every compilation. These passes consume unchecked IR only. A checked module supplied directly (even with -O) is left unchanged: promotion and substitution do not understand proofs or flight projections.

usesinline
usescopy
usesirmodule_stage/2, local_objects/2
useslists
optimize+Options, +M0, -Msemidet

Options is o0 (the reference path), o1 (promote, copy and local storage), o1(small) (inlining of small static functions first, --inline=small), o1(Inline, Simplify) with Inline none, small, wide or wide(Attributes) (--inline=wide, Attributes from c_module/3) and Simplify none or full (the cheap middle end after copy, --simplify=full), or passes(Names), an ordered subset of all five. Invalid selections fail. Checked or already-local IR stays unchanged.

47optimizeOptions, M0, M ←
optimization_passesOptions, Passes
foldlreleased_pass, Passes, M0, M
released_pass+Pass, +M0, -M

optimize_pass/3, then the stacks back to what is live. SWI's limit counts what is allocated, and each pass leaves a module's worth of garbage: on SQLite the global stack had grown to 1 GB with 250 MB live, so collections were rare and the trail between them doubled to 0.5 GB, overflowing the 1 GB default.

56released_passPass, M0, M ←
optimize_passPass, M0, M
catchgarbage_collect, errorexistence_error_, _, _, true
catchtrim_stacks, errorexistence_error_, _, _, true
optimization_passes+Options, -Passessemidet

Passes is the selected sequence. A selection can omit passes but cannot reorder or repeat them, or name an unknown pass.

65optimization_passeso0, []
66optimization_passeso1, promote, copy, local_storage
67optimization_passeso1small, inline, promote, copy, local_storage
68optimization_passeso1Inline, Simplify, Passes ←
inline_passesInline, Simplify, I
simplify_passesSimplify, S
appendI, promote, copy, S, local_storage, Passes
71optimization_passespassesPasses, Passes ←
appendBefore, inline_simplified|After, Passes
→
appendBefore, inline|After, Ordered
else
Ordered = Passes
ordered_passesOrdered, inline, promote, copy, simplify, local_storage

With simplification, inlining's second round takes simplified callees (inline.pl's prepared/3).

80inline_passesnone, _, []
81inline_passessmall, none, inline
82inline_passessmall, full, inline_simplified
83inline_passeswide, Simplify, Passes ←
inline_passeswide[], Simplify, Passes
84inline_passeswideAs, none, inline_wideplain, As
85inline_passeswideAs, full, inline_widesimplified, As
86simplify_passesnone, []
87simplify_passesfull, simplify
89ordered_passes[], _
90ordered_passesP|Ps, All ←
append_, P|Rest, All
optimize_pass+Name, +M0, -Msemidet

Run one named transformation on unchecked, ordinary-address IR. Each pass transports its source contexts alongside executable IR. Unknown names fail; all guards remain in the existing pass modules.

99optimize_passName, M0, M ←
pass_predicateName, Goal
→
M = M0
else
callGoal, M0, M
pass_predicate
106inlineinline_module
107inline_simplifiedinline_modulesimplified
108inline_wideCallees, Asinline_modulewideCallees, As
109promotepromote_module
110copycopy_module
111simplifysimplify_module
112local_storagelocal_storage_module

Named local objects are already the storage pass's product. Earlier passes consume ordinary addresses, not this lowered vocabulary.

116has_local_storagemoduleItems ←
memberF, Items
F = func_, _, _, _, _
refines+Before, +Aftersemidet

After is an outcome the original program allows, given that the original's outcome was Before, both from interp:run/5 on the same inputs:

- undefined behaviour allows anything;
- running out of memory allows anything: whether an allocation is
  refused is the runtime's choice (Runtime.v's `alloc` may refuse
  any), so a program that allocates less may run on;
- an unknown result (read of uninitialized bytes in plain mode)
  allows any value consistent with its constraints;
- otherwise the outcomes must be equal. A pointer into an alloc
  compares by its offset in the object, because removing allocs
  moves the ones left; data and function addresses do not move.
136refinesub_, _ ←
!
137refinestrapout_of_memory, _ ←
!
138refinesBefore0, After0 ←
outcome_keyBefore0, Before
outcome_keyAfter0, After
groundBefore
→
Before ≡ After
else
¬¬Before = After
outcome_key+Outcome, -Key

Key is Outcome, except that a returned pointer into an alloc is keyed by its offset in the object.

148outcome_keyretP, retobj_ptrOff ←
nonvarP
P = ptrAddr, objBase
integerAddr
!
Off is Addr−Base
154outcome_keyOutcome, Outcome