[JS IR BE] Implement block decomposition lowering

This commit is contained in:
Roman Artemev
2018-04-27 01:18:57 +03:00
parent 1eeb202a1a
commit 6811c87fc7
7 changed files with 761 additions and 15 deletions
@@ -31,8 +31,8 @@ import org.jetbrains.kotlin.resolve.scopes.MemberScope
class JsIrBackendContext(
val module: ModuleDescriptor,
override val irBuiltIns: IrBuiltIns,
irModuleFragment: IrModuleFragment,
symbolTable: SymbolTable
val symbolTable: SymbolTable,
irModuleFragment: IrModuleFragment
) : CommonBackendContext {
val intrinsics = JsIntrinsics(module, irBuiltIns, symbolTable)
@@ -9,7 +9,10 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.common.runOnFilePostfix
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.lower.BlockDecomposerLowering
import org.jetbrains.kotlin.ir.backend.js.lower.FunctionReferenceLowering
import org.jetbrains.kotlin.ir.backend.js.lower.IntrinsicifyCallsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.SecondaryCtorLowering
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
@@ -38,8 +41,8 @@ fun compile(
val context = JsIrBackendContext(
analysisResult.moduleDescriptor,
psi2IrContext.irBuiltIns,
moduleFragment,
psi2IrContext.symbolTable
psi2IrContext.symbolTable,
moduleFragment
)
ExternalDependenciesGenerator(psi2IrContext.symbolTable, psi2IrContext.irBuiltIns).generateUnboundSymbolsAsDependencies(moduleFragment)
@@ -62,6 +65,7 @@ fun JsIrBackendContext.lower(file: IrFile) {
InnerClassConstructorCallsLowering(this).runOnFilePostfix(file)
PropertiesLowering().lower(file)
InitializersLowering(this, JsLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER, false).runOnFilePostfix(file)
BlockDecomposerLowering(this).runOnFilePostfix(file)
SecondaryCtorLowering(this).runOnFilePostfix(file)
IntrinsicifyCallsLowering(this).lower(file)
FunctionReferenceLowering(this).lower(file)
@@ -5,17 +5,20 @@
package org.jetbrains.kotlin.ir.backend.js.descriptors
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.ir.descriptors.IrTemporaryVariableDescriptorImpl
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.createFunctionSymbol
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.KotlinType
@@ -85,6 +88,15 @@ object JsSymbolBuilder {
SourceElement.NO_SOURCE
)
)
fun buildTempVar(containingSymbol: IrFunctionSymbol, type: KotlinType, name: String? = null, mutable: Boolean = false) = IrVariableSymbolImpl(
IrTemporaryVariableDescriptorImpl(
containingSymbol.descriptor,
Name.identifier(name ?: "tmp"),
type, mutable
)
)
}
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.expressions.IrStatementOriginImpl
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
@@ -42,13 +43,39 @@ object JsIrBuilder {
fun buildFunction(symbol: IrSimpleFunctionSymbol) = IrFunctionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, SYNTHESIZED_DECLARATION, symbol)
fun buildGetValue(symbol: IrValueSymbol) = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, SYNTHESIZED_STATEMENT)
fun buildGetObjectValue(type: KotlinType, classSymbol: IrClassSymbol) =
IrGetObjectValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, classSymbol)
fun buildGetClass(expression: IrExpression, type: KotlinType) = IrGetClassImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, expression)
fun buildBlockBody(stmts: List<IrStatement>) = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, stmts)
fun buildGetValue(symbol: IrValueSymbol) = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, SYNTHESIZED_STATEMENT)
fun buildSetVariable(symbol: IrVariableSymbol, value: IrExpression) =
IrSetVariableImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, value, SYNTHESIZED_STATEMENT)
fun buildGetField(symbol: IrFieldSymbol, receiver: IrExpression?, superQualifierSymbol: IrClassSymbol? = null) =
IrGetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, receiver, SYNTHESIZED_STATEMENT, superQualifierSymbol)
fun buildSetField(symbol: IrFieldSymbol, receiver: IrExpression?, value: IrExpression, superQualifierSymbol: IrClassSymbol? = null) =
IrSetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, receiver, value, SYNTHESIZED_STATEMENT, superQualifierSymbol)
fun buildBlockBody(statements: List<IrStatement>) = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, statements)
fun buildBlock(type: KotlinType) = IrBlockImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, SYNTHESIZED_STATEMENT)
fun buildBlock(type: KotlinType, statements: List<IrStatement>) =
IrBlockImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, SYNTHESIZED_STATEMENT, statements)
fun buildFunctionReference(type: KotlinType, symbol: IrFunctionSymbol) =
IrFunctionReferenceImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, symbol, symbol.descriptor)
fun buildVar(symbol: IrVariableSymbol) = IrVariableImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, SYNTHESIZED_DECLARATION, symbol)
fun buildVar(symbol: IrVariableSymbol, initializer: IrExpression? = null) =
IrVariableImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, SYNTHESIZED_DECLARATION, symbol).apply { this.initializer = initializer }
fun buildBreak(type: KotlinType, loop: IrLoop) = IrBreakImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, loop)
fun buildContinue(type: KotlinType, loop: IrLoop) = IrContinueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, loop)
fun buildIfElse(type: KotlinType, cond: IrExpression, thenBranch: IrExpression, elseBranch: IrExpression? = null) = IrIfThenElseImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, cond, thenBranch, elseBranch, SYNTHESIZED_STATEMENT
)
fun buildNull(type: KotlinType) = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type)
fun buildBoolean(type: KotlinType, v: Boolean) = IrConstImpl.boolean(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v)
}
@@ -0,0 +1,708 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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.FunctionLoweringPass
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.descriptors.JsSymbolBuilder
import org.jetbrains.kotlin.ir.backend.js.descriptors.initialize
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.util.transformFlat
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.types.KotlinType
private typealias VisitData = Nothing?
class BlockDecomposerLowering(val context: JsIrBackendContext) : FunctionLoweringPass {
private lateinit var function: IrFunction
private var tmpVarCounter: Int = 0
private val statementVisitor = StatementVisitor()
private val expressionVisitor = ExpressionVisitor()
private val constTrue = JsIrBuilder.buildBoolean(context.builtIns.booleanType, true)
private val constFalse = JsIrBuilder.buildBoolean(context.builtIns.booleanType, false)
private val nothingType = context.builtIns.nullableNothingType
private val unitValue =
JsIrBuilder.buildGetObjectValue(context.builtIns.unitType, context.symbolTable.referenceClass(context.builtIns.unit))
private val unreachableFunction =
JsSymbolBuilder.buildSimpleFunction(context.module, Namer.UNREACHABLE_NAME).initialize(type = nothingType)
override fun lower(irFunction: IrFunction) {
function = irFunction
tmpVarCounter = 0
irFunction.body?.accept(statementVisitor, null)
}
enum class VisitStatus {
DECOMPOSED,
TERMINATED,
KEPT
}
abstract class VisitResult(val status: VisitStatus) {
open val statements: MutableList<IrStatement> get() = error("No statement is possible here")
open val resultValue: IrExpression get() = error("This result has no value")
abstract fun runIfChanged(action: VisitResult.() -> VisitResult): VisitResult
abstract fun <T> runIfChangedOrDefault(default: T, action: VisitResult.(d: T) -> T): T
abstract fun applyIfChanged(action: VisitResult.() -> Unit): VisitResult
}
object KeptResult : VisitResult(VisitStatus.KEPT) {
override fun runIfChanged(action: VisitResult.() -> VisitResult) = this
override fun <T> runIfChangedOrDefault(default: T, action: VisitResult.(d: T) -> T) = default
override fun applyIfChanged(action: VisitResult.() -> Unit) = this
}
abstract class ChangedResult(
override val statements: MutableList<IrStatement>,
override val resultValue: IrExpression,
status: VisitStatus
) :
VisitResult(status) {
override fun runIfChanged(action: VisitResult.() -> VisitResult) = run { action() }
override fun <T> runIfChangedOrDefault(default: T, action: VisitResult.(d: T) -> T) = run { action(default) }
override fun applyIfChanged(action: VisitResult.() -> Unit) = apply { action() }
}
class DecomposedResult(statements: MutableList<IrStatement>, resultValue: IrExpression) :
ChangedResult(statements, resultValue, VisitStatus.DECOMPOSED) {
constructor(statement: IrStatement, resultValue: IrExpression) : this(mutableListOf(statement), resultValue)
}
class TerminatedResult(statements: MutableList<IrStatement>, resultValue: IrExpression) :
ChangedResult(statements, resultValue, VisitStatus.TERMINATED)
abstract inner class DecomposerVisitor : IrElementVisitor<VisitResult, VisitData> {
override fun visitElement(element: IrElement, data: VisitData) = KeptResult
}
inner class StatementVisitor : DecomposerVisitor() {
override fun visitBlockBody(body: IrBlockBody, data: VisitData): VisitResult {
body.statements.transformFlat {
processStatement(it, data)
}
return KeptResult
}
override fun visitContainerExpression(expression: IrContainerExpression, data: VisitData): VisitResult {
expression.statements.transformFlat {
processStatement(it, data)
}
return KeptResult
}
private fun processStatement(statement: IrStatement, data: VisitData): List<IrStatement>? {
val result = statement.accept(this, data)
if (result == KeptResult) return null
return result.statements
}
override fun visitExpression(expression: IrExpression, data: VisitData): VisitResult = expression.accept(expressionVisitor, data)
override fun visitReturn(expression: IrReturn, data: VisitData): VisitResult {
val expressionResult = expression.value.accept(expressionVisitor, data)
return expressionResult.runIfChanged {
val returnValue = expressionResult.resultValue
expressionResult.statements += IrReturnImpl(
expression.startOffset,
expression.endOffset,
expression.type,
expression.returnTargetSymbol,
returnValue
)
DecomposedResult(expressionResult.statements, unitValue)
}
}
override fun visitBreakContinue(jump: IrBreakContinue, data: VisitData) = KeptResult as VisitResult
override fun visitThrow(expression: IrThrow, data: VisitData): VisitResult {
val expressionResult = expression.value.accept(expressionVisitor, data)
return expressionResult.runIfChanged {
val returnValue = expressionResult.resultValue
expressionResult.statements += IrThrowImpl(expression.startOffset, expression.endOffset, expression.type, returnValue)
DecomposedResult(expressionResult.statements, unitValue)
}
}
override fun visitVariable(declaration: IrVariable, data: VisitData): VisitResult {
declaration.initializer?.let {
val initResult = it.accept(expressionVisitor, data)
return initResult.runIfChanged {
statements += declaration.apply { initializer = resultValue }
DecomposedResult(statements, unitValue)
}
}
return KeptResult
}
override fun visitWhen(expression: IrWhen, data: VisitData): VisitResult {
val newWhen = processWhen(expression, data, expressionVisitor, this) { visitResult, original ->
visitResult.runIfChangedOrDefault(listOf(original)) { statements }
}
if (newWhen != expression) {
return DecomposedResult(newWhen, unitValue)
}
return KeptResult
}
private inner class BreakContinueUpdater(val breakLoop: IrLoop, val continueLoop: IrLoop) : IrElementTransformer<IrLoop> {
override fun visitBreak(jump: IrBreak, data: IrLoop) = jump.apply {
if (loop == data) loop = breakLoop
}
override fun visitContinue(jump: IrContinue, data: IrLoop) = jump.apply {
if (loop == data) loop = continueLoop
}
}
// while (c_block {}) {
// body {}
// }
//
// is transformed into
//
// while (true) {
// var cond = c_block {}
// if (!cond) break
// body {}
// }
//
override fun visitWhileLoop(loop: IrWhileLoop, data: VisitData): VisitResult {
val conditionResult = loop.condition.accept(expressionVisitor, data)
val bodyResult = loop.body?.accept(this, data)
val unitType = context.builtIns.unitType
return conditionResult.runIfChanged {
bodyResult?.run { assert(status == VisitStatus.KEPT) }
val condVariable = conditionResult.resultValue
val thenBlock = JsIrBuilder.buildBlock(unitType, listOf(JsIrBuilder.buildBreak(unitType, loop)))
val breakCond = JsIrBuilder.buildCall(context.irBuiltIns.booleanNotSymbol).apply {
putValueArgument(0, condVariable)
}
statements.add(JsIrBuilder.buildIfElse(unitType, breakCond, thenBlock))
val oldBody = loop.body!!
val newBody = statements + oldBody
val newLoop = IrWhileLoopImpl(loop.startOffset, loop.endOffset, loop.type, loop.origin).apply {
label = loop.label
condition = constTrue
body = oldBody.run { IrBlockImpl(startOffset, endOffset, type, origin, newBody) }
}
newLoop.body?.transform(BreakContinueUpdater(newLoop, newLoop), loop)
DecomposedResult(newLoop, unitValue)
}
}
// do {
// body {}
// } while (c_block {})
//
// is transformed into
//
// do {
// do {
// body {}
// } while (false)
// cond = c_block {}
// } while (cond)
//
override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: VisitData): VisitResult {
val bodyResult = loop.body?.accept(this, data)
val conditionResult = loop.condition.accept(expressionVisitor, data)
val unitType = context.builtIns.unitType
return conditionResult.runIfChanged {
bodyResult?.run { assert(status == VisitStatus.KEPT) }
val body = loop.body!!
val innerLoop = IrDoWhileLoopImpl(loop.startOffset, loop.endOffset, unitType, loop.origin, body, constFalse).apply {
label = makeLoopLabel()
}
val condVariable = conditionResult.resultValue
val newBody = JsIrBuilder.buildBlock(body.type, listOf(innerLoop) + statements)
val newLoop = IrDoWhileLoopImpl(loop.startOffset, loop.endOffset, unitType, loop.origin, newBody, condVariable).apply {
label = loop.label ?: makeLoopLabel()
}
body.transform(BreakContinueUpdater(newLoop, innerLoop), loop)
DecomposedResult(newLoop, unitValue)
}
}
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: VisitData): VisitResult {
val argumentResult = expression.argument.accept(expressionVisitor, data)
return argumentResult.runIfChanged {
val newOperator = IrTypeOperatorCallImpl(
expression.startOffset,
expression.endOffset,
expression.type,
expression.operator,
expression.typeOperand,
resultValue,
expression.typeOperandClassifier
)
val resValue: IrExpression
if (!KotlinBuiltIns.isUnit(expression.type)) {
val resVar = makeTempVar(expression.type)
statements += JsIrBuilder.buildVar(resVar, newOperator)
resValue = JsIrBuilder.buildGetValue(resVar)
} else {
statements += newOperator
resValue = unitValue
}
DecomposedResult(statements, resValue)
}
}
override fun visitField(declaration: IrField, data: VisitData): VisitResult {
if (declaration.initializer == null) return KeptResult
val result = declaration.initializer!!.accept(expressionVisitor, data)
return result.runIfChanged {
TODO()
}
}
override fun visitSetField(expression: IrSetField, data: VisitData): VisitResult {
var needNew = false
val receiverResult = expression.receiver?.accept(expressionVisitor, data)
val valueResult = expression.value.accept(expressionVisitor, data)
val newStatements = mutableListOf<IrStatement>()
val newReceiver = receiverResult?.runIfChangedOrDefault(expression.receiver) {
newStatements += statements
needNew = true
resultValue
}
val newValue = valueResult.runIfChangedOrDefault(expression.value) {
newStatements += statements
needNew = true
resultValue
}
if (needNew) {
newStatements += JsIrBuilder.buildSetField(expression.symbol, newReceiver, newValue, expression.superQualifierSymbol)
return DecomposedResult(newStatements, unitValue)
}
return KeptResult
}
override fun visitSetVariable(expression: IrSetVariable, data: VisitData): VisitResult {
val valueResult = expression.value.accept(expressionVisitor, data)
return valueResult.runIfChanged {
statements += JsIrBuilder.buildSetVariable(expression.symbol, resultValue)
DecomposedResult(statements, unitValue)
}
}
}
inner class ExpressionVisitor : DecomposerVisitor() {
override fun visitExpression(expression: IrExpression, data: VisitData) = KeptResult
//
// val x = block {
// ...
// expr
// }
//
// is transformed into
//
// val x_tmp
// block {
// ...
// x_tmp = expr
// }
// val x = x_tmp
override fun visitContainerExpression(expression: IrContainerExpression, data: VisitData): VisitResult {
val variable = makeTempVar(expression.type)
val varDeclaration = JsIrBuilder.buildVar(variable)
val blockStatements = expression.statements
val lastStatement: IrStatement? = blockStatements.lastOrNull()
val body = when (expression) {
is IrBlock -> IrBlockImpl(
expression.startOffset,
expression.endOffset,
context.builtIns.unitType,
expression.origin
)
is IrComposite -> IrCompositeImpl(
expression.startOffset,
expression.endOffset,
context.builtIns.unitType,
expression.origin
)
else -> error("Unsupported block type")
}
val collectingList = body.statements
for (i in 0 until blockStatements.size - 1) {
val statement = blockStatements[i]
val tempResult = statement.accept(statementVisitor, data)
collectingList += tempResult.runIfChangedOrDefault(listOf(statement)) { statements }
}
if (lastStatement != null) {
val result = lastStatement.accept(expressionVisitor, data).runIfChangedOrDefault(lastStatement as IrExpression) {
collectingList += statements
resultValue
}
collectingList += JsIrBuilder.buildSetVariable(variable, result)
return DecomposedResult(mutableListOf(varDeclaration, body), JsIrBuilder.buildGetValue(variable))
} else {
// do not allow variable to be uninitialized
return DecomposedResult(mutableListOf(), unitValue)
}
}
override fun visitGetClass(expression: IrGetClass, data: VisitData): VisitResult {
val res = super.visitGetClass(expression, data)
return res.runIfChanged { DecomposedResult(statements, JsIrBuilder.buildGetClass(resultValue, expression.type)) }
}
override fun visitGetField(expression: IrGetField, data: VisitData): VisitResult {
val res = super.visitGetField(expression, data)
return res.runIfChanged {
DecomposedResult(statements, JsIrBuilder.buildGetField(expression.symbol, resultValue, expression.superQualifierSymbol))
}
}
private fun prepareArgument(arg: IrExpression, needWrap: Boolean, statements: MutableList<IrStatement>): IrExpression {
return if (needWrap) {
val wrapVar = makeTempVar(arg.type)
statements += JsIrBuilder.buildVar(wrapVar, arg)
JsIrBuilder.buildGetValue(wrapVar)
} else arg
}
private fun mapArguments(
argResults: List<Pair<IrExpression?, VisitResult?>>,
toDecompose: Int,
newStatements: MutableList<IrStatement>
): List<IrExpression?> {
var decomposed = 0
return argResults.map { (original, result) ->
val needWrap = decomposed < toDecompose
original?.let {
val evaluated = result!!.runIfChangedOrDefault(it) {
newStatements += statements
decomposed++
resultValue
}
prepareArgument(evaluated, needWrap, newStatements)
}
}
}
// The point here is to keep original evaluation order so (there is the same story for StringConcat)
// d.foo(p1, p2, block {}, p4, block {}, p6, p7)
//
// is transformed into
//
// var d_tmp = d
// var p1_tmp = p1
// var p2_tmp = p2
// var p3_tmp = block {}
// var p4_tmp = p4
// var p5_tmp = block {}
// d_tmp.foo(p1_tmp, p2_tmp, p3_tmp, p4_tmp, p5_tmp, p6, p7)
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: VisitData): VisitResult {
var argsCounter = 0
val dispatchResult = expression.dispatchReceiver?.accept(this, data)?.applyIfChanged { argsCounter++ }
val extensionResult = expression.extensionReceiver?.accept(this, data)?.applyIfChanged { argsCounter++ }
val argumentResults = mutableListOf<Pair<IrExpression?, VisitResult?>>().also {
for (i in 0 until expression.valueArgumentsCount) {
val arg = expression.getValueArgument(i)
val result = arg?.accept(this, data)?.applyIfChanged { argsCounter++ }
it += Pair(arg, result)
}
}
if (argsCounter == 0) return KeptResult
val newStatements = mutableListOf<IrStatement>()
val needWrapDR = 0 != argsCounter
val newDispatchReceiverValue = dispatchResult?.runIfChangedOrDefault(expression.dispatchReceiver) {
newStatements += statements
argsCounter--
resultValue
}
val newDispatchReceiver = newDispatchReceiverValue?.let { prepareArgument(it, needWrapDR, newStatements) }
val needWrapER = 0 != argsCounter
val newExtensionReceiverValue = extensionResult?.runIfChangedOrDefault(expression.extensionReceiver) {
newStatements += statements
argsCounter--
resultValue
}
val newExtensionReceiver = newExtensionReceiverValue?.let { prepareArgument(it, needWrapER, newStatements) }
val newArguments = mapArguments(argumentResults, argsCounter, newStatements)
expression.dispatchReceiver = newDispatchReceiver
expression.extensionReceiver = newExtensionReceiver
newArguments.forEachIndexed { i, v -> expression.putValueArgument(i, v) }
val resultVar = makeTempVar(expression.type)
// TODO: get rid of temporary variable
newStatements += JsIrBuilder.buildVar(resultVar, expression)
return DecomposedResult(newStatements, JsIrBuilder.buildGetValue(resultVar))
}
override fun visitWhen(expression: IrWhen, data: VisitData): VisitResult {
val collectiveVar = makeTempVar(expression.type)
val varDeclaration = JsIrBuilder.buildVar(collectiveVar)
val newWhen = processWhen(expression, data, this, this) { visitResult, original ->
val resultList = mutableListOf<IrStatement>()
val newResult = visitResult.runIfChangedOrDefault(original as IrExpression) {
resultList += statements
resultValue
}
resultList.apply { add(JsIrBuilder.buildSetVariable(collectiveVar, newResult)) }
}
if (newWhen != expression) {
return DecomposedResult(mutableListOf(varDeclaration, newWhen), JsIrBuilder.buildGetValue(collectiveVar))
}
return KeptResult
}
override fun visitStringConcatenation(expression: IrStringConcatenation, data: VisitData): VisitResult {
var decomposed = 0
val arguments = expression.arguments.map {
Pair(it, it.accept(this, data).applyIfChanged { decomposed++ })
}
if (decomposed == 0) return KeptResult
val newStatements = mutableListOf<IrStatement>()
val newArguments = mapArguments(arguments, decomposed, newStatements)
val newExpression =
IrStringConcatenationImpl(expression.startOffset, expression.endOffset, expression.type, newArguments.map { it!! })
// Unlike to IrCall string concatenation has no side effects itself so it is possible to pass it as return value
return DecomposedResult(newStatements, newExpression)
}
private fun <T : IrStatement> transformTermination(
value: IrExpression,
instantiater: (value: IrExpression) -> T,
data: VisitData
): TerminatedResult {
val valueResult = value.accept(this, data)
val newStatements = mutableListOf<IrStatement>()
val returnValue = valueResult.runIfChangedOrDefault(value) {
newStatements += statements
resultValue
}
newStatements += instantiater(returnValue)
return TerminatedResult(newStatements, JsIrBuilder.buildCall(unreachableFunction))
}
override fun visitReturn(expression: IrReturn, data: VisitData) = transformTermination(
expression.value,
{ v -> IrReturnImpl(expression.startOffset, expression.endOffset, expression.type, expression.returnTargetSymbol, v) },
data
)
override fun visitThrow(expression: IrThrow, data: VisitData) = transformTermination(
expression.value,
{ v -> IrThrowImpl(expression.startOffset, expression.endOffset, expression.type, v) },
data
)
override fun visitBreakContinue(jump: IrBreakContinue, data: VisitData): VisitResult {
return DecomposedResult(jump, JsIrBuilder.buildCall(unreachableFunction))
}
}
fun makeTempVar(type: KotlinType) =
JsSymbolBuilder.buildTempVar(function.symbol, type, "tmp\$dcms\$${tmpVarCounter++}", true)
fun makeLoopLabel() = "\$l\$${tmpVarCounter++}"
private fun processWhen(
expression: IrWhen,
data: VisitData,
expressionVisitor: DecomposerVisitor,
statementVisitor: DecomposerVisitor,
bodyBuilder: (VisitResult, IrStatement) -> List<IrStatement>
): IrStatement {
var needNewConds = false
var needNewBodies = false
val branches = expression.branches.map {
val conditionResult = it.condition.accept(expressionVisitor, data).applyIfChanged { needNewConds = true }
val bodyResult = it.result.accept(statementVisitor, data).applyIfChanged { needNewBodies = true }
Pair(conditionResult, bodyResult)
}
// keep it as is
if (!(needNewConds || needNewBodies)) return expression
val unitType = context.builtIns.unitType
if (needNewConds) {
// [val x = ] when {
// c1_block {} -> b1_block {}
// ....
// cn_block {} -> bn_block {}
// else -> else_block {}
// }
//
// transformed into if-else chain
// [var x_tmp]
// val c1 = c1_block {}
// if (c1) {
// [x_tmp =] b1_block {}
// } else {
// val c2 = c2_block {}
// if (c2) {
// [x_tmp =] b2_block{}
// } else {
// ...
// else {
// [x_tmp =] else_block {}
// }
// }
// [val x = x_tmp]
//
val block = IrBlockImpl(expression.startOffset, expression.endOffset, unitType, expression.origin)
branches.foldIndexed(block) { i, appendBlock, (condResult, bodyResult) ->
val originalBranch = expression.branches[i]
val originalCondition = originalBranch.condition
val originalResult = originalBranch.result
val irCondition = condResult.runIfChangedOrDefault(originalCondition) {
appendBlock.statements += statements
resultValue
}
val thenBlock = IrBlockImpl(originalResult.startOffset, originalResult.endOffset, unitType).apply {
statements += bodyBuilder(bodyResult, originalResult)
}
JsIrBuilder.buildBlock(unitType).also {
val elseBlock = if (originalBranch is IrElseBranch) null else it
val ifElseNode = IrIfThenElseImpl(
originalBranch.startOffset,
originalBranch.endOffset,
unitType,
irCondition,
thenBlock,
elseBlock,
expression.origin
)
appendBlock.statements += ifElseNode
}
}
return block
}
// [val x = ] when {
// c1 -> b1_block {}
// ....
// cn -> bn_block {}
// else -> else_block {}
// }
//
// transformed into if-else chain
//
// [var x_tmp]
// when {
// c1 -> [x_tmp =] b1_block {}
// ...
// cn -> [x_tmp =] bn_block {}
// else -> [x_tmp =] else_block {}
// }
// [val x = x_tmp]
val newWhen = IrWhenImpl(expression.startOffset, expression.endOffset, unitType, expression.origin)
branches.forEachIndexed { i, (_, bodyResult) ->
val originalBranch = expression.branches[i]
val condition = originalBranch.condition
val originalResult = originalBranch.result
val body = IrBlockImpl(originalResult.startOffset, originalResult.endOffset, unitType).apply {
statements += bodyBuilder(bodyResult, originalResult)
}
newWhen.branches += when (originalBranch) {
is IrElseBranch -> IrElseBranchImpl(originalBranch.startOffset, originalBranch.endOffset, condition, body)
else /* IrBranch */ -> IrBranchImpl(originalBranch.startOffset, originalBranch.endOffset, condition, body)
}
}
return newWhen
}
}
@@ -19,13 +19,6 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsExpression, JsGenerationContext> {
private val neutralExpression: JsExpression = JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(1))
override fun visitContainerExpression(expression: IrContainerExpression, context: JsGenerationContext): JsExpression =
expression.statements.map { it.accept(this, context) }.fold(neutralExpression) { left, right ->
if (left != neutralExpression) JsBinaryOperation(JsBinaryOperator.COMMA, left, right) else right
}
override fun visitExpressionBody(body: IrExpressionBody, context: JsGenerationContext): JsExpression =
body.expression.accept(this, context)
@@ -34,6 +34,8 @@ object Namer {
val CALL_FUNCTION = "call"
val APPLY_FUNCTION = "apply"
val UNREACHABLE_NAME = "\$unreachable"
val OUTER_FIELD_NAME = "\$outer"
val DELEGATE = "\$delegate"