Skip to content

Commit dd6e778

Browse files
michaelpjJakub Zalewski
andauthored
Single-occurrence inlining mk IV (IntersectMBO#3939)
Finally, we've got rid of all the issues it turned up, and it now appears to work fine. I also added a little bit of type inlining on the basis of similar heuristics to terms: trivial types or single-occurrence types. This does make things a bit slower, the Marlowe validator goes from taking 1.7s to 3.4s. This *isn't* due to the type inlining, I tried turning that off, it's just that we're inlining more stuff generally now. We could still make the inliner faster (or be cleverer with renaming), but it's not a catastrophic increase (and much lower than it was before when we were unconditionally inlining all the types!). Co-authored-by: Jakub Zalewski <[email protected]>
1 parent 6e59c75 commit dd6e778

File tree

21 files changed

+75403
-75037
lines changed

21 files changed

+75403
-75037
lines changed

plutus-core/plutus-ir/src/PlutusIR/Analysis/Usages.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{-# LANGUAGE FlexibleContexts #-}
22
-- | Functions for computing variable usage inside terms and types.
3-
module PlutusIR.Analysis.Usages (runTermUsages, runTypeUsages, Usages, isUsed, allUsed) where
3+
module PlutusIR.Analysis.Usages (runTermUsages, runTypeUsages, Usages, isUsed, isUsedOnce, allUsed) where
44

55
import PlutusIR
66

@@ -26,9 +26,15 @@ addUsage n usages =
2626
old = Map.findWithDefault 0 u usages
2727
in Map.insert u (old+1) usages
2828

29+
-- | Check if @n@ is used at least once.
2930
isUsed :: (PLC.HasUnique n unique) => n -> Usages -> Bool
3031
isUsed n usages = Map.findWithDefault 0 (n ^. PLC.unique . coerced) usages > 0
3132

33+
-- | Check if @n@ is used exactly once.
34+
isUsedOnce :: (PLC.HasUnique n unique) => n -> Usages -> Bool
35+
isUsedOnce n usages = Map.findWithDefault 0 (n ^. PLC.unique . coerced) usages == 1
36+
37+
-- | Get a set of all used @n@s.
3238
allUsed :: Usages -> Set.Set PLC.Unique
3339
allUsed usages = Map.keysSet $ Map.filter (> 0) usages
3440

plutus-core/plutus-ir/src/PlutusIR/Transform/Beta.hs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{-# LANGUAGE LambdaCase #-}
22
{-|
33
A simple beta-reduction pass.
4-
54
-}
65
module PlutusIR.Transform.Beta (
76
beta
@@ -15,7 +14,6 @@ import Control.Lens (transformOf)
1514

1615
{-|
1716
A single non-recursive application of the beta rule.
18-
1917
-}
2018
betaStep
2119
:: Term tyname name uni fun a
@@ -56,7 +54,6 @@ and types
5654
@
5755
5856
-}
59-
6057
beta
6158
:: Term tyname name uni fun a
6259
-> Term tyname name uni fun a

plutus-core/plutus-ir/src/PlutusIR/Transform/Inline.hs

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module PlutusIR.Transform.Inline (inline) where
1515

1616
import PlutusIR
1717
import qualified PlutusIR.Analysis.Dependencies as Deps
18+
import qualified PlutusIR.Analysis.Usages as Usages
1819
import PlutusIR.MkPir
1920
import PlutusIR.Purity
2021
import PlutusIR.Transform.Rename ()
@@ -108,8 +109,13 @@ type InliningConstraints tyname name uni fun =
108109
, PLC.ToBuiltinMeaning uni fun
109110
)
110111

112+
113+
data InlineInfo = InlineInfo { _strictnessMap :: Deps.StrictnessMap
114+
, _usages :: Usages.Usages
115+
}
116+
111117
-- Using a concrete monad makes a very large difference to the performance of this module (determined from profiling)
112-
type InlineM tyname name uni fun a = ReaderT Deps.StrictnessMap (StateT (Subst tyname name uni fun a) Quote)
118+
type InlineM tyname name uni fun a = ReaderT InlineInfo (StateT (Subst tyname name uni fun a) Quote)
113119

114120
lookupTerm
115121
:: (HasUnique name TermUnique)
@@ -159,16 +165,19 @@ inline
159165
:: ExternalConstraints tyname name uni fun m
160166
=> Term tyname name uni fun a
161167
-> m (Term tyname name uni fun a)
162-
inline t =
163-
let
168+
inline t = let
169+
inlineInfo :: InlineInfo
170+
inlineInfo = InlineInfo (snd deps) usgs
164171
-- We actually just want the variable strictness information here!
165172
deps :: (G.Graph Deps.Node, Map.Map PLC.Unique Strictness)
166173
deps = Deps.runTermDeps t
167-
in liftQuote $ flip evalStateT mempty $ flip runReaderT (snd deps) $ processTerm t
174+
usgs :: Map.Map Unique Int
175+
usgs = Usages.runTermUsages t
176+
in liftQuote $ flip evalStateT mempty $ flip runReaderT inlineInfo $ processTerm t
168177

169178
{- Note [Removing inlined bindings]
170179
We *do* remove bindings that we inline (since we only do unconditional inlining). We *could*
171-
leave this to the dead code pass, but we m
180+
leave this to the dead code pass, but it's helpful to do it here.
172181
Crucially, we have to do the same reasoning wrt strict bindings and purity (see Note [Inlining and purity]):
173182
we can only inline *pure* strict bindings, which is effectively the same as what we do in the dead
174183
code pass.
@@ -198,6 +207,14 @@ processTerm = handleTerm <=< traverseOf termSubtypes applyTypeSubstitution where
198207
-- Use 'mkLet': we're using lists of bindings rather than NonEmpty since we might actually
199208
-- have got rid of all of them!
200209
pure $ mkLet a NonRec bs' t'
210+
-- We cannot currently soundly do beta for types (see SCP-2570), so we just recognize
211+
-- immediately instantiated type abstractions here directly.
212+
(TyInst a (TyAbs a' tn k t) rhs) -> do
213+
b' <- maybeAddTySubst tn rhs
214+
t' <- processTerm t
215+
case b' of
216+
Just rhs' -> pure $ TyInst a (TyAbs a' tn k t') rhs'
217+
Nothing -> pure t'
201218
-- This includes recursive let terms, we don't even consider inlining them at the moment
202219
t -> forMOf termSubterms t processTerm
203220
applyTypeSubstitution :: Type tyname uni a -> InlineM tyname name uni fun a (Type tyname uni a)
@@ -218,17 +235,6 @@ processTerm = handleTerm <=< traverseOf termSubtypes applyTypeSubstitution where
218235
-- further optimization here.
219236
Done t -> PLC.rename t
220237

221-
222-
{- Note [Inlining various kinds of binding]
223-
We can inline term and type bindings, we can't do anything with datatype bindings.
224-
225-
We inline type bindings unconditionally as it is safe to do so, because PlutusIR
226-
only permits non-recursive type bindings. Doing so might duplicate some type
227-
information, but that information will be stripped once we reach
228-
UntypedPlutusCore, hence inlining type bindings will not increase the code size
229-
of the final program.
230-
-}
231-
232238
{- Note [Renaming strategy]
233239
Since we assume global uniqueness, we can take a slightly different approach to
234240
renaming: we rename the term we are substituting in, instead of renaming
@@ -243,32 +249,62 @@ processSingleBinding
243249
=> Binding tyname name uni fun a
244250
-> InlineM tyname name uni fun a (Maybe (Binding tyname name uni fun a))
245251
processSingleBinding = \case
246-
-- See Note [Inlining various kinds of binding]
247252
TermBind a s v@(VarDecl _ n _) rhs -> do
248253
maybeRhs' <- maybeAddSubst s n rhs
249254
pure $ TermBind a s v <$> maybeRhs'
250-
-- See Note [Inlining various kinds of binding]
251-
TypeBind _ (TyVarDecl _ tn _) rhs -> do
252-
modify' (extendType tn rhs)
253-
pure Nothing
254-
-- Not a strict binding, just process all the subterms
255+
TypeBind a v@(TyVarDecl _ n _) rhs -> do
256+
maybeRhs' <- maybeAddTySubst n rhs
257+
pure $ TypeBind a v <$> maybeRhs'
258+
-- Just process all the subterms
255259
b -> Just <$> forMOf bindingSubterms b processTerm
256260

261+
-- NOTE: Nothing means that we are inlining the term:
262+
-- * we have extended the substitution, and
263+
-- * we are removing the binding (hence we return Nothing)
257264
maybeAddSubst
258265
:: forall tyname name uni fun a. InliningConstraints tyname name uni fun
259266
=> Strictness
260267
-> name
261268
-> Term tyname name uni fun a
262269
-> InlineM tyname name uni fun a (Maybe (Term tyname name uni fun a))
263270
maybeAddSubst s n rhs = do
264-
-- Only do PostInlineUnconditional
265-
-- See Note [Inlining approach and 'Secrets of the GHC Inliner']
266271
rhs' <- processTerm rhs
267-
doInline <- postInlineUnconditional s rhs'
268-
if doInline then do
269-
modify (\subst -> extendTerm n (Done rhs') subst)
270-
pure Nothing
271-
else pure $ Just rhs'
272+
preUnconditional <- preInlineUnconditional rhs'
273+
if preUnconditional
274+
then extendAndDrop (Done rhs')
275+
else do
276+
-- See Note [Inlining approach and 'Secrets of the GHC Inliner']
277+
postUnconditional <- postInlineUnconditional rhs'
278+
if postUnconditional
279+
then extendAndDrop (Done rhs')
280+
else pure $ Just rhs'
281+
where
282+
extendAndDrop :: forall b . InlineTerm tyname name uni fun a -> InlineM tyname name uni fun a (Maybe b)
283+
extendAndDrop t = modify' (extendTerm n t) >> pure Nothing
284+
285+
checkPurity :: Term tyname name uni fun a -> InlineM tyname name uni fun a Bool
286+
checkPurity t = do
287+
strctMap <- asks _strictnessMap
288+
let strictnessFun = \n' -> Map.findWithDefault NonStrict (n' ^. theUnique) strctMap
289+
pure $ isPure strictnessFun t
290+
291+
preInlineUnconditional :: Term tyname name uni fun a -> InlineM tyname name uni fun a Bool
292+
preInlineUnconditional t = do
293+
usgs <- asks _usages
294+
let termIsUsedOnce = Usages.isUsedOnce n usgs
295+
-- See Note [Inlining and purity]
296+
termIsPure <- checkPurity t
297+
pure $ termIsUsedOnce && case s of { Strict -> termIsPure; NonStrict -> True; }
298+
299+
-- | Should we inline? Should only inline things that won't duplicate work or code.
300+
-- See Note [Inlining approach and 'Secrets of the GHC Inliner']
301+
postInlineUnconditional :: Term tyname name uni fun a -> InlineM tyname name uni fun a Bool
302+
postInlineUnconditional t = do
303+
-- See Note [Inlining criteria]
304+
let termIsTrivial = trivialTerm t
305+
-- See Note [Inlining and purity]
306+
termIsPure <- checkPurity t
307+
pure $ termIsTrivial && case s of { Strict -> termIsPure; NonStrict -> True; }
272308

273309
{- Note [Inlining criteria]
274310
What gets inlined? We don't really care about performance here, so we're really just
@@ -290,19 +326,20 @@ For non-strict bindings, the effects already happened at the use site, so it's f
290326
unconditionally.
291327
-}
292328

293-
-- | Should we inline? Should only inline things that won't duplicate work or code.
294-
-- See Note [Inlining approach and 'Secrets of the GHC Inliner']
295-
postInlineUnconditional
296-
:: forall tyname name uni fun a. InliningConstraints tyname name uni fun
297-
=> Strictness -> Term tyname name uni fun a -> InlineM tyname name uni fun a Bool
298-
postInlineUnconditional s t = do
299-
strictnessMap <- ask
300-
let -- See Note [Inlining criteria]
301-
termIsTrivial = trivialTerm t
302-
-- See Note [Inlining and purity]
303-
strictnessFun = \n' -> Map.findWithDefault NonStrict (n' ^. theUnique) strictnessMap
304-
termIsPure = case s of { Strict -> isPure strictnessFun t; NonStrict -> True; }
305-
pure $ termIsTrivial && termIsPure
329+
maybeAddTySubst
330+
:: forall tyname name uni fun a . InliningConstraints tyname name uni fun
331+
=> tyname
332+
-> Type tyname uni a
333+
-> InlineM tyname name uni fun a (Maybe (Type tyname uni a))
334+
maybeAddTySubst tn rhs = do
335+
usgs <- asks _usages
336+
-- No need for multiple phases here
337+
let typeIsUsedOnce = Usages.isUsedOnce tn usgs
338+
if typeIsUsedOnce || trivialType rhs
339+
then do
340+
modify' (extendType tn rhs)
341+
pure Nothing
342+
else pure $ Just rhs
306343

307344
-- | Is this a an utterly trivial term which might as well be inlined?
308345
trivialTerm :: Term tyname name uni fun a -> Bool
@@ -312,3 +349,10 @@ trivialTerm = \case
312349
-- TODO: Should this depend on the size of the constant?
313350
Constant{} -> True
314351
_ -> False
352+
353+
-- | Is this a an utterly trivial type which might as well be inlined?
354+
trivialType :: Type tyname uni a -> Bool
355+
trivialType = \case
356+
TyBuiltin{} -> True
357+
TyVar{} -> True
358+
_ -> False

plutus-core/plutus-ir/test/TransformSpec.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ inline =
126126
, "constant"
127127
, "transitive"
128128
, "tyvar"
129+
, "single"
129130
]
130131

131132

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
E002: Error during compilation: Type bindings cannot appear in recursive let, use datatypebind instead((recursive) let binding; from [ recursiveTypeBind:1:2
2-
, recursiveTypeBind:4:4 ])
1+
E002: Error during compilation: Type bindings cannot appear in recursive let, use datatypebind instead((recursive) let binding; from recursiveTypeBind:1:2)
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
(program
2-
1.0.0
3-
[
4-
(lam unitval_i0 (all a_i0 (type) (fun a_i1 a_i1)) unitval_i1)
5-
(abs a_i0 (type) (lam x_i0 a_i2 x_i1))
6-
]
7-
)
1+
(program 1.0.0 (abs a_i0 (type) (lam x_i0 a_i2 x_i1)))

0 commit comments

Comments
 (0)