Initial try/catch/finally support

This commit is contained in:
Michael Bogdanov
2016-10-05 10:40:46 +03:00
committed by Dmitry Petrov
parent 4bcd3d1bdb
commit 5121fcb287
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil.*
import org.jetbrains.kotlin.codegen.StackValue.*
import org.jetbrains.kotlin.codegen.pseudoInsns.fixStackAndJump
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
import org.jetbrains.kotlin.ir.IrElement
@@ -38,32 +39,46 @@ import org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_THROWABLE_TYPE
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.util.*
class LoopInfo(val irLoop: IrLoop, val continueLabel: Label, val breakLabel: Label)
open class ExpressionInfo(val expression: IrExpression)
class LoopInfo(val loop: IrLoop, val continueLabel: Label, val breakLabel: Label): ExpressionInfo(loop)
class TryInfo(val tryBlock: IrTry) : ExpressionInfo(tryBlock) {
val gaps = mutableListOf<Label>()
}
class BlockInfo private constructor(val parent: BlockInfo?) {
val variables: MutableList<VariableInfo> = mutableListOf()
private val loops = mutableListOf<LoopInfo>()
private val infos = Stack<ExpressionInfo>()
fun create() = BlockInfo(this)
fun addLoop(loop: LoopInfo) {
loops.add(loop)
fun create() = BlockInfo(this).apply {
this@apply.infos.addAll(this@BlockInfo.infos)
}
fun removeLoop(loop: LoopInfo) {
assert (loops.last() == loop)
loops.dropLast(1)
fun addInfo(loop: ExpressionInfo) {
infos.add(loop)
}
fun findLoop(loop: IrLoop): LoopInfo? {
return loops.firstOrNull { it.irLoop == loop } ?: parent?.findLoop(loop)
fun removeInfo(info: ExpressionInfo) {
assert (peek() == info)
pop()
}
fun pop(): ExpressionInfo = infos.pop()
fun peek(): ExpressionInfo = infos.peek()
fun isEmpty(): Boolean = infos.isEmpty()
fun hasFinallyBlocks(): Boolean = infos.firstIsInstanceOrNull<TryInfo>() != null
companion object {
fun create() = BlockInfo(null)
}
@@ -372,10 +387,15 @@ class ExpressionCodegen(
}
override fun visitReturn(expression: IrReturn, data: BlockInfo): StackValue {
val value = expression.value?.apply {
val value = expression.value.apply {
gen(this, returnType, data)
}
val afterReturnLabel = Label()
generateFinallyBlocksIfNeeded(returnType, afterReturnLabel, data)
mv.areturn(returnType)
mv.mark(afterReturnLabel)
return expression.onStack
}
@@ -467,47 +487,76 @@ class ExpressionCodegen(
}
override fun visitWhileLoop(loop: IrWhileLoop, data: BlockInfo): StackValue {
val continueLabel = Label()
mv.mark(continueLabel)
val continueLabel = markNewLabel()
val endLabel = Label()
val condition = loop.condition
gen(condition, data)
BranchedValue.condJump(StackValue.onStack(condition.asmType), endLabel, true, mv)
with(LoopInfo(loop, continueLabel, endLabel)) {
data.addLoop(this)
data.addInfo(this)
loop.body?.apply {
gen(this, data)
}
data.removeLoop(this)
data.removeInfo(this)
}
mv.goTo(continueLabel)
mv.mark(endLabel)
return loop.onStack
}
override fun visitBreakContinue(jump: IrBreakContinue, data: BlockInfo): StackValue {
val loop = data.findLoop(jump.loop)!!
val label = if (jump is IrBreak) loop.breakLabel else loop.continueLabel
mv.goTo(label)
generateBreakOrContinueExpression(jump, Label(), data)
return none()
}
private fun generateBreakOrContinueExpression(
expression: IrBreakContinue,
afterBreakContinueLabel: Label,
data: BlockInfo
) {
if (data.isEmpty()) {
throw UnsupportedOperationException("Target label for break/continue not found")
}
val stackElement = data.peek()
if (stackElement is TryInfo) {
//noinspection ConstantConditions
genFinallyBlockOrGoto(stackElement, null, afterBreakContinueLabel, data)
}
else if (stackElement is LoopInfo) {
val loop = expression.loop
//noinspection ConstantConditions
if (loop == stackElement.loop) {
val label = if (expression is IrBreak) stackElement.breakLabel else stackElement.continueLabel
mv.fixStackAndJump(label)
mv.mark(afterBreakContinueLabel)
return
}
}
else {
throw UnsupportedOperationException("Wrong BlockStackElement in processing stack")
}
data.pop()
val result = generateBreakOrContinueExpression(expression, afterBreakContinueLabel, data)
data.addInfo(stackElement)
return result
}
override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: BlockInfo): StackValue {
val entry = Label()
mv.visitLabel(entry)
val entry = markNewLabel()
val endLabel = Label()
val continueLabel = Label()
with(LoopInfo(loop, continueLabel, endLabel)) {
data.addLoop(this)
data.addInfo(this)
loop.body?.apply {
gen(this, data)
}
data.removeLoop(this)
data.removeInfo(this)
}
mv.visitLabel(continueLabel)
@@ -519,6 +568,173 @@ class ExpressionCodegen(
return loop.onStack
}
override fun visitTry(aTry: IrTry, data: BlockInfo): StackValue {
val finallyExpression = aTry.finallyExpression
val tryInfo = if (finallyExpression != null) TryInfo(aTry) else null
if (tryInfo != null) {
data.addInfo(tryInfo)
}
val tryBlockStart = markNewLabel()
mv.nop()
gen(aTry.tryResult, aTry.asmType, data)
val tryBlockEnd = markNewLabel()
val tryRegions = getCurrentTryIntervals(tryInfo, tryBlockStart, tryBlockEnd)
val tryCatchBlockEnd = Label()
genFinallyBlockOrGoto(tryInfo, tryCatchBlockEnd, null, data)
val catches = aTry.catches
for (clause in catches) {
val clauseStart = markNewLabel()
val descriptor = clause.parameter
val descriptorType = descriptor.asmType
val index = frame.enter(descriptor, descriptorType)
mv.store(index, descriptorType)
val catchBody = clause.result
gen(catchBody, catchBody.asmType, data)
frame.leave(descriptor)
val clauseEnd = markNewLabel()
mv.visitLocalVariable(descriptor.name.asString(), descriptorType.descriptor, null, clauseStart, clauseEnd,
index)
genFinallyBlockOrGoto(tryInfo, if (clause != catches.last() || finallyExpression != null) tryCatchBlockEnd else null, null, data)
generateExceptionTable(clauseStart, tryRegions, descriptorType.internalName)
}
//for default catch clause
if (finallyExpression != null) {
val defaultCatchStart = Label()
mv.mark(defaultCatchStart)
val savedException = frame.enterTemp(JAVA_THROWABLE_TYPE)
mv.store(savedException, JAVA_THROWABLE_TYPE)
val defaultCatchEnd = Label()
mv.mark(defaultCatchEnd)
//do it before finally block generation
//javac also generates entry in exception table for default catch clause too!!!! so defaultCatchEnd as end parameter
val defaultCatchRegions = getCurrentTryIntervals(tryInfo, tryBlockStart, defaultCatchEnd)
genFinallyBlockOrGoto(tryInfo, null, null, data)
mv.load(savedException, JAVA_THROWABLE_TYPE)
frame.leaveTemp(JAVA_THROWABLE_TYPE)
mv.athrow()
generateExceptionTable(defaultCatchStart, defaultCatchRegions, null)
}
mv.mark(tryCatchBlockEnd)
if (tryInfo != null) {
data.removeInfo(tryInfo)
}
return aTry.onStack
}
private fun getCurrentTryIntervals(
finallyBlockStackElement: TryInfo?,
blockStart: Label,
blockEnd: Label
): List<Label> {
val gapsInBlock = if (finallyBlockStackElement != null) ArrayList<Label>(finallyBlockStackElement.gaps) else emptyList<Label>()
assert(gapsInBlock.size % 2 == 0)
val blockRegions = ArrayList<Label>(gapsInBlock.size + 2)
blockRegions.add(blockStart)
blockRegions.addAll(gapsInBlock)
blockRegions.add(blockEnd)
return blockRegions
}
private fun generateExceptionTable(catchStart: Label, catchedRegions: List<Label>, exception: String?) {
var i = 0
while (i < catchedRegions.size) {
val startRegion = catchedRegions[i]
val endRegion = catchedRegions[i + 1]
mv.visitTryCatchBlock(startRegion, endRegion, catchStart, exception)
i += 2
}
}
private fun genFinallyBlockOrGoto(
tryInfo: TryInfo?,
tryCatchBlockEnd: Label?,
afterJumpLabel: Label?,
data: BlockInfo
) {
if (tryInfo != null) {
assert(tryInfo.gaps.size % 2 == 0) { "Finally block gaps are inconsistent" }
val topOfStack = data.pop()
assert(topOfStack === tryInfo) { "Top element of stack doesn't equals processing finally block" }
val tryBlock = tryInfo.tryBlock
val finallyStart = markNewLabel()
tryInfo.gaps.add(finallyStart)
//noinspection ConstantConditions
gen(tryBlock.finallyExpression!!, Type.VOID_TYPE, data)
}
if (tryCatchBlockEnd != null) {
mv.goTo(tryCatchBlockEnd)
}
if (tryInfo != null) {
val finallyEnd = afterJumpLabel ?: Label()
if (afterJumpLabel == null) {
mv.mark(finallyEnd)
}
tryInfo.gaps.add(finallyEnd)
data.addInfo(tryInfo)
}
}
fun generateFinallyBlocksIfNeeded(returnType: Type, afterReturnLabel: Label, data: BlockInfo) {
if (data.hasFinallyBlocks()) {
if (Type.VOID_TYPE != returnType) {
val returnValIndex = frame.enterTemp(returnType)
val localForReturnValue = StackValue.local(returnValIndex, returnType)
localForReturnValue.store(StackValue.onStack(returnType), mv)
doFinallyOnReturn(afterReturnLabel, data)
localForReturnValue.put(returnType, mv)
frame.leaveTemp(returnType)
}
else {
doFinallyOnReturn(afterReturnLabel, data)
}
}
}
private fun doFinallyOnReturn(afterReturnLabel: Label, data: BlockInfo) {
if (!data.isEmpty()) {
val stackElement = data.peek()
if (stackElement is TryInfo) {
genFinallyBlockOrGoto(stackElement, null, afterReturnLabel, data)
}
else if (stackElement is LoopInfo) {
}
else {
throw UnsupportedOperationException("Wrong BlockStackElement in processing stack")
}
data.pop()
doFinallyOnReturn(afterReturnLabel, data)
data.addInfo(stackElement)
}
}
override fun visitThrow(expression: IrThrow, data: BlockInfo): StackValue {
gen(expression.value, JAVA_THROWABLE_TYPE, data)
mv.athrow()