Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
The Float Delay optimization floats Delay
from arguments into function bodies,
if possible. It turns (n -> ...Force n...Force n...) (Delay arg)
into
(n -> ...Force (Delay n)...Force (Delay n)...) arg
.
The above transformation is performed if:
- All occurrences of
arg
are underForce
. arg
is essentially work-free.
This achieves a similar effect to Plutonomy's "Split Delay" optimization. The difference
is that Split Delay simply splits the Delay
argument into multiple arguments, turning the
above example into (m -> (n -> ...Force n...Force n...) (Delay m)) arg
, and then relies
on other optimizations to simplify it further. Specifically, once the inliner inlines
Delay m
, it will be identical to the result of Float Delay.
The advantages of Float Delay are:
- It doesn't rely on the inliner. In this example, Split Delay relies on the inliner to
inline
Delay m
, but there's no guarantee that the inliner will do so, because inlining it may increase the program size.
We can potentially modify the inliner such that it is aware of Float Delay and
Force-Delay Cancel, and makes inlining decisions with these other optimizations in mind.
The problem is that, not only does this makes the inlining heuristics much more
complex, but it could easily lead to code duplication. Other optimizations often
need to do some calculation in order to make certain optimization decisions (e.g., in
this case, we want to check whether all occurrences of arg
are under Force
), and
if we rely on the inliner to inline the Delay
, then the same check would need to be
performed by the inliner.
- Because Force Delay requires that all occurrences of
arg
are underForce
, it guarantees to not increase the size or the cost of the program. This is not the case with Split Delay: in this example, if the occurrences ofn
are not underForce
, then Split Delay may increase the size of the program, regardless of whether or notDelay m
is inlined. IfDelay m
is not inlined, then it will also increase the cost of the program, due to the additional application.
The alternative approach that always floats the Delay
regardless of whether or not all
occurences of arg
are under Force
was implemented and tested, and it is strictly worse than
Float Delay on our current test suite (specifically, Split Delay causes one test case
to have a slightly bigger program, and everything else is equal).
Why is this optimization performed on UPLC, not PIR?
- Not only are the types and let-bindings in PIR not useful for this optimization,
they can also get in the way. For example, we cannot transform
let f = /a. ...a... in ...{f t1}...{f t2}...
intoket f = ...a... in ...f...f...
. - This optimization mainly interacts with ForceDelayCancel and the inliner, and both are part of the UPLC simplifier.
Documentation
floatDelay ∷ (MonadQuote m, Rename (Term name uni fun a), HasUnique name TermUnique) ⇒ Term name uni fun a → m (Term name uni fun a) Source #