[JS IR BE] Support try-catch-finally in new backend

This commit is contained in:
Roman Artemev
2018-06-13 16:59:31 +03:00
committed by Roman Artemev
parent cdfcedb897
commit b65443dd25
11 changed files with 207 additions and 19 deletions
@@ -86,6 +86,7 @@ fun JsIrBackendContext.lower(file: IrFile) {
InnerClassConstructorCallsLowering(this).runOnFilePostfix(file)
PropertiesLowering().lower(file)
InitializersLowering(this, JsLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER, false).runOnFilePostfix(file)
MultipleCatchesLowering(this).lower(file)
TypeOperatorLowering(this).lower(file)
BlockDecomposerLowering(this).runOnFilePostfix(file)
SecondaryCtorLowering(this).runOnFilePostfix(file)
@@ -12,9 +12,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationOriginImpl
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.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.types.KotlinType
@@ -38,6 +36,8 @@ object JsIrBuilder {
fun buildReturn(targetSymbol: IrFunctionSymbol, value: IrExpression) =
IrReturnImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, targetSymbol, value)
fun buildThrow(type: KotlinType, value: IrExpression) = IrThrowImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, value)
fun buildValueParameter(symbol: IrValueParameterSymbol) =
IrValueParameterImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, SYNTHESIZED_DECLARATION, symbol)
@@ -45,6 +45,7 @@ object JsIrBuilder {
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 buildGetValue(symbol: IrValueSymbol) = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, SYNTHESIZED_STATEMENT)
@@ -76,6 +77,12 @@ object JsIrBuilder {
UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, cond, thenBranch, elseBranch, SYNTHESIZED_STATEMENT
)
fun buildWhen(type: KotlinType, branches: List<IrBranch>) =
IrWhenImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, SYNTHESIZED_STATEMENT, branches)
fun buildTypeOperator(type: KotlinType, operator: IrTypeOperator, argument: IrExpression, toType: KotlinType, symbol: IrClassifierSymbol) =
IrTypeOperatorCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, operator, toType, argument, symbol)
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)
fun buildInt(type: KotlinType, v: Int) = IrConstImpl.int(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v)
@@ -34,8 +34,8 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont
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 unitType = context.builtIns.unitType
private val unitValue = JsIrBuilder.buildGetObjectValue(unitType, context.symbolTable.referenceClass(context.builtIns.unit))
private val unreachableFunction =
JsSymbolBuilder.buildSimpleFunction(context.module, Namer.UNREACHABLE_NAME).initialize(type = nothingType)
@@ -237,7 +237,6 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont
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) }
@@ -283,7 +282,6 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont
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) }
@@ -411,13 +409,13 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont
is IrBlock -> IrBlockImpl(
expression.startOffset,
expression.endOffset,
context.builtIns.unitType,
unitType,
expression.origin
)
is IrComposite -> IrCompositeImpl(
expression.startOffset,
expression.endOffset,
context.builtIns.unitType,
unitType,
expression.origin
)
else -> error("Unsupported block type")
@@ -663,6 +661,43 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont
return DecomposedResult(jump, JsIrBuilder.buildCall(unreachableFunction))
}
override fun visitTry(aTry: IrTry, data: VisitData): VisitResult {
val tryResult = aTry.tryResult.accept(expressionVisitor, data)
val catchResults = aTry.catches.map { Pair(it, it.result.accept(expressionVisitor, data)) }
val finallyResult = aTry.finallyExpression?.accept(statementVisitor, data)
finallyResult?.run { assert(status == VisitStatus.KEPT) }
val resultSymbol = makeTempVar(aTry.type)
val resultDeclaration = JsIrBuilder.buildVar(resultSymbol)
val newTryValue = tryResult.runIfChangedOrDefault(aTry.tryResult) { resultValue }
val trySetResult = JsIrBuilder.buildSetVariable(resultSymbol, newTryValue) as IrStatement
val tryBlock = IrBlockImpl(aTry.tryResult.startOffset, aTry.tryResult.endOffset, unitType).apply {
statements += tryResult.runIfChangedOrDefault(listOf(trySetResult)) { statements + trySetResult }
}
val catchBlocks = catchResults.map { (original, result) ->
val newCatchResult = result.runIfChangedOrDefault(original.result) { resultValue }
val catchSetResult = JsIrBuilder.buildSetVariable(resultSymbol, newCatchResult) as IrStatement
val catchBlock = IrBlockImpl(original.result.startOffset, original.result.endOffset, unitType)
catchBlock.statements += result.runIfChangedOrDefault(listOf(catchSetResult)) { statements + catchSetResult }
IrCatchImpl(original.startOffset, original.endOffset, original.catchParameter, catchBlock)
}
val newTry = aTry.run { IrTryImpl(startOffset, endOffset, unitType, tryBlock, catchBlocks, finallyExpression) }
return DecomposedResult(mutableListOf(resultDeclaration, newTry), JsIrBuilder.buildGetValue(resultSymbol))
}
override fun visitSetVariable(expression: IrSetVariable, data: VisitData): VisitResult {
val result = expression.accept(statementVisitor, data)
return if (result.status == VisitStatus.KEPT) {
DecomposedResult(expression, unitValue)
} else result
}
override fun visitLoop(loop: IrLoop, data: VisitData): VisitResult {
val result = loop.accept(statementVisitor, data)
return if (result.status == VisitStatus.KEPT) {
@@ -702,8 +737,6 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont
// keep it as is
if (!(needNewConds || needNewBodies)) return expression
val unitType = context.builtIns.unitType
if (needNewConds) {
// [val x = ] when {
@@ -0,0 +1,121 @@
/*
* 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.FileLoweringPass
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.symbols.JsSymbolBuilder
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBranchImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCatchImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrElseBranchImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTryImpl
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
import org.jetbrains.kotlin.types.CommonSupertypes
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isDynamic
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
/**
* Since JS does not support multiple catch blocks by default we should replace them with similar `when` statement, so
*
* try {}
* catch (ex1: Ex1) { catch1(ex1) }
* catch (ex2: Ex2) { catch2(ex2) }
* catch (ex3: Ex3) { catch3(ex3) }
* [catch (exd: dynamic) { catch_dynamic(exd) } ]
* finally {}
*
* is converted into
*
* try {}
* catch (ex: Ex = LCA(Ex1, Ex2, Ex3) {
* when (ex) {
* ex is Ex1 -> catch1((Ex1)ex)
* ex is Ex2 -> catch2((Ex2)ex)
* ex is Ex3 -> catch3((Ex3)ex)
* else throw ex [ | catch_dynamic(ex) ]
* }
* }
* finally {}
*/
class MultipleCatchesLowering(val context: JsIrBackendContext) : FileLoweringPass {
val litTrue = JsIrBuilder.buildBoolean(context.irBuiltIns.bool, true)
val unitType = context.irBuiltIns.unit
val nothingType = context.irBuiltIns.nothing
override fun lower(irFile: IrFile) {
irFile.transformChildren(object : IrElementTransformer<IrDeclaration?> {
override fun visitDeclaration(declaration: IrDeclaration, data: IrDeclaration?) =
super.visitDeclaration(declaration, declaration)
override fun visitTry(aTry: IrTry, data: IrDeclaration?): IrExpression {
aTry.transformChildren(this, data)
if (aTry.catches.isEmpty()) return aTry.apply { assert(finallyExpression != null) }
val commonType = mergeTypes(aTry.catches.map { it.catchParameter.type })
val pendingExceptionSymbol = JsSymbolBuilder.buildVar(data!!.descriptor, commonType, "\$pending\$", false)
val pendingExceptionDeclaration = JsIrBuilder.buildVar(pendingExceptionSymbol)
val pendingException = JsIrBuilder.buildGetValue(pendingExceptionSymbol)
val branches = mutableListOf<IrBranch>()
for (catch in aTry.catches) {
assert(!catch.catchParameter.isVar) { "caught exception parameter has to immutable" }
val type = catch.catchParameter.type
val typeSymbol = context.symbolTable.referenceClassifier(type.constructor.declarationDescriptor!!)
val castedPendingException = buildImplicitCast(pendingException, type, typeSymbol)
val catchBody = catch.result.transform(object : IrElementTransformer<VariableDescriptor> {
override fun visitGetValue(expression: IrGetValue, data: VariableDescriptor) =
if (expression.descriptor == data) castedPendingException else expression
}, catch.parameter)
if (type.isDynamic()) {
branches += IrElseBranchImpl(catch.startOffset, catch.endOffset, litTrue, catchBody)
break
} else {
val typeCheck = buildIsCheck(pendingException, type, typeSymbol)
branches += IrBranchImpl(catch.startOffset, catch.endOffset, typeCheck, catchBody)
}
}
if (!commonType.isDynamic()) {
val throwStatement = JsIrBuilder.buildThrow(nothingType, pendingException)
branches += IrElseBranchImpl(litTrue, JsIrBuilder.buildBlock(nothingType, listOf(throwStatement)))
}
val whenStatement = JsIrBuilder.buildWhen(aTry.type, branches)
val newCatch = aTry.run {
IrCatchImpl(catches.first().startOffset, catches.last().endOffset, pendingExceptionDeclaration, whenStatement)
}
return aTry.run { IrTryImpl(startOffset, endOffset, type, tryResult, listOf(newCatch), finallyExpression) }
}
private fun buildIsCheck(value: IrExpression, toType: KotlinType, toTypeSymbol: IrClassifierSymbol) =
JsIrBuilder.buildTypeOperator(context.irBuiltIns.bool, IrTypeOperator.INSTANCEOF, value, toType, toTypeSymbol)
private fun buildImplicitCast(value: IrExpression, toType: KotlinType, toTypeSymbol: IrClassifierSymbol) =
JsIrBuilder.buildTypeOperator(toType, IrTypeOperator.IMPLICIT_CAST, value, toType, toTypeSymbol)
private fun mergeTypes(types: List<KotlinType>) = CommonSupertypes.commonSupertype(types).also {
assert(it.isSubtypeOf(context.builtIns.throwable.defaultType) || it.isDynamic())
}
}, null)
}
}
@@ -225,7 +225,9 @@ class SecondaryCtorLowering(val context: JsIrBackendContext) : IrElementTransfor
}
val fromPrimary = ownerFunc!! is IrConstructor
val newCall = redirectCall(expression, context.secondaryConstructorsMap[target]!!.delegate)
// TODO: what is `deserialized` constructor?
val ctor = context.secondaryConstructorsMap[target] ?: return expression
val newCall = redirectCall(expression, ctor.delegate)
val readThis = if (fromPrimary) {
IrGetValueImpl(
@@ -122,7 +122,7 @@ class TypeOperatorLowering(val context: JsIrBackendContext) : FileLoweringPass {
private fun lowerImplicitCast(expression: IrTypeOperatorCall) = expression.run {
assert(operator == IrTypeOperator.IMPLICIT_CAST)
IrCompositeImpl(startOffset, endOffset, typeOperand, null, listOf(argument))
argument
}
// Note: native `instanceOf` is not used which is important because of null-behaviour
@@ -65,6 +65,21 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
return JsEmpty
}
override fun visitTry(aTry: IrTry, context: JsGenerationContext): JsStatement {
val jsTryBlock = aTry.tryResult.accept(this, context).asBlock()
val jsCatch = aTry.catches.singleOrNull()?.let {
val name = context.getNameForSymbol(it.catchParameter.symbol)
val jsCatchBlock = it.result.accept(this, context)
JsCatch(context.currentScope, name.ident, jsCatchBlock)
}
val jsFinallyBlock = aTry.finallyExpression?.accept(this, context)?.asBlock()
return JsTry(jsTryBlock, jsCatch, jsFinallyBlock)
}
override fun visitWhen(expression: IrWhen, context: JsGenerationContext): JsStatement {
return expression.toJsNode(this, context, ::JsIf) ?: JsEmpty
}
@@ -17,10 +17,15 @@ class IrModuleToJsTransformer(val backendContext: JsIrBackendContext) : BaseIrEl
val rootContext = JsGenerationContext(JsRootScope(program), backendContext)
// TODO: fix it up with new name generator
val kotlinAny = "kotlin\$${backendContext.builtIns.any.name.asString()}"
val anyName = rootContext.currentScope.declareName(kotlinAny)
val symbolTable = backendContext.symbolTable
val anySymbol = symbolTable.referenceClass(backendContext.builtIns.any)
val throwableSymbol = symbolTable.referenceClass(backendContext.builtIns.throwable)
val anyName = rootContext.getNameForSymbol(anySymbol)
val throwableName = rootContext.getNameForSymbol(throwableSymbol)
program.globalBlock.statements += JsVars(JsVars.JsVar(anyName, Namer.JS_OBJECT))
program.globalBlock.statements += JsVars(JsVars.JsVar(throwableName, Namer.JS_ERROR))
declaration.files.forEach {
program.globalBlock.statements.add(it.accept(IrFileToJsTransformer(), rootContext))
@@ -64,4 +64,6 @@ fun translateCallArguments(expression: IrMemberAccessExpression, context: JsGene
}
}
val IrFunction.isStatic: Boolean get() = this.dispatchReceiverParameter == null
val IrFunction.isStatic: Boolean get() = this.dispatchReceiverParameter == null
fun JsStatement.asBlock() = this as? JsBlock ?: JsBlock(this)
@@ -46,7 +46,7 @@ object Namer {
val EXTENSION_RECEIVER_NAME = "\$receiver"
val IMPLICIT_RECEIVER_NAME = "this"
val ANOTHER_THIS_PARAMETER_NAME = "$this"
val ANOTHER_THIS_PARAMETER_NAME = "\$this"
val THROW_CLASS_CAST_EXCEPTION_FUN_NAME = "throwCCE"
val THROW_ILLEGAL_STATE_EXCEPTION_FUN_NAME = "throwISE"
@@ -60,6 +60,8 @@ object Namer {
val DEFINE_INLINE_FUNCTION = "defineInlineFunction"
val DEFAULT_PARAMETER_IMPLEMENTOR_SUFFIX = "\$default"
val JS_ERROR = JsNameRef("Error")
val JS_OBJECT = JsNameRef("Object")
val JS_OBJECT_CREATE_FUNCTION = JsNameRef("create", JS_OBJECT)
@@ -367,9 +367,9 @@ class SymbolTable {
}
}
inline fun <T> SymbolTable.withScope(owner: DeclarationDescriptor, block: () -> T): T {
inline fun <T> SymbolTable.withScope(owner: DeclarationDescriptor, block: SymbolTable.() -> T): T {
enterScope(owner)
val result = block()
val result = block(this)
leaveScope(owner)
return result
}