@@ -15,6 +15,7 @@ module PlutusIR.Transform.Inline (inline) where
1515
1616import PlutusIR
1717import qualified PlutusIR.Analysis.Dependencies as Deps
18+ import qualified PlutusIR.Analysis.Usages as Usages
1819import PlutusIR.MkPir
1920import PlutusIR.Purity
2021import 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
114120lookupTerm
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]
170179We *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.
172181Crucially, we have to do the same reasoning wrt strict bindings and purity (see Note [Inlining and purity]):
173182we can only inline *pure* strict bindings, which is effectively the same as what we do in the dead
174183code 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]
233239Since we assume global uniqueness, we can take a slightly different approach to
234240renaming: 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 ))
245251processSingleBinding = \ 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)
257264maybeAddSubst
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 ))
263270maybeAddSubst 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]
274310What 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
290326unconditionally.
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?
308345trivialTerm :: 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
0 commit comments