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 {
|
is JsFunction -> expression.name?.let { context.nodes[it]?.original }?.let {
|
||||||
nodeMap[x] = it
|
nodeMap[x] = it
|
||||||
it.functions += expression
|
it.addFunction(expression)
|
||||||
}
|
}
|
||||||
is JsInvocation -> {
|
is JsInvocation -> {
|
||||||
val function = expression.qualifier
|
val function = expression.qualifier
|
||||||
@@ -161,8 +161,8 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val dependencyNodes = dependencies.expressions
|
val dependencyNodes = dependencies.expressions
|
||||||
.map { it as? JsStringLiteral ?: return }
|
.map { it as? JsStringLiteral ?: return }
|
||||||
.map { if (it.value == "exports") context.currentModule else context.globalScope.member(it.value) }
|
.map { if (it.value == "exports") context.currentModule else context.globalScope.member(it.value) }
|
||||||
|
|
||||||
enterFunctionWithGivenNodes(function, dependencyNodes)
|
enterFunctionWithGivenNodes(function, dependencyNodes)
|
||||||
astNodesToSkip += invocation.qualifier
|
astNodesToSkip += invocation.qualifier
|
||||||
@@ -237,19 +237,23 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
// - wrapFunction(function() { ... })
|
// - wrapFunction(function() { ... })
|
||||||
if (context.isDefineInlineFunction(function) && rhs.arguments.size == 2) {
|
if (context.isDefineInlineFunction(function) && rhs.arguments.size == 2) {
|
||||||
tryExtractFunction(rhs.arguments[1])?.let { (inlineableFunction, additionalDeps) ->
|
tryExtractFunction(rhs.arguments[1])?.let { (inlineableFunction, additionalDeps) ->
|
||||||
leftNode.functions += inlineableFunction
|
leftNode.addFunction(inlineableFunction)
|
||||||
val defineInlineFunctionNode = context.extractNode(function)
|
val defineInlineFunctionNode = context.extractNode(function)
|
||||||
if (defineInlineFunctionNode != null) {
|
if (defineInlineFunctionNode != null) {
|
||||||
leftNode.dependencies += defineInlineFunctionNode
|
leftNode.addDependency(defineInlineFunctionNode)
|
||||||
|
}
|
||||||
|
additionalDeps.forEach {
|
||||||
|
leftNode.addDependency(it)
|
||||||
}
|
}
|
||||||
leftNode.dependencies += additionalDeps
|
|
||||||
return leftNode
|
return leftNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tryExtractFunction(rhs)?.let { (functionBody, additionalDeps) ->
|
tryExtractFunction(rhs)?.let { (functionBody, additionalDeps) ->
|
||||||
leftNode.functions += functionBody
|
leftNode.addFunction(functionBody)
|
||||||
leftNode.dependencies += additionalDeps
|
additionalDeps.forEach {
|
||||||
|
leftNode.addDependency(it)
|
||||||
|
}
|
||||||
return leftNode
|
return leftNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,14 +275,14 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
rhs is JsFunction -> {
|
rhs is JsFunction -> {
|
||||||
// lhs = function() { ... }
|
// lhs = function() { ... }
|
||||||
// During reachability tracking phase: eliminate it if lhs is unreachable, traverse function otherwise
|
// During reachability tracking phase: eliminate it if lhs is unreachable, traverse function otherwise
|
||||||
leftNode.functions += rhs
|
leftNode.addFunction(rhs)
|
||||||
return leftNode
|
return leftNode
|
||||||
}
|
}
|
||||||
leftNode.qualifier?.memberName == Namer.METADATA -> {
|
leftNode.qualifier?.memberName == Namer.METADATA -> {
|
||||||
// lhs.$metadata$ = expression
|
// lhs.$metadata$ = expression
|
||||||
// During reachability tracking phase: eliminate it if lhs is unreachable, traverse expression
|
// During reachability tracking phase: eliminate it if lhs is unreachable, traverse expression
|
||||||
// It's commonly used to supply class's metadata
|
// It's commonly used to supply class's metadata
|
||||||
leftNode.expressions += rhs
|
leftNode.addExpression(rhs)
|
||||||
return leftNode
|
return leftNode
|
||||||
}
|
}
|
||||||
rhs is JsObjectLiteral && rhs.propertyInitializers.isEmpty() -> 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
|
if (arg == null) return
|
||||||
|
|
||||||
val prototypeNode = context.extractNode(arg) ?: return
|
val prototypeNode = context.extractNode(arg) ?: return
|
||||||
target.dependencies += prototypeNode.original
|
target.addDependency(prototypeNode.original)
|
||||||
target.expressions += arg
|
target.addExpression(arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle typeof foo === 'undefined' ? {} : foo, where foo is FQN
|
// Handle typeof foo === 'undefined' ? {} : foo, where foo is FQN
|
||||||
|
|||||||
@@ -127,21 +127,35 @@ class Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Node private constructor(val localName: JsName?, qualifier: Qualifier?) {
|
class Node private constructor(val localName: JsName?, qualifier: Qualifier?) {
|
||||||
private val dependenciesImpl = mutableSetOf<Node>()
|
private var _dependenciesImpl: MutableSet<Node>? = null
|
||||||
private val expressionsImpl = mutableSetOf<JsExpression>()
|
private var _expressionsImpl: MutableSet<JsExpression>? = null
|
||||||
private val functionsImpl = mutableSetOf<JsFunction>()
|
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 hasSideEffectsImpl = false
|
||||||
private var reachableImpl = false
|
private var reachableImpl = false
|
||||||
private var declarationReachableImpl = 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
|
var hasSideEffects: Boolean
|
||||||
get() = original.hasSideEffectsImpl
|
get() = original.hasSideEffectsImpl
|
||||||
@@ -162,12 +176,9 @@ class Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var qualifier: Qualifier? = qualifier
|
var qualifier: Qualifier? = qualifier
|
||||||
get
|
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val usedByAstNodes: MutableSet<JsNode> get() = original.usedByAstNodesImpl
|
val memberNames: Set<String> get() = original._membersImpl?.keys ?: emptySet()
|
||||||
|
|
||||||
val memberNames: MutableSet<String> get() = original.membersImpl.keys
|
|
||||||
|
|
||||||
constructor(localName: JsName? = null) : this(localName, null)
|
constructor(localName: JsName? = null) : this(localName, null)
|
||||||
|
|
||||||
@@ -180,7 +191,23 @@ class Context {
|
|||||||
}
|
}
|
||||||
private set
|
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
|
fun member(name: String): Node = original.membersImpl.getOrPut(name) { Node(null, Qualifier(this, name)) }.original
|
||||||
|
|
||||||
@@ -224,15 +251,23 @@ class Context {
|
|||||||
other.membersImpl.clear()
|
other.membersImpl.clear()
|
||||||
|
|
||||||
hasSideEffectsImpl = hasSideEffectsImpl || other.hasSideEffectsImpl
|
hasSideEffectsImpl = hasSideEffectsImpl || other.hasSideEffectsImpl
|
||||||
expressionsImpl += other.expressionsImpl
|
if (!other._expressionsImpl.isNullOrEmpty()) {
|
||||||
functionsImpl += other.functionsImpl
|
expressionsImpl += other.expressionsImpl
|
||||||
dependenciesImpl += other.dependenciesImpl
|
}
|
||||||
usedByAstNodesImpl += other.usedByAstNodesImpl
|
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._expressionsImpl = null
|
||||||
other.functionsImpl.clear()
|
other._functionsImpl = null
|
||||||
other.dependenciesImpl.clear()
|
other._dependenciesImpl = null
|
||||||
other.usedByAstNodesImpl.clear()
|
other._usedByAstNodesImpl = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun merge(other: Node) {
|
private fun merge(other: Node) {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class ReachabilityTracker(
|
|||||||
if (!node.reachable) {
|
if (!node.reachable) {
|
||||||
reportAndNest("reach: referenced name $node", currentNodeWithLocation) {
|
reportAndNest("reach: referenced name $node", currentNodeWithLocation) {
|
||||||
reach(node)
|
reach(node)
|
||||||
currentNodeWithLocation?.let { node.usedByAstNodes += it }
|
currentNodeWithLocation?.let { node.addUsedByAstNode(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -101,7 +101,7 @@ class ReachabilityTracker(
|
|||||||
if (node != null && node.qualifier?.memberName in CALL_FUNCTIONS) {
|
if (node != null && node.qualifier?.memberName in CALL_FUNCTIONS) {
|
||||||
val parent = node.qualifier!!.parent
|
val parent = node.qualifier!!.parent
|
||||||
reach(parent)
|
reach(parent)
|
||||||
currentNodeWithLocation?.let { parent.usedByAstNodes += it }
|
currentNodeWithLocation?.let { parent.addUsedByAstNode(it) }
|
||||||
}
|
}
|
||||||
super.visitInvocation(invocation)
|
super.visitInvocation(invocation)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user