[JS IR BE] Support try-catch-finally in new backend
This commit is contained in:
committed by
Roman Artemev
parent
cdfcedb897
commit
b65443dd25
@@ -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)
|
||||
|
||||
+41
-8
@@ -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 {
|
||||
|
||||
+121
@@ -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)
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -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(
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+15
@@ -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
|
||||
}
|
||||
|
||||
+7
-2
@@ -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))
|
||||
|
||||
+3
-1
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user