Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add a desugaring pass between the base linker and the optimizer.
Previously, the emitters and the optimizer all had to perform the
same desugaring for `LinkTimeProperty` nodes. Instead, we now
perform the desugaring in a dedicated phase, after the base
linker.

The reachability analysis records whether each method needs
desugaring or not. We mark those that do so that the desugaring
pass knows what to process. Methods that do not require
desugaring are not processed, and so incur no additional cost.

No caching is performed in `Desugarer`. It processes so few
methods that caching makes it (slightly) *slower*.

The machinery is heavy. It definitely outweighs the benefits in
terms of duplication for `LinkTimeProperty` alone. However, the
same machinery will be used to desugar `NewLambda` nodes. This
commit serves as a stepping stone in that direction.
  • Loading branch information
sjrd committed Feb 6, 2025
commit 1987d8726b40405147bad5b4f3ee5bf06452afd3
22 changes: 14 additions & 8 deletions ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -234,14 +234,8 @@ object Transformers {
case jsMethodDef: JSMethodDef =>
transformJSMethodDef(jsMethodDef)

case JSPropertyDef(flags, name, getterBody, setterArgAndBody) =>
JSPropertyDef(
flags,
transform(name),
transformTreeOpt(getterBody),
setterArgAndBody.map { case (arg, body) =>
(arg, transform(body))
})(Unversioned)(jsMethodPropDef.pos)
case jsPropertyDef: JSPropertyDef =>
transformJSPropertyDef(jsPropertyDef)
}
}

Expand All @@ -251,6 +245,18 @@ object Transformers {
jsMethodDef.optimizerHints, Unversioned)(jsMethodDef.pos)
}

def transformJSPropertyDef(jsPropertyDef: JSPropertyDef): JSPropertyDef = {
val JSPropertyDef(flags, name, getterBody, setterArgAndBody) = jsPropertyDef
JSPropertyDef(
flags,
transform(name),
transformTreeOpt(getterBody),
setterArgAndBody.map { case (arg, body) =>
(arg, transform(body))
}
)(Unversioned)(jsPropertyDef.pos)
}

