[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.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.jsFunctionSignature
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
class JsBridgesConstruction(context: JsIrBackendContext) : BridgesConstruction<JsIrBackendContext>(context) {
override fun getFunctionSignature(function: IrSimpleFunction): Signature =
override fun getFunctionSignature(function: IrSimpleFunction): String =
jsFunctionSignature(function, context)
override fun getBridgeOrigin(bridge: IrSimpleFunction): IrDeclarationOrigin =
@@ -6,8 +6,8 @@
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
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.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
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> {
override fun visitSimpleFunction(declaration: IrSimpleFunction, context: JsGenerationContext): JsFunction {
val funcName = if (declaration.dispatchReceiverParameter == null) {
context.getNameForStaticFunction(declaration)
if (declaration.parent is IrFunction) {
context.getNameForValueDeclaration(declaration)
} else {
context.getNameForStaticFunction(declaration)
}
} else {
context.getNameForMemberFunction(declaration)
}
@@ -127,16 +127,18 @@ class IrModuleToJsTransformer(
): String {
val nameGenerator = refInfo.withReferenceTracking(
IrNamerImpl(newNameTables = namer),
IrNamerImpl(newNameTables = namer, backendContext),
modules
)
val staticContext = JsStaticContext(
backendContext = backendContext,
irNamer = nameGenerator
irNamer = nameGenerator,
globalNameScope = namer.globalNames
)
val rootContext = JsGenerationContext(
currentFunction = null,
staticContext = staticContext
staticContext = staticContext,
localNames = LocalNameGenerator(NameScope.EmptyScope)
)
val (importStatements, importedJsModules) =
@@ -5,12 +5,10 @@
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.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.*
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.IrClassifierSymbol
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 classNameRef = className.makeRef()
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 classBlock = JsGlobalBlock()
@@ -14,6 +14,8 @@ import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
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.util.OperatorNameConventions
@@ -50,7 +52,14 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat
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 body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock()
@@ -6,21 +6,81 @@
package org.jetbrains.kotlin.ir.backend.js.utils
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.JsNameRef
interface IrNamer {
fun getNameForConstructor(constructor: IrConstructor): JsName
fun getNameForMemberFunction(function: IrSimpleFunction): 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 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 getNameForLoop(loop: IrLoop): JsName?
fun getNameForProperty(property: IrProperty): JsName
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
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
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.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 =
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 {
require(function.dispatchReceiverParameter != null)
return newNameTables.getNameForMemberFunction(function).toJsName()
val signature = jsFunctionSignature(function, context)
return signature.toJsName()
}
override fun getNameForMemberField(field: IrField): JsName {
require(!field.isStatic)
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
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrFunction
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.util.isSuspend
import org.jetbrains.kotlin.js.backend.ast.JsName
@@ -23,12 +25,14 @@ val emptyScope: JsScope
class JsGenerationContext(
val currentFunction: IrFunction?,
val staticContext: JsStaticContext
val staticContext: JsStaticContext,
val localNames: LocalNameGenerator? = null
): IrNamer by staticContext {
fun newDeclaration(func: IrFunction? = null): JsGenerationContext {
fun newDeclaration(func: IrFunction? = null, localNames: LocalNameGenerator? = null): JsGenerationContext {
return JsGenerationContext(
currentFunction = func,
staticContext = staticContext
staticContext = staticContext,
localNames = localNames,
)
}
@@ -39,10 +43,21 @@ class JsGenerationContext(
if (currentFunction!!.isSuspend) {
JsNameRef(Namer.CONTINUATION)
} 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 {
val overriddenSymbols = (currentFunction as? IrSimpleFunction)?.overriddenSymbols ?: return false
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.transformers.irToJs.JsIntrinsicTransformers
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.js.backend.ast.JsGlobalBlock
class JsStaticContext(
val backendContext: JsIrBackendContext,
private val irNamer: IrNamer
private val irNamer: IrNamer,
val globalNameScope: NameScope
) : IrNamer by irNamer {
val intrinsics = JsIntrinsicTransformers(backendContext)
val classModels = mutableMapOf<IrClassSymbol, JsIrClassModel>()
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.util.fqNameWhenAvailable
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.visitors.IrElementVisitorVoid
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.isES5IdentifierStart
import org.jetbrains.kotlin.js.naming.isValidES5Identifier
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
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.
private fun <T> mapToKey(declaration: T): String {
return with(JsManglerIr) {
if (declaration is IrDeclaration) {
declaration.hashedMangle.toString()
} else if (declaration is Signature) {
declaration.toString().hashMangle.toString()
} else if (declaration is String) {
declaration.hashMangle.toString()
} else {
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>(
val parent: NameTable<*>? = null,
val parent: NameScope = EmptyScope,
val reserved: MutableSet<String> = mutableSetOf(),
val mappedNames: MutableMap<String, String>? = null
) {
var finished = false
) : NameScope() {
val names = mutableMapOf<T, String>()
private fun isReserved(name: String): Boolean {
if (parent != null && parent.isReserved(name))
return true
return name in reserved
private val suggestedNameLastIdx = mutableMapOf<String, Int>()
override fun isReserved(name: String): Boolean {
return parent.isReserved(name) || name in reserved
}
fun declareStableName(declaration: T, name: String) {
if (parent != null) assert(parent.finished)
assert(!finished)
names[declaration] = name
reserved.add(name)
mappedNames?.set(mapToKey(declaration), name)
@@ -74,7 +79,7 @@ class NameTable<T>(
if (!isReserved(suggestedName))
return suggestedName
var i = 0
var i = suggestedNameLastIdx[suggestedName] ?: 0
fun freshName() =
suggestedName + "_" + i
@@ -82,6 +87,9 @@ class NameTable<T>(
while (isReserved(freshName())) {
i++
}
suggestedNameLastIdx[suggestedName] = i
return freshName()
}
}
@@ -93,33 +101,25 @@ fun NameTable<IrDeclaration>.dump(): String =
"--- $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 {
if (field.isEffectivelyExternal()) {
return StableNameSignature(field.name.identifier)
}
private const val RESERVED_MEMBER_NAME_SUFFIX = "_k$"
return BackingFieldSignature(field)
}
fun jsFunctionSignature(declaration: IrFunction, context: JsIrBackendContext?): Signature {
fun jsFunctionSignature(declaration: IrFunction, context: JsIrBackendContext?): String {
require(!declaration.isStaticMethodOfClass)
require(declaration.dispatchReceiverParameter != null)
val declarationName = declaration.getJsNameOrKotlinName().asString()
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()
nameBuilder.append(declarationName)
// TODO should we skip type parameters and use upper bound of type parameter when print type of value parameters?
declaration.typeParameters.ifNotEmpty {
nameBuilder.append("_\$t")
@@ -141,20 +141,19 @@ fun jsFunctionSignature(declaration: IrFunction, context: JsIrBackendContext?):
val signature = nameBuilder.toString()
// TODO: Check reserved names
return ParameterTypeBasedSignature(signature, declarationName)
// TODO: Use better hashCode
return sanitizeName(declarationName) + "_" + abs(signature.hashCode()).toString(Character.MAX_RADIX) + RESERVED_MEMBER_NAME_SUFFIX
}
class NameTables(
packages: List<IrPackageFragment>,
packages: Iterable<IrPackageFragment>,
reservedForGlobal: MutableSet<String> = mutableSetOf(),
reservedForMember: MutableSet<String> = mutableSetOf(),
val mappedNames: MutableMap<String, String>? = null,
private val context: JsIrBackendContext? = null
) {
val globalNames: NameTable<IrDeclaration>
private val memberNames: NameTable<Signature>
private val localNames = mutableMapOf<IrDeclaration, NameTable<IrDeclaration>>()
private val memberNames: NameTable<IrField>
private val loopNames = mutableMapOf<IrLoop, String>()
init {
@@ -171,74 +170,48 @@ class NameTables(
mappedNames = mappedNames
)
val classDeclaration = mutableListOf<IrClass>()
for (p in packages) {
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) {
for (memberDecl in declaration.statements) {
if (memberDecl is IrDeclaration) {
generateNamesForTopLevelDecl(memberDecl)
processTopLevelLocalDecl(memberDecl)
if (memberDecl is IrClass) {
classDeclaration += memberDecl
processNonTopLevelLocalDecl(memberDecl)
}
}
}
}
}
}
}
globalNames.finished = true
for (declaration in classDeclaration) {
acceptDeclaration(declaration)
}
for (p in packages) {
for (declaration in p.declarations) {
acceptDeclaration(declaration)
private fun acceptNonExternalClass(declaration: IrClass) {
for (memberDecl in declaration.declarations) {
when (memberDecl) {
is IrField ->
generateNameForMemberField(memberDecl)
}
}
}
private fun acceptDeclaration(declaration: IrDeclaration) {
val localNameGenerator = LocalNameGenerator(declaration)
private fun processNonTopLevelLocalDecl(declaration: IrDeclaration) {
if (declaration is IrClass) {
if (declaration.isEffectivelyExternal()) {
declaration.acceptChildrenVoid(object : IrElementVisitorVoid {
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)
}
}
if (!declaration.isEffectivelyExternal()) {
acceptNonExternalClass(declaration)
}
} else {
declaration.acceptChildrenVoid(localNameGenerator)
}
}
@@ -262,7 +235,6 @@ class NameTables(
globalNames.names.addAllIfAbsent(table.globalNames.names)
memberNames.names.addAllIfAbsent(table.memberNames.names)
localNames.addAllIfAbsent(table.localNames)
loopNames.addAllIfAbsent(table.loopNames)
globalNames.reserved.addAll(table.globalNames.reserved)
@@ -272,49 +244,23 @@ class NameTables(
private fun generateNameForMemberField(field: IrField) {
require(!field.isTopLevel)
require(!field.isStatic)
val signature = fieldSignature(field)
if (field.isEffectivelyExternal()) {
memberNames.declareStableName(signature, field.name.identifier)
memberNames.declareStableName(field, field.name.identifier)
} else {
memberNames.declareFreshName(signature, "_" + 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)
memberNames.declareFreshName(field, "_" + sanitizeName(field.name.asString()))
}
}
@Suppress("unused")
fun dump(): String {
val local = localNames.toList().joinToString("\n") { (decl, table) ->
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"
return "Global names:\n${globalNames.dump()}"
}
fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): String {
val global: String? = globalNames.names[declaration]
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 {
return it
}
@@ -323,8 +269,7 @@ class NameTables(
}
fun getNameForMemberField(field: IrField): String {
val signature = fieldSignature(field)
val name = memberNames.names[signature] ?: mappedNames?.get(mapToKey(signature))
val name = memberNames.names[field] ?: mappedNames?.get(mapToKey(field))
// TODO investigate
if (name == null) {
@@ -334,24 +279,12 @@ class NameTables(
return name
}
fun getNameForMemberFunction(function: IrSimpleFunction): String {
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) {
private fun processTopLevelLocalDecl(declaration: IrDeclaration) {
when {
declaration !is IrDeclarationWithName ->
return
// TODO: Handle JsQualifier
declaration.isEffectivelyExternal() && (declaration.getJsModule() == null || declaration.isJsNonModule()) ->
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 {
localNames[parentDeclaration] = table
}
private val breakableDeque: Deque<IrExpression> = LinkedList()
private val localLoopNames = NameTable<IrLoop>()
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitDeclaration(declaration: IrDeclarationBase) {
if (declaration is IrDeclarationWithName) {
table.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]!!
override fun visitDeclaration(declaration: IrDeclarationBase) {
super.visitDeclaration(declaration)
if (declaration is IrDeclarationWithName) {
variableNames.declareFreshName(declaration, declaration.name.asString())
}
}
fun getNameForLoop(loop: IrLoop): String? =
loopNames[loop]
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)
}
}
@@ -427,8 +353,17 @@ fun sanitizeName(name: String): String {
if (name.isValidES5Identifier()) return name
if (name.isEmpty()) return "_"
val builder = StringBuilder()
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"
@@ -399,7 +399,6 @@ fun Char.isES5IdentifierPart(): Boolean {
Character.CONNECTOR_PUNCTUATION -> true
else -> false
} ||
// Nl which is missing in Character.isLetter, but present in UnicodeLetter in spec
this == '\u200C' || // Zero-width non-joiner
this == '\u200D' // Zero-width joiner
}