[JS IR] Namer improvements

- Compute and store local names locally when translating a body.
  It is a step towards separate JS generation and hopefully reduces memory usage.

- Use stable mangled names for member names. Needed for separate JS generation.

- Add `abstract class IrNamerBase` with just 3 abstract methods to simplify
  creating new IrNamer implementations.

- Fix O(N^2) of findFreshName when it is called wtih the same name suggestion a lot of times.

- Refactor NameTables initialisation: factor out some functions and use descriptive names.

- Use StringBuilder in sanitizeName
This commit is contained in:
Svyatoslav Kuzmich
2020-12-22 18:58:55 +03:00
parent c9cb7bc0fd
commit cdb488149f
11 changed files with 244 additions and 276 deletions
@@ -7,14 +7,13 @@ package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.utils.Signature
import org.jetbrains.kotlin.ir.backend.js.utils.hasStableJsName import org.jetbrains.kotlin.ir.backend.js.utils.hasStableJsName
import org.jetbrains.kotlin.ir.backend.js.utils.jsFunctionSignature import org.jetbrains.kotlin.ir.backend.js.utils.jsFunctionSignature
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
class JsBridgesConstruction(context: JsIrBackendContext) : BridgesConstruction<JsIrBackendContext>(context) { class JsBridgesConstruction(context: JsIrBackendContext) : BridgesConstruction<JsIrBackendContext>(context) {
override fun getFunctionSignature(function: IrSimpleFunction): Signature = override fun getFunctionSignature(function: IrSimpleFunction): String =
jsFunctionSignature(function, context) jsFunctionSignature(function, context)
override fun getBridgeOrigin(bridge: IrSimpleFunction): IrDeclarationOrigin = override fun getBridgeOrigin(bridge: IrSimpleFunction): IrDeclarationOrigin =
@@ -6,8 +6,8 @@
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.js.backend.ast.JsFunction import org.jetbrains.kotlin.js.backend.ast.JsFunction
@@ -15,7 +15,11 @@ import org.jetbrains.kotlin.js.backend.ast.JsFunction
class IrFunctionToJsTransformer : BaseIrElementToJsNodeTransformer<JsFunction, JsGenerationContext> { class IrFunctionToJsTransformer : BaseIrElementToJsNodeTransformer<JsFunction, JsGenerationContext> {
override fun visitSimpleFunction(declaration: IrSimpleFunction, context: JsGenerationContext): JsFunction { override fun visitSimpleFunction(declaration: IrSimpleFunction, context: JsGenerationContext): JsFunction {
val funcName = if (declaration.dispatchReceiverParameter == null) { val funcName = if (declaration.dispatchReceiverParameter == null) {
context.getNameForStaticFunction(declaration) if (declaration.parent is IrFunction) {
context.getNameForValueDeclaration(declaration)
} else {
context.getNameForStaticFunction(declaration)
}
} else { } else {
context.getNameForMemberFunction(declaration) context.getNameForMemberFunction(declaration)
} }
@@ -127,16 +127,18 @@ class IrModuleToJsTransformer(
): String { ): String {
val nameGenerator = refInfo.withReferenceTracking( val nameGenerator = refInfo.withReferenceTracking(
IrNamerImpl(newNameTables = namer), IrNamerImpl(newNameTables = namer, backendContext),
modules modules
) )
val staticContext = JsStaticContext( val staticContext = JsStaticContext(
backendContext = backendContext, backendContext = backendContext,
irNamer = nameGenerator irNamer = nameGenerator,
globalNameScope = namer.globalNames
) )
val rootContext = JsGenerationContext( val rootContext = JsGenerationContext(
currentFunction = null, currentFunction = null,
staticContext = staticContext staticContext = staticContext,
localNames = LocalNameGenerator(NameScope.EmptyScope)
) )
val (importStatements, importedJsModules) = val (importStatements, importedJsModules) =
@@ -5,12 +5,10 @@
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.backend.js.export.isExported import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrType
@@ -26,8 +24,11 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
private val className = context.getNameForClass(irClass) private val className = context.getNameForClass(irClass)
private val classNameRef = className.makeRef() private val classNameRef = className.makeRef()
private val baseClass: IrType? = irClass.superTypes.firstOrNull { !it.classifierOrFail.isInterface } private val baseClass: IrType? = irClass.superTypes.firstOrNull { !it.classifierOrFail.isInterface }
private val baseClassName = baseClass?.let {
context.getNameForClass(baseClass.classifierOrFail.owner as IrClass) private val baseClassName by lazy { // Lazy in case was not collected by namer during JsClassGenerator construction
baseClass?.let {
context.getNameForClass(baseClass.classifierOrFail.owner as IrClass)
}
} }
private val classPrototypeRef = prototypeOf(classNameRef) private val classPrototypeRef = prototypeOf(classNameRef)
private val classBlock = JsGlobalBlock() private val classBlock = JsGlobalBlock()
@@ -14,6 +14,8 @@ import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -50,7 +52,14 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat
return function return function
} }
val functionContext = context.newDeclaration(declaration) val localNameGenerator = context.localNames
?: LocalNameGenerator(context.staticContext.globalNameScope).also {
declaration.acceptChildrenVoid(it)
declaration.parentClassOrNull?.thisReceiver?.acceptVoid(it)
}
val functionContext = context.newDeclaration(declaration, localNameGenerator)
val functionParams = declaration.valueParameters.map { functionContext.getNameForValueDeclaration(it) } val functionParams = declaration.valueParameters.map { functionContext.getNameForValueDeclaration(it) }
val body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock() val body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock()
@@ -6,21 +6,81 @@
package org.jetbrains.kotlin.ir.backend.js.utils package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrLoop import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.js.backend.ast.JsName import org.jetbrains.kotlin.js.backend.ast.JsName
import org.jetbrains.kotlin.js.backend.ast.JsNameRef import org.jetbrains.kotlin.js.backend.ast.JsNameRef
interface IrNamer { interface IrNamer {
fun getNameForConstructor(constructor: IrConstructor): JsName
fun getNameForMemberFunction(function: IrSimpleFunction): JsName fun getNameForMemberFunction(function: IrSimpleFunction): JsName
fun getNameForMemberField(field: IrField): JsName fun getNameForMemberField(field: IrField): JsName
fun getNameForField(field: IrField): JsName
fun getNameForValueDeclaration(declaration: IrValueDeclaration): JsName
fun getNameForClass(klass: IrClass): JsName
fun getNameForStaticFunction(function: IrSimpleFunction): JsName
fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName
fun getNameForProperty(property: IrProperty): JsName fun getNameForStaticFunction(function: IrSimpleFunction): JsName
fun getNameForField(field: IrField): JsName
fun getNameForConstructor(constructor: IrConstructor): JsName
fun getNameForClass(klass: IrClass): JsName
fun getRefForExternalClass(klass: IrClass): JsNameRef fun getRefForExternalClass(klass: IrClass): JsNameRef
fun getNameForLoop(loop: IrLoop): JsName? fun getNameForProperty(property: IrProperty): JsName
fun getAssociatedObjectKey(irClass: IrClass): Int? fun getAssociatedObjectKey(irClass: IrClass): Int?
}
abstract class IrNamerBase : IrNamer {
abstract override fun getNameForMemberFunction(function: IrSimpleFunction): JsName
abstract override fun getNameForMemberField(field: IrField): JsName
abstract override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName
protected fun String.toJsName() = JsName(this)
override fun getNameForStaticFunction(function: IrSimpleFunction): JsName =
getNameForStaticDeclaration(function)
override fun getNameForField(field: IrField): JsName {
return if (field.isStatic || field.parent is IrScript) {
getNameForStaticDeclaration(field)
} else {
getNameForMemberField(field)
}
}
override fun getNameForConstructor(constructor: IrConstructor): JsName =
getNameForStaticDeclaration(constructor.parentAsClass)
override fun getNameForClass(klass: IrClass): JsName =
getNameForStaticDeclaration(klass)
override fun getRefForExternalClass(klass: IrClass): JsNameRef {
val parent = klass.parent
if (klass.isCompanion)
return getRefForExternalClass(parent as IrClass)
val currentClassName = klass.getJsNameOrKotlinName().identifier
return when (parent) {
is IrClass ->
JsNameRef(currentClassName, getRefForExternalClass(parent))
is IrPackageFragment -> {
getNameForStaticDeclaration(klass).makeRef()
}
else ->
error("Unsupported external class parent $parent")
}
}
override fun getNameForProperty(property: IrProperty): JsName {
return if (property.parent is IrClass) {
property.getJsNameOrKotlinName().asString().toJsName()
} else {
getNameForStaticDeclaration(property)
}
}
private val associatedObjectKeyMap = mutableMapOf<IrClass, Int>()
override fun getAssociatedObjectKey(irClass: IrClass): Int? {
if (irClass.isAssociatedObjectAnnotatedAnnotation) {
return associatedObjectKeyMap.getOrPut(irClass) { associatedObjectKeyMap.size }
}
return null
}
} }
@@ -5,82 +5,27 @@
package org.jetbrains.kotlin.ir.backend.js.utils package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.expressions.IrLoop import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.js.backend.ast.JsName import org.jetbrains.kotlin.js.backend.ast.JsName
import org.jetbrains.kotlin.js.backend.ast.JsNameRef
import org.jetbrains.kotlin.js.backend.ast.JsRootScope
class IrNamerImpl(private val newNameTables: NameTables) : IrNamer {
private fun String.toJsName() = JsName(this)
class IrNamerImpl(
private val newNameTables: NameTables,
private val context: JsIrBackendContext,
) : IrNamerBase() {
override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName = override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName =
newNameTables.getNameForStaticDeclaration(declaration).toJsName() newNameTables.getNameForStaticDeclaration(declaration).toJsName()
override fun getNameForLoop(loop: IrLoop): JsName? =
newNameTables.getNameForLoop(loop)?.toJsName()
override fun getNameForConstructor(constructor: IrConstructor): JsName {
return getNameForStaticDeclaration(constructor.parentAsClass)
}
override fun getNameForMemberFunction(function: IrSimpleFunction): JsName { override fun getNameForMemberFunction(function: IrSimpleFunction): JsName {
require(function.dispatchReceiverParameter != null) require(function.dispatchReceiverParameter != null)
return newNameTables.getNameForMemberFunction(function).toJsName() val signature = jsFunctionSignature(function, context)
return signature.toJsName()
} }
override fun getNameForMemberField(field: IrField): JsName { override fun getNameForMemberField(field: IrField): JsName {
require(!field.isStatic) require(!field.isStatic)
return newNameTables.getNameForMemberField(field).toJsName() return newNameTables.getNameForMemberField(field).toJsName()
} }
override fun getNameForField(field: IrField): JsName {
return if (field.isStatic || field.parent is IrScript) {
getNameForStaticDeclaration(field)
} else {
getNameForMemberField(field)
}
}
override fun getNameForValueDeclaration(declaration: IrValueDeclaration): JsName =
getNameForStaticDeclaration(declaration)
override fun getNameForClass(klass: IrClass): JsName =
getNameForStaticDeclaration(klass)
override fun getNameForStaticFunction(function: IrSimpleFunction): JsName =
getNameForStaticDeclaration(function)
override fun getNameForProperty(property: IrProperty): JsName =
property.getJsNameOrKotlinName().asString().toJsName()
override fun getRefForExternalClass(klass: IrClass): JsNameRef {
val parent = klass.parent
if (klass.isCompanion)
return getRefForExternalClass(parent as IrClass)
val currentClassName = klass.getJsNameOrKotlinName().identifier
return when (parent) {
is IrClass ->
JsNameRef(currentClassName, getRefForExternalClass(parent))
is IrPackageFragment ->
JsNameRef(currentClassName)
else ->
error("Unsupported external class parent $parent")
}
}
private val associatedObjectKeyMap = mutableMapOf<IrClass, Int>()
override fun getAssociatedObjectKey(irClass: IrClass): Int? {
if (irClass.isAssociatedObjectAnnotatedAnnotation) {
return associatedObjectKeyMap.getOrPut(irClass) { associatedObjectKeyMap.size }
}
return null
}
} }
@@ -5,8 +5,10 @@
package org.jetbrains.kotlin.ir.backend.js.utils package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.isSuspend import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.js.backend.ast.JsName import org.jetbrains.kotlin.js.backend.ast.JsName
@@ -23,12 +25,14 @@ val emptyScope: JsScope
class JsGenerationContext( class JsGenerationContext(
val currentFunction: IrFunction?, val currentFunction: IrFunction?,
val staticContext: JsStaticContext val staticContext: JsStaticContext,
val localNames: LocalNameGenerator? = null
): IrNamer by staticContext { ): IrNamer by staticContext {
fun newDeclaration(func: IrFunction? = null): JsGenerationContext { fun newDeclaration(func: IrFunction? = null, localNames: LocalNameGenerator? = null): JsGenerationContext {
return JsGenerationContext( return JsGenerationContext(
currentFunction = func, currentFunction = func,
staticContext = staticContext staticContext = staticContext,
localNames = localNames,
) )
} }
@@ -39,10 +43,21 @@ class JsGenerationContext(
if (currentFunction!!.isSuspend) { if (currentFunction!!.isSuspend) {
JsNameRef(Namer.CONTINUATION) JsNameRef(Namer.CONTINUATION)
} else { } else {
getNameForValueDeclaration(currentFunction.valueParameters.last()).makeRef() JsNameRef(this.getNameForValueDeclaration(currentFunction.valueParameters.last()))
} }
} }
fun getNameForValueDeclaration(declaration: IrDeclarationWithName): JsName {
val name = localNames!!.variableNames.names[declaration]
?: error("Variable name is not found ${declaration.name}")
return JsName(name)
}
fun getNameForLoop(loop: IrLoop): JsName? {
val name = localNames!!.localLoopNames.names[loop] ?: return null
return JsName(name)
}
private fun isCoroutineDoResume(): Boolean { private fun isCoroutineDoResume(): Boolean {
val overriddenSymbols = (currentFunction as? IrSimpleFunction)?.overriddenSymbols ?: return false val overriddenSymbols = (currentFunction as? IrSimpleFunction)?.overriddenSymbols ?: return false
return overriddenSymbols.any { return overriddenSymbols.any {
@@ -8,16 +8,15 @@ package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIntrinsicTransformers import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIntrinsicTransformers
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrClassModel import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrClassModel
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.js.backend.ast.JsGlobalBlock import org.jetbrains.kotlin.js.backend.ast.JsGlobalBlock
class JsStaticContext( class JsStaticContext(
val backendContext: JsIrBackendContext, val backendContext: JsIrBackendContext,
private val irNamer: IrNamer private val irNamer: IrNamer,
val globalNameScope: NameScope
) : IrNamer by irNamer { ) : IrNamer by irNamer {
val intrinsics = JsIntrinsicTransformers(backendContext) val intrinsics = JsIntrinsicTransformers(backendContext)
val classModels = mutableMapOf<IrClassSymbol, JsIrClassModel>() val classModels = mutableMapOf<IrClassSymbol, JsIrClassModel>()
val coroutineImplDeclaration = backendContext.ir.symbols.coroutineImpl.owner val coroutineImplDeclaration = backendContext.ir.symbols.coroutineImpl.owner
@@ -17,48 +17,53 @@ import org.jetbrains.kotlin.ir.expressions.IrWhen
import org.jetbrains.kotlin.ir.types.isUnit import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.isEnumClass
import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.naming.isES5IdentifierPart import org.jetbrains.kotlin.js.naming.isES5IdentifierPart
import org.jetbrains.kotlin.js.naming.isES5IdentifierStart import org.jetbrains.kotlin.js.naming.isES5IdentifierStart
import org.jetbrains.kotlin.js.naming.isValidES5Identifier import org.jetbrains.kotlin.js.naming.isValidES5Identifier
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import java.util.* import java.util.*
import kotlin.collections.set
import kotlin.math.abs
// TODO remove direct usages of [mapToKey] from [NameTable] & co and move it to scripting & REPL infrastructure. Review usages. // TODO remove direct usages of [mapToKey] from [NameTable] & co and move it to scripting & REPL infrastructure. Review usages.
private fun <T> mapToKey(declaration: T): String { private fun <T> mapToKey(declaration: T): String {
return with(JsManglerIr) { return with(JsManglerIr) {
if (declaration is IrDeclaration) { if (declaration is IrDeclaration) {
declaration.hashedMangle.toString() declaration.hashedMangle.toString()
} else if (declaration is Signature) { } else if (declaration is String) {
declaration.toString().hashMangle.toString() declaration.hashMangle.toString()
} else { } else {
error("Key is not generated for " + declaration?.let { it::class.simpleName }) error("Key is not generated for " + declaration?.let { it::class.simpleName })
} }
} }
} }
abstract class NameScope {
abstract fun isReserved(name: String): Boolean
object EmptyScope : NameScope() {
override fun isReserved(name: String): Boolean = false
}
}
class NameTable<T>( class NameTable<T>(
val parent: NameTable<*>? = null, val parent: NameScope = EmptyScope,
val reserved: MutableSet<String> = mutableSetOf(), val reserved: MutableSet<String> = mutableSetOf(),
val mappedNames: MutableMap<String, String>? = null val mappedNames: MutableMap<String, String>? = null
) { ) : NameScope() {
var finished = false
val names = mutableMapOf<T, String>() val names = mutableMapOf<T, String>()
private fun isReserved(name: String): Boolean { private val suggestedNameLastIdx = mutableMapOf<String, Int>()
if (parent != null && parent.isReserved(name))
return true override fun isReserved(name: String): Boolean {
return name in reserved return parent.isReserved(name) || name in reserved
} }
fun declareStableName(declaration: T, name: String) { fun declareStableName(declaration: T, name: String) {
if (parent != null) assert(parent.finished)
assert(!finished)
names[declaration] = name names[declaration] = name
reserved.add(name) reserved.add(name)
mappedNames?.set(mapToKey(declaration), name) mappedNames?.set(mapToKey(declaration), name)
@@ -74,7 +79,7 @@ class NameTable<T>(
if (!isReserved(suggestedName)) if (!isReserved(suggestedName))
return suggestedName return suggestedName
var i = 0 var i = suggestedNameLastIdx[suggestedName] ?: 0
fun freshName() = fun freshName() =
suggestedName + "_" + i suggestedName + "_" + i
@@ -82,6 +87,9 @@ class NameTable<T>(
while (isReserved(freshName())) { while (isReserved(freshName())) {
i++ i++
} }
suggestedNameLastIdx[suggestedName] = i
return freshName() return freshName()
} }
} }
@@ -93,33 +101,25 @@ fun NameTable<IrDeclaration>.dump(): String =
"--- $declRef => $name" "--- $declRef => $name"
} }
sealed class Signature
data class StableNameSignature(val name: String) : Signature()
data class BackingFieldSignature(val field: IrField) : Signature()
data class ParameterTypeBasedSignature(val mangledName: String, val suggestedName: String) : Signature()
fun fieldSignature(field: IrField): Signature { private const val RESERVED_MEMBER_NAME_SUFFIX = "_k$"
if (field.isEffectivelyExternal()) {
return StableNameSignature(field.name.identifier)
}
return BackingFieldSignature(field) fun jsFunctionSignature(declaration: IrFunction, context: JsIrBackendContext?): String {
}
fun jsFunctionSignature(declaration: IrFunction, context: JsIrBackendContext?): Signature {
require(!declaration.isStaticMethodOfClass) require(!declaration.isStaticMethodOfClass)
require(declaration.dispatchReceiverParameter != null) require(declaration.dispatchReceiverParameter != null)
val declarationName = declaration.getJsNameOrKotlinName().asString() val declarationName = declaration.getJsNameOrKotlinName().asString()
if (declaration.hasStableJsName(context)) { if (declaration.hasStableJsName(context)) {
return StableNameSignature(declarationName) // TODO: Handle reserved suffix in FE
require(!declarationName.endsWith(RESERVED_MEMBER_NAME_SUFFIX)) {
"Function ${declaration.fqNameWhenAvailable} uses reserved name suffix \"$RESERVED_MEMBER_NAME_SUFFIX\""
}
return declarationName
} }
val nameBuilder = StringBuilder() val nameBuilder = StringBuilder()
nameBuilder.append(declarationName)
// TODO should we skip type parameters and use upper bound of type parameter when print type of value parameters? // TODO should we skip type parameters and use upper bound of type parameter when print type of value parameters?
declaration.typeParameters.ifNotEmpty { declaration.typeParameters.ifNotEmpty {
nameBuilder.append("_\$t") nameBuilder.append("_\$t")
@@ -141,20 +141,19 @@ fun jsFunctionSignature(declaration: IrFunction, context: JsIrBackendContext?):
val signature = nameBuilder.toString() val signature = nameBuilder.toString()
// TODO: Check reserved names // TODO: Use better hashCode
return ParameterTypeBasedSignature(signature, declarationName) return sanitizeName(declarationName) + "_" + abs(signature.hashCode()).toString(Character.MAX_RADIX) + RESERVED_MEMBER_NAME_SUFFIX
} }
class NameTables( class NameTables(
packages: List<IrPackageFragment>, packages: Iterable<IrPackageFragment>,
reservedForGlobal: MutableSet<String> = mutableSetOf(), reservedForGlobal: MutableSet<String> = mutableSetOf(),
reservedForMember: MutableSet<String> = mutableSetOf(), reservedForMember: MutableSet<String> = mutableSetOf(),
val mappedNames: MutableMap<String, String>? = null, val mappedNames: MutableMap<String, String>? = null,
private val context: JsIrBackendContext? = null private val context: JsIrBackendContext? = null
) { ) {
val globalNames: NameTable<IrDeclaration> val globalNames: NameTable<IrDeclaration>
private val memberNames: NameTable<Signature> private val memberNames: NameTable<IrField>
private val localNames = mutableMapOf<IrDeclaration, NameTable<IrDeclaration>>()
private val loopNames = mutableMapOf<IrLoop, String>() private val loopNames = mutableMapOf<IrLoop, String>()
init { init {
@@ -171,74 +170,48 @@ class NameTables(
mappedNames = mappedNames mappedNames = mappedNames
) )
val classDeclaration = mutableListOf<IrClass>()
for (p in packages) { for (p in packages) {
for (declaration in p.declarations) { for (declaration in p.declarations) {
generateNamesForTopLevelDecl(declaration) processTopLevelLocalDecl(declaration)
processNonTopLevelLocalDecl(declaration)
declaration.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitDeclaration(declaration: IrDeclarationBase) {
processNonTopLevelLocalDecl(declaration)
super.visitDeclaration(declaration)
}
})
if (declaration is IrScript) { if (declaration is IrScript) {
for (memberDecl in declaration.statements) { for (memberDecl in declaration.statements) {
if (memberDecl is IrDeclaration) { if (memberDecl is IrDeclaration) {
generateNamesForTopLevelDecl(memberDecl) processTopLevelLocalDecl(memberDecl)
if (memberDecl is IrClass) { if (memberDecl is IrClass) {
classDeclaration += memberDecl processNonTopLevelLocalDecl(memberDecl)
} }
} }
} }
} }
} }
} }
}
globalNames.finished = true private fun acceptNonExternalClass(declaration: IrClass) {
for (memberDecl in declaration.declarations) {
for (declaration in classDeclaration) { when (memberDecl) {
acceptDeclaration(declaration) is IrField ->
} generateNameForMemberField(memberDecl)
for (p in packages) {
for (declaration in p.declarations) {
acceptDeclaration(declaration)
} }
} }
} }
private fun acceptDeclaration(declaration: IrDeclaration) { private fun processNonTopLevelLocalDecl(declaration: IrDeclaration) {
val localNameGenerator = LocalNameGenerator(declaration)
if (declaration is IrClass) { if (declaration is IrClass) {
if (declaration.isEffectivelyExternal()) { if (!declaration.isEffectivelyExternal()) {
declaration.acceptChildrenVoid(object : IrElementVisitorVoid { acceptNonExternalClass(declaration)
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
val parent = declaration.parent
if (parent is IrClass && !parent.isEnumClass) {
generateNameForMemberFunction(declaration)
}
}
override fun visitField(declaration: IrField) {
val parent = declaration.parent
if (parent is IrClass && !parent.isEnumClass) {
generateNameForMemberField(declaration)
}
}
})
} else {
declaration.thisReceiver!!.acceptVoid(localNameGenerator)
for (memberDecl in declaration.declarations) {
memberDecl.acceptChildrenVoid(LocalNameGenerator(memberDecl))
when (memberDecl) {
is IrSimpleFunction ->
generateNameForMemberFunction(memberDecl)
is IrField ->
generateNameForMemberField(memberDecl)
}
}
} }
} else {
declaration.acceptChildrenVoid(localNameGenerator)
} }
} }
@@ -262,7 +235,6 @@ class NameTables(
globalNames.names.addAllIfAbsent(table.globalNames.names) globalNames.names.addAllIfAbsent(table.globalNames.names)
memberNames.names.addAllIfAbsent(table.memberNames.names) memberNames.names.addAllIfAbsent(table.memberNames.names)
localNames.addAllIfAbsent(table.localNames)
loopNames.addAllIfAbsent(table.loopNames) loopNames.addAllIfAbsent(table.loopNames)
globalNames.reserved.addAll(table.globalNames.reserved) globalNames.reserved.addAll(table.globalNames.reserved)
@@ -272,49 +244,23 @@ class NameTables(
private fun generateNameForMemberField(field: IrField) { private fun generateNameForMemberField(field: IrField) {
require(!field.isTopLevel) require(!field.isTopLevel)
require(!field.isStatic) require(!field.isStatic)
val signature = fieldSignature(field)
if (field.isEffectivelyExternal()) { if (field.isEffectivelyExternal()) {
memberNames.declareStableName(signature, field.name.identifier) memberNames.declareStableName(field, field.name.identifier)
} else { } else {
memberNames.declareFreshName(signature, "_" + sanitizeName(field.name.asString())) memberNames.declareFreshName(field, "_" + sanitizeName(field.name.asString()))
}
}
private fun generateNameForMemberFunction(declaration: IrSimpleFunction) {
when (val signature = jsFunctionSignature(declaration, context)) {
is StableNameSignature -> memberNames.declareStableName(signature, signature.name)
is ParameterTypeBasedSignature -> memberNames.declareFreshName(signature, signature.suggestedName)
} }
} }
@Suppress("unused") @Suppress("unused")
fun dump(): String { fun dump(): String {
val local = localNames.toList().joinToString("\n") { (decl, table) -> return "Global names:\n${globalNames.dump()}"
val declRef = (decl as? IrDeclarationWithName)?.fqNameWhenAvailable ?: decl
"\nLocal names for $declRef:\n${table.dump()}\n"
}
return "Global names:\n${globalNames.dump()}" +
// "\nMember names:\n${memberNames.dump()}" +
"\nLocal names:\n$local\n"
} }
fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): String { fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): String {
val global: String? = globalNames.names[declaration] val global: String? = globalNames.names[declaration]
if (global != null) return global if (global != null) return global
var parent: IrDeclarationParent = declaration.parent
while (parent is IrDeclaration) {
val parentLocalNames = localNames[parent]
if (parentLocalNames != null) {
val localName = parentLocalNames.names[declaration]
if (localName != null)
return localName
}
parent = parent.parent
}
mappedNames?.get(mapToKey(declaration))?.let { mappedNames?.get(mapToKey(declaration))?.let {
return it return it
} }
@@ -323,8 +269,7 @@ class NameTables(
} }
fun getNameForMemberField(field: IrField): String { fun getNameForMemberField(field: IrField): String {
val signature = fieldSignature(field) val name = memberNames.names[field] ?: mappedNames?.get(mapToKey(field))
val name = memberNames.names[signature] ?: mappedNames?.get(mapToKey(signature))
// TODO investigate // TODO investigate
if (name == null) { if (name == null) {
@@ -334,24 +279,12 @@ class NameTables(
return name return name
} }
fun getNameForMemberFunction(function: IrSimpleFunction): String { private fun processTopLevelLocalDecl(declaration: IrDeclaration) {
val signature = jsFunctionSignature(function, context)
val name = memberNames.names[signature] ?: mappedNames?.get(mapToKey(signature))
// TODO Add a compiler flag, which enables this behaviour
// TODO remove in DCE
if (name == null) {
return sanitizeName(function.name.asString()) + "__error" // TODO one case is a virtual method of an abstract class with no implementation
}
return name
}
private fun generateNamesForTopLevelDecl(declaration: IrDeclaration) {
when { when {
declaration !is IrDeclarationWithName -> declaration !is IrDeclarationWithName ->
return return
// TODO: Handle JsQualifier
declaration.isEffectivelyExternal() && (declaration.getJsModule() == null || declaration.isJsNonModule()) -> declaration.isEffectivelyExternal() && (declaration.getJsModule() == null || declaration.isJsNonModule()) ->
globalNames.declareStableName(declaration, declaration.getJsNameOrKotlinName().identifier) globalNames.declareStableName(declaration, declaration.getJsNameOrKotlinName().identifier)
@@ -360,66 +293,59 @@ class NameTables(
} }
} }
inner class LocalNameGenerator(parentDeclaration: IrDeclaration) : IrElementVisitorVoid { }
val table = NameTable<IrDeclaration>(globalNames)
private val breakableDeque: Deque<IrExpression> = LinkedList() class LocalNameGenerator(parentScope: NameScope) : IrElementVisitorVoid {
val variableNames = NameTable<IrDeclarationWithName>(parentScope)
val localLoopNames = NameTable<IrLoop>()
init { private val breakableDeque: Deque<IrExpression> = LinkedList()
localNames[parentDeclaration] = table
}
private val localLoopNames = NameTable<IrLoop>() override fun visitElement(element: IrElement) {
override fun visitElement(element: IrElement) { element.acceptChildrenVoid(this)
element.acceptChildrenVoid(this) }
}
override fun visitDeclaration(declaration: IrDeclarationBase) { override fun visitDeclaration(declaration: IrDeclarationBase) {
if (declaration is IrDeclarationWithName) { super.visitDeclaration(declaration)
table.declareFreshName(declaration, declaration.name.asString()) if (declaration is IrDeclarationWithName) {
} variableNames.declareFreshName(declaration, declaration.name.asString())
super.visitDeclaration(declaration)
}
override fun visitBreak(jump: IrBreak) {
val loop = jump.loop
if (loop.label == null && loop != breakableDeque.firstOrNull()) {
persistLoopName(SYNTHETIC_LOOP_LABEL, loop)
}
super.visitBreak(jump)
}
override fun visitWhen(expression: IrWhen) {
breakableDeque.push(expression)
super.visitWhen(expression)
breakableDeque.pop()
}
override fun visitLoop(loop: IrLoop) {
breakableDeque.push(loop)
super.visitLoop(loop)
breakableDeque.pop()
val label = loop.label
if (label != null) {
persistLoopName(label, loop)
}
}
private fun persistLoopName(label: String, loop: IrLoop) {
localLoopNames.declareFreshName(loop, label)
loopNames[loop] = localLoopNames.names[loop]!!
} }
} }
fun getNameForLoop(loop: IrLoop): String? = override fun visitBreak(jump: IrBreak) {
loopNames[loop] val loop = jump.loop
if (loop.label == null && loop != breakableDeque.firstOrNull()) {
persistLoopName(SYNTHETIC_LOOP_LABEL, loop)
}
super.visitBreak(jump)
}
override fun visitWhen(expression: IrWhen) {
breakableDeque.push(expression)
super.visitWhen(expression)
breakableDeque.pop()
}
override fun visitLoop(loop: IrLoop) {
breakableDeque.push(loop)
super.visitLoop(loop)
breakableDeque.pop()
val label = loop.label
if (label != null) {
persistLoopName(label, loop)
}
}
private fun persistLoopName(label: String, loop: IrLoop) {
localLoopNames.declareFreshName(loop, label)
}
} }
@@ -427,8 +353,17 @@ fun sanitizeName(name: String): String {
if (name.isValidES5Identifier()) return name if (name.isValidES5Identifier()) return name
if (name.isEmpty()) return "_" if (name.isEmpty()) return "_"
val builder = StringBuilder()
val first = name.first().let { if (it.isES5IdentifierStart()) it else '_' } val first = name.first().let { if (it.isES5IdentifierStart()) it else '_' }
return first.toString() + name.drop(1).map { if (it.isES5IdentifierPart()) it else '_' }.joinToString("") builder.append(first)
for (idx in 1..name.lastIndex) {
val c = name[idx]
builder.append(if (c.isES5IdentifierPart()) c else '_')
}
return builder.toString()
} }
private const val SYNTHETIC_LOOP_LABEL = "\$l\$break" private const val SYNTHETIC_LOOP_LABEL = "\$l\$break"
@@ -399,7 +399,6 @@ fun Char.isES5IdentifierPart(): Boolean {
Character.CONNECTOR_PUNCTUATION -> true Character.CONNECTOR_PUNCTUATION -> true
else -> false else -> false
} || } ||
// Nl which is missing in Character.isLetter, but present in UnicodeLetter in spec
this == '\u200C' || // Zero-width non-joiner this == '\u200C' || // Zero-width non-joiner
this == '\u200D' // Zero-width joiner this == '\u200D' // Zero-width joiner
} }