Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Scala.js (https://www.scala-js.org/)
*
* Copyright EPFL.
*
* Licensed under Apache License 2.0
* (https://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package org.scalajs.linker.backend.emitter
import scala.annotation.tailrec
import scala.collection.mutable
import org.scalajs.ir.{ClassKind, Position, Version}
import org.scalajs.ir.Names._
import org.scalajs.ir.OriginalName.NoOriginalName
import org.scalajs.ir.Trees.{JSNativeLoadSpec, MemberNamespace}
import org.scalajs.logging._
import org.scalajs.linker.interface._
import org.scalajs.linker.standard._
import org.scalajs.linker.standard.ModuleSet.ModuleID
import org.scalajs.linker.backend.javascript.{Trees => js, _}
import org.scalajs.linker.CollectionsCompat.MutableMapCompatOps
import EmitterNames._
import GlobalRefUtils._
/** Emits a desugared JS tree to a builder */
final class Emitter[E >: Null <: js.Tree](
config: Emitter.Config, postTransformer: Emitter.PostTransformer[E]) {
import Emitter._
import config._
require(!config.minify || postTransformer == PostTransformer.Identity,
"When using the 'minify' option, the postTransformer must be Identity.")
private implicit val globalRefTracking: GlobalRefTracking =
config.topLevelGlobalRefTracking
private val knowledgeGuardian = new KnowledgeGuardian(config)
private val uncachedKnowledge = new knowledgeGuardian.KnowledgeAccessor {}
private val nameGen: NameGen = new NameGen
private class State(val lastMentionedDangerousGlobalRefs: Set[String]) {
val nameCompressor =
if (minify) Some(new NameCompressor(config))
else None
val sjsGen: SJSGen = {
val jsGen = new JSGen(config)
val varGen = new VarGen(jsGen, nameGen, lastMentionedDangerousGlobalRefs)
new SJSGen(jsGen, nameGen, varGen, nameCompressor)
}
val classEmitter: ClassEmitter = new ClassEmitter(sjsGen)
val everyFileStart: List[E] = {
// This postTransform does not count in the statistics
postTransformer.transformStats(sjsGen.declarePrototypeVar, 0)
}
val coreJSLibCache: CoreJSLibCache = new CoreJSLibCache
val moduleCaches: mutable.Map[ModuleID, ModuleCache] = mutable.Map.empty
val classCaches: mutable.Map[ClassID, ClassCache] = mutable.Map.empty
}
private var state: State = new State(Set.empty)
private def jsGen: JSGen = state.sjsGen.jsGen
private def sjsGen: SJSGen = state.sjsGen
private def classEmitter: ClassEmitter = state.classEmitter
private def classCaches: mutable.Map[ClassID, ClassCache] = state.classCaches
private[this] var statsClassesReused: Int = 0
private[this] var statsClassesInvalidated: Int = 0
private[this] var statsMethodsReused: Int = 0
private[this] var statsMethodsInvalidated: Int = 0
private[this] var statsPostTransforms: Int = 0
private[this] var statsNestedPostTransforms: Int = 0
private[this] var statsNestedPostTransformsAvoided: Int = 0
val symbolRequirements: SymbolRequirement =
Emitter.symbolRequirements(config)
val injectedIRFiles: Seq[IRFile] = PrivateLibHolder.files
def emit(moduleSet: ModuleSet, logger: Logger): Result[E] = {
val WithGlobals(body, globalRefs) = emitInternal(moduleSet, logger)
val result = moduleKind match {
case ModuleKind.NoModule =>
assert(moduleSet.modules.size <= 1)
val topLevelVars = moduleSet.modules
.headOption.toList
.flatMap(_.topLevelExports)
.map(_.exportName)
val header = {
val maybeTopLevelVarDecls = if (topLevelVars.nonEmpty) {
val kw = if (esFeatures.useECMAScript2015Semantics) "let " else "var "
topLevelVars.mkString(kw, ",", ";\n")
} else {
""
}
config.jsHeader + maybeTopLevelVarDecls + "(function(){\n"
}
val footer = "}).call(this);\n"
new Result(header, body, footer, topLevelVars, globalRefs)
case ModuleKind.ESModule | ModuleKind.CommonJSModule =>
new Result(config.jsHeader, body, "", Nil, globalRefs)
}
for (compressor <- state.nameCompressor) {
compressor.allocateNames(moduleSet, logger)
/* Throw away the whole state, but keep the mentioned dangerous global refs.
* Note that instances of the name compressor's entries are still alive
* at this point, since they are referenced from `DelayedIdent` nodes in
* the result trees.
*/
state = new State(state.lastMentionedDangerousGlobalRefs)
}
result
}
private def emitInternal(moduleSet: ModuleSet,
logger: Logger): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = {
// Reset caching stats.
statsClassesReused = 0
statsClassesInvalidated = 0
statsMethodsReused = 0
statsMethodsInvalidated = 0
statsPostTransforms = 0
statsNestedPostTransforms = 0
statsNestedPostTransformsAvoided = 0
// Update GlobalKnowledge.
val invalidateAll = knowledgeGuardian.update(moduleSet)
if (invalidateAll) {
state.coreJSLibCache.invalidate()
classCaches.clear()
}
// Inform caches about new run.
classCaches.valuesIterator.foreach(_.startRun())
try {
emitAvoidGlobalClash(moduleSet, logger, secondAttempt = false)
} finally {
// Report caching stats (extracted in EmitterTest).
logger.debug(
s"Emitter: Class tree cache stats: reused: $statsClassesReused -- "+
s"invalidated: $statsClassesInvalidated")
logger.debug(
s"Emitter: Method tree cache stats: reused: $statsMethodsReused -- "+
s"invalidated: $statsMethodsInvalidated")
logger.debug(
s"Emitter: Post transforms: total: $statsPostTransforms -- " +
s"nested: $statsNestedPostTransforms -- " +
s"nested avoided: $statsNestedPostTransformsAvoided")
// Inform caches about run completion.
state.moduleCaches.filterInPlace((_, c) => c.cleanAfterRun())
classCaches.filterInPlace((_, c) => c.cleanAfterRun())
}
}
private def postTransform(trees: List[js.Tree], indent: Int): List[E] = {
statsPostTransforms += 1
postTransformer.transformStats(trees, indent)
}
private def postTransform(tree: js.Tree, indent: Int): List[E] =
postTransform(tree :: Nil, indent)
/** Emits all JavaScript code avoiding clashes with global refs.
*
* If, at the end of the process, the set of accessed dangerous globals has
* changed, invalidate *everything* and start over. If at first you don't
* succeed, ...
*/
@tailrec
private def emitAvoidGlobalClash(moduleSet: ModuleSet,
logger: Logger, secondAttempt: Boolean): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = {
val result = emitOnce(moduleSet, logger)
val mentionedDangerousGlobalRefs =
GlobalRefTracking.Dangerous.refineFrom(topLevelGlobalRefTracking, result.globalVarNames)
if (mentionedDangerousGlobalRefs == state.lastMentionedDangerousGlobalRefs) {
result
} else {
assert(!secondAttempt,
"Uh oh! The second attempt gave a different set of dangerous " +
"global refs than the first one.\n" +
"Before:" + state.lastMentionedDangerousGlobalRefs.toList.sorted.mkString("\n ", "\n ", "\n") +
"After:" + mentionedDangerousGlobalRefs.toList.sorted.mkString("\n ", "\n ", ""))
// !!! This log message is tested in EmitterTest
logger.debug(
"Emitter: The set of dangerous global refs has changed. " +
"Going to re-generate the world.")
state = new State(mentionedDangerousGlobalRefs)
emitAvoidGlobalClash(moduleSet, logger, secondAttempt = true)
}
}
private def emitOnce(moduleSet: ModuleSet,
logger: Logger): WithGlobals[Map[ModuleID, (List[E], Boolean)]] = {
// Genreate classes first so we can measure time separately.
val generatedClasses = logger.time("Emitter: Generate Classes") {
moduleSet.modules.map { module =>
val moduleContext = ModuleContext.fromModule(module)
val orderedClasses = module.classDefs.sortWith(compareClasses)
module.id -> orderedClasses.map(genClass(_, moduleContext))
}.toMap
}
var trackedGlobalRefs = Set.empty[String]
def extractWithGlobals[T](x: WithGlobals[T]) = {
trackedGlobalRefs = unionPreserveEmpty(trackedGlobalRefs, x.globalVarNames)
x.value
}
val moduleTrees = logger.time("Emitter: Write trees") {
moduleSet.modules.map { module =>
var changed = false
def extractChangedAndWithGlobals[T](x: (WithGlobals[T], Boolean)): T = {
changed ||= x._2
extractWithGlobals(x._1)
}
val moduleContext = ModuleContext.fromModule(module)
val moduleCache = state.moduleCaches.getOrElseUpdate(module.id, new ModuleCache)
val moduleClasses = generatedClasses(module.id)
changed ||= moduleClasses.exists(_.changed)
val moduleImports = extractChangedAndWithGlobals {
moduleCache.getOrComputeImports(module.externalDependencies, module.internalDependencies) {
genModuleImports(module).map(postTransform(_, 0))
}
}
val topLevelExports = extractChangedAndWithGlobals {
/* We cache top level exports all together, rather than individually,
* since typically there are few.
*/
moduleCache.getOrComputeTopLevelExports(module.topLevelExports) {
classEmitter.genTopLevelExports(module.topLevelExports)(
moduleContext, moduleCache).map(postTransform(_, 0))
}
}
val moduleInitializers = extractChangedAndWithGlobals {
val initializers = module.initializers.toList
moduleCache.getOrComputeInitializers(initializers) {
WithGlobals.list(initializers.map { initializer =>
classEmitter.genModuleInitializer(initializer)(
moduleContext, moduleCache)
}).map(postTransform(_, 0))
}
}
val coreJSLib =
if (module.isRoot) Some(extractWithGlobals(state.coreJSLibCache.build(moduleContext)))
else None
def classIter = moduleClasses.iterator
def objectClass =
if (!module.isRoot) Iterator.empty
else classIter.filter(_.className == ObjectClass)
/* Emit everything but module imports in the appropriate order.
*
* We do not emit module imports to be able to assert that the
* resulting module is non-empty. This is a non-trivial condition that
* requires consistency between the Analyzer and the Emitter. As such,
* it is crucial that we verify it.
*/
val defTrees: List[E] = (
/* The declaration of the `$p` variable that temporarily holds
* prototypes.
*/
state.everyFileStart.iterator ++
/* The definitions of the CoreJSLib that come before the definition
* of `j.l.Object`. They depend on nothing else.
*/
coreJSLib.iterator.flatMap(_.preObjectDefinitions) ++
/* The definition of `j.l.Object` class. Unlike other classes, this
* does not include its instance tests nor metadata.
*/
objectClass.flatMap(_.main) ++
/* The definitions of the CoreJSLib that come after the definition
* of `j.l.Object` because they depend on it. This includes the
* definitions of the array classes, as well as type data for
* primitive types and for `j.l.Object`.
*/
coreJSLib.iterator.flatMap(_.postObjectDefinitions) ++
/* All class definitions, except `j.l.Object`, which depend on
* nothing but their superclasses.
*/
classIter.filterNot(_.className == ObjectClass).flatMap(_.main) ++
/* The initialization of the CoreJSLib, which depends on the
* definition of classes (n.b. the RuntimeLong class).
*/
coreJSLib.iterator.flatMap(_.initialization) ++
/* All static field definitions, which depend on nothing, except
* those of type Long which need $L0.
*/
classIter.flatMap(_.staticFields) ++
/* All static initializers, which in the worst case can observe some
* "zero" state of other static field definitions, but must not
* observe a *non-initialized* (undefined) state.
*/
classIter.flatMap(_.staticInitialization) ++
/* All the exports, during which some JS class creation can happen,
* causing JS static initializers to run. Those also must not observe
* a non-initialized state of other static fields.
*/
topLevelExports.iterator ++
/* Module initializers, which by spec run at the end. */
moduleInitializers.iterator
).toList
// Make sure that there is at least one non-import definition.
assert(!defTrees.isEmpty, {
val classNames = module.classDefs.map(_.fullName).mkString(", ")
s"Module ${module.id} is empty. Classes in this module: $classNames"
})
/* Add module imports, which depend on nothing, at the front.
* All classes potentially depend on them.
*/
val allTrees = moduleImports ::: defTrees
classIter.foreach { genClass =>
trackedGlobalRefs = unionPreserveEmpty(trackedGlobalRefs, genClass.trackedGlobalRefs)
}
module.id -> (allTrees, changed)
}
}
WithGlobals(moduleTrees.toMap, trackedGlobalRefs)
}
private def genModuleImports(module: ModuleSet.Module): WithGlobals[List[js.Tree]] = {
implicit val pos = Position.NoPosition
def importParts = (
(
module.externalDependencies.map { x =>
sjsGen.varGen.externalModuleFieldIdent(x) -> x
}
) ++ (
module.internalDependencies.map { x =>
sjsGen.varGen.internalModuleFieldIdent(x) -> config.internalModulePattern(x)
}
)
).toList.sortBy(_._1.name)
moduleKind match {
case ModuleKind.NoModule =>
WithGlobals.nil
case ModuleKind.ESModule =>
val imports = importParts.map { case (ident, moduleName) =>
val from = js.StringLiteral(moduleName)
js.ImportNamespace(ident, from)
}
WithGlobals(imports)
case ModuleKind.CommonJSModule =>
val imports = importParts.map { case (ident, moduleName) =>
for (requireRef <- jsGen.globalRef("require")) yield {
val rhs = js.Apply(requireRef, List(js.StringLiteral(moduleName)))
jsGen.genLet(ident, mutable = false, rhs)
}
}
WithGlobals.list(imports)
}
}
private def compareClasses(lhs: LinkedClass, rhs: LinkedClass) = {
val lhsAC = lhs.ancestors.size
val rhsAC = rhs.ancestors.size
if (lhsAC != rhsAC) lhsAC < rhsAC
else lhs.className.compareTo(rhs.className) < 0
}
private def genClass(linkedClass: LinkedClass,
moduleContext: ModuleContext): GeneratedClass[E] = {
val className = linkedClass.className
val classCache = classCaches.getOrElseUpdate(
new ClassID(linkedClass.ancestors, moduleContext), new ClassCache)
var changed = false
def extractChanged[T](x: (T, Boolean)): T = {
changed ||= x._2
x._1
}
val classTreeCache =
extractChanged(classCache.getCache(linkedClass.version))
val kind = linkedClass.kind
// Global ref management
var trackedGlobalRefs: Set[String] = Set.empty
def extractWithGlobals[T](withGlobals: WithGlobals[T]): T = {
trackedGlobalRefs = unionPreserveEmpty(trackedGlobalRefs, withGlobals.globalVarNames)
withGlobals.value
}
def extractWithGlobalsAndChanged[T](x: (WithGlobals[T], Boolean)): T =
extractWithGlobals(extractChanged(x))
// Main part
val main = List.newBuilder[E]
val (linkedInlineableInit, linkedMethods) =
classEmitter.extractInlineableInit(linkedClass)(classCache)
// Symbols for private JS fields
if (kind.isJSClass) {
val fieldDefs = classTreeCache.privateJSFields.getOrElseUpdate {
classEmitter.genCreatePrivateJSFieldDefsOfJSClass(className)(
moduleContext, classCache).map(postTransform(_, 0))
}
main ++= extractWithGlobals(fieldDefs)
}
// Static-like methods
for (methodDef <- linkedMethods) {
val namespace = methodDef.flags.namespace
val emitAsStaticLike = {
namespace != MemberNamespace.Public ||
kind == ClassKind.Interface ||
kind == ClassKind.HijackedClass
}
if (emitAsStaticLike) {
val methodCache =
classCache.getStaticLikeMethodCache(namespace, methodDef.methodName)
main ++= extractWithGlobalsAndChanged(methodCache.getOrElseUpdate(methodDef.version, {
classEmitter.genStaticLikeMethod(className, methodDef)(moduleContext, methodCache)
.map(postTransform(_, 0))
}))
}
}
val isJSClass = kind.isJSClass
// Class definition
if (linkedClass.hasInstances && kind.isAnyNonNativeClass) {
val isJSClassVersion = Version.fromByte(if (isJSClass) 1 else 0)
/* Is this class compiled as an ECMAScript `class`?
*
* See JSGen.useClassesForRegularClasses for the rationale here.
* Accessing `ancestors` without cache invalidation is fine because it
* is part of the identity of a class for its cache in the first place.
*
* Note that `useClassesForRegularClasses` implies
* `useClassesForJSClassesAndThrowables`, so the short-cut is valid.
*
* Compared to `ClassEmitter.shouldExtendJSError`, which is used below,
* we do not check here that `Throwable` directly extends `Object`. If
* that is not the case (for some obscure reason), then we are going to
* uselessly emit `class`es for Throwables, but that will not make any
* observable change; whereas rewiring Throwable to extend `Error` when
* it does not actually directly extend `Object` would break everything,
* so we need to be more careful there.
*
* For caching, isJSClassVersion can be used to guard use of `useESClass`:
* it is the only "dynamic" value it depends on. The rest is configuration
* or part of the cache key (ancestors).
*/
val useESClass = if (jsGen.useClassesForRegularClasses) {
assert(jsGen.useClassesForJSClassesAndThrowables)
true
} else {
jsGen.useClassesForJSClassesAndThrowables &&
(isJSClass || linkedClass.ancestors.contains(ThrowableClass))
}
val memberIndent = {
(if (isJSClass) 1 else 0) + // accessor function
(if (useESClass) 1 else 0) // nesting from class
}
val hasJSSuperClass = linkedClass.jsSuperClass.isDefined
val storeJSSuperClass = if (hasJSSuperClass) {
extractWithGlobals(classTreeCache.storeJSSuperClass.getOrElseUpdate({
val jsSuperClass = linkedClass.jsSuperClass.get
classEmitter.genStoreJSSuperClass(jsSuperClass)(moduleContext, classCache, linkedClass.pos)
.map(postTransform(_, 1))
}))
} else {
Nil
}
// JS constructor
val ctorWithGlobals = extractChanged {
/* The constructor depends both on the class version, and the version
* of the inlineable init, if there is one.
*
* If it is a JS class, it depends on the jsConstructorDef.
*/
val ctorCache = classCache.getConstructorCache()
if (isJSClass) {
assert(linkedInlineableInit.isEmpty)
val jsConstructorDef = linkedClass.jsConstructorDef.getOrElse {
throw new IllegalArgumentException(s"$className does not have an exported constructor")
}
val ctorVersion = Version.combine(linkedClass.version, jsConstructorDef.version)
ctorCache.getOrElseUpdate(ctorVersion,
classEmitter.genJSConstructor(
className, // invalidated by overall class cache (part of ancestors)
linkedClass.superClass, // invalidated by class version
hasJSSuperClass, // invalidated by class version
useESClass, // invalidated by class version
jsConstructorDef // part of ctor version
)(moduleContext, ctorCache, linkedClass.pos).map(postTransform(_, memberIndent)))
} else {
val ctorVersion = linkedInlineableInit.fold {
Version.combine(linkedClass.version)
} { linkedInit =>
Version.combine(linkedClass.version, linkedInit.version)
}
ctorCache.getOrElseUpdate(ctorVersion,
classEmitter.genScalaClassConstructor(
className, // invalidated by overall class cache (part of ancestors)
linkedClass.superClass, // invalidated by class version
useESClass, // invalidated by class version,
linkedInlineableInit // part of ctor version
)(moduleContext, ctorCache, linkedClass.pos).map(postTransform(_, memberIndent)))
}
}
/* Bridges from Throwable to methods of Object, which are necessary
* because Throwable is rewired to extend JavaScript's Error instead of
* j.l.Object.
*/
val linkedMethodsAndBridges = if (ClassEmitter.shouldExtendJSError(className, linkedClass.superClass)) {
val existingMethods = linkedMethods
.withFilter(_.flags.namespace == MemberNamespace.Public)
.map(_.methodName)
.toSet
val bridges = for {
methodDef <- uncachedKnowledge.methodsInObject()
if !existingMethods.contains(methodDef.methodName)
} yield {
import org.scalajs.ir.Trees._
import org.scalajs.ir.Types._
implicit val pos = methodDef.pos
val methodName = methodDef.name
val newBody = ApplyStatically(ApplyFlags.empty,
This()(ClassType(className)), ObjectClass, methodName,
methodDef.args.map(_.ref))(
methodDef.resultType)
MethodDef(MemberFlags.empty, methodName,
methodDef.originalName, methodDef.args, methodDef.resultType,
Some(newBody))(
OptimizerHints.empty, methodDef.version)
}
linkedMethods ++ bridges
} else {
linkedMethods
}
// Normal methods
val memberMethodsWithGlobals = for {
method <- linkedMethodsAndBridges
if method.flags.namespace == MemberNamespace.Public
} yield {
val methodCache =
classCache.getMemberMethodCache(method.methodName)
val version = Version.combine(isJSClassVersion, method.version)
extractChanged(methodCache.getOrElseUpdate(version,
classEmitter.genMemberMethod(
className, // invalidated by overall class cache
isJSClass, // invalidated by isJSClassVersion
useESClass, // invalidated by isJSClassVersion
method // invalidated by method.version
)(moduleContext, methodCache).map(postTransform(_, memberIndent))))
}
// Exported Members
val exportedMembersWithGlobals = for {
(member, idx) <- linkedClass.exportedMembers.zipWithIndex
} yield {
val memberCache = classCache.getExportedMemberCache(idx)
val version = Version.combine(isJSClassVersion, member.version)
extractChanged(memberCache.getOrElseUpdate(version,
classEmitter.genExportedMember(
className, // invalidated by overall class cache
isJSClass, // invalidated by isJSClassVersion
useESClass, // invalidated by isJSClassVersion
member // invalidated by version
)(moduleContext, memberCache).map(postTransform(_, memberIndent))))
}
val hasClassInitializer: Boolean = {
linkedClass.methods.exists { m =>
m.flags.namespace == MemberNamespace.StaticConstructor &&
m.methodName.isClassInitializer
}
}
val fullClass = {
val fullClassChangeTracker = classCache.getFullClassChangeTracker()
// Put changed state into a val to avoid short circuiting behavior of ||.
val classChanged = fullClassChangeTracker.trackChanged(
linkedClass.version, ctorWithGlobals,
memberMethodsWithGlobals, exportedMembersWithGlobals)
changed ||= classChanged
for {
ctor <- ctorWithGlobals
memberMethods <- WithGlobals.flatten(memberMethodsWithGlobals)
exportedMembers <- WithGlobals.flatten(exportedMembersWithGlobals)
allMembers = ctor ::: memberMethods ::: exportedMembers
clazz <- classEmitter.buildClass(
className, // invalidated by overall class cache (part of ancestors)
isJSClass, // invalidated by class version
linkedClass.jsClassCaptures, // invalidated by class version
hasClassInitializer, // invalidated by class version (optimizer cannot remove it)
linkedClass.superClass, // invalidated by class version
storeJSSuperClass, // invalidated by class version
useESClass, // invalidated by class version (depends on kind, config and ancestry only)
allMembers // invalidated directly
)(moduleContext, fullClassChangeTracker, linkedClass.pos) // pos invalidated by class version
} yield {
// Avoid a nested post transform if we just got the original members back.
if (clazz eq allMembers) {
statsNestedPostTransformsAvoided += 1
allMembers
} else {
statsNestedPostTransforms += 1
postTransform(clazz, 0)
}
}
}
main ++= extractWithGlobals(fullClass)
}
if (className != ObjectClass) {
/* Instance tests and type data are hardcoded in the CoreJSLib for
* j.l.Object. This is important because their definitions depend on the
* `$TypeData` definition, which only comes in the `postObjectDefinitions`
* of the CoreJSLib. If we wanted to define them here as part of the
* normal logic of `ClassEmitter`, we would have to further divide `main`
* into two parts. Since the code paths are in fact completely different
* for `j.l.Object` anyway, we do not do this, and instead hard-code them
* in the CoreJSLib. This explains why we exclude `j.l.Object` as this
* level, rather than inside `ClassEmitter.needInstanceTests` and
* similar: it is a concern that goes beyond the organization of the
* class `j.l.Object`.
*/
if (classEmitter.needInstanceTests(linkedClass)(classCache)) {
main ++= extractWithGlobals(classTreeCache.instanceTests.getOrElseUpdate({
classEmitter.genInstanceTests(className, kind)(moduleContext, classCache, linkedClass.pos)
.map(postTransform(_, 0))
}))
}
if (linkedClass.hasRuntimeTypeInfo) {
main ++= extractWithGlobals(classTreeCache.typeData.getOrElseUpdate(
linkedClass.hasDirectInstances,
classEmitter.genTypeData(
className, // invalidated by overall class cache (part of ancestors)
kind, // invalidated by class version
linkedClass.superClass, // invalidated by class version
linkedClass.ancestors, // invalidated by overall class cache (identity)
linkedClass.jsNativeLoadSpec, // invalidated by class version
linkedClass.hasDirectInstances // invalidated directly (it is the input to `getOrElseUpdate`)
)(moduleContext, classCache, linkedClass.pos).map(postTransform(_, 0))))
}
}
if (linkedClass.kind.hasModuleAccessor && linkedClass.hasInstances) {
main ++= extractWithGlobals(classTreeCache.moduleAccessor.getOrElseUpdate({
classEmitter.genModuleAccessor(className, isJSClass)(moduleContext, classCache, linkedClass.pos)
.map(postTransform(_, 0))
}))
}
// Static fields
val staticFields = if (linkedClass.kind.isJSType) {
Nil
} else {
extractWithGlobals(classTreeCache.staticFields.getOrElseUpdate({
classEmitter.genCreateStaticFieldsOfScalaClass(className)(moduleContext, classCache)
.map(postTransform(_, 0))
}))
}
// Static initialization
val staticInitialization = if (classEmitter.needStaticInitialization(linkedClass)) {
classTreeCache.staticInitialization.getOrElseUpdate({
val tree = classEmitter.genStaticInitialization(className)(moduleContext, classCache, linkedClass.pos)
postTransform(tree, 0)
})
} else {
Nil
}
// Build the result
new GeneratedClass(
className,
main.result(),
staticFields,
staticInitialization,
trackedGlobalRefs,
changed
)
}
// Caching
private final class ModuleCache extends knowledgeGuardian.KnowledgeAccessor {
private[this] var _cacheUsed: Boolean = false
private[this] var _importsCache: WithGlobals[List[E]] = WithGlobals.nil
private[this] var _lastExternalDependencies: Set[String] = Set.empty
private[this] var _lastInternalDependencies: Set[ModuleID] = Set.empty
private[this] var _topLevelExportsCache: WithGlobals[List[E]] = WithGlobals.nil
private[this] var _lastTopLevelExports: List[LinkedTopLevelExport] = Nil
private[this] var _initializersCache: WithGlobals[List[E]] = WithGlobals.nil
private[this] var _lastInitializers: List[ModuleInitializer.Initializer] = Nil
override def invalidate(): Unit = {
super.invalidate()
/* In order to keep reasoning as local as possible, we also invalidate
* the imports cache, although imports do not use any global knowledge.
*/
_importsCache = WithGlobals.nil
_lastExternalDependencies = Set.empty
_lastInternalDependencies = Set.empty
_topLevelExportsCache = WithGlobals.nil
_lastTopLevelExports = Nil
_initializersCache = WithGlobals.nil
_lastInitializers = Nil
}
def getOrComputeImports(externalDependencies: Set[String], internalDependencies: Set[ModuleID])(
compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = {
_cacheUsed = true
if (externalDependencies != _lastExternalDependencies || internalDependencies != _lastInternalDependencies) {
_importsCache = compute
_lastExternalDependencies = externalDependencies
_lastInternalDependencies = internalDependencies
(_importsCache, true)
} else {
(_importsCache, false)
}
}
def getOrComputeTopLevelExports(topLevelExports: List[LinkedTopLevelExport])(
compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = {
_cacheUsed = true
if (!sameTopLevelExports(topLevelExports, _lastTopLevelExports)) {
_topLevelExportsCache = compute
_lastTopLevelExports = topLevelExports
(_topLevelExportsCache, true)
} else {
(_topLevelExportsCache, false)
}
}
private def sameTopLevelExports(tles1: List[LinkedTopLevelExport], tles2: List[LinkedTopLevelExport]): Boolean = {
import org.scalajs.ir.Trees._
/* Because of how/when we use this method, we already know that all the
* `tles1` and `tles2` have the same `moduleID` (namely the ID of the
* module represented by this `ModuleCache`). Therefore, we do not
* compare that field.
*/
tles1.corresponds(tles2) { (tle1, tle2) =>
tle1.tree.pos == tle2.tree.pos && tle1.owningClass == tle2.owningClass && {
(tle1.tree, tle2.tree) match {
case (TopLevelJSClassExportDef(_, exportName1), TopLevelJSClassExportDef(_, exportName2)) =>
exportName1 == exportName2
case (TopLevelModuleExportDef(_, exportName1), TopLevelModuleExportDef(_, exportName2)) =>
exportName1 == exportName2
case (TopLevelMethodExportDef(_, methodDef1), TopLevelMethodExportDef(_, methodDef2)) =>
methodDef1.version.sameVersion(methodDef2.version)
case (TopLevelFieldExportDef(_, exportName1, field1), TopLevelFieldExportDef(_, exportName2, field2)) =>
exportName1 == exportName2 && field1.name == field2.name && field1.pos == field2.pos
case _ =>
false
}
}
}
}
def getOrComputeInitializers(initializers: List[ModuleInitializer.Initializer])(
compute: => WithGlobals[List[E]]): (WithGlobals[List[E]], Boolean) = {
_cacheUsed = true
if (initializers != _lastInitializers) {
_initializersCache = compute
_lastInitializers = initializers
(_initializersCache, true)
} else {
(_initializersCache, false)
}
}
def cleanAfterRun(): Boolean = {
val result = _cacheUsed
_cacheUsed = false
result
}
}
private final class ClassCache extends knowledgeGuardian.KnowledgeAccessor {
private[this] var _cache: DesugaredClassCache[List[E]] = null
private[this] var _lastVersion: Version = Version.Unversioned
private[this] var _cacheUsed = false
private[this] val _methodCaches =
Array.fill(MemberNamespace.Count)(mutable.Map.empty[MethodName, MethodCache[List[E]]])
private[this] val _memberMethodCache =
mutable.Map.empty[MethodName, MethodCache[List[E]]]
private[this] var _constructorCache: Option[MethodCache[List[E]]] = None
private[this] val _exportedMembersCache =
mutable.Map.empty[Int, MethodCache[List[E]]]
private[this] var _fullClassChangeTracker: Option[FullClassChangeTracker] = None
override def invalidate(): Unit = {
/* Do not invalidate contained methods, as they have their own
* invalidation logic.
*/
super.invalidate()
_cache = null
_lastVersion = Version.Unversioned
}
def startRun(): Unit = {
_cacheUsed = false
_methodCaches.foreach(_.valuesIterator.foreach(_.startRun()))
_memberMethodCache.valuesIterator.foreach(_.startRun())
_constructorCache.foreach(_.startRun())
_fullClassChangeTracker.foreach(_.startRun())
}
def getCache(version: Version): (DesugaredClassCache[List[E]], Boolean) = {
_cacheUsed = true
if (_cache == null || !_lastVersion.sameVersion(version)) {
invalidate()
statsClassesInvalidated += 1
_lastVersion = version
_cache = new DesugaredClassCache[List[E]]
(_cache, true)
} else {
statsClassesReused += 1
(_cache, false)
}
}
def getMemberMethodCache(
methodName: MethodName): MethodCache[List[E]] = {
_memberMethodCache.getOrElseUpdate(methodName, new MethodCache)
}
def getStaticLikeMethodCache(namespace: MemberNamespace,
methodName: MethodName): MethodCache[List[E]] = {
_methodCaches(namespace.ordinal)
.getOrElseUpdate(methodName, new MethodCache)
}
def getConstructorCache(): MethodCache[List[E]] = {
_constructorCache.getOrElse {
val cache = new MethodCache[List[E]]
_constructorCache = Some(cache)
cache
}
}
def getExportedMemberCache(idx: Int): MethodCache[List[E]] =
_exportedMembersCache.getOrElseUpdate(idx, new MethodCache)
def getFullClassChangeTracker(): FullClassChangeTracker = {
_fullClassChangeTracker.getOrElse {
val cache = new FullClassChangeTracker
_fullClassChangeTracker = Some(cache)
cache
}
}
def cleanAfterRun(): Boolean = {
_methodCaches.foreach(_.filterInPlace((_, c) => c.cleanAfterRun()))
_memberMethodCache.filterInPlace((_, c) => c.cleanAfterRun())
if (_constructorCache.exists(!_.cleanAfterRun()))
_constructorCache = None
_exportedMembersCache.filterInPlace((_, c) => c.cleanAfterRun())
if (_fullClassChangeTracker.exists(!_.cleanAfterRun()))
_fullClassChangeTracker = None
if (!_cacheUsed)
invalidate()
_methodCaches.exists(_.nonEmpty) || _cacheUsed
}
}
private final class MethodCache[T] extends knowledgeGuardian.KnowledgeAccessor {
private[this] var _tree: WithGlobals[T] = null
private[this] var _lastVersion: Version = Version.Unversioned
private[this] var _cacheUsed = false
override def invalidate(): Unit = {
super.invalidate()
_tree = null
_lastVersion = Version.Unversioned
}
def startRun(): Unit = _cacheUsed = false
def getOrElseUpdate(version: Version,
v: => WithGlobals[T]): (WithGlobals[T], Boolean) = {
_cacheUsed = true
if (_tree == null || !_lastVersion.sameVersion(version)) {
invalidate()
statsMethodsInvalidated += 1
_tree = v
_lastVersion = version
(_tree, true)
} else {
statsMethodsReused += 1
(_tree, false)
}
}
def cleanAfterRun(): Boolean = {
if (!_cacheUsed)
invalidate()
_cacheUsed
}
}
private class FullClassChangeTracker extends knowledgeGuardian.KnowledgeAccessor {
private[this] var _lastVersion: Version = Version.Unversioned
private[this] var _lastCtor: WithGlobals[List[E]] = null
private[this] var _lastMemberMethods: List[WithGlobals[List[E]]] = null
private[this] var _lastExportedMembers: List[WithGlobals[List[E]]] = null
private[this] var _trackerUsed = false
override def invalidate(): Unit = {
super.invalidate()
_lastVersion = Version.Unversioned
_lastCtor = null
_lastMemberMethods = null
_lastExportedMembers = null
}
def startRun(): Unit = _trackerUsed = false
def trackChanged(version: Version, ctor: WithGlobals[List[E]],
memberMethods: List[WithGlobals[List[E]]],
exportedMembers: List[WithGlobals[List[E]]]): Boolean = {
@tailrec
def allSame[A <: AnyRef](xs: List[A], ys: List[A]): Boolean = {
xs.isEmpty == ys.isEmpty && {
xs.isEmpty ||
((xs.head eq ys.head) && allSame(xs.tail, ys.tail))
}
}
_trackerUsed = true
val changed = {
!version.sameVersion(_lastVersion) ||
(_lastCtor ne ctor) ||
!allSame(_lastMemberMethods, memberMethods) ||
!allSame(_lastExportedMembers, exportedMembers)
}
if (changed) {
// Input has changed or we were invalidated.
// Clean knowledge tracking and re-track dependencies.
invalidate()
_lastVersion = version
_lastCtor = ctor
_lastMemberMethods = memberMethods
_lastExportedMembers = exportedMembers
}
changed
}
def cleanAfterRun(): Boolean = {
if (!_trackerUsed)
invalidate()
_trackerUsed
}
}
private class CoreJSLibCache extends knowledgeGuardian.KnowledgeAccessor {
private[this] var _lastModuleContext: ModuleContext = _
private[this] var _lib: WithGlobals[CoreJSLib.Lib[List[E]]] = _
def build(moduleContext: ModuleContext): WithGlobals[CoreJSLib.Lib[List[E]]] = {
if (_lib == null || _lastModuleContext != moduleContext) {
_lib = CoreJSLib.build(sjsGen, postTransform(_, 0), moduleContext, this)
_lastModuleContext = moduleContext
}
_lib
}
override def invalidate(): Unit = {
super.invalidate()
_lib = null
}
}
}
object Emitter {
/** Result of an emitter run. */
final class Result[E] private[Emitter](
val header: String,
val body: Map[ModuleID, (List[E], Boolean)],
val footer: String,
val topLevelVarDecls: List[String],
val globalRefs: Set[String]
)
/** Configuration for the Emitter. */
final class Config private (
val semantics: Semantics,
val moduleKind: ModuleKind,
val esFeatures: ESFeatures,
val jsHeader: String,
val internalModulePattern: ModuleID => String,
val optimizeBracketSelects: Boolean,
val trackAllGlobalRefs: Boolean,
val minify: Boolean
) {
private def this(
semantics: Semantics,
moduleKind: ModuleKind,
esFeatures: ESFeatures) = {
this(
semantics,
moduleKind,
esFeatures,
jsHeader = "",
internalModulePattern = "./" + _.id,
optimizeBracketSelects = true,
trackAllGlobalRefs = false,
minify = false
)
}
private[emitter] val topLevelGlobalRefTracking: GlobalRefTracking =
if (trackAllGlobalRefs) GlobalRefTracking.All
else GlobalRefTracking.Dangerous
def withSemantics(f: Semantics => Semantics): Config =
copy(semantics = f(semantics))
def withModuleKind(moduleKind: ModuleKind): Config =
copy(moduleKind = moduleKind)
def withESFeatures(f: ESFeatures => ESFeatures): Config =
copy(esFeatures = f(esFeatures))
def withJSHeader(jsHeader: String): Config = {
require(StandardConfig.isValidJSHeader(jsHeader), jsHeader)
copy(jsHeader = jsHeader)
}
def withInternalModulePattern(internalModulePattern: ModuleID => String): Config =
copy(internalModulePattern = internalModulePattern)
def withOptimizeBracketSelects(optimizeBracketSelects: Boolean): Config =
copy(optimizeBracketSelects = optimizeBracketSelects)
def withTrackAllGlobalRefs(trackAllGlobalRefs: Boolean): Config =
copy(trackAllGlobalRefs = trackAllGlobalRefs)
def withMinify(minify: Boolean): Config =
copy(minify = minify)
private def copy(
semantics: Semantics = semantics,
moduleKind: ModuleKind = moduleKind,
esFeatures: ESFeatures = esFeatures,
jsHeader: String = jsHeader,
internalModulePattern: ModuleID => String = internalModulePattern,
optimizeBracketSelects: Boolean = optimizeBracketSelects,
trackAllGlobalRefs: Boolean = trackAllGlobalRefs,
minify: Boolean = minify
): Config = {
new Config(semantics, moduleKind, esFeatures, jsHeader,
internalModulePattern, optimizeBracketSelects, trackAllGlobalRefs,
minify)
}
}
object Config {
def apply(coreSpec: CoreSpec): Config =
new Config(coreSpec.semantics, coreSpec.moduleKind, coreSpec.esFeatures)
}
trait PostTransformer[E] {
def transformStats(trees: List[js.Tree], indent: Int): List[E]
}
object PostTransformer {
object Identity extends PostTransformer[js.Tree] {
def transformStats(trees: List[js.Tree], indent: Int): List[js.Tree] = trees
}
}
private final class DesugaredClassCache[E >: Null] {
val privateJSFields = new OneTimeCache[WithGlobals[E]]
val storeJSSuperClass = new OneTimeCache[WithGlobals[E]]
val instanceTests = new OneTimeCache[WithGlobals[E]]
val typeData = new InputEqualityCache[Boolean, WithGlobals[E]]
val setTypeData = new OneTimeCache[E]
val moduleAccessor = new OneTimeCache[WithGlobals[E]]
val staticInitialization = new OneTimeCache[E]
val staticFields = new OneTimeCache[WithGlobals[E]]
}
private final class GeneratedClass[E](
val className: ClassName,
val main: List[E],
val staticFields: List[E],
val staticInitialization: List[E],
val trackedGlobalRefs: Set[String],
val changed: Boolean
)
private final class OneTimeCache[A >: Null] {
private[this] var value: A = null
def getOrElseUpdate(v: => A): A = {
if (value == null)
value = v
value
}
}
/** A cache that depends on an `input: I`, testing with `==`.
*
* @tparam I
* the type of input, for which `==` must meaningful
*/
private final class InputEqualityCache[I, A >: Null] {
private[this] var lastInput: Option[I] = None
private[this] var value: A = null
def getOrElseUpdate(input: I, v: => A): A = {
if (!lastInput.contains(input)) {
value = v
lastInput = Some(input)
}
value
}
}
private case class ClassID(
ancestors: List[ClassName], moduleContext: ModuleContext)
private def symbolRequirements(config: Config): SymbolRequirement = {
import config.semantics._
import CheckedBehavior._
val factory = SymbolRequirement.factory("emitter")
import factory._
def cond(p: Boolean)(v: => SymbolRequirement): SymbolRequirement =
if (p) v else none()
def isAnyFatal(behaviors: CheckedBehavior*): Boolean =
behaviors.contains(Fatal)
multiple(
cond(asInstanceOfs != Unchecked) {
instantiateClass(ClassCastExceptionClass, StringArgConstructorName)
},
cond(arrayIndexOutOfBounds != Unchecked) {
instantiateClass(ArrayIndexOutOfBoundsExceptionClass,
StringArgConstructorName)
},
cond(arrayStores != Unchecked) {
instantiateClass(ArrayStoreExceptionClass,
StringArgConstructorName)
},
cond(negativeArraySizes != Unchecked) {
instantiateClass(NegativeArraySizeExceptionClass,
NoArgConstructorName)
},
cond(nullPointers != Unchecked) {
instantiateClass(NullPointerExceptionClass, NoArgConstructorName)
},
cond(stringIndexOutOfBounds != Unchecked) {
instantiateClass(StringIndexOutOfBoundsExceptionClass,
IntArgConstructorName)
},
cond(isAnyFatal(asInstanceOfs, arrayIndexOutOfBounds, arrayStores,
negativeArraySizes, nullPointers, stringIndexOutOfBounds)) {
instantiateClass(UndefinedBehaviorErrorClass,
ThrowableArgConsructorName)
},
cond(moduleInit == Fatal) {
instantiateClass(UndefinedBehaviorErrorClass,
StringArgConstructorName)
},
// See systemIdentityHashCode in CoreJSLib
callMethod(BoxedDoubleClass, hashCodeMethodName),
callMethod(BoxedStringClass, hashCodeMethodName),
cond(!config.esFeatures.allowBigIntsForLongs) {
multiple(
instanceTests(LongImpl.RuntimeLongClass),
instantiateClass(LongImpl.RuntimeLongClass, LongImpl.AllConstructors.toList),
callMethods(LongImpl.RuntimeLongClass, LongImpl.AllMethods.toList),
callOnModule(LongImpl.RuntimeLongModuleClass, LongImpl.AllModuleMethods.toList)
)
}
)
}
}