def transformJSConstructorBody(body: JSConstructorBody): JSConstructorBody = {
implicit val pos = body.pos

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ object Analysis {
def methodInfos(
namespace: MemberNamespace): scala.collection.Map[MethodName, MethodInfo]

def anyJSMemberNeedsDesugaring: Boolean

def displayName: String = className.nameString
}

Expand All @@ -103,6 +105,7 @@ object Analysis {
def instantiatedSubclasses: scala.collection.Seq[ClassInfo]
def nonExistent: Boolean
def syntheticKind: MethodSyntheticKind
def needsDesugaring: Boolean

def displayName: String = methodName.displayName

Expand Down Expand Up @@ -161,6 +164,7 @@ object Analysis {
def owningClass: ClassName
def staticDependencies: scala.collection.Set[ClassName]
def externalDependencies: scala.collection.Set[String]
def needsDesugaring: Boolean
}

sealed trait Error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
val publicMethodInfos: mutable.Map[MethodName, MethodInfo] =
methodInfos(MemberNamespace.Public)

def anyJSMemberNeedsDesugaring: Boolean =
data.jsMethodProps.exists(info => (info.globalFlags & ReachabilityInfo.FlagNeedsDesugaring) != 0)

def lookupAbstractMethod(methodName: MethodName): MethodInfo = {
val candidatesIterator = for {
ancestor <- ancestors.iterator
Expand Down Expand Up @@ -1285,6 +1288,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
def isDefaultBridge: Boolean =
syntheticKind.isInstanceOf[MethodSyntheticKind.DefaultBridge]

def needsDesugaring: Boolean =
(data.globalFlags & ReachabilityInfo.FlagNeedsDesugaring) != 0

/** Throws MatchError if `!isDefaultBridge`. */
def defaultBridgeTarget: ClassName = (syntheticKind: @unchecked) match {
case MethodSyntheticKind.DefaultBridge(target) => target
Expand Down Expand Up @@ -1367,6 +1373,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
def staticDependencies: scala.collection.Set[ClassName] = _staticDependencies.keySet
def externalDependencies: scala.collection.Set[String] = _externalDependencies.keySet

def needsDesugaring: Boolean =
(data.reachability.globalFlags & ReachabilityInfo.FlagNeedsDesugaring) != 0

def reach(): Unit = followReachabilityInfo(data.reachability, this)(FromExports)
}

Expand Down Expand Up @@ -1441,7 +1450,7 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean,
}
}

val globalFlags = data.globalFlags
val globalFlags = data.globalFlags & ~ReachabilityInfo.FlagNeedsDesugaring

if (globalFlags != 0) {
if ((globalFlags & ReachabilityInfo.FlagAccessedClassClass) != 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ object Infos {
final val FlagAccessedImportMeta = 1 << 2
final val FlagUsedExponentOperator = 1 << 3
final val FlagUsedClassSuperClass = 1 << 4
final val FlagNeedsDesugaring = 1 << 5
}

/** Things from a given class that are reached by one method. */
Expand Down Expand Up @@ -395,6 +396,7 @@ object Infos {
setFlag(ReachabilityInfo.FlagUsedClassSuperClass)

def addReferencedLinkTimeProperty(linkTimeProperty: LinkTimeProperty): this.type = {
setFlag(ReachabilityInfo.FlagNeedsDesugaring)
linkTimeProperties.append((linkTimeProperty.name, linkTimeProperty.tpe))
this
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1260,9 +1260,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {

def test(tree: Tree): Boolean = tree match {
// Atomic expressions
case _: Literal => true
case _: JSNewTarget => true
case _: LinkTimeProperty => true
case _: Literal => true
case _: JSNewTarget => true

// Vars (side-effect free, pure if immutable)
case VarRef(name) =>
Expand Down Expand Up @@ -2811,11 +2810,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
case AsInstanceOf(expr, tpe) =>
extractWithGlobals(genAsInstanceOf(transformExprNoChar(expr), tpe))

case prop: LinkTimeProperty =>
transformExpr(
config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop),
preserveChar)

// Transients

case Transient(Cast(expr, tpe)) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ object DerivedClasses {
staticDependencies = Set.empty,
externalDependencies = Set.empty,
dynamicDependencies = Set.empty,
desugaringRequirements = LinkedClass.DesugaringRequirements.Empty,
clazz.version
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,6 @@ private class FunctionEmitter private (
case t: Match => genMatch(t, expectedType)
case t: Debugger => VoidType // ignore
case t: Skip => VoidType
case t: LinkTimeProperty => genLinkTimeProperty(t)

// JavaScript expressions
case t: JSNew => genJSNew(t)
Expand Down Expand Up @@ -590,7 +589,7 @@ private class FunctionEmitter private (
// Transients (only generated by the optimizer)
case t: Transient => genTransient(t)

case _: JSSuperConstructorCall =>
case _:JSSuperConstructorCall | _:LinkTimeProperty =>
throw new AssertionError(s"Invalid tree: $tree")
}

Expand Down Expand Up @@ -2649,12 +2648,6 @@ private class FunctionEmitter private (
ClassType(boxClassName, nullable = false)
}

private def genLinkTimeProperty(tree: LinkTimeProperty): Type = {
val lit = ctx.coreSpec.linkTimeProperties.transformLinkTimeProperty(tree)
genLiteral(lit, lit.tpe)
lit.tpe
}

private def genJSNew(tree: JSNew): Type = {
val JSNew(ctor, args) = tree

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ sealed abstract class CheckingPhase
object CheckingPhase {
case object Compiler extends CheckingPhase
case object BaseLinker extends CheckingPhase
case object Desugarer extends CheckingPhase
case object Optimizer extends CheckingPhase
}
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,8 @@ private final class ClassDefChecker(classDef: ClassDef,
}

case LinkTimeProperty(name) =>
if (!featureSet.supports(FeatureSet.LinkTimeProperty))
reportError(i"Illegal link-time property '$name' after desugaring")

// JavaScript expressions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,20 @@ private[checker] object FeatureSet {

// Individual features

/** The `LinkTimeProperty` IR node. */
val LinkTimeProperty = new FeatureSet(1 << 0)

/** Optional constructors in module classes and JS classes. */
val OptionalConstructors = new FeatureSet(1 << 0)
val OptionalConstructors = new FeatureSet(1 << 1)

/** Explicit reflective proxy definitions. */
val ReflectiveProxies = new FeatureSet(1 << 1)
val ReflectiveProxies = new FeatureSet(1 << 2)

/** Transients that are the result of optimizations. */
val OptimizedTransients = new FeatureSet(1 << 2)
val OptimizedTransients = new FeatureSet(1 << 3)

/** Records and record types. */
val Records = new FeatureSet(1 << 3)
val Records = new FeatureSet(1 << 4)

/** Relaxed constructor discipline.
*
Expand All @@ -55,22 +58,31 @@ private[checker] object FeatureSet {
* - `this.x = ...` assignments before the delegate call can assign super class fields.
* - `StoreModule` can be anywhere, or not be there at all.
*/
val RelaxedCtorBodies = new FeatureSet(1 << 4)
val RelaxedCtorBodies = new FeatureSet(1 << 5)

// Common sets

/** Features introduced by the base linker. */
private val Linked =
OptionalConstructors | ReflectiveProxies

/** Features that must be desugared away. */
private val NeedsDesugaring =
LinkTimeProperty

/** IR that is only the result of desugaring (currently empty). */
private val Desugared =
Empty

/** IR that is only the result of optimizations. */
private val Optimized =
OptimizedTransients | Records | RelaxedCtorBodies

/** The set of features allowed as output of the given phase. */
def allowedAfter(phase: CheckingPhase): FeatureSet = phase match {
case Compiler => Empty
case BaseLinker => Linked
case Optimizer => Linked | Optimized
case Compiler => NeedsDesugaring
case BaseLinker => Linked | NeedsDesugaring
case Desugarer => Linked | Desugared
case Optimizer => Linked | Desugared | Optimized
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter,
typecheckAny(expr, env)
checkIsAsInstanceTargetType(tpe)

case LinkTimeProperty(name) =>
case LinkTimeProperty(name) if featureSet.supports(FeatureSet.LinkTimeProperty) =>

// JavaScript expressions

Expand Down Expand Up @@ -760,7 +760,8 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter,
typecheck(elem, env)
}

case _:RecordSelect | _:RecordValue | _:Transient | _:JSSuperConstructorCall =>
case _:RecordSelect | _:RecordValue | _:Transient |
_:JSSuperConstructorCall | _:LinkTimeProperty =>
reportError("invalid tree")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,20 @@ private[frontend] object BaseLinker {
classInfo.isAnySubclassInstantiated
}

val methods = classDef.methods.filter { m =>
val methodInfo =
classInfo.methodInfos(m.flags.namespace)(m.methodName)

val reachable = methodInfo.isReachable
assert(m.body.isDefined || !reachable,
s"The abstract method ${classDef.name.name}.${m.methodName} " +
"is reachable.")

reachable
}
// Will stay empty for most classes
var desugaringRequirements = LinkedClass.DesugaringRequirements.Empty

val methods: List[MethodDef] = classDef.methods.iterator
.map(m => m -> classInfo.methodInfos(m.flags.namespace)(m.methodName))
.filter(_._2.isReachable)
.map { case (m, info) =>
assert(m.body.isDefined,
s"The abstract method ${classDef.name.name}.${m.methodName} is reachable.")
if (info.needsDesugaring)
desugaringRequirements = desugaringRequirements.addMethod(m.flags.namespace, m.methodName)
m
}
.toList

val jsConstructor =
if (classInfo.isAnySubclassInstantiated) classDef.jsConstructor
Expand All @@ -149,6 +152,9 @@ private[frontend] object BaseLinker {
if (classInfo.isAnySubclassInstantiated) classDef.jsMethodProps
else Nil

if (classInfo.anyJSMemberNeedsDesugaring)
desugaringRequirements = desugaringRequirements.addAnyExportedMember()

val jsNativeMembers = classDef.jsNativeMembers
.filter(m => classInfo.jsNativeMembersUsed.contains(m.name.name))

Expand Down Expand Up @@ -181,6 +187,7 @@ private[frontend] object BaseLinker {
staticDependencies = classInfo.staticDependencies.toSet,
externalDependencies = classInfo.externalDependencies.toSet,
dynamicDependencies = classInfo.dynamicDependencies.toSet,
desugaringRequirements,
version)

val linkedTopLevelExports = for {
Expand All @@ -189,7 +196,8 @@ private[frontend] object BaseLinker {
val infos = analysis.topLevelExportInfos(
(ModuleID(topLevelExport.moduleID), topLevelExport.topLevelExportName))
new LinkedTopLevelExport(classDef.className, topLevelExport,
infos.staticDependencies.toSet, infos.externalDependencies.toSet)
infos.staticDependencies.toSet, infos.externalDependencies.toSet,
needsDesugaring = infos.needsDesugaring)
}

(linkedClass, linkedTopLevelExports)
Expand Down
Loading