[JS IR] Move isPure check to backend.common and add platform-specific check on calls with side effects

This commit is contained in:
Ilya Goncharov
2021-03-03 15:29:43 +03:00
parent 7bbb5b77db
commit 120b5b9e05
11 changed files with 79 additions and 48 deletions
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFileSymbol
@@ -45,4 +46,8 @@ interface CommonBackendContext : BackendContext, LoggingContext {
classSymbolMap: MutableMap<IrClassSymbol, IrClassSymbol>,
functionSymbolMap: MutableMap<IrSimpleFunctionSymbol, IrSimpleFunctionSymbol>
) {}
fun isSideEffectFree(call: IrCall): Boolean {
return false
}
}
@@ -644,3 +644,42 @@ private fun computeAllOverridden(function: IrSimpleFunction, result: MutableSet<
}
}
}
// TODO: support more cases like built-in operator call and so on
fun IrExpression?.isPure(
anyVariable: Boolean,
checkFields: Boolean = true,
context: CommonBackendContext? = null
): Boolean {
if (this == null) return true
fun IrExpression.isPureImpl(): Boolean {
return when (this) {
is IrConst<*> -> true
is IrGetValue -> {
if (anyVariable) return true
val valueDeclaration = symbol.owner
if (valueDeclaration is IrVariable) !valueDeclaration.isVar
else true
}
is IrCall -> context?.isSideEffectFree(this) ?: false
is IrGetObjectValue -> type.isUnit()
else -> false
}
}
if (isPureImpl()) return true
if (!checkFields) return false
if (this is IrGetField) {
if (!symbol.owner.isFinal) {
if (!anyVariable) {
return false
}
}
return receiver.isPure(anyVariable)
}
return false
}
@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.isPure
import org.jetbrains.kotlin.backend.common.ir.isPure
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmentSymbol
import org.jetbrains.kotlin.ir.types.*
@@ -58,6 +59,9 @@ class JsIrBackendContext(
override var inVerbosePhase: Boolean = false
override fun isSideEffectFree(call: IrCall): Boolean =
call.symbol in intrinsics.primitiveToLiteralConstructor.values
val devMode = configuration[JSConfigurationKeys.DEVELOPER_MODE] ?: false
val errorPolicy = configuration[JSConfigurationKeys.ERROR_TOLERANCE_POLICY] ?: ErrorTolerancePolicy.DEFAULT
@@ -131,6 +135,7 @@ class JsIrBackendContext(
private val COROUTINE_CONTEXT_NAME = Name.identifier("coroutineContext")
private val COROUTINE_IMPL_NAME = Name.identifier("CoroutineImpl")
private val CONTINUATION_NAME = Name.identifier("Continuation")
// TODO: what is more clear way reference this getter?
private val CONTINUATION_CONTEXT_GETTER_NAME = Name.special("<get-context>")
@@ -262,15 +267,29 @@ class JsIrBackendContext(
val coroutineGetContext: IrSimpleFunctionSymbol
get() {
val contextGetter =
continuationClass.owner.declarations.filterIsInstance<IrSimpleFunction>().atMostOne { it.name == CONTINUATION_CONTEXT_GETTER_NAME }
?: continuationClass.owner.declarations.filterIsInstance<IrProperty>().atMostOne { it.name == CONTINUATION_CONTEXT_PROPERTY_NAME }?.getter!!
continuationClass.owner.declarations.filterIsInstance<IrSimpleFunction>()
.atMostOne { it.name == CONTINUATION_CONTEXT_GETTER_NAME }
?: continuationClass.owner.declarations.filterIsInstance<IrProperty>()
.atMostOne { it.name == CONTINUATION_CONTEXT_PROPERTY_NAME }?.getter!!
return contextGetter.symbol
}
val coroutineGetContextJs
get() = ir.symbols.coroutineGetContext
val coroutineEmptyContinuation = symbolTable.referenceProperty(getProperty(FqName.fromSegments(listOf("kotlin", "coroutines", "js", "internal", "EmptyContinuation"))))
val coroutineEmptyContinuation = symbolTable.referenceProperty(
getProperty(
FqName.fromSegments(
listOf(
"kotlin",
"coroutines",
"js",
"internal",
"EmptyContinuation"
)
)
)
)
val coroutineContextProperty: PropertyDescriptor
get() {
@@ -283,7 +302,8 @@ class JsIrBackendContext(
val newThrowableSymbol = symbolTable.referenceSimpleFunction(getJsInternalFunction("newThrowable"))
val extendThrowableSymbol = symbolTable.referenceSimpleFunction(getJsInternalFunction("extendThrowable"))
val setPropertiesToThrowableInstanceSymbol = symbolTable.referenceSimpleFunction(getJsInternalFunction("setPropertiesToThrowableInstance"))
val setPropertiesToThrowableInstanceSymbol =
symbolTable.referenceSimpleFunction(getJsInternalFunction("setPropertiesToThrowableInstance"))
val throwISEsymbol = symbolTable.referenceSimpleFunction(getFunctions(kotlinPackageFqn.child(Name.identifier("THROW_ISE"))).single())
val throwIAEsymbol = symbolTable.referenceSimpleFunction(getFunctions(kotlinPackageFqn.child(Name.identifier("THROW_IAE"))).single())
@@ -314,7 +334,8 @@ class JsIrBackendContext(
val defaultThrowableCtor by lazy2 { throwableConstructors.single { !it.owner.isPrimary && it.owner.valueParameters.size == 0 } }
val kpropertyBuilder = getFunctions(FqName("kotlin.js.getPropertyCallableRef")).single().let { symbolTable.referenceSimpleFunction(it) }
val klocalDelegateBuilder = getFunctions(FqName("kotlin.js.getLocalDelegateReference")).single().let { symbolTable.referenceSimpleFunction(it) }
val klocalDelegateBuilder =
getFunctions(FqName("kotlin.js.getLocalDelegateReference")).single().let { symbolTable.referenceSimpleFunction(it) }
private fun referenceOperators(): Map<Name, MutableMap<IrClassifierSymbol, IrSimpleFunctionSymbol>> {
val primitiveIrSymbols = irBuiltIns.primitiveIrTypes.map { it.classifierOrFail as IrClassSymbol }
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.util.isPure
import org.jetbrains.kotlin.backend.common.ir.isPure
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
@@ -519,7 +519,7 @@ class BlockDecomposerTransformer(
compositesLeft == 0 -> value
index == 0 && dontDetachFirstArgument -> value
value == null -> value
value.isPure(false) -> value
value.isPure(anyVariable = false, context = context) -> value
else -> {
// TODO: do not wrap if value is pure (const, variable, etc)
val irVar = makeTempVar(value.type, value)
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrArithBuilder
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.prependFunctionCall
import org.jetbrains.kotlin.ir.util.isPure
import org.jetbrains.kotlin.backend.common.ir.isPure
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
import org.jetbrains.kotlin.ir.builders.declarations.buildField
import org.jetbrains.kotlin.ir.declarations.*
@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrArithBuilder
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.util.isPure
import org.jetbrains.kotlin.backend.common.ir.isPure
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower.cleanup
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.util.isPure
import org.jetbrains.kotlin.backend.common.ir.isPure
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.isNothing
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.util.isPure
import org.jetbrains.kotlin.backend.common.ir.isPure
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.wasm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.ir.isPure
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
@@ -558,39 +558,4 @@ val IrFunction.originalFunction: IrFunction
get() = (this as? IrAttributeContainer)?.attributeOwnerId as? IrFunction ?: this
val IrProperty.originalProperty: IrProperty
get() = attributeOwnerId as? IrProperty ?: this
// TODO: support more cases like built-in operator call and so on
fun IrExpression?.isPure(anyVariable: Boolean, checkFields: Boolean = true): Boolean {
if (this == null) return true
fun IrExpression.isPureImpl(): Boolean {
return when (this) {
is IrConst<*> -> true
is IrGetValue -> {
if (anyVariable) return true
val valueDeclaration = symbol.owner
if (valueDeclaration is IrVariable) !valueDeclaration.isVar
else true
}
is IrGetObjectValue -> type.isUnit()
else -> false
}
}
if (isPureImpl()) return true
if (!checkFields) return false
if (this is IrGetField) {
if (!symbol.owner.isFinal) {
if (!anyVariable) {
return false
}
}
return receiver.isPure(anyVariable)
}
return false
}
get() = attributeOwnerId as? IrProperty ?: this