JS DCE: create collections on demand
Based on Vladislav Saifulin's PR #4031
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user