Skip to content

Commit 5744e79

Browse files
committed
Optimizations of code patterns around maybes
1 parent 82420da commit 5744e79

9 files changed

Lines changed: 207 additions & 58 deletions

File tree

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
9898
def If(cond: Tree, thenp: Tree, elsep: Tree)(using Context): If =
9999
ta.assignType(untpd.If(cond, thenp, elsep), thenp, elsep)
100100

101+
def conditional(cond: Tree, thenp: Tree, elsep: Tree)(using Context): Tree =
102+
cond match
103+
case Literal(Constant(true)) => thenp
104+
case Literal(Constant(false)) => elsep
105+
case _ => If(cond, thenp, elsep)
106+
101107
def InlineIf(cond: Tree, thenp: Tree, elsep: Tree)(using Context): If =
102108
ta.assignType(untpd.InlineIf(cond, thenp, elsep), thenp, elsep)
103109

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ class Definitions {
492492
@tu lazy val Magic_CanErr: Symbol = MagicPackageClass.requiredType("CanErr")
493493

494494
@tu lazy val MagicOkModule: Symbol = requiredModule("scala.magic.Ok")
495+
@tu lazy val Magic_OkApply: Symbol = MagicOkModule.requiredMethod(nme.apply)
495496
@tu lazy val Magic_OkUnapply: Symbol = MagicOkModule.requiredMethod(nme.unapply)
496497

497498
@tu lazy val MagicErrModule: Symbol = requiredModule("scala.magic.Err")
@@ -501,6 +502,8 @@ class Definitions {
501502
@tu lazy val Magic_spec: Symbol = MagicCompiletimePackage.requiredMethod("$spec")
502503
@tu lazy val Magic_wrappedType: Symbol = MagicCompiletimePackage.requiredMethod("$wrappedType")
503504

505+
@tu lazy val MagicRuntimePackageClass = requiredPackage("scala.magic.runtime").moduleClass.asClass
506+
504507
// More synthetic symbols
505508
@tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _))
506509
@tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false))

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import config.Printers.inlining
1515
import ErrorReporting.errorTree
1616
import util.{SimpleIdentitySet, SrcPos}
1717
import Nullables.computeNullableDeeply
18+
import config.Printers.transforms
1819

1920
import collection.mutable
2021
import reporting.trace
@@ -210,6 +211,33 @@ object Inliner:
210211
else
211212
constToLiteral(rootTree)
212213

214+
/** If tree is an equality test == with known outcome and no side effects, replace it
215+
* by a constant true or false.
216+
* Known outcome means currently:
217+
* - arguments are both constants, or
218+
* - at least one argument is of Unit type
219+
*/
220+
def reduceEQ(tree: Tree)(using Context): Tree = tree match
221+
case Apply(sel @ Select(arg1, nme.EQ), arg2 :: Nil) if isPureExpr(arg1) && isPureExpr(arg2) =>
222+
val tp1 = arg1.tpe.widen
223+
val tp2 = arg2.tpe.widen
224+
def const(b: Boolean) =
225+
cpy.Literal(tree)(Constant(b))
226+
.showing(i"REDUCE $tree to $result in ${ctx.compilationUnit} in ${ctx.owner.ownersIterator.toList}/${arg1.tpe},${arg2.tpe}", transforms)
227+
def reduceUnit(tp1: Type, tp2: Type) =
228+
if tp1.isRef(defn.UnitClass) then
229+
if tp2.isRef(defn.UnitClass) then const(true)
230+
else if !tp2.isBottomType && !tp2.isTopType then const(false)
231+
else EmptyTree
232+
else EmptyTree
233+
(tp1, tp2) match
234+
case (ConstantType(c1), ConstantType(c2)) =>
235+
if c1 == c2 then const(true) else const(false)
236+
case _ =>
237+
reduceUnit(tp1, tp2).orElse(reduceUnit(tp2, tp1)).orElse(tree)
238+
case _ =>
239+
tree
240+
213241
private[inlines] def newSym(name: Name, flags: FlagSet, info: Type, span: Span)(using Context): Symbol =
214242
newSymbol(ctx.owner, name, flags, info, coord = span)
215243
end Inliner
@@ -806,7 +834,7 @@ class Inliner(val call: tpd.Tree)(using Context):
806834
// corresponding arguments or proxies on the type and term level. It also changes
807835
// the owner from the inlined method to the current owner.
808836

