[K/JS, K/Wasm] Optimize simple objects declaration and usage ^Fixed KT-58797
This commit is contained in:
+1
-1
@@ -43,7 +43,7 @@ open class PropertyAccessorInlineLowering(
|
||||
private fun canBeInlined(callee: IrSimpleFunction): Boolean {
|
||||
val property = callee.correspondingPropertySymbol?.owner ?: return false
|
||||
|
||||
// Some devirtualization required here
|
||||
// Some de-virtualization required here
|
||||
if (!property.isSafeToInline(container)) return false
|
||||
|
||||
val parent = property.parent
|
||||
|
||||
@@ -1000,12 +1000,27 @@ private val es6PrimaryConstructorUsageOptimizationLowering = makeBodyLoweringPha
|
||||
prerequisite = setOf(es6BoxParameterOptimization, es6PrimaryConstructorOptimizationLowering)
|
||||
)
|
||||
|
||||
private val purifyObjectInstanceGetters = makeDeclarationTransformerPhase(
|
||||
::PurifyObjectInstanceGettersLowering,
|
||||
name = "PurifyObjectInstanceGettersLowering",
|
||||
description = "[Optimization] Make object instance getter functions pure whenever it's possible",
|
||||
)
|
||||
|
||||
private val inlineObjectsWithPureInitialization = makeBodyLoweringPhase(
|
||||
::InlineObjectsWithPureInitializationLowering,
|
||||
name = "InlineObjectsWithPureInitializationLowering",
|
||||
description = "[Optimization] Inline object instance fields getters whenever it's possible",
|
||||
prerequisite = setOf(purifyObjectInstanceGetters)
|
||||
)
|
||||
|
||||
val optimizationLoweringList = listOf<Lowering>(
|
||||
es6CollectConstructorsWhichNeedBoxParameterLowering,
|
||||
es6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering,
|
||||
es6BoxParameterOptimization,
|
||||
es6PrimaryConstructorOptimizationLowering,
|
||||
es6PrimaryConstructorUsageOptimizationLowering,
|
||||
purifyObjectInstanceGetters,
|
||||
inlineObjectsWithPureInitialization
|
||||
)
|
||||
|
||||
val jsOptimizationPhases = SameTypeNamedCompilerPhase(
|
||||
|
||||
@@ -35,6 +35,8 @@ class JsMapping : DefaultMapping() {
|
||||
|
||||
val suspendArityStore = DefaultDelegateFactory.newDeclarationToDeclarationCollectionMapping<IrClass, Collection<IrSimpleFunction>>()
|
||||
|
||||
val objectsWithPureInitialization = DefaultDelegateFactory.newDeclarationToValueMapping<IrClass, Boolean>()
|
||||
|
||||
val inlineFunctionsBeforeInlining = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrFunction, IrFunction>()
|
||||
|
||||
// Wasm mappings
|
||||
|
||||
+1
-1
@@ -199,7 +199,7 @@ internal class JsUsefulDeclarationProcessor(
|
||||
!isExternal && !isExpect && !isBuiltInClass(this)
|
||||
|
||||
override fun processConstructedClassDeclaration(declaration: IrDeclaration) {
|
||||
if (declaration in result) return
|
||||
if (declaration.isReachable()) return
|
||||
|
||||
super.processConstructedClassDeclaration(declaration)
|
||||
|
||||
|
||||
+4
-2
@@ -108,7 +108,7 @@ abstract class UsefulDeclarationProcessor(
|
||||
contagiousReachableDeclarations.add(this as IrOverridableDeclaration<*>)
|
||||
}
|
||||
|
||||
if (this !in result) {
|
||||
if (!isReachable()) {
|
||||
result.add(this)
|
||||
queue.addLast(this)
|
||||
|
||||
@@ -181,7 +181,7 @@ abstract class UsefulDeclarationProcessor(
|
||||
}
|
||||
|
||||
protected open fun processConstructedClassDeclaration(declaration: IrDeclaration) {
|
||||
if (declaration in result) return
|
||||
if (declaration.isReachable()) return
|
||||
|
||||
fun IrOverridableDeclaration<*>.findOverriddenContagiousDeclaration(): IrOverridableDeclaration<*>? {
|
||||
for (overriddenSymbol in this.overriddenSymbols) {
|
||||
@@ -287,6 +287,8 @@ abstract class UsefulDeclarationProcessor(
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
protected fun IrDeclaration.isReachable(): Boolean = this in result
|
||||
}
|
||||
|
||||
private data class ReachabilityInfo(
|
||||
|
||||
@@ -143,14 +143,22 @@ object JsIrBuilder {
|
||||
fun buildSetVariable(symbol: IrVariableSymbol, value: IrExpression, type: IrType) =
|
||||
IrSetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, symbol, value, JsStatementOrigins.SYNTHESIZED_STATEMENT)
|
||||
|
||||
fun buildGetField(symbol: IrFieldSymbol, receiver: IrExpression?, superQualifierSymbol: IrClassSymbol? = null, type: IrType? = null) =
|
||||
fun buildGetField(
|
||||
symbol: IrFieldSymbol,
|
||||
receiver: IrExpression? = null,
|
||||
superQualifierSymbol: IrClassSymbol? = null,
|
||||
type: IrType? = null,
|
||||
startOffset: Int = UNDEFINED_OFFSET,
|
||||
endOffset: Int = UNDEFINED_OFFSET,
|
||||
origin: IrStatementOrigin? = JsStatementOrigins.SYNTHESIZED_STATEMENT
|
||||
) =
|
||||
IrGetFieldImpl(
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET,
|
||||
startOffset,
|
||||
endOffset,
|
||||
symbol,
|
||||
type ?: symbol.owner.type,
|
||||
receiver,
|
||||
JsStatementOrigins.SYNTHESIZED_STATEMENT,
|
||||
origin,
|
||||
superQualifierSymbol
|
||||
)
|
||||
|
||||
|
||||
+3
@@ -245,6 +245,9 @@ private tailrec fun IrExpression.isGetUnit(irBuiltIns: IrBuiltIns): Boolean =
|
||||
else -> false
|
||||
}
|
||||
|
||||
is IrConstructorCall ->
|
||||
this.type == irBuiltIns.unitType
|
||||
|
||||
is IrGetObjectValue ->
|
||||
this.symbol == irBuiltIns.unitClass
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceGetter
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
|
||||
class InlineObjectsWithPureInitializationLowering(val context: JsCommonBackendContext) : BodyLoweringPass {
|
||||
private val IrClass.instanceField by context.mapping.objectToInstanceField
|
||||
private val IrClass.hasPureInitialization by context.mapping.objectsWithPureInitialization
|
||||
|
||||
override fun lower(irBody: IrBody, container: IrDeclaration) {
|
||||
irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
if (!expression.symbol.owner.isObjectInstanceGetter()) return super.visitCall(expression)
|
||||
val objectToCreate = expression.symbol.owner.returnType.classOrNull?.owner ?: error("Expect return type of an object getter is an object type")
|
||||
if (objectToCreate.hasPureInitialization != true) return super.visitCall(expression)
|
||||
val instanceFieldForObject = objectToCreate.instanceField ?: error("An instance field for an object should exist")
|
||||
return JsIrBuilder.buildGetField(
|
||||
instanceFieldForObject.symbol,
|
||||
startOffset = expression.startOffset,
|
||||
endOffset = expression.endOffset,
|
||||
origin = expression.origin
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
|
||||
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBody
|
||||
@@ -28,7 +29,15 @@ class InvokeStaticInitializersLowering(val context: JsIrBackendContext) : BodyLo
|
||||
|
||||
val instance = context.mapping.objectToGetInstanceFunction[companionObject] ?: return
|
||||
|
||||
val getInstanceCall = IrCallImpl(irClass.startOffset, irClass.endOffset, context.irBuiltIns.unitType, instance.symbol, 0, 0)
|
||||
val getInstanceCall = IrCallImpl(
|
||||
irClass.startOffset,
|
||||
irClass.endOffset,
|
||||
context.irBuiltIns.unitType,
|
||||
instance.symbol,
|
||||
0,
|
||||
0,
|
||||
JsStatementOrigins.SYNTHESIZED_STATEMENT
|
||||
)
|
||||
|
||||
(irBody as IrStatementContainer).statements.add(0, getInstanceCall)
|
||||
}
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
|
||||
import org.jetbrains.kotlin.backend.common.getOrPut
|
||||
import org.jetbrains.kotlin.backend.common.ir.isPure
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceField
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceGetter
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl
|
||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
|
||||
class PurifyObjectInstanceGettersLowering(val context: JsCommonBackendContext) : DeclarationTransformer {
|
||||
private var IrClass.instanceField by context.mapping.objectToInstanceField
|
||||
|
||||
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
|
||||
if (
|
||||
!declaration.isObjectInstanceGetter() &&
|
||||
!declaration.isObjectInstanceField() &&
|
||||
!declaration.isObjectConstructor()
|
||||
) return null
|
||||
|
||||
return when (declaration) {
|
||||
is IrSimpleFunction -> declaration.purifyObjectGetterIfPossible()
|
||||
is IrField -> declaration.purifyObjectInstanceFieldIfPossible()
|
||||
is IrConstructor -> declaration.removeInstanceFieldInitializationIfPossible()
|
||||
else -> error("Unexpected IR type ${declaration::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrConstructor.removeInstanceFieldInitializationIfPossible(): List<IrDeclaration>? {
|
||||
if (parentAsClass.isPureObject()) {
|
||||
(body as? IrBlockBody)?.statements?.removeIf {
|
||||
it is IrSetField && it.symbol.owner.isObjectInstanceField()
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun IrSimpleFunction.purifyObjectGetterIfPossible(): List<IrDeclaration>? {
|
||||
val objectToCreate = returnType.classOrNull?.owner ?: return null
|
||||
|
||||
if (objectToCreate.isPureObject()) {
|
||||
val body = (body as? IrBlockBody) ?: return null
|
||||
val instanceField = objectToCreate.instanceField ?: error("Expect the object instance field to be created")
|
||||
|
||||
body.statements.clear()
|
||||
body.statements += JsIrBuilder.buildReturn(
|
||||
symbol,
|
||||
JsIrBuilder.buildGetField(instanceField.symbol),
|
||||
objectToCreate.defaultType
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun IrField.purifyObjectInstanceFieldIfPossible(): List<IrDeclaration>? {
|
||||
val objectToCreate = type.classOrNull?.owner ?: return null
|
||||
|
||||
if (objectToCreate.isPureObject()) {
|
||||
val objectConstructor = objectToCreate.primaryConstructor ?: error("Object should contain a primary constructor")
|
||||
initializer = IrExpressionBodyImpl(JsIrBuilder.buildConstructorCall(objectConstructor.symbol))
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun IrDeclaration.isObjectConstructor(): Boolean {
|
||||
return this is IrConstructor && parentAsClass.isObject
|
||||
}
|
||||
|
||||
private fun IrClass.isPureObject(): Boolean {
|
||||
return context.mapping.objectsWithPureInitialization.getOrPut(this) {
|
||||
superClass == null && primaryConstructor?.body?.statements?.all { it.isPureStatementForObjectInitialization(this@isPureObject) } != false
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrStatement.isPureStatementForObjectInitialization(owner: IrClass): Boolean {
|
||||
return (
|
||||
// Only objects which don't have a class parent
|
||||
(this is IrDelegatingConstructorCall && symbol.owner.parent == context.irBuiltIns.anyClass.owner) ||
|
||||
(this is IrExpression && isPure(anyVariable = true, checkFields = false, context = context)) ||
|
||||
(this is IrContainerExpression && statements.all { it.isPureStatementForObjectInitialization(owner) }) ||
|
||||
(this is IrVariable && initializer?.isPureStatementForObjectInitialization(owner) != false) ||
|
||||
// Only fields of the objects are safe to not save an intermediate state of another class/object/global
|
||||
(this is IrGetField && receiver?.isPureStatementForObjectInitialization(owner) == true) ||
|
||||
(this is IrSetField && receiver?.isPureStatementForObjectInitialization(owner) == true && value.isPureStatementForObjectInitialization(owner)) ||
|
||||
// Only current object could be initialized inside the object constructor, so we need to ignore it as an effect
|
||||
(this is IrSetField && symbol.owner.isObjectInstanceField()) ||
|
||||
(this is IrSetValue && symbol.owner.isLocal && value.isPureStatementForObjectInitialization(owner))
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,10 @@ import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
|
||||
import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsClass
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
||||
import org.jetbrains.kotlin.js.backend.ast.RecursiveJsVisitor
|
||||
import org.jetbrains.kotlin.js.inline.clean.ClassPostProcessor
|
||||
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor
|
||||
|
||||
fun optimizeProgramByIr(
|
||||
@@ -24,11 +26,20 @@ fun optimizeProgramByIr(
|
||||
}
|
||||
|
||||
fun optimizeFragmentByJsAst(fragment: JsIrProgramFragment) {
|
||||
fragment.declarations.statements.forEach {
|
||||
it.accept(object : RecursiveJsVisitor() {
|
||||
override fun visitFunction(x: JsFunction) {
|
||||
FunctionPostProcessor(x).apply()
|
||||
}
|
||||
})
|
||||
val optimizer = object : RecursiveJsVisitor() {
|
||||
override fun visitFunction(x: JsFunction) {
|
||||
FunctionPostProcessor(x).apply()
|
||||
}
|
||||
|
||||
override fun visitClass(x: JsClass) {
|
||||
super.visitClass(x)
|
||||
ClassPostProcessor(x).apply()
|
||||
}
|
||||
}
|
||||
|
||||
fragment.declarations.statements.forEach { it.accept(optimizer) }
|
||||
fragment.classes.values.forEach { klass ->
|
||||
klass.postDeclarationBlock.statements.forEach { it.accept(optimizer)}
|
||||
klass.preDeclarationBlock.statements.forEach { it.accept(optimizer)}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.isUnitInstanceFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.types.isString
|
||||
@@ -130,6 +129,12 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
||||
}
|
||||
val fieldName = context.getNameForField(field)
|
||||
return jsElementAccess(fieldName, expression.receiver?.accept(this, context)).withSource(expression, context)
|
||||
.apply {
|
||||
if (field.origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE && expression.origin == JsStatementOrigins.SYNTHESIZED_STATEMENT) {
|
||||
synthetic = true
|
||||
sideEffects = SideEffectKind.PURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitGetValue(expression: IrGetValue, context: JsGenerationContext): JsExpression {
|
||||
|
||||
@@ -112,3 +112,18 @@ fun irEmpty(context: JsIrBackendContext): IrExpression {
|
||||
return JsIrBuilder.buildComposite(context.dynamicType, emptyList())
|
||||
}
|
||||
|
||||
fun IrDeclaration.isObjectInstanceGetter(): Boolean {
|
||||
return this is IrSimpleFunction && isObjectInstanceGetter()
|
||||
}
|
||||
|
||||
fun IrSimpleFunction.isObjectInstanceGetter(): Boolean {
|
||||
return origin == JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION
|
||||
}
|
||||
|
||||
fun IrDeclaration.isObjectInstanceField(): Boolean {
|
||||
return this is IrField && isObjectInstanceField()
|
||||
}
|
||||
|
||||
fun IrField.isObjectInstanceField(): Boolean {
|
||||
return origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE
|
||||
}
|
||||
|
||||
@@ -107,6 +107,9 @@ fun IrBody.prependFunctionCall(
|
||||
fun JsCommonBackendContext.findUnitGetInstanceFunction(): IrSimpleFunction =
|
||||
mapping.objectToGetInstanceFunction[irBuiltIns.unitClass.owner]!!
|
||||
|
||||
fun JsCommonBackendContext.findUnitInstanceField(): IrField =
|
||||
mapping.objectToInstanceField[irBuiltIns.unitClass.owner]!!
|
||||
|
||||
fun IrDeclaration.isImportedFromModuleOnly(): Boolean {
|
||||
return isTopLevel && isEffectivelyExternal() && (getJsModule() != null && !isJsNonModule() || (parent as? IrAnnotationContainer)?.getJsModule() != null)
|
||||
}
|
||||
|
||||
+20
-2
@@ -610,6 +610,20 @@ private val unitToVoidLowering = makeWasmModulePhase(
|
||||
description = "Replace some Unit's with Void's"
|
||||
)
|
||||
|
||||
private val purifyObjectInstanceGettersLoweringPhase = makeWasmModulePhase(
|
||||
::PurifyObjectInstanceGettersLowering,
|
||||
name = "PurifyObjectInstanceGettersLowering",
|
||||
description = "[Optimization] Make object instance getter functions pure whenever it's possible",
|
||||
prerequisite = setOf(objectDeclarationLoweringPhase, objectUsageLoweringPhase)
|
||||
)
|
||||
|
||||
private val inlineObjectsWithPureInitializationLoweringPhase = makeWasmModulePhase(
|
||||
::InlineObjectsWithPureInitializationLowering,
|
||||
name = "InlineObjectsWithPureInitializationLowering",
|
||||
description = "[Optimization] Inline object instance fields getters whenever it's possible",
|
||||
prerequisite = setOf(purifyObjectInstanceGettersLoweringPhase)
|
||||
)
|
||||
|
||||
val wasmPhases = SameTypeNamedCompilerPhase(
|
||||
name = "IrModuleLowering",
|
||||
description = "IR module lowering",
|
||||
@@ -707,7 +721,6 @@ val wasmPhases = SameTypeNamedCompilerPhase(
|
||||
associatedObjectsLowering then
|
||||
|
||||
objectDeclarationLoweringPhase then
|
||||
fieldInitializersLoweringPhase then
|
||||
genericReturnTypeLowering then
|
||||
unitToVoidLowering then
|
||||
|
||||
@@ -715,8 +728,12 @@ val wasmPhases = SameTypeNamedCompilerPhase(
|
||||
builtInsLoweringPhase0 then
|
||||
|
||||
autoboxingTransformerPhase then
|
||||
explicitlyCastExternalTypesPhase then
|
||||
|
||||
objectUsageLoweringPhase then
|
||||
purifyObjectInstanceGettersLoweringPhase then
|
||||
fieldInitializersLoweringPhase then
|
||||
|
||||
explicitlyCastExternalTypesPhase then
|
||||
typeOperatorLoweringPhase then
|
||||
|
||||
// Clean up built-ins after type operator lowering
|
||||
@@ -724,5 +741,6 @@ val wasmPhases = SameTypeNamedCompilerPhase(
|
||||
|
||||
virtualDispatchReceiverExtractionPhase then
|
||||
staticMembersLoweringPhase then
|
||||
inlineObjectsWithPureInitializationLoweringPhase then
|
||||
validateIrAfterLowering
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBody
|
||||
import org.jetbrains.kotlin.ir.util.primaryConstructor
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
@@ -30,7 +31,7 @@ fun eliminateDeadDeclarations(modules: List<IrModuleFragment>, context: WasmBack
|
||||
dumpReachabilityInfoToFile
|
||||
).collectDeclarations(rootDeclarations = buildRoots(modules, context))
|
||||
|
||||
val remover = WasmUselessDeclarationsRemover(usefulDeclarations)
|
||||
val remover = WasmUselessDeclarationsRemover(context, usefulDeclarations)
|
||||
modules.onAllFiles {
|
||||
acceptVoid(remover)
|
||||
}
|
||||
@@ -58,8 +59,8 @@ private fun buildRoots(modules: List<IrModuleFragment>, context: WasmBackendCont
|
||||
add(context.irBuiltIns.throwableClass.owner)
|
||||
add(context.mainCallsWrapperFunction)
|
||||
add(context.fieldInitFunction)
|
||||
// TODO move Unit related optimization on IR level and make unit usages explicit
|
||||
add(context.findUnitGetInstanceFunction())
|
||||
add(context.findUnitInstanceField())
|
||||
add(context.irBuiltIns.unitClass.owner.primaryConstructor!!)
|
||||
|
||||
// Remove all functions used to call a kotlin closure from JS side, reachable ones will be added back later.
|
||||
removeAll(context.closureCallExports.values)
|
||||
|
||||
+16
-5
@@ -22,8 +22,6 @@ internal class WasmUsefulDeclarationProcessor(
|
||||
dumpReachabilityInfoToFile: String?
|
||||
) : UsefulDeclarationProcessor(printReachabilityInfo, removeUnusedAssociatedObjects = false, dumpReachabilityInfoToFile) {
|
||||
|
||||
private val unitGetInstance: IrSimpleFunction = context.findUnitGetInstanceFunction()
|
||||
|
||||
// The mapping from function for wrapping a kotlin closure/lambda with JS closure to function used to call a kotlin closure from JS side.
|
||||
private val kotlinClosureToJsClosureConvertFunToKotlinClosureCallFun =
|
||||
context.kotlinClosureToJsConverters.entries.associate { (k, v) -> v to context.closureCallExports[k] }
|
||||
@@ -49,6 +47,22 @@ internal class WasmUsefulDeclarationProcessor(
|
||||
super.visitVararg(expression, data)
|
||||
}
|
||||
|
||||
override fun visitSetField(expression: IrSetField, data: IrDeclaration) {
|
||||
if (!expression.symbol.owner.isObjectInstanceField()) {
|
||||
super.visitSetField(expression, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitGetField(expression: IrGetField, data: IrDeclaration) {
|
||||
val field = expression.symbol.owner
|
||||
|
||||
if (field.isObjectInstanceField()) {
|
||||
field.type.classOrFail.owner.primaryConstructor?.enqueue(field, "object lazy initialization")
|
||||
}
|
||||
|
||||
super.visitGetField(expression, data)
|
||||
}
|
||||
|
||||
private fun tryToProcessIntrinsicCall(from: IrDeclaration, call: IrCall): Boolean = when (call.symbol) {
|
||||
context.wasmSymbols.unboxIntrinsic -> {
|
||||
val fromType = call.getTypeArgument(0)
|
||||
@@ -77,9 +91,6 @@ internal class WasmUsefulDeclarationProcessor(
|
||||
super.visitCall(expression, data)
|
||||
|
||||
val function: IrFunction = expression.symbol.owner.realOverrideTarget
|
||||
if (function.returnType == context.irBuiltIns.unitType) {
|
||||
unitGetInstance.enqueue(data, "function Unit return type")
|
||||
}
|
||||
|
||||
if (tryToProcessIntrinsicCall(data, expression)) return
|
||||
if (function.hasWasmNoOpCastAnnotation()) return
|
||||
|
||||
+22
-4
@@ -5,17 +5,21 @@
|
||||
|
||||
package org.jetbrains.kotlin.backend.wasm.dce
|
||||
|
||||
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceField
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrSetField
|
||||
import org.jetbrains.kotlin.ir.types.classOrFail
|
||||
import org.jetbrains.kotlin.ir.util.primaryConstructor
|
||||
import org.jetbrains.kotlin.ir.util.transformFlat
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
|
||||
class WasmUselessDeclarationsRemover(
|
||||
private val context: WasmBackendContext,
|
||||
private val usefulDeclarations: Set<IrDeclaration>
|
||||
) : IrElementVisitorVoid {
|
||||
override fun visitElement(element: IrElement) {
|
||||
@@ -30,6 +34,14 @@ class WasmUselessDeclarationsRemover(
|
||||
process(declaration)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
|
||||
if (declaration == context.fieldInitFunction) {
|
||||
declaration.removeUnusedObjectsInitializers()
|
||||
}
|
||||
|
||||
super.visitSimpleFunction(declaration)
|
||||
}
|
||||
|
||||
// TODO bring back the primary constructor fix
|
||||
private fun process(container: IrDeclarationContainer) {
|
||||
container.declarations.transformFlat { member ->
|
||||
@@ -41,4 +53,10 @@ class WasmUselessDeclarationsRemover(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrSimpleFunction.removeUnusedObjectsInitializers() {
|
||||
(body as? IrBlockBody)?.statements?.removeIf {
|
||||
it is IrSetField && it.symbol.owner.isObjectInstanceField() && it.symbol.owner !in usefulDeclarations
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-34
@@ -14,10 +14,7 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.PrimaryConstructorLowering
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.erasedUpperBound
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.findUnitGetInstanceFunction
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.isDispatchReceiver
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
@@ -34,7 +31,6 @@ class BodyGenerator(
|
||||
val context: WasmModuleCodegenContext,
|
||||
val functionContext: WasmFunctionCodegenContext,
|
||||
private val hierarchyDisjointUnions: DisjointUnions<IrClassSymbol>,
|
||||
private val isGetUnitFunction: Boolean,
|
||||
) : IrElementVisitorVoid {
|
||||
val body: WasmExpressionBuilder = functionContext.bodyGen
|
||||
|
||||
@@ -44,16 +40,13 @@ class BodyGenerator(
|
||||
private val irBuiltIns: IrBuiltIns = backendContext.irBuiltIns
|
||||
|
||||
private val unitGetInstance by lazy { backendContext.findUnitGetInstanceFunction() }
|
||||
fun WasmExpressionBuilder.buildGetUnit() {
|
||||
buildInstr(
|
||||
WasmOp.GET_UNIT,
|
||||
SourceLocation.NoLocation("GET_UNIT"),
|
||||
WasmImmediate.FuncIdx(context.referenceFunction(unitGetInstance.symbol))
|
||||
)
|
||||
}
|
||||
private val unitInstanceField by lazy { backendContext.findUnitInstanceField() }
|
||||
|
||||
private val anyConstructor by lazy {
|
||||
wasmSymbols.any.constructors.first { it.owner.valueParameters.isEmpty() }
|
||||
fun WasmExpressionBuilder.buildGetUnit() {
|
||||
buildGetGlobal(
|
||||
context.referenceGlobalField(unitInstanceField.symbol),
|
||||
SourceLocation.NoLocation("GET_UNIT")
|
||||
)
|
||||
}
|
||||
|
||||
// Generates code for the given IR element. Leaves something on the stack unless expression was of the type Void.
|
||||
@@ -184,6 +177,7 @@ class BodyGenerator(
|
||||
val field: IrField = expression.symbol.owner
|
||||
val receiver: IrExpression? = expression.receiver
|
||||
val location = expression.getSourceLocation()
|
||||
|
||||
if (receiver != null) {
|
||||
generateExpression(receiver)
|
||||
if (backendContext.inlineClassesUtils.isClassInlineLike(field.parentAsClass)) {
|
||||
@@ -378,12 +372,6 @@ class BodyGenerator(
|
||||
return
|
||||
}
|
||||
|
||||
// Get unit is a special case because it is the only function which returns the real unit instance.
|
||||
if (call.symbol == unitGetInstance.symbol) {
|
||||
body.buildGetUnit()
|
||||
return
|
||||
}
|
||||
|
||||
// Some intrinsics are a special case because we want to remove them completely, including their arguments.
|
||||
if (!backendContext.configuration.getNotNull(JSConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS)) {
|
||||
if (call.symbol == wasmSymbols.rangeCheck) {
|
||||
@@ -473,8 +461,9 @@ class BodyGenerator(
|
||||
}
|
||||
|
||||
// Unit types don't cross function boundaries
|
||||
if (function.returnType.isUnit())
|
||||
if (function.returnType.isUnit() && function !is IrConstructor) {
|
||||
body.buildGetUnit()
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateRefNullCast(fromType: IrType, toType: IrType, location: SourceLocation) {
|
||||
@@ -701,14 +690,12 @@ class BodyGenerator(
|
||||
|
||||
private fun visitFunctionReturn(expression: IrReturn) {
|
||||
val returnType = expression.returnTargetSymbol.owner.returnType(backendContext)
|
||||
if (returnType == irBuiltIns.unitType && expression.returnTargetSymbol.owner != unitGetInstance) {
|
||||
generateAsStatement(expression.value)
|
||||
} else {
|
||||
if (isGetUnitFunction) {
|
||||
generateExpression(expression.value)
|
||||
} else {
|
||||
generateWithExpectedType(expression.value, returnType)
|
||||
}
|
||||
val isGetUnitFunction = expression.returnTargetSymbol.owner == unitGetInstance
|
||||
|
||||
when {
|
||||
isGetUnitFunction -> generateExpression(expression.value)
|
||||
returnType == irBuiltIns.unitType -> generateAsStatement(expression.value)
|
||||
else -> generateWithExpectedType(expression.value, returnType)
|
||||
}
|
||||
|
||||
if (functionContext.irFunction is IrConstructor) {
|
||||
@@ -839,11 +826,7 @@ class BodyGenerator(
|
||||
if (!seenElse && resultType != null) {
|
||||
assert(expression.type != irBuiltIns.nothingType)
|
||||
if (expression.type.isUnit()) {
|
||||
if (isGetUnitFunction) {
|
||||
generateDefaultInitializerForType(resultType, body)
|
||||
} else {
|
||||
body.buildGetUnit()
|
||||
}
|
||||
body.buildGetUnit()
|
||||
} else {
|
||||
error("'When' without else branch and non Unit type: ${expression.type.dumpKotlinLike()}")
|
||||
}
|
||||
|
||||
+6
-7
@@ -38,6 +38,7 @@ class DeclarationGenerator(
|
||||
private val irBuiltIns: IrBuiltIns = backendContext.irBuiltIns
|
||||
|
||||
private val unitGetInstanceFunction: IrSimpleFunction by lazy { backendContext.findUnitGetInstanceFunction() }
|
||||
private val unitPrimaryConstructor: IrConstructor? by lazy { backendContext.irBuiltIns.unitClass.owner.primaryConstructor }
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
error("Unexpected element of type ${element::class}")
|
||||
@@ -98,12 +99,11 @@ class DeclarationGenerator(
|
||||
// Generate function type
|
||||
val watName = declaration.fqNameWhenAvailable.toString()
|
||||
val irParameters = declaration.getEffectiveValueParameters()
|
||||
val resultType =
|
||||
when {
|
||||
// Unit_getInstance returns true Unit reference instead of "void"
|
||||
declaration == unitGetInstanceFunction -> context.transformType(declaration.returnType)
|
||||
else -> context.transformResultType(declaration.returnType)
|
||||
}
|
||||
val resultType = when (declaration) {
|
||||
// Unit_getInstance returns true Unit reference instead of "void"
|
||||
unitGetInstanceFunction, unitPrimaryConstructor -> context.transformType(declaration.returnType)
|
||||
else -> context.transformResultType(declaration.returnType)
|
||||
}
|
||||
|
||||
val wasmFunctionType =
|
||||
WasmFunctionType(
|
||||
@@ -149,7 +149,6 @@ class DeclarationGenerator(
|
||||
context = context,
|
||||
functionContext = functionCodegenContext,
|
||||
hierarchyDisjointUnions = hierarchyDisjointUnions,
|
||||
isGetUnitFunction = declaration == unitGetInstanceFunction
|
||||
)
|
||||
|
||||
if (declaration is IrConstructor) {
|
||||
|
||||
@@ -103,6 +103,9 @@ val IrType.classOrNull: IrClassSymbol?
|
||||
else -> null
|
||||
}
|
||||
|
||||
val IrType.classOrFail: IrClassSymbol
|
||||
get() = classOrNull ?: error("Expect type to be a class type")
|
||||
|
||||
val IrType.classFqName: FqName?
|
||||
get() = classOrNull?.owner?.fqNameWhenAvailable
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_463
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_338
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_579
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 48_188
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 47_948
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_605
|
||||
|
||||
fun box(): String {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 13_828
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 13_699
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 6_134
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 18_008
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_451
|
||||
|
||||
object Simple
|
||||
|
||||
object SimpleWithConstVal {
|
||||
const val MAX = 4
|
||||
}
|
||||
|
||||
object SimpleWithPureProperty {
|
||||
val text = "Hello"
|
||||
}
|
||||
|
||||
object SimpleWithPropertyInitializedDurintInit {
|
||||
val text: String
|
||||
init {
|
||||
text = "Hello"
|
||||
}
|
||||
}
|
||||
|
||||
object SimpleWithFunctionsOnly {
|
||||
fun foo() = "Foo"
|
||||
fun bar() = "Bar"
|
||||
}
|
||||
|
||||
object SimpleWithDifferentMembers {
|
||||
val foo = "Foo"
|
||||
fun bar() = "Bar"
|
||||
}
|
||||
|
||||
interface Callable {
|
||||
fun call(): String
|
||||
}
|
||||
|
||||
object SimpleWithInterface : Callable {
|
||||
override fun call() = "OK"
|
||||
}
|
||||
|
||||
object UsedGetFieldInside {
|
||||
val anotherText = SimpleWithPureProperty.text
|
||||
}
|
||||
|
||||
class ClassWithCompanion {
|
||||
companion object
|
||||
}
|
||||
|
||||
class ClassWithCompanionWithConst {
|
||||
companion object {
|
||||
const val MAX = 5
|
||||
}
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
if (Simple !is Any) return "Fail simple object"
|
||||
if (SimpleWithConstVal.MAX != 4) return "Fail simple case with const val"
|
||||
if (SimpleWithPureProperty.text != "Hello") return "Fail simple case with pure property"
|
||||
if (SimpleWithPropertyInitializedDurintInit.text != "Hello") return "Fail simple case with pure property initialized inside init block"
|
||||
if (SimpleWithFunctionsOnly.foo() != "Foo" || SimpleWithFunctionsOnly.bar() != "Bar") return "Fail simple case with functions only"
|
||||
if (SimpleWithInterface.call() != "OK") return "Fail simple case with interface implementing"
|
||||
if (UsedGetFieldInside.anotherText != "Hello") return "Fail object which used another object inside its initialization block"
|
||||
if (ClassWithCompanion.Companion !is Any) return "Fail simple companion object"
|
||||
if (ClassWithCompanionWithConst.MAX != 5) return "Fail simple companion object with const val"
|
||||
SimpleWithDifferentMembers
|
||||
return "OK"
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_505
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_380
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: mjs 5_451
|
||||
|
||||
fun box() = "OK"
|
||||
@@ -1,7 +1,7 @@
|
||||
// TARGET_BACKEND: JS_IR
|
||||
// TARGET_BACKEND: WASM
|
||||
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 13_005
|
||||
// WASM_DCE_EXPECTED_OUTPUT_SIZE: wasm 12_883
|
||||
|
||||
interface I {
|
||||
fun foo() = "OK"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsClass
|
||||
|
||||
class ClassPostProcessor(val root: JsClass) {
|
||||
val optimizations = listOf(
|
||||
{ EmptyConstructorRemoval(root).apply() }
|
||||
)
|
||||
|
||||
fun apply() {
|
||||
do {
|
||||
var hasChanges = false
|
||||
for (opt in optimizations) {
|
||||
hasChanges = hasChanges or opt()
|
||||
}
|
||||
} while (hasChanges)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsClass
|
||||
|
||||
class EmptyConstructorRemoval(private val klass: JsClass) {
|
||||
fun apply(): Boolean {
|
||||
if (klass.constructor?.body?.statements?.isEmpty() != true) return false
|
||||
klass.constructor = null
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -328,6 +328,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/classObject/objectInCompanionObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("objectLazyInitialized.kt")
|
||||
public void testObjectLazyInitialized() throws Exception {
|
||||
runTest("js/js.translator/testData/box/classObject/objectLazyInitialized.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("setVar.kt")
|
||||
public void testSetVar() throws Exception {
|
||||
|
||||
+6
@@ -328,6 +328,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest {
|
||||
runTest("js/js.translator/testData/box/classObject/objectInCompanionObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("objectLazyInitialized.kt")
|
||||
public void testObjectLazyInitialized() throws Exception {
|
||||
runTest("js/js.translator/testData/box/classObject/objectLazyInitialized.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("setVar.kt")
|
||||
public void testSetVar() throws Exception {
|
||||
|
||||
+6
@@ -328,6 +328,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
|
||||
runTest("js/js.translator/testData/box/classObject/objectInCompanionObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("objectLazyInitialized.kt")
|
||||
public void testObjectLazyInitialized() throws Exception {
|
||||
runTest("js/js.translator/testData/box/classObject/objectLazyInitialized.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("setVar.kt")
|
||||
public void testSetVar() throws Exception {
|
||||
|
||||
+6
@@ -328,6 +328,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/classObject/objectInCompanionObject.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("objectLazyInitialized.kt")
|
||||
public void testObjectLazyInitialized() throws Exception {
|
||||
runTest("js/js.translator/testData/box/classObject/objectLazyInitialized.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("setVar.kt")
|
||||
public void testSetVar() throws Exception {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// EXPECTED_REACHABLE_NODES: 1291
|
||||
// See KT-6201
|
||||
package foo
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
var log = ""
|
||||
|
||||
open class Mixin {
|
||||
init {
|
||||
log = "mixin"
|
||||
}
|
||||
}
|
||||
|
||||
fun initCall(): Number {
|
||||
log = "initCall"
|
||||
return 2
|
||||
}
|
||||
|
||||
object O1 {
|
||||
init {
|
||||
log = "O1 init"
|
||||
}
|
||||
}
|
||||
|
||||
object O2 {
|
||||
init {
|
||||
initCall()
|
||||
}
|
||||
}
|
||||
|
||||
object O3 : Mixin()
|
||||
|
||||
object O4 {
|
||||
val someValue = initCall()
|
||||
}
|
||||
|
||||
object O5 {
|
||||
val someValue = O3.also { log = "O5 also" }
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
if (log != "") return "Fail: something was initialized before any object was used"
|
||||
O1
|
||||
if (log != "O1 init") return "Fail: O1 didn't initialized lazy"
|
||||
O2
|
||||
if (log != "initCall") return "Fail: O2 didn't initialized lazy"
|
||||
O3
|
||||
if (log != "mixin") return "Fail: O3 didn't initialized lazy"
|
||||
O4
|
||||
if (log != "initCall") return "Fail: O4 didn't initialized lazy"
|
||||
O5
|
||||
if (log != "O5 also") return "Fail: O5 didn't initialized lazy"
|
||||
return "OK"
|
||||
}
|
||||
@@ -255,7 +255,6 @@ internal annotation class WasmOp(val name: String) {
|
||||
const val BR_ON_CAST_FAIL = "BR_ON_CAST_FAIL"
|
||||
const val EXTERN_INTERNALIZE = "EXTERN_INTERNALIZE"
|
||||
const val EXTERN_EXTERNALIZE = "EXTERN_EXTERNALIZE"
|
||||
const val GET_UNIT = "GET_UNIT"
|
||||
const val PSEUDO_COMMENT_PREVIOUS_INSTR = "PSEUDO_COMMENT_PREVIOUS_INSTR"
|
||||
const val PSEUDO_COMMENT_GROUP_START = "PSEUDO_COMMENT_GROUP_START"
|
||||
const val PSEUDO_COMMENT_GROUP_END = "PSEUDO_COMMENT_GROUP_END"
|
||||
|
||||
@@ -6,6 +6,8 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":core:util.runtime"))
|
||||
|
||||
implementation(kotlinStdlib())
|
||||
implementation(kotlinxCollectionsImmutable())
|
||||
testImplementation(commonDependency("junit:junit"))
|
||||
|
||||
@@ -386,9 +386,6 @@ enum class WasmOp(
|
||||
EXTERN_EXTERNALIZE("extern.externalize", 0xfb71), // anyref -> externref
|
||||
|
||||
// ============================================================
|
||||
// Pseudo-instruction, just alias for a normal call. It's used to easily spot get_unit on the wasm level.
|
||||
GET_UNIT("call", 0x10, FUNC_IDX),
|
||||
|
||||
PSEUDO_COMMENT_PREVIOUS_INSTR("<comment-single>", WASM_OP_PSEUDO_OPCODE),
|
||||
PSEUDO_COMMENT_GROUP_START("<comment-group-start>", WASM_OP_PSEUDO_OPCODE),
|
||||
PSEUDO_COMMENT_GROUP_END("<comment-group-end>", WASM_OP_PSEUDO_OPCODE),
|
||||
@@ -400,4 +397,4 @@ enum class WasmOp(
|
||||
const val WASM_OP_PSEUDO_OPCODE = 0xFFFF
|
||||
|
||||
val opcodesToOp: Map<Int, WasmOp> =
|
||||
enumValues<WasmOp>().associateBy { it.opcode }
|
||||
enumValues<WasmOp>().associateBy { it.opcode }
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.wasm.ir
|
||||
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.trimToSize
|
||||
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
|
||||
|
||||
private fun WasmOp.isOutCfgNode() = when (this) {
|
||||
@@ -18,7 +19,7 @@ private fun WasmOp.isInCfgNode() = when (this) {
|
||||
}
|
||||
|
||||
private fun WasmOp.pureStacklessInstruction() = when (this) {
|
||||
WasmOp.GET_UNIT, WasmOp.REF_NULL, WasmOp.I32_CONST, WasmOp.I64_CONST, WasmOp.F32_CONST, WasmOp.F64_CONST, WasmOp.LOCAL_GET, WasmOp.GLOBAL_GET -> true
|
||||
WasmOp.REF_NULL, WasmOp.I32_CONST, WasmOp.I64_CONST, WasmOp.F32_CONST, WasmOp.F64_CONST, WasmOp.LOCAL_GET, WasmOp.GLOBAL_GET -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -30,13 +31,12 @@ class WasmIrExpressionBuilder(
|
||||
val expression: MutableList<WasmInstr>
|
||||
) : WasmExpressionBuilder() {
|
||||
|
||||
private val lastInstr: WasmInstr?
|
||||
get() = expression.lastOrNull()
|
||||
private var eatEverythingUntilLevel: Int? = null
|
||||
private var lastInstructionIndex: Int = expression.indexOfLast { !it.operator.isPseudoInstruction }
|
||||
|
||||
private fun addInstruction(op: WasmOp, location: SourceLocation, immediates: Array<out WasmImmediate>) {
|
||||
val newInstruction = WasmInstrWithLocation(op, immediates.toList(), location)
|
||||
expression.add(newInstruction)
|
||||
expression += WasmInstrWithLocation(op, immediates.toList(), location)
|
||||
if (!op.isPseudoInstruction) lastInstructionIndex = expression.lastIndex
|
||||
}
|
||||
|
||||
private fun getCurrentEatLevel(op: WasmOp): Int? {
|
||||
@@ -64,16 +64,17 @@ class WasmIrExpressionBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
val lastInstruction = lastInstr
|
||||
if (lastInstruction == null) {
|
||||
if (lastInstructionIndex == -1) {
|
||||
addInstruction(op, location, immediates)
|
||||
return
|
||||
}
|
||||
|
||||
val lastInstruction = expression[lastInstructionIndex]
|
||||
val lastOperator = lastInstruction.operator
|
||||
|
||||
// droppable instructions + drop/unreachable -> nothing
|
||||
if ((op == WasmOp.DROP || op == WasmOp.UNREACHABLE) && lastOperator.pureStacklessInstruction()) {
|
||||
expression.removeLast()
|
||||
trimInstructionsUntil(lastInstructionIndex)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ class WasmIrExpressionBuilder(
|
||||
if (localSetNumber != null) {
|
||||
val localGetNumber = (immediates.firstOrNull() as? WasmImmediate.LocalIdx)?.value
|
||||
if (localGetNumber == localSetNumber) {
|
||||
expression.removeLast()
|
||||
trimInstructionsUntil(lastInstructionIndex)
|
||||
addInstruction(WasmOp.LOCAL_TEE, location, immediates)
|
||||
return
|
||||
}
|
||||
@@ -93,11 +94,19 @@ class WasmIrExpressionBuilder(
|
||||
addInstruction(op, location, immediates)
|
||||
}
|
||||
|
||||
private fun trimInstructionsUntil(index: Int) {
|
||||
expression.trimToSize(index)
|
||||
lastInstructionIndex = index - 1
|
||||
}
|
||||
|
||||
override var numberOfNestedBlocks: Int = 0
|
||||
set(value) {
|
||||
assert(value >= 0) { "end without matching block" }
|
||||
field = value
|
||||
}
|
||||
|
||||
private val WasmOp.isPseudoInstruction: Boolean
|
||||
get() = opcode == WASM_OP_PSEUDO_OPCODE
|
||||
}
|
||||
|
||||
inline fun buildWasmExpression(body: WasmExpressionBuilder.() -> Unit): MutableList<WasmInstr> {
|
||||
|
||||
Generated
+5
@@ -31450,6 +31450,11 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
|
||||
runTest("compiler/testData/codegen/box/size/helloWorldDOM.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("objectsOptimization.kt")
|
||||
public void testObjectsOptimization() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/size/objectsOptimization.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ok.kt")
|
||||
public void testOk() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/size/ok.kt");
|
||||
|
||||
Reference in New Issue
Block a user