JS DCE: create collections on demand

Based on Vladislav Saifulin's PR #4031
This commit is contained in:
Anton Bannykh
2021-01-28 12:10:03 +03:00
parent 0ebb39a26e
commit 3c0b226344
3 changed files with 75 additions and 36 deletions
@@ -70,7 +70,7 @@ class Analyzer(private val context: Context) : JsVisitor() {
}
is JsFunction -> expression.name?.let { context.nodes[it]?.original }?.let {
nodeMap[x] = it
it.functions += expression
it.addFunction(expression)
}
is JsInvocation -> {
val function = expression.qualifier
@@ -161,8 +161,8 @@ class Analyzer(private val context: Context) : JsVisitor() {
}
val dependencyNodes = dependencies.expressions
.map { it as? JsStringLiteral ?: return }
.map { if (it.value == "exports") context.currentModule else context.globalScope.member(it.value) }
.map { it as? JsStringLiteral ?: return }
.map { if (it.value == "exports") context.currentModule else context.globalScope.member(it.value) }
enterFunctionWithGivenNodes(function, dependencyNodes)
astNodesToSkip += invocation.qualifier
@@ -237,19 +237,23 @@ class Analyzer(private val context: Context) : JsVisitor() {
// - wrapFunction(function() { ... })
if (context.isDefineInlineFunction(function) && rhs.arguments.size == 2) {
tryExtractFunction(rhs.arguments[1])?.let { (inlineableFunction, additionalDeps) ->
leftNode.functions += inlineableFunction
leftNode.addFunction(inlineableFunction)
val defineInlineFunctionNode = context.extractNode(function)
if (defineInlineFunctionNode != null) {
leftNode.dependencies += defineInlineFunctionNode
leftNode.addDependency(defineInlineFunctionNode)
}
additionalDeps.forEach {
leftNode.addDependency(it)
}
leftNode.dependencies += additionalDeps
return leftNode
}
}
tryExtractFunction(rhs)?.let { (functionBody, additionalDeps) ->
leftNode.functions += functionBody
leftNode.dependencies += additionalDeps
leftNode.addFunction(functionBody)
additionalDeps.forEach {
leftNode.addDependency(it)
}
return leftNode
}
}
@@ -271,14 +275,14 @@ class Analyzer(private val context: Context) : JsVisitor() {
rhs is JsFunction -> {
// lhs = function() { ... }
// During reachability tracking phase: eliminate it if lhs is unreachable, traverse function otherwise
leftNode.functions += rhs
leftNode.addFunction(rhs)
return leftNode
}
leftNode.qualifier?.memberName == Namer.METADATA -> {
// lhs.$metadata$ = expression
// During reachability tracking phase: eliminate it if lhs is unreachable, traverse expression
// It's commonly used to supply class's metadata
leftNode.expressions += rhs
leftNode.addExpression(rhs)
return leftNode
}
rhs is JsObjectLiteral && rhs.propertyInitializers.isEmpty() -> return leftNode
@@ -323,8 +327,8 @@ class Analyzer(private val context: Context) : JsVisitor() {
if (arg == null) return
val prototypeNode = context.extractNode(arg) ?: return
target.dependencies += prototypeNode.original
target.expressions += arg
target.addDependency(prototypeNode.original)
target.addExpression(arg)
}
// Handle typeof foo === 'undefined' ? {} : foo, where foo is FQN
@@ -127,21 +127,35 @@ class Context {
}
class Node private constructor(val localName: JsName?, qualifier: Qualifier?) {
private val dependenciesImpl = mutableSetOf<Node>()
private val expressionsImpl = mutableSetOf<JsExpression>()
private val functionsImpl = mutableSetOf<JsFunction>()
private var _dependenciesImpl: MutableSet<Node>? = null
private var _expressionsImpl: MutableSet<JsExpression>? = null
private var _functionsImpl: MutableSet<JsFunction>? = null
private var _membersImpl: MutableMap<String, Node>? = null
private var _usedByAstNodesImpl: MutableSet<JsNode>? = null
private val dependenciesImpl: MutableSet<Node>
get() = _dependenciesImpl ?: mutableSetOf<Node>().also { _dependenciesImpl = it }
private val expressionsImpl: MutableSet<JsExpression>
get() = _expressionsImpl ?: mutableSetOf<JsExpression>().also { _expressionsImpl = it }
private val functionsImpl: MutableSet<JsFunction>
get() = _functionsImpl ?: mutableSetOf<JsFunction>().also { _functionsImpl = it }
private val membersImpl: MutableMap<String, Node>
get() = _membersImpl ?: mutableMapOf<String, Node>().also { _membersImpl = it }
private val usedByAstNodesImpl: MutableSet<JsNode>
get() = _usedByAstNodesImpl ?: mutableSetOf<JsNode>().also { _usedByAstNodesImpl = it }
private var rank = 0
private var hasSideEffectsImpl = false
private var reachableImpl = false
private var declarationReachableImpl = false
private val membersImpl = mutableMapOf<String, Node>()
private val usedByAstNodesImpl = mutableSetOf<JsNode>()
private var rank = 0
val dependencies: MutableSet<Node> get() = original.dependenciesImpl
val dependencies: Set<Node> get() = original._dependenciesImpl ?: emptySet()
val expressions: MutableSet<JsExpression> get() = original.expressionsImpl
val expressions: Set<JsExpression> get() = original._expressionsImpl ?: emptySet()
val functions: MutableSet<JsFunction> get() = original.functionsImpl
val functions: Set<JsFunction> get() = original._functionsImpl ?: emptySet()
val usedByAstNodes: Set<JsNode> get() = original._usedByAstNodesImpl ?: emptySet()
var hasSideEffects: Boolean
get() = original.hasSideEffectsImpl
@@ -162,12 +176,9 @@ class Context {
}
var qualifier: Qualifier? = qualifier
get
private set
val usedByAstNodes: MutableSet<JsNode> get() = original.usedByAstNodesImpl
val memberNames: MutableSet<String> get() = original.membersImpl.keys
val memberNames: Set<String> get() = original._membersImpl?.keys ?: emptySet()
constructor(localName: JsName? = null) : this(localName, null)
@@ -180,7 +191,23 @@ class Context {
}
private set
val members: Map<String, Node> get() = original.membersImpl
val members: Map<String, Node> get() = original._membersImpl ?: emptyMap()
fun addDependency(node: Node) {
original.dependenciesImpl += node
}
fun addFunction(function: JsFunction) {
original.functionsImpl += function
}
fun addExpression(expression: JsExpression) {
original.expressionsImpl += expression
}
fun addUsedByAstNode(node: JsNode) {
original.usedByAstNodesImpl += node
}
fun member(name: String): Node = original.membersImpl.getOrPut(name) { Node(null, Qualifier(this, name)) }.original
@@ -224,15 +251,23 @@ class Context {
other.membersImpl.clear()
hasSideEffectsImpl = hasSideEffectsImpl || other.hasSideEffectsImpl
expressionsImpl += other.expressionsImpl
functionsImpl += other.functionsImpl
dependenciesImpl += other.dependenciesImpl
usedByAstNodesImpl += other.usedByAstNodesImpl
if (!other._expressionsImpl.isNullOrEmpty()) {
expressionsImpl += other.expressionsImpl
}
if (!other._functionsImpl.isNullOrEmpty()) {
functionsImpl += other.functionsImpl
}
if (!other._dependenciesImpl.isNullOrEmpty()) {
dependenciesImpl += other.dependenciesImpl
}
if (!other._usedByAstNodesImpl.isNullOrEmpty()) {
usedByAstNodesImpl += other.usedByAstNodesImpl
}
other.expressionsImpl.clear()
other.functionsImpl.clear()
other.dependenciesImpl.clear()
other.usedByAstNodesImpl.clear()
other._expressionsImpl = null
other._functionsImpl = null
other._dependenciesImpl = null
other._usedByAstNodesImpl = null
}
private fun merge(other: Node) {
@@ -76,7 +76,7 @@ class ReachabilityTracker(
if (!node.reachable) {
reportAndNest("reach: referenced name $node", currentNodeWithLocation) {
reach(node)
currentNodeWithLocation?.let { node.usedByAstNodes += it }
currentNodeWithLocation?.let { node.addUsedByAstNode(it) }
}
}
return false
@@ -101,7 +101,7 @@ class ReachabilityTracker(
if (node != null && node.qualifier?.memberName in CALL_FUNCTIONS) {
val parent = node.qualifier!!.parent
reach(parent)
currentNodeWithLocation?.let { parent.usedByAstNodes += it }
currentNodeWithLocation?.let { parent.addUsedByAstNode(it) }
}
super.visitInvocation(invocation)
}