809-
// This is reused through InlineTraitAncestors for inline traits, so inlinedMethod might not exist there
837+
// This is reused through InlineTraitAncestors for inline traits, so inlinedMethod might not exist there
810838
val oldOwners = if (inlinedMethod.exists) then inlinedMethod :: Nil else Nil
811839
val newOwners = if (inlinedMethod.exists) then ctx.owner :: Nil else Nil
812840

compiler/src/dotty/tools/dotc/inlines/Inlines.scala

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,15 @@ object Inlines:
129129
private def inlineTraitAncestors(cls: TypeDef)(using Context): List[Tree] = cls match {
130130
case tpd.TypeDef(_, tmpl: Template) =>
131131
val parentTrees: Map[Symbol, Tree] = tmpl.parents.map(par => symbolFromParent(par) -> par).toMap.filter(_._1.isInlineTrait)
132-
132+
133133
// TODO: We need to stop inlining if there is a non-inline trait or class that sits between the inline trait and the current class.
134-
// Because we also inline into other inline traits, it should be possible to do this by just
134+
// Because we also inline into other inline traits, it should be possible to do this by just
135135
// looking at the direct parents of the class instead of also needing to look at the indirect parents (baseClasses).
136136
// See inline-trait-non-inline-blocks-inlining.scala
137137
val ancestors: List[ClassSymbol] =
138138
cls.tpe.baseClasses.filter(sym => sym != cls.symbol && sym.isInlineTrait
139139
&& !(cls.symbol.asClass.ownersIterator.toList.tail.exists(p => p.isInlineTrait)) // We can skip anything that would be inlined into a class that lives somewhere inside an inline trait
140-
// because it must be on the RHS of a member definition in the inline trait and so pruned out later
140+
// because it must be on the RHS of a member definition in the inline trait and so pruned out later
141141
)
142142

143143
ancestors.flatMap(ancestor =>
@@ -151,7 +151,7 @@ object Inlines:
151151
report.error(s"unknown base type ${baseTpe.show} for ancestor ${ancestor.show} of ${cls.symbol.show}")
152152
None
153153
parentTrees.get(ancestor).orElse(baseTree.map(_.withSpan(cls.span)))
154-
).flatMap { tree =>
154+
).flatMap { tree =>
155155
tree.tpe match {
156156
case Specialization(spec) if spec.hasSpecializedParams && !spec.isFullySpecialized => None // these can only exist in cases where we don't want to inline because:
157157
// 1) they will be pruned out later anyway and if we inline them we will create a loop (as in tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala)
@@ -285,7 +285,7 @@ object Inlines:
285285
tree3
286286
end inlineCall
287287

288-
private def updateFlagsFromInlinedParent(child: FlagSet, parent: FlagSet): FlagSet =
288+
private def updateFlagsFromInlinedParent(child: FlagSet, parent: FlagSet): FlagSet =
289289
var updatedFlags = child
290290
// Parent needs to be initialised so child must also as initialisers have been inlined
291291
if (!parent.is(NoInits))
@@ -296,8 +296,8 @@ object Inlines:
296296
updatedFlags &~= PureInterface
297297
updatedFlags
298298

299-
private def checkInnerClasses(tmpl: Template)(using Context) =
300-
tmpl.body.foreach {
299+
private def checkInnerClasses(tmpl: Template)(using Context) =
300+
tmpl.body.foreach {
301301
// If we want to add these back, some work was done on this in the original Master Thesis
302302
// (https://infoscience.epfl.ch/server/api/core/bitstreams/9413f583-46bc-4106-b994-0be32f20eeba/content)
303303
case innerClass: TypeDef if innerClass.symbol.isClass => report.error("Inline traits may not define inner classes or traits.", innerClass.srcPos)
@@ -319,13 +319,13 @@ object Inlines:
319319
end checkAndTransformInlineTrait
320320

321321

322-
private def checkInlineTraitOverrides(clsSym: ClassSymbol)(using Context) =
323-
// We need to enforce `override` modifier constraints to ensure that the behaviour is the same as ordinary traits.
322+
private def checkInlineTraitOverrides(clsSym: ClassSymbol)(using Context) =
323+
// We need to enforce `override` modifier constraints to ensure that the behaviour is the same as ordinary traits.
324324
// The usual checks only apply in refChecks which is too late for us.
325325
def checkInlineTraitOverride(member: Symbol, other: Symbol) =
326326
if !member.is(Override) && !other.is(Deferred) && member.owner == clsSym then
327327
report.error(
328-
OverrideError("needs `override` modifier",
328+
OverrideError("needs `override` modifier",
329329
other.info,
330330
member,
331331
other,
@@ -335,11 +335,11 @@ object Inlines:
335335
)
336336
else if member.owner != clsSym && other.owner != clsSym
337337
&& !other.owner.derivesFrom(member.owner)
338-
&& !(member.isAnyOverride || member.hasAnnotation(defn.UncheckedOverrideAnnot))
339-
&& (!other.is(Deferred) || other.isAllOf(Given | HasDefault))
340-
&& !member.is(Deferred)
338+
&& !(member.isAnyOverride || member.hasAnnotation(defn.UncheckedOverrideAnnot))
339+
&& (!other.is(Deferred) || other.isAllOf(Given | HasDefault))
340+
&& !member.is(Deferred)
341341
&& !other.name.is(DefaultGetterName) then
342-
342+
343343
report.error(
344344
OverrideError(
345345
s"${clsSym} inherits conflicting members:\n "
@@ -354,7 +354,7 @@ object Inlines:
354354
,
355355
clsSym.srcPos
356356
)
357-
OverridingPairsChecker(clsSym, clsSym.thisType).checkAll(checkInlineTraitOverride)
357+
OverridingPairsChecker(clsSym, clsSym.thisType).checkAll(checkInlineTraitOverride)
358358

359359
def inlineParentInlineTraits(cls: Tree)(using Context): Tree =
360360
cls match {
@@ -366,31 +366,31 @@ object Inlines:
366366
if cls.symbol.isAnonymousClass && ancestors.exists(tree => Specialization.unapply(tree.tpe).exists(anc => anc.isSpecialized || anc.isFullySpecializedToTopClassesOrNothing)) then
367367
// No need to inline into specialized trait anonymous class instances; these will later be replaced by $impl$ classes.
368368
return cls
369-
369+
370370
val cycleFound = ancestors.exists { parent =>
371371
val parentSym = symbolFromParent(parent)
372-
val errorPos =
372+
val errorPos =
373373
// Trying to inline into the tree which defines parentSym (need to catch this separately
374-
// as need to catch it before we inline the second time to avoid tripping an assertion)
374+
// as need to catch it before we inline the second time to avoid tripping an assertion)
375375
if cls.symbol.ownersIterator.contains(parentSym) then
376-
Some(cls.srcPos)
377-
else if ctx.inlineTraitState.inlineOrigins(cls.symbol).contains(parentSym) then
376+
Some(cls.srcPos)
377+
else if ctx.inlineTraitState.inlineOrigins(cls.symbol).contains(parentSym) then
378378
// Select the user code that caused this error so we get two errors if there are two problematic inlines, not one
379-
val userPos = tpd.enclosingInlineds.last.srcPos
379+
val userPos = tpd.enclosingInlineds.last.srcPos
380380
// Trying to inline into the inlined body of parentSym not in the defn tree
381-
Some(userPos)
381+
Some(userPos)
382382
else None // Fine
383-
383+
384384
errorPos.foreach(pos =>
385385
report.error(s"Inlining of inline traits looped. Tried to inline ${parentSym} into its own body.", pos)
386386
)
387-
387+
388388
errorPos.nonEmpty
389389
}
390390

391-
if cycleFound then
391+
if cycleFound then
392392
return cls
393-
393+
394394
val newDefs = inContext(ctx.withOwner(cls.symbol)) {
395395
ancestors.foldLeft((List.empty[Tree], impl.body)) {
396396
case ((inlineDefs, childDefs), parent) =>
@@ -399,22 +399,22 @@ object Inlines:
399399
val overriddenSymbols = clsOverriddenSyms ++ inlineDefs.flatMap(_.symbol.allOverriddenSymbols)
400400
// Need to put the new defs first because we process in linearization order to make overridees correct,
401401
// but we want parent definitions to come first so that if child inline traits refer to values defined in a parent
402-
// inline trait these are defined.
403-
val inlinedDefs1 = parentTraitInliner.expandDefs(overriddenSymbols) ::: inlineDefs
402+
// inline trait these are defined.
403+
val inlinedDefs1 = parentTraitInliner.expandDefs(overriddenSymbols) ::: inlineDefs
404404
cls.symbol.flags = updateFlagsFromInlinedParent(cls.symbol.flags, parent.symbol.flags)
405-
405+
406406
val childDefs1 = parentTraitInliner.adaptSuperCalls(childDefs)
407407
(parentTraitInliner.adaptSuperCalls(inlinedDefs1), childDefs1)
408408
}
409409
}
410410

411411
val newbody = newDefs._1 ::: newDefs._2
412412
val paramAccessors = newbody.filter(_.symbol.is(ParamAccessor))
413-
413+
414414
for pacc <- paramAccessors
415415
otherstat <- newbody if !otherstat.symbol.is(ParamAccessor) && otherstat.denot.matches(pacc.denot.asSingleDenotation)
416-
do report.error(s"Inlining of inline trait created name conflict on ${pacc.denot.name}. Constructor parameters of inline receivers may not collide with members of inline traits.", pacc.srcPos)
417-
416+
do report.error(s"Inlining of inline trait created name conflict on ${pacc.denot.name}. Constructor parameters of inline receivers may not collide with members of inline traits.", pacc.srcPos)
417+
418418
val impl1 = cpy.Template(impl)(body = newbody)
419419

420420
cpy.TypeDef(cls)(rhs = impl1)
@@ -720,7 +720,7 @@ object Inlines:
720720
/** The Inlined node representing the inlined call */
721721
def expand(rhsToInline: Tree): Tree =
722722

723-
// Special handling of `requireConst` and `codeOf`
723+
// Special handling of `requireConst`, `codeOf`, and `magic.Ok`
724724
callValueArgss match
725725
case (arg :: Nil) :: Nil =>
726726
if inlinedMethod == defn.Compiletime_requireConst then
@@ -730,6 +730,8 @@ object Inlines:
730730
return unitLiteral.withSpan(call.span)
731731
else if inlinedMethod == defn.Compiletime_codeOf then
732732
return Intrinsics.codeOf(arg, call.srcPos)
733+
else if inlinedMethod == defn.Magic_OkApply && arg.tpe.isNotNullNorMaybe then
734+
return arg
733735
case _ =>
734736

735737
// Special handling of `constValue[T]`, `constValueOpt[T]`, `constValueTuple[T]`, `summonInline[T]` and `summonAll[T]`
@@ -917,11 +919,11 @@ object Inlines:
917919
}
918920
end expandDefs
919921

920-
def adaptSuperCalls(defs: List[Tree]) =
922+
def adaptSuperCalls(defs: List[Tree]) =
921923
val ttmap = TreeTypeMap(treeMap = {
922924
// We go through all ancestor inline traits so eventually we will find the one with matching parentSym
923925
case sel@Select(Super(qual, mix), name) if sel.symbol.owner == parentSym =>
924-
// At that point either the method is overridden so needs mangling (and we just copied and mangled it in this inlining phase),
926+
// At that point either the method is overridden so needs mangling (and we just copied and mangled it in this inlining phase),
925927
// or not, in which case call directly by original name. In both cases we are calling the method resulting from inlining, on the
926928
// inline receiver class.
927929
Select(This(ctx.owner.asClass), paramAccessorsMapper.getParamAccessorName(sel.symbol.owner, name).getOrElse(name))
@@ -977,11 +979,11 @@ object Inlines:
977979
}
978980

979981
override protected val inlinerTypeMap: InlinerTypeMap = InlineTraitTypeMap()
980-
982+
981983
override protected val inlinerTreeMap: InlinerTreeMap = InlineTraitTreeMap()
982984

983985
override protected def computeThisBindings(): Unit = ()
984-
986+
985987
override protected def canElideThis(tpe: ThisType): Boolean = true
986988

987989
override protected def inlineCtx(inlineTyper: InlineTyper)(using Context): Context =
@@ -1024,7 +1026,7 @@ object Inlines:
10241026
paramAccessorsMapper
10251027
.getParamAccessorRhs(vdef.symbol.owner, vdef.symbol.name)
10261028
.getOrElse(inlinedRhs(vdef, inlinedSym))
1027-
1029+
10281030
val rhs1 = rhs.changeNonLocalOwners(inlinedSym)
10291031

10301032
tpd.ValDef(inlinedSym.asTerm, rhs1).withSpan(parent.span)
@@ -1049,7 +1051,7 @@ object Inlines:
10491051
ctx.typeAssigner.assignType(untpd.TypeDef(inlinedSym.name.asTypeName, TypeTree(inlinedRhsType)), inlinedSym).withSpan(parent.span)
10501052
else
10511053
tpd.TypeDef(inlinedSym.asType).withSpan(parent.span)
1052-
1054+
10531055

10541056
private def inlinedRhs(vddef: ValOrDefDef, inlinedSym: Symbol)(using Context): Tree =
10551057
val rhs = vddef.rhs.changeOwner(vddef.symbol, inlinedSym)
@@ -1060,14 +1062,14 @@ object Inlines:
10601062
rhs
10611063
else
10621064
val symbolMap = mutable.Map[Symbol, Symbol]()
1063-
// TODO: This inlines also some calls to inline defs that were made in the inline trait body, is that ok?
1065+
// TODO: This inlines also some calls to inline defs that were made in the inline trait body, is that ok?
10641066
val rhs1 = Inlined(tpd.ref(parentSym).withSpan(parent.span), Nil, inlined(rhs)._2.withSpan(parent.span).cloneIn(parentSym.source)).withSpan(parent.span)
1065-
1067+
10661068
// In case of nested inline trait inlines, because BodyAnnotation is out of date,
10671069
// body inlined misses nested expansion, but we have the symbols for the items that should be there
10681070
// Remove them so that they can be inlined properly later.
10691071
val ttmap = TreeTypeMap(treeMap = {
1070-
case tree@TypeDef(name, tmpl: Template) if Inlines.needsInlining(tree) =>
1072+
case tree@TypeDef(name, tmpl: Template) if Inlines.needsInlining(tree) =>
10711073
val newSym = tree.symbol.copy(coord = spanCoord(tree.span)) // Coord should correspond to original location because we will inline from there.
10721074
newSym.info = ClassInfo(tree.symbol.owner.thisType, newSym.asClass, tree.symbol.asClass.parentTypes, Scopes.newScope)
10731075

@@ -1153,7 +1155,7 @@ object Inlines:
11531155

11541156
class InlineTraitState(
11551157
// For a class symbol created during inlining of an inline trait,
1156-
// the chain of inlined traits which produced it. We don't actually care about the order.
1158+
// the chain of inlined traits which produced it. We don't actually care about the order.
11571159
// Used as a "seen list" for cycle checking. Persists across invocations of InlineParentTrait
11581160
val inlineOrigins: mutable.Map[Symbol, Set[Symbol]] = mutable.HashMap[Symbol, Set[Symbol]]().withDefaultValue(Set.empty),
11591161
val inlineTraitsPhase: InlineTraitState.InlineContext = InlineTraitState.InlineContext.None

compiler/src/dotty/tools/dotc/transform/BetaReduce.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ class BetaReduce extends MiniPhase:
4545
if app1 ne app then report.log(i"beta reduce $app -> $app1")
4646
app1
4747

48+
/** Cleanup ifs after reduceEQ */
49+
override def transformIf(tree: If)(using Context): Tree = tree.cond match
50+
case Literal(Constant(true)) => tree.thenp
51+
case Literal(Constant(false)) => tree.elsep
52+
case _ => tree
53+
4854
object BetaReduce:
4955
import ast.tpd.*
5056

@@ -71,6 +77,9 @@ object BetaReduce:
7177
* type X1 = T1; ...; type Xm = Tm;val/def x1 = e1; ...; val/def xn = en; b
7278
*
7379
* This beta-reduction preserves the integrity of `Inlined` tree nodes.
80+
*
81+
* Also, replace some == tests between constants with known outcomes by true/false.
82+
* This is useful since such tests can arise though inlining, e.g. in maybe-translation.scala.
7483
*/
7584
def apply(tree: Tree)(using Context): Tree =
7685
val bindingsBuf = new ListBuffer[DefTree]
@@ -111,7 +120,7 @@ object BetaReduce:
111120
case None =>
112121
tree
113122
case _ =>
114-
tree
123+
inlines.Inliner.reduceEQ(tree)
115124

116125
/** Beta-reduces a call to `ddef` with arguments `args` and registers new bindings.
117126
* @return optionally, the expanded call, or none if the actual argument

0 commit comments

Comments
 (0)