diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index 1219a917211..de3679fad8e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.ir.declarations.IrConstructor import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.types.KotlinType -internal class CodeGenerator(override val context:Context) : ContextUtils { +internal class CodeGenerator(override val context: Context) : ContextUtils { var currentFunction:FunctionDescriptor? = null fun function(declaration: IrFunction) { @@ -47,11 +47,10 @@ internal class CodeGenerator(override val context:Context) : ContextUtils { private fun prolog(declaration: IrFunction): LLVMValueRef? { variableIndex = 0 - labelIndex = 0 currentFunction = declaration.descriptor val fn = declaration.descriptor.llvmFunction.getLlvmValue() - val block = LLVMAppendBasicBlock(fn, "entry") - LLVMPositionBuilderAtEnd(context.llvmBuilder, block) + val block = LLVMAppendBasicBlock(fn, "entry")!! + positionAtEnd(block) function2variables.put(declaration.descriptor, mutableMapOf()) return fn } @@ -66,13 +65,7 @@ internal class CodeGenerator(override val context:Context) : ContextUtils { get() = variableIndex set(i:Int) { variableIndex = i} - private var labelIndex:Int = 0 - private var FunctionDescriptor.bbLabelIndex: Int - get() = labelIndex - set(i:Int) { labelIndex = i} - fun FunctionDescriptor.tmpVariable():String = "tmp_${tmpVariableIndex++}" - fun FunctionDescriptor.bbLabel():String = "label_${bbLabelIndex++}" fun registerVariable(varName: String, value: LLVMValueRef) = currentFunction!!.registerVariable(varName, value) @@ -89,59 +82,78 @@ internal class CodeGenerator(override val context:Context) : ContextUtils { fun FunctionDescriptor.registerVariable(varName: String, value: LLVMValueRef?) = variables.put(varName, value) private fun FunctionDescriptor.variable(varName: String): LLVMValueRef? = variables[varName] - fun plus (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildAdd (context.llvmBuilder, arg0, arg1, result)!! - fun mul (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildMul (context.llvmBuilder, arg0, arg1, result)!! - fun minus (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildSub (context.llvmBuilder, arg0, arg1, result)!! - fun div (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildSDiv(context.llvmBuilder, arg0, arg1, result)!! - fun srem (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildSRem(context.llvmBuilder, arg0, arg1, result)!! + fun plus (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildAdd (builder, arg0, arg1, result)!! + fun mul (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildMul (builder, arg0, arg1, result)!! + fun minus (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildSub (builder, arg0, arg1, result)!! + fun div (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildSDiv(builder, arg0, arg1, result)!! + fun srem (arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildSRem(builder, arg0, arg1, result)!! /* integers comparisons */ - fun icmpEq(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(context.llvmBuilder, LLVMIntPredicate.LLVMIntEQ, arg0, arg1, result)!! - fun icmpGt(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(context.llvmBuilder, LLVMIntPredicate.LLVMIntSGT, arg0, arg1, result)!! - fun icmpGe(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(context.llvmBuilder, LLVMIntPredicate.LLVMIntSGE, arg0, arg1, result)!! - fun icmpLt(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(context.llvmBuilder, LLVMIntPredicate.LLVMIntSLT, arg0, arg1, result)!! - fun icmpLe(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(context.llvmBuilder, LLVMIntPredicate.LLVMIntSLE, arg0, arg1, result)!! - fun icmpNe(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(context.llvmBuilder, LLVMIntPredicate.LLVMIntNE, arg0, arg1, result)!! + fun icmpEq(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntEQ, arg0, arg1, result)!! + fun icmpGt(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntSGT, arg0, arg1, result)!! + fun icmpGe(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntSGE, arg0, arg1, result)!! + fun icmpLt(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntSLT, arg0, arg1, result)!! + fun icmpLe(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntSLE, arg0, arg1, result)!! + fun icmpNe(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntNE, arg0, arg1, result)!! /* floating-point comparisons */ - fun fcmpEq(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildFCmp(context.llvmBuilder, LLVMRealPredicate.LLVMRealOEQ, arg0, arg1, result)!! + fun fcmpEq(arg0: LLVMValueRef, arg1: LLVMValueRef, result: String): LLVMValueRef = LLVMBuildFCmp(builder, LLVMRealPredicate.LLVMRealOEQ, arg0, arg1, result)!! - fun bitcast(type: LLVMTypeRef?, value: LLVMValueRef, result: String) = LLVMBuildBitCast(context.llvmBuilder, value, type, result) + fun bitcast(type: LLVMTypeRef?, value: LLVMValueRef, result: String) = LLVMBuildBitCast(builder, value, type, result)!! fun alloca(type: KotlinType, varName: String): LLVMValueRef = alloca( getLLVMType(type), varName) - fun alloca(type: LLVMTypeRef?, varName: String): LLVMValueRef = LLVMBuildAlloca(context.llvmBuilder, type, varName)!! - fun load(value: LLVMValueRef, varName: String): LLVMValueRef = LLVMBuildLoad(context.llvmBuilder, value, varName)!! - fun store(value: LLVMValueRef, ptr: LLVMValueRef): LLVMValueRef = LLVMBuildStore(context.llvmBuilder, value, ptr)!! + fun alloca(type: LLVMTypeRef?, varName: String): LLVMValueRef = LLVMBuildAlloca(builder, type, varName)!! + fun load(value: LLVMValueRef, varName: String): LLVMValueRef = LLVMBuildLoad(builder, value, varName)!! + fun store(value: LLVMValueRef, ptr: LLVMValueRef): LLVMValueRef = LLVMBuildStore(builder, value, ptr)!! //-------------------------------------------------------------------------// - fun call(llvmFunction: LLVMValueRef?, args: List, result: String?): LLVMValueRef? { - if (args.size == 0) return LLVMBuildCall(context.llvmBuilder, llvmFunction, null, 0, result) + fun invoke(llvmFunction: LLVMValueRef?, args: List, + then: LLVMBasicBlockRef, landingpad: LLVMBasicBlockRef, + result: String?): LLVMValueRef { + memScoped { - val rargs = allocArrayOf(args) - return LLVMBuildCall(context.llvmBuilder, llvmFunction, rargs[0].ptr, args.size, result) + val rargs = if (args.size != 0) allocArrayOf(args)[0].ptr else null + return LLVMBuildInvoke(builder, llvmFunction, rargs, args.size, then, landingpad, result)!! + } + + } + + fun call(llvmFunction: LLVMValueRef?, args: List, result: String?): LLVMValueRef { + memScoped { + val rargs = if (args.size != 0) allocArrayOf(args)[0].ptr else null + return LLVMBuildCall(builder, llvmFunction, rargs, args.size, result)!! } } //-------------------------------------------------------------------------// - fun trap() { - // LLVM doesn't seem to provide API to get intrinsics; - // workaround this by declaring the intrinsic explicitly: - val trapFun = LLVMGetNamedFunction(context.llvmModule, "llvm.trap") ?: - LLVMAddFunction(context.llvmModule, "llvm.trap", LLVMFunctionType(LLVMVoidType(), null, 0, 0))!! - - LLVMBuildCall(context.llvmBuilder, trapFun, null, 0, "") - - // LLVM seems to require an explicit return instruction even after noreturn intrinsic: - val returnType = LLVMGetReturnType(getFunctionType(currentFunction!!.llvmFunction.getLlvmValue())) - LLVMBuildRet(context.llvmBuilder, LLVMConstNull(returnType)) + fun phi(type: LLVMTypeRef, result: String): LLVMValueRef { + return LLVMBuildPhi(builder, type, result)!! } + fun addPhiIncoming(phi: LLVMValueRef, vararg incoming: Pair) { + memScoped { + val incomingValues = allocArrayOf(incoming.map { it.second }) + val incomingBlocks = allocArrayOf(incoming.map { it.first }) + + LLVMAddIncoming(phi, incomingValues[0].ptr, incomingBlocks[0].ptr, incoming.size) + } + } + + fun assignPhis(vararg phiToValue: Pair) { + val currentBlock = this.currentBlock + phiToValue.forEach { + addPhiIncoming(it.first, currentBlock to it.second) + } + } + + //-------------------------------------------------------------------------// + /* to class descriptor */ fun classType(descriptor: ClassDescriptor): LLVMTypeRef = LLVMGetTypeByName(context.llvmModule, descriptor.symbolName)!! - fun typeInfoType(descriptor: ClassDescriptor): LLVMTypeRef? = descriptor.llvmTypeInfoPtr.getLlvmType() - fun typeInfoValue(descriptor: ClassDescriptor): LLVMValueRef? = descriptor.llvmTypeInfoPtr.getLlvmValue() + fun typeInfoType(descriptor: ClassDescriptor): LLVMTypeRef = descriptor.llvmTypeInfoPtr.getLlvmType() + fun typeInfoValue(descriptor: ClassDescriptor): LLVMValueRef = descriptor.llvmTypeInfoPtr.getLlvmValue()!! fun thisVariable() = variable("this")!! fun param(fn: FunctionDescriptor?, i: Int): LLVMValueRef? = LLVMGetParam(fn!!.llvmFunction.getLlvmValue(), i) @@ -149,21 +161,124 @@ internal class CodeGenerator(override val context:Context) : ContextUtils { fun indexInClass(p:PropertyDescriptor):Int = (p.containingDeclaration as ClassDescriptor).fields.indexOf(p) - fun basicBlock(): LLVMBasicBlockRef? = LLVMAppendBasicBlock(currentFunction!!.llvmFunction.getLlvmValue(), currentFunction!!.bbLabel()) + fun basicBlock(name: String = "label_"): LLVMBasicBlockRef = + LLVMAppendBasicBlock(currentFunction!!.llvmFunction.getLlvmValue(), name)!! + + fun basicBlock(name: String, code: () -> Unit) = basicBlock(name).apply { + appendingTo(this) { + code() + } + } + fun lastBasicBlock(): LLVMBasicBlockRef? = LLVMGetLastBasicBlock(currentFunction!!.llvmFunction.getLlvmValue()) - fun functionLlvmValue(descriptor: FunctionDescriptor) = descriptor.llvmFunction.getLlvmValue() - fun functionHash(descriptor: FunctionDescriptor): LLVMValueRef? = descriptor.functionName.localHash.getLlvmValue() + fun functionLlvmValue(descriptor: FunctionDescriptor) = descriptor.llvmFunction.getLlvmValue()!! + fun functionHash(descriptor: FunctionDescriptor): LLVMValueRef = descriptor.functionName.localHash.getLlvmValue()!! - fun br(bbLabel: LLVMBasicBlockRef) = LLVMBuildBr(context.llvmBuilder, bbLabel) - fun condBr(condition: LLVMValueRef?, bbTrue: LLVMBasicBlockRef?, bbFalse: LLVMBasicBlockRef?) - = LLVMBuildCondBr(context.llvmBuilder, condition, bbTrue, bbFalse) + fun br(bbLabel: LLVMBasicBlockRef): LLVMValueRef { + val res = LLVMBuildBr(builder, bbLabel)!! + currentPositionHolder.setAfterTerminator() + return res + } - fun positionAtEnd(bbLabel: LLVMBasicBlockRef) - = LLVMPositionBuilderAtEnd(context.llvmBuilder, bbLabel) + fun condBr(condition: LLVMValueRef?, bbTrue: LLVMBasicBlockRef?, bbFalse: LLVMBasicBlockRef?): LLVMValueRef? { + val res = LLVMBuildCondBr(builder, condition, bbTrue, bbFalse) + currentPositionHolder.setAfterTerminator() + return res + } + + fun ret(value: LLVMValueRef?): LLVMValueRef { + val res = LLVMBuildRet(builder, value)!! + currentPositionHolder.setAfterTerminator() + return res + } + + fun unreachable(): LLVMValueRef? { + val res = LLVMBuildUnreachable(builder) + currentPositionHolder.setAfterTerminator() + return res + } + + //-------------------------------------------------------------------------// + + /** + * Represents the mutable position of instructions being inserted. + * + * This class is introduced to workaround unreachable code handling. + */ + inner class PositionHolder { + private val builder: LLVMBuilderRef = LLVMCreateBuilder()!! + + fun getBuilder(): LLVMBuilderRef { + if (isAfterTerminator) { + positionAtEnd(basicBlock("unreachable")) + } + + return builder + } + + /** + * Should be `true` iff the position is located after terminator instruction. + */ + var isAfterTerminator: Boolean = false + private set + + fun setAfterTerminator() { + isAfterTerminator = true + } + + val currentBlock: LLVMBasicBlockRef + get() = LLVMGetInsertBlock(builder)!! + + fun positionAtEnd(block: LLVMBasicBlockRef) { + LLVMPositionBuilderAtEnd(builder, block) + val lastInstr = LLVMGetLastInstruction(block) + isAfterTerminator = lastInstr != null && (LLVMIsATerminatorInst(lastInstr) != null) + } + + fun dispose() { + LLVMDisposeBuilder(builder) + } + } + + private var currentPositionHolder: PositionHolder = PositionHolder() + + /** + * Returns `true` iff the current code generation position is located after terminator instruction. + */ + fun isAfterTerminator() = currentPositionHolder.isAfterTerminator + + val currentBlock: LLVMBasicBlockRef + get() = currentPositionHolder.currentBlock + + /** + * The builder representing the current code generation position. + * + * Note that it shouldn't be positioned directly using LLVM API due to some hacks. + * Use e.g. [positionAtEnd] instead. See [PositionHolder] for details. + */ + val builder: LLVMBuilderRef + get() = currentPositionHolder.getBuilder() + + fun positionAtEnd(bbLabel: LLVMBasicBlockRef) = currentPositionHolder.positionAtEnd(bbLabel) + + inline fun preservingPosition(code: () -> R): R { + val oldPositionHolder = currentPositionHolder + val newPositionHolder = PositionHolder() + currentPositionHolder = newPositionHolder + try { + return code() + } finally { + currentPositionHolder = oldPositionHolder + newPositionHolder.dispose() + } + } + + inline fun appendingTo(block: LLVMBasicBlockRef, code: CodeGenerator.() -> R) = preservingPosition { + positionAtEnd(block) + code() + } - fun ret(value: LLVMValueRef?) = LLVMBuildRet(context.llvmBuilder, value) - fun unreachable(): LLVMValueRef? = LLVMBuildUnreachable(context.llvmBuilder) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Context.kt index f800aa0ab46..cd0f3678434 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Context.kt @@ -7,8 +7,6 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment internal class Context(val irModule: IrModuleFragment, val runtime: Runtime, val llvmModule: LLVMModuleRef) { val moduleIndex = ModuleIndex(irModule) - val llvmBuilder = LLVMCreateBuilder() - private fun importFunction(name: String, otherModule: LLVMModuleRef): LLVMValueRef { if (LLVMGetNamedFunction(llvmModule, name) != null) { throw IllegalArgumentException("function $name already exists") @@ -31,8 +29,4 @@ internal class Context(val irModule: IrModuleFragment, val runtime: Runtime, val val isInstanceFunction = importRtFunction("IsInstance") val checkInstanceFunction = importRtFunction("CheckInstance") val throwExceptionFunction = importRtFunction("ThrowException") - - fun dispose() { - LLVMDisposeBuilder(llvmBuilder) - } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 1bdd7f6e845..60ef734b4ca 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -26,7 +26,9 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isNullableNothing +import org.jetbrains.kotlin.types.typeUtil.isUnit fun emitLLVM(module: IrModuleFragment, runtimeFile: String, outFile: String) { val llvmModule = LLVMModuleCreateWithName("out")!! // TODO: dispose @@ -90,6 +92,71 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// + // TODO: consider eliminating mutable state + private var currentCodeContext: CodeContext = TopLevelCodeContext + + /** + * Defines how to generate context-dependent operations. + */ + private interface CodeContext { + + fun genReturn(target: CallableDescriptor, value: LLVMValueRef?) + + fun genBreak(loop: IrLoop) + + fun genContinue(loop: IrLoop) + + fun genCall(function: LLVMValueRef, args: List, result: String?): LLVMValueRef + + fun genThrow(exception: LLVMValueRef) + } + + /** + * Fake [CodeContext] that doesn't support any operation. + * + * During function code generation [FunctionScope] should be set up. + */ + private object TopLevelCodeContext : CodeContext { + + override fun genReturn(target: CallableDescriptor, value: LLVMValueRef?) = throw UnsupportedOperationException() + + override fun genBreak(loop: IrLoop) = throw UnsupportedOperationException() + + override fun genContinue(loop: IrLoop) = throw UnsupportedOperationException() + + override fun genCall(function: LLVMValueRef, args: List, result: String?) + = throw UnsupportedOperationException() + + override fun genThrow(exception: LLVMValueRef) = throw UnsupportedOperationException() + } + + /** + * The [CodeContext] which can define some operations and delegate other ones to [outerContext] + */ + private abstract class InnerScope(val outerContext: CodeContext) : CodeContext by outerContext + + /** + * Convenient [InnerScope] implementation that is bound to the [currentCodeContext]. + */ + private abstract inner class InnerScopeImpl : InnerScope(currentCodeContext) + + /** + * Executes [block] with [codeContext] substituted as [currentCodeContext]. + */ + private inline fun using(codeContext: CodeContext?, block: () -> R): R { + val oldCodeContext = currentCodeContext + if (codeContext != null) { + currentCodeContext = codeContext + } + try { + return block() + } finally { + currentCodeContext = oldCodeContext + } + } + + //-------------------------------------------------------------------------// + override fun visitElement(element: IrElement) { element.acceptChildrenVoid(this) } @@ -121,17 +188,17 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid val loopBody = codegen.basicBlock() val loopExit = codegen.basicBlock() - LLVMBuildBr(context.llvmBuilder, loopEnter) + codegen.br(loopEnter) - codegen.positionAtEnd(loopEnter!!) + codegen.positionAtEnd(loopEnter) val condition = evaluateExpression(codegen.newVar(), loop.condition) - LLVMBuildCondBr(context.llvmBuilder, condition, loopBody, loopExit) + codegen.condBr(condition, loopBody, loopExit) - codegen.positionAtEnd(loopBody!!) + codegen.positionAtEnd(loopBody) evaluateExpression(codegen.newVar(), loop.body) - LLVMBuildBr(context.llvmBuilder, loopEnter) - codegen.positionAtEnd(loopExit!!) + codegen.br(loopEnter) + codegen.positionAtEnd(loopExit) } //-------------------------------------------------------------------------// @@ -141,17 +208,17 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid val loopCheck = codegen.basicBlock() val loopExit = codegen.basicBlock() - LLVMBuildBr(context.llvmBuilder, loopBody) + codegen.br(loopBody) - codegen.positionAtEnd(loopBody!!) + codegen.positionAtEnd(loopBody) evaluateExpression(codegen.newVar(), loop.body) - LLVMBuildBr(context.llvmBuilder, loopCheck) + codegen.br(loopCheck) - codegen.positionAtEnd(loopCheck!!) + codegen.positionAtEnd(loopCheck) val condition = evaluateExpression(codegen.newVar(), loop.condition) - LLVMBuildCondBr(context.llvmBuilder, condition, loopBody, loopExit) + codegen.condBr(condition, loopBody, loopExit) - codegen.positionAtEnd(loopExit!!) + codegen.positionAtEnd(loopExit) } //-------------------------------------------------------------------------// @@ -162,53 +229,55 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid val classDescriptor = DescriptorUtils.getContainingClass(constructorDescriptor) val thisPtr = codegen.load(codegen.thisVariable(), codegen.newVar()) - /** - * IR for kotlin.Any is: - * BLOCK_BODY - * DELEGATING_CONSTRUCTOR_CALL 'constructor Any()' - * INSTANCE_INITIALIZER_CALL classDescriptor='Any' - * - * to avoid possible recursion we manually reject body generation for Any. - */ + using(FunctionScope(constructorDeclaration)) { - if (!skipConstructorBodyGeneration(constructorDeclaration)) - constructorDeclaration.acceptChildrenVoid(this) + /** + * IR for kotlin.Any is: + * BLOCK_BODY + * DELEGATING_CONSTRUCTOR_CALL 'constructor Any()' + * INSTANCE_INITIALIZER_CALL classDescriptor='Any' + * + * to avoid possible recursion we manually reject body generation for Any. + */ - if (constructorDescriptor.isPrimary && !DescriptorUtils.isCompanionObject(constructorDescriptor.constructedClass)) { - val irOfCurrentClass = context.moduleIndex.classes[classDescriptor!!.classId] - irOfCurrentClass!!.acceptChildrenVoid(object : IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } + if (!skipConstructorBodyGeneration(constructorDeclaration)) + constructorDeclaration.acceptChildrenVoid(this) - override fun visitField(fieldDeclaration: IrField) { - - val fieldDescriptor = fieldDeclaration.descriptor - val fieldPtr = fieldPtrOfClass(thisPtr, fieldDescriptor) - var value:LLVMValueRef? = null - if (constructorDescriptor.valueParameters.any{ it.name == fieldDescriptor.name }) { - val variable = codegen.variable(fieldDescriptor.name.asString()) - value = codegen.load(variable!!, codegen.newVar()) + if (constructorDescriptor.isPrimary && !DescriptorUtils.isCompanionObject(constructorDescriptor.constructedClass)) { + val irOfCurrentClass = context.moduleIndex.classes[classDescriptor!!.classId] + irOfCurrentClass!!.acceptChildrenVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) } - else { - value = evaluateExpression(codegen.newVar(), fieldDeclaration.initializer) + + override fun visitField(fieldDeclaration: IrField) { + + val fieldDescriptor = fieldDeclaration.descriptor + val fieldPtr = fieldPtrOfClass(thisPtr, fieldDescriptor) + var value: LLVMValueRef? = null + if (constructorDescriptor.valueParameters.any { it.name == fieldDescriptor.name }) { + val variable = codegen.variable(fieldDescriptor.name.asString()) + value = codegen.load(variable!!, codegen.newVar()) + } else { + value = evaluateExpression(codegen.newVar(), fieldDeclaration.initializer) + } + if (value != null) + codegen.store(value, fieldPtr!!) } - if (value != null) - codegen.store(value!!, fieldPtr!!) - } - override fun visitAnonymousInitializer(declaration: IrAnonymousInitializer) { - evaluateExpression("", declaration.body) - } + override fun visitAnonymousInitializer(declaration: IrAnonymousInitializer) { + evaluateExpression("", declaration.body) + } - override fun visitClass(declaration: IrClass) { - return - } + override fun visitClass(declaration: IrClass) { + return + } - override fun visitConstructor(declaration: IrConstructor) { - return - } - }) + override fun visitConstructor(declaration: IrConstructor) { + return + } + }) + } } codegen.ret(thisPtr) @@ -225,9 +294,13 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid override fun visitBlockBody(body: IrBlockBody) { super.visitBlockBody(body) - if (KotlinBuiltIns.isUnit(codegen.currentFunction!!.returnType!!) - && body.statements.lastOrNull() !is IrThrow) { // Don't try generate return at the end of the throwing block. - codegen.ret(null) + val function = codegen.currentFunction!! + if (function !is ConstructorDescriptor && !codegen.isAfterTerminator()) { + if (function.returnType!!.isUnit()) { + codegen.ret(null) + } else { + codegen.unreachable() + } } logger.log("visitBlockBody : ${ir2string(body)}") } @@ -256,13 +329,48 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// + override fun visitTry(expression: IrTry) { + logger.log("visitTry : ${ir2string(expression)}") + evaluateExpression("", expression) + } + + //-------------------------------------------------------------------------// + + /** + * The [CodeContext] enclosing the entire function body. + */ + private inner class FunctionScope(val declaration: IrFunction) : InnerScopeImpl() { + override fun genReturn(target: CallableDescriptor, value: LLVMValueRef?) { + if (target == declaration.descriptor) { + codegen.ret(value) + } else { + super.genReturn(target, value) + } + } + + override fun genCall(function: LLVMValueRef, args: List, result: String?) + = codegen.call(function, args, result) + + override fun genThrow(exception: LLVMValueRef) { + val objHeaderPtr = codegen.bitcast(kObjHeaderPtr, exception, codegen.newVar()) + val args = listOf(objHeaderPtr) + codegen.call(context.throwExceptionFunction, args, "") + codegen.unreachable() + } + } + override fun visitFunction(declaration: IrFunction) { logger.log("visitFunction : ${ir2string(declaration)}") if (declaration.descriptor.modality == Modality.ABSTRACT || declaration.body == null) return + codegen.function(declaration) metadator.function(declaration) - declaration.acceptChildrenVoid(this) + + using(FunctionScope(declaration)) { + declaration.acceptChildrenVoid(this) + } + verifyModule(context.llvmModule) } @@ -290,10 +398,21 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid logger.log("visitVariable : ${ir2string(declaration)}") val variableName = declaration.descriptor.name.asString() val variableType = declaration.descriptor.type - val newVariable = codegen.alloca(variableType, variableName) // Create LLVM variable. - codegen.registerVariable(variableName, newVariable) // Map variableName -> LLVM variable. - val value = evaluateExpression(variableName, declaration.initializer) // Generate initialization code. - codegen.store(value!!, codegen.variable(variableName)!!) // Store init result in the variable + val value = evaluateExpression("", declaration.initializer) // Generate initialization code. + createVariable(declaration.descriptor, value) // Create and initialize variable. + } + + private fun createVariable(descriptor: VariableDescriptor, value: LLVMValueRef? = null): LLVMValueRef { + val name = descriptor.name.asString() + val type = descriptor.type + + val newVariable = codegen.alloca(type, name) // Create LLVM variable. + codegen.registerVariable(name, newVariable) // Map variableName -> LLVM variable. + + if (value != null) { + codegen.store(value, newVariable) + } + return newVariable } //-------------------------------------------------------------------------// @@ -380,6 +499,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid is IrExpressionBody -> return evaluateExpression (tmpVariableName, value.expression) is IrWhen -> return evaluateWhen (tmpVariableName, value) is IrThrow -> return evaluateThrow (tmpVariableName, value) + is IrTry -> return evaluateTry ( value) is IrStringConcatenation -> return evaluateStringConcatenation(tmpVariableName, value) is IrBlockBody -> return evaluateBlock (tmpVariableName, value as IrStatementContainer) is IrWhileLoop -> return evaluateWhileLoop ( value) @@ -390,14 +510,91 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid } } + private fun evaluateExpressionAndJump(expression: IrExpression, destination: ContinuationBlock) { + val result = evaluateExpression("", expression) + + if (!codegen.isAfterTerminator()) { + jump(destination, result) + } else { + // Workaround for unreachable code handling. + // Note: cannot rely on checking that expression type is Nothing. + } + } + + //-------------------------------------------------------------------------// + + /** + * Represents the basic block which may expect a value: + * when generating a [jump] to this block, one should provide the value. + * Inside the block that value is accessible as [valuePhi]. + * + * This class is designed to be used to generate Kotlin expressions that have a value and require branching. + * + * [valuePhi] may be `null`, which would mean that no value is passed. + */ + private data class ContinuationBlock(val block: LLVMBasicBlockRef, val valuePhi: LLVMValueRef?) + + /** + * Jumps to [target] passing [value]. + */ + private fun jump(target: ContinuationBlock, value: LLVMValueRef?) { + val entry = target.block + codegen.br(entry) + if (target.valuePhi != null) { + codegen.assignPhis(target.valuePhi to value!!) + } + } + + /** + * Creates new [ContinuationBlock] and generates [code] starting from its beginning. + */ + private inline fun continuationBlock(type: LLVMTypeRef, code: (LLVMValueRef) -> Unit): ContinuationBlock { + val entry = codegen.basicBlock() + + codegen.appendingTo(entry) { + val valuePhi = codegen.phi(type, codegen.newVar()) + code(valuePhi) + return ContinuationBlock(entry, valuePhi) + } + } + + /** + * Creates new [ContinuationBlock] that don't receive the value and generates [code] starting from its beginning. + */ + private inline fun continuationBlock(code: () -> Unit): ContinuationBlock { + val block = codegen.basicBlock() + + codegen.appendingTo(block) { + code() + } + + return ContinuationBlock(block, null) + } + + /** + * Creates new [ContinuationBlock] that receives the value of type corresponding to given Kotlin type + * and generates [code] starting from its beginning. + */ + private fun continuationBlock(type: KotlinType, code: (LLVMValueRef?) -> Unit = {}): ContinuationBlock { + return if (type.isUnitOrNothing()) { + continuationBlock { + code(null) + } + } else { + continuationBlock(getLLVMType(type), code) + } + } + + //-------------------------------------------------------------------------// + private fun evaluateStringConcatenation(tmpVariableName: String, value: IrStringConcatenation): LLVMValueRef? { val stringPlus = KonanPlatform.builtIns.stringType.memberScope.getContributedFunctions(Name.identifier("plus"), NoLookupLocation.FROM_BACKEND).first() var res:LLVMValueRef? = null - val strings:List = value.arguments.map { + val strings:List = value.arguments.map { val descriptor = getToString(it.type) - val args = listOf(evaluateExpression(codegen.newVar(), it)) - return@map evaluateSimpleFunctionCall(codegen.newVar(), descriptor, args)!! + val args = listOf(evaluateExpression(codegen.newVar(), it)!!) + return@map evaluateSimpleFunctionCall(codegen.newVar(), descriptor, args) } val concatResult = strings.take(1).fold(strings.first()) {res, it -> evaluateSimpleFunctionCall(codegen.newVar(), stringPlus, listOf(res, it))} return concatResult @@ -407,11 +604,243 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// private fun evaluateThrow(tmpVariableName: String, expression: IrThrow): LLVMValueRef? { - val objPointer = evaluateExpression(codegen.newVar(), expression.value) - val objHeaderPtr = codegen.bitcast(kObjHeaderPtr, objPointer!!, codegen.newVar()) - val args = listOf(objHeaderPtr) // Create arg list. - codegen.call(context.throwExceptionFunction, args, "") - return codegen.unreachable() + val exception = evaluateExpression(codegen.newVar(), expression.value)!! + currentCodeContext.genThrow(exception) + return null + } + + //-------------------------------------------------------------------------// + + /** + * The [CodeContext] that catches exceptions. + */ + private inner abstract class CatchingScope : InnerScopeImpl() { + + /** + * The LLVM `landingpad` such that if invoked function throws an exception, + * then this exception is passed to [handler]. + */ + private val landingpad: LLVMBasicBlockRef by lazy { + using(outerContext) { + codegen.basicBlock("landingpad") { + genLandingpad() + } + } + } + + /** + * The Kotlin exception handler, i.e. the [ContinuationBlock] which gets started + * when the exception is caught, receiving this exception as its value. + */ + private val handler by lazy { + using(outerContext) { + continuationBlock(kInt8Ptr) { exception -> + genHandler(exception) + } + } + } + + private fun jumpToHandler(exception: LLVMValueRef) { + jump(this.handler, exception) + } + + /** + * Generates the LLVM `landingpad` that catches C++ exception with type `KotlinException`, + * unwraps the Kotlin exception object and jumps to [handler]. + * + * This method generates nearly the same code as `clang++` does for the following: + * ``` + * catch (KotlinException& e) { + * KRef exception = e.exception_; + * return exception; + * } + * ``` + * except that our code doesn't check exception `typeid`. + * + * TODO: why does `clang++` check `typeid` even if there is only one catch clause? + */ + private fun genLandingpad() { + with(codegen) { + // Type of `landingpad` instruction result (depends on personality function): + val landingpadType = structType(int8TypePtr, int32Type) + + val personalityFunction = externalFunction("__gxx_personality_v0", functionType(int32Type, true)) + val personalityFunctionRaw = LLVMConstBitCast(personalityFunction, int8TypePtr)!! + + val numClauses = 1 + val landingpadResult = + LLVMBuildLandingPad(codegen.builder, landingpadType, personalityFunctionRaw, numClauses, "lp") + + // Configure landingpad to catch C++ exception with type `KotlinException`: + LLVMAddClause(landingpadResult, externalGlobal("_ZTI15KotlinException", int8Type)) + + // FIXME: properly handle C++ exceptions: currently C++ exception can be thrown out from try-finally + // bypassing the finally block. + + val exceptionRecord = LLVMBuildExtractValue(codegen.builder, landingpadResult, 0, "er") + + // __cxa_begin_catch returns pointer to C++ exception object. + val beginCatch = externalFunction("__cxa_begin_catch", functionType(int8TypePtr, false, int8TypePtr)) + val exceptionRawPtr = call(beginCatch, listOf(exceptionRecord), "") + + // Pointer to KotlinException instance: + val exceptionPtr = bitcast(pointerType(int8TypePtr), exceptionRawPtr, "") + + // Pointer to Kotlin exception object: + val exception = load(exceptionPtr, "exception") + + // __cxa_end_catch performs some C++ cleanup, including calling `KotlinException` class destructor. + val endCatch = externalFunction("__cxa_end_catch", functionType(voidType, false)) + call(endCatch, listOf(), "") + + jumpToHandler(exception) + } + } + + // The call inside [CatchingScope] must be configured to dispatch exception to the scope's handler. + override fun genCall(function: LLVMValueRef, args: List, result: String?): LLVMValueRef { + val then = codegen.basicBlock() + val res = codegen.invoke(function, args, then, landingpad, result) + codegen.positionAtEnd(then) + return res + } + + override fun genThrow(exception: LLVMValueRef) { + jumpToHandler(exception) + } + + protected abstract fun genHandler(exception: LLVMValueRef) + } + + /** + * The [CatchingScope] that handles exceptions using Kotlin `catch` clauses. + * + * @param success the block to be used when the exception is successfully handled; + * expects `catch` expression result as its value. + */ + private inner class CatchScope(private val catches: List, + private val success: ContinuationBlock) : CatchingScope() { + + override fun genHandler(exception: LLVMValueRef) { + // TODO: optimize for `Throwable` clause. + catches.forEach { + val isInstance = genInstanceOf("", exception, it.parameter.type) + val body = codegen.basicBlock("catch") + val nextCheck = codegen.basicBlock("catchCheck") + codegen.condBr(isInstance, body, nextCheck) + + codegen.appendingTo(body) { + createVariable(it.parameter, exception) + evaluateExpressionAndJump(it.result, success) + } + + codegen.positionAtEnd(nextCheck) + } + // rethrow the exception if no clause can handle it. + outerContext.genThrow(exception) + } + } + + /** + * The [InnerScope] that includes code generated by [genFinalize] when leaving it + * with `return`, `break`, `continue` or throwing exception. + */ + private abstract inner class FinalizingScope() : CatchingScope() { + + /** + * Cache for [genReturn]. + * + * `returnBlocks[func]` contains the [ContinuationBlock] to be used for `return` from `func`; + * the block expects return value as its value. + */ + private val returnBlocks = mutableMapOf() + + // Jump to finalize-and-return instead of simply returning. + override fun genReturn(target: CallableDescriptor, value: LLVMValueRef?) { + val block = returnBlocks.getOrPut(target) { + using(outerContext) { + continuationBlock(target.returnType!!) { returnValue -> + genFinalize() + outerContext.genReturn(target, returnValue) + } + } + } + + jump(block, value) + } + + override fun genBreak(loop: IrLoop) = TODO() + override fun genContinue(loop: IrLoop) = TODO() + + // When an exception is caught, finalize the scope and rethrow the exception. + override fun genHandler(exception: LLVMValueRef) { + genFinalize() + outerContext.genThrow(exception) + } + + protected abstract fun genFinalize() + } + + /** + * Generates the code which gets "finalized" exactly once when completed either normally or abnormally. + * + * @param code generates the code to be post-dominated by cleanup. + * It must jump to given [ContinuationBlock] with its result when completed normally. + * + * @param finalize generates the cleanup code that must be executed when code generated by [code] is completed. + * + * @param type Kotlin type of the result of generated code. + * + * @return the result of the generated code. + */ + private fun genFinalizedBy(finalize: (() -> Unit)?, + type: KotlinType, code: (ContinuationBlock) -> Unit): LLVMValueRef? { + + val scope = if (finalize == null) null else { + object : FinalizingScope() { + override fun genFinalize() { + finalize() + } + } + } + + val continuation = continuationBlock(type) + + using(scope) { + code(continuation) + } + codegen.positionAtEnd(continuation.block) + finalize?.invoke() + + // TODO: finalize is duplicated many times (just as in C++, Java or Kotlin JVM); + // it is very important to optimize this. + + return continuation.valuePhi + } + + /** + * Generates code that is "finalized" by given expression. + */ + private fun genFinalizedBy(finalize: IrExpression?, type: KotlinType, + code: (ContinuationBlock) -> Unit): LLVMValueRef? { + + return genFinalizedBy({ evaluateExpression("", finalize) }, type, code) + } + + private fun KotlinType.isUnitOrNothing() = isUnit() || isNothing() + + private fun evaluateTry(expression: IrTry): LLVMValueRef? { + // TODO: does basic block order influence machine code order? + // If so, consider reordering blocks to reduce exception tables size. + + return genFinalizedBy(expression.finallyExpression, expression.type) { continuation -> + + val catchScope = if (expression.catches.isEmpty()) null else CatchScope(expression.catches, continuation) + + using(catchScope) { + evaluateExpressionAndJump(expression.tryResult, continuation) + } + } } //-------------------------------------------------------------------------// @@ -553,7 +982,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid val srcArg = evaluateExpression(codegen.newVar(), value.argument)!! // Evaluate src expression. val srcObjInfoPtr = codegen.bitcast(kObjHeaderPtr, srcArg, codegen.newVar()) // Cast src to ObjInfoPtr. val args = listOf(srcObjInfoPtr, dstTypeInfo) // Create arg list. - codegen.call(context.checkInstanceFunction, args, "") // Check if dst is subclass of src. + currentCodeContext.genCall(context.checkInstanceFunction, args, "") // Check if dst is subclass of src. return srcArg } @@ -562,21 +991,29 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid private fun evaluateInstanceOf(tmpVariableName: String, value: IrTypeOperatorCall): LLVMValueRef { logger.log("evaluateInstanceOf : ${ir2string(value)}") - val dstDescriptor = TypeUtils.getClassDescriptor(value.typeOperand) // Get class descriptor for dst type. + val type = value.typeOperand + val srcArg = evaluateExpression(codegen.newVar(), value.argument)!! // Evaluate src expression. + + return genInstanceOf(tmpVariableName, srcArg, type) + } + + private fun genInstanceOf(tmpVariableName: String, obj: LLVMValueRef, type: KotlinType): LLVMValueRef { + val dstDescriptor = TypeUtils.getClassDescriptor(type) // Get class descriptor for dst type. val dstTypeInfo = codegen.typeInfoValue(dstDescriptor!!) // Get TypeInfo for dst type. - val srcArg = evaluateExpression(codegen.newVar(), value.argument)!! // Evaluate src expression. - val srcObjInfoPtr = codegen.bitcast(kObjHeaderPtr, srcArg, codegen.newVar()) // Cast src to ObjInfoPtr. + val srcObjInfoPtr = codegen.bitcast(kObjHeaderPtr, obj, codegen.newVar()) // Cast src to ObjInfoPtr. val args = listOf(srcObjInfoPtr, dstTypeInfo) // Create arg list. - val result = codegen.call(context.isInstanceFunction, args, codegen.newVar()) // Check if dst is subclass of src. - return LLVMBuildTrunc(context.llvmBuilder, result!!, kInt1, tmpVariableName)!! // Truncate result to boolean + val result = currentCodeContext.genCall( // Check if dst is subclass of src. + context.isInstanceFunction, args, codegen.newVar()) + + return LLVMBuildTrunc(codegen.builder, result, kInt1, tmpVariableName)!! // Truncate result to boolean } //-------------------------------------------------------------------------// private fun evaluateNotInstanceOf(tmpVariableName: String, value: IrTypeOperatorCall): LLVMValueRef { val instanceOfResult = evaluateInstanceOf(codegen.newVar(), value) - return LLVMBuildNot(context.llvmBuilder, instanceOfResult, tmpVariableName)!! + return LLVMBuildNot(codegen.builder, instanceOfResult, tmpVariableName)!! } //-------------------------------------------------------------------------// @@ -647,9 +1084,9 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid val typePtr = pointerType(codegen.classType(value.containingDeclaration as ClassDescriptor)) memScoped { val args = allocArrayOf(kImmOne) - val objectPtr = LLVMBuildGEP(codegen.context.llvmBuilder, objHeaderPtr, args[0].ptr, 1, codegen.newVar()) + val objectPtr = LLVMBuildGEP(codegen.builder, objHeaderPtr, args[0].ptr, 1, codegen.newVar()) val typedObjPtr = codegen.bitcast(typePtr, objectPtr!!, codegen.newVar()) - val fieldPtr = LLVMBuildStructGEP(codegen.context.llvmBuilder, typedObjPtr, codegen.indexInClass(value), codegen.newVar()) + val fieldPtr = LLVMBuildStructGEP(codegen.builder, typedObjPtr, codegen.indexInClass(value), codegen.newVar()) return fieldPtr } } @@ -679,12 +1116,25 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// - private fun evaluateReturn(value: IrReturn): LLVMValueRef? { - logger.log("evaluateReturn : ${ir2string(value)}") - val ret = evaluateExpression(codegen.newVar(), value.value) - if (KotlinBuiltIns.isUnit(value.value.type)) - return ret - return codegen.ret(ret) + private fun evaluateReturn(expression: IrReturn): LLVMValueRef? { + logger.log("evaluateReturn : ${ir2string(expression)}") + val value = expression.value + + val evaluated = if (value is IrGetObjectValue && value.type.isUnit()) { + // hack to make "return without value" work + null + } else { + evaluateExpression(codegen.newVar(), value) + } + + val ret = if (!value.type.isUnit()) { + evaluated + } else { + null // hack for bad Unit type handling + } + + currentCodeContext.genReturn(expression.returnTarget, ret) + return null } //-------------------------------------------------------------------------// @@ -699,13 +1149,13 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// - private fun evaluateCall(tmpVariableName: String, value: IrMemberAccessExpression?): LLVMValueRef? { + private fun evaluateCall(tmpVariableName: String, value: IrMemberAccessExpression?): LLVMValueRef { logger.log("evaluateCall : $tmpVariableName = ${ir2string(value)}") - val args = mutableListOf() // Create list of function args. + val args = mutableListOf() // Create list of function args. value!!.acceptChildrenVoid(object: IrElementVisitorVoid { // Iterate args of the function. override fun visitElement(element: IrElement) { // Visit arg. val tmp = codegen.newVar() // Create variable representing the arg in codegen - args.add(evaluateExpression(tmp, element as IrExpression)) // Evaluate expression and get LLVM arg + args.add(evaluateExpression(tmp, element as IrExpression)!!) // Evaluate expression and get LLVM arg } }) @@ -720,7 +1170,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// - private fun evaluateFunctionCall(tmpVariableName: String, callee: IrCall, args: List): LLVMValueRef? { + private fun evaluateFunctionCall(tmpVariableName: String, callee: IrCall, args: List): LLVMValueRef { val descriptor:FunctionDescriptor = callee.descriptor as FunctionDescriptor when (descriptor) { is IrBuiltinOperatorDescriptorBase -> return evaluateOperatorCall (tmpVariableName, callee, args) @@ -731,7 +1181,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// - private fun evaluateSimpleFunctionCall(tmpVariableName: String, descriptor: FunctionDescriptor, args: List): LLVMValueRef? { + private fun evaluateSimpleFunctionCall(tmpVariableName: String, descriptor: FunctionDescriptor, args: List): LLVMValueRef { //logger.log("evaluateSimpleFunctionCall : $tmpVariableName = ${ir2string(value)}") if (descriptor.isOverridable) return callVirtual(descriptor, args, tmpVariableName) @@ -743,28 +1193,28 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid private fun evaluateSetterCall(tmpVariableName: String, value: IrSetterCallImpl): LLVMValueRef? { val descriptor = value.descriptor as FunctionDescriptor - val args = mutableListOf() + val args = mutableListOf() if (descriptor.dispatchReceiverParameter != null) - args.add(evaluateExpression(codegen.newVar(), value.dispatchReceiver)) //add this ptr - args.add(evaluateExpression(codegen.newVar(), value.getValueArgument(0))) + args.add(evaluateExpression(codegen.newVar(), value.dispatchReceiver)!!) //add this ptr + args.add(evaluateExpression(codegen.newVar(), value.getValueArgument(0))!!) return evaluateSimpleFunctionCall(tmpVariableName, descriptor, args) } //-------------------------------------------------------------------------// - private fun evaluateConstructorCall(variableName: String, callee: IrCall, args: List): LLVMValueRef? { + private fun evaluateConstructorCall(variableName: String, callee: IrCall, args: List): LLVMValueRef { logger.log("evaluateConstructorCall : $variableName = ${ir2string(callee)}") memScoped { val containingClass = (callee.descriptor as ClassConstructorDescriptor).containingDeclaration val typeInfo = codegen.typeInfoValue(containingClass) - val allocHint = Int32(1).getLlvmValue() + val allocHint = Int32(1).getLlvmValue()!! val thisValue = if (containingClass.isArray) { assert(args.size == 1 && LLVMTypeOf(args[0]) == LLVMInt32Type()) - codegen.call(context.allocArrayFunction, listOf(typeInfo, allocHint, args[0]), variableName) + currentCodeContext.genCall(context.allocArrayFunction, listOf(typeInfo, allocHint, args[0]), variableName) } else { - codegen.call(context.allocInstanceFunction, listOf(typeInfo, allocHint), variableName) + currentCodeContext.genCall(context.allocInstanceFunction, listOf(typeInfo, allocHint), variableName) } - val constructorParams: MutableList = mutableListOf() + val constructorParams: MutableList = mutableListOf() constructorParams += thisValue constructorParams += args return evaluateSimpleFunctionCall(variableName, callee.descriptor as FunctionDescriptor, constructorParams) @@ -843,9 +1293,9 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid val arg0Type = callee.argument0.type val descriptor = getEquals(arg0Type, "equals") // Get descriptor for "arg0.equals". if (arg0Type.isMarkedNullable) { // If arg0 is nullable. - val bbEq2 = codegen.basicBlock()!! // Block to process "eqeq". - val bbEq3 = codegen.basicBlock()!! // Block to process "eqeqeq". - val bbExit = codegen.basicBlock()!! // Exit block for feather generation. + val bbEq2 = codegen.basicBlock() // Block to process "eqeq". + val bbEq3 = codegen.basicBlock() // Block to process "eqeqeq". + val bbExit = codegen.basicBlock() // Exit block for feather generation. val result = codegen.alloca(getLLVMType(callee.type), codegen.newVar()) val condition = codegen.icmpEq(arg0, kNullPtr, codegen.newVar()) // Compare arg0 with "null". @@ -857,14 +1307,14 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid codegen.br(bbExit) // codegen.positionAtEnd(bbEq2) // Get generation to bbEq2. - val resultEquals = evaluateSimpleFunctionCall(codegen.newVar(), descriptor, listOf(arg0, arg1))!! + val resultEquals = evaluateSimpleFunctionCall(codegen.newVar(), descriptor, listOf(arg0, arg1)) codegen.store(resultEquals, result) // Store "eqeqeq" result in "result". codegen.br(bbExit) // codegen.positionAtEnd(bbExit) // Get generation to bbExit. return codegen.load(result, tmpVariableName) // Load result in tmpVariable. } else { - return evaluateSimpleFunctionCall(tmpVariableName, descriptor, listOf(arg0, arg1))!! + return evaluateSimpleFunctionCall(tmpVariableName, descriptor, listOf(arg0, arg1)) } } @@ -899,7 +1349,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid val bbCurr = codegen.basicBlock() // Create block for clause body. val condition = evaluateExpression(codegen.newVar(), branch.condition) // Generate cmp instruction. codegen.condBr(condition, bbCurr, bbNext) // Conditional branch depending on cmp result. - codegen.positionAtEnd(bbCurr!!) // Switch generation to block for clause body. + codegen.positionAtEnd(bbCurr) // Switch generation to block for clause body. val brResult = evaluateExpression(codegen.newVar(), branch.result) // Generate clause body. if (neitherUnitNorNothing) // If nor unit neither result ends with return codegen.store(brResult!!, resultPtr!!) // we store result to temporal variable. @@ -918,7 +1368,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// - fun callDirect(descriptor: FunctionDescriptor, args: List, result: String?): LLVMValueRef? { + fun callDirect(descriptor: FunctionDescriptor, args: List, result: String?): LLVMValueRef { val realDescriptor = DescriptorUtils.unwrapFakeOverride(descriptor) val llvmFunction = codegen.functionLlvmValue(realDescriptor) return call(descriptor, llvmFunction, args, result) @@ -938,38 +1388,35 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// - fun callVirtual(descriptor: FunctionDescriptor, args: List, result: String?): LLVMValueRef? { - val thisI8PtrPtr = codegen.bitcast(kInt8PtrPtr, args[0]!!, codegen.newVar()) // Cast "this (i8*)" to i8**. - val typeInfoI8Ptr = codegen.load(thisI8PtrPtr!!, codegen.newVar()) // Load TypeInfo address. + fun callVirtual(descriptor: FunctionDescriptor, args: List, result: String?): LLVMValueRef { + val thisI8PtrPtr = codegen.bitcast(kInt8PtrPtr, args[0], codegen.newVar()) // Cast "this (i8*)" to i8**. + val typeInfoI8Ptr = codegen.load(thisI8PtrPtr, codegen.newVar()) // Load TypeInfo address. val typeInfoPtr = codegen.bitcast(kTypeInfoPtr, typeInfoI8Ptr, codegen.newVar()) // Cast TypeInfo (i8*) to TypeInfo*. val methodHash = codegen.functionHash(descriptor) // Calculate hash of the method to be invoked val lookupArgs = listOf(typeInfoPtr, methodHash) // Prepare args for lookup - val llvmMethod = codegen.call( + val llvmMethod = currentCodeContext.genCall( context.lookupOpenMethodFunction, lookupArgs, codegen.newVar()) // Get method ptr to be invoked val functionPtrType = pointerType(getLlvmFunctionType(descriptor)) // Construct type of the method to be invoked - val function = codegen.bitcast(functionPtrType, llvmMethod!!, codegen.newVar()) // Cast method address to the type + val function = codegen.bitcast(functionPtrType, llvmMethod, codegen.newVar()) // Cast method address to the type return call(descriptor, function, args, result) // Invoke the method } //-------------------------------------------------------------------------// - private fun call(descriptor: FunctionDescriptor, function: LLVMValueRef?, args: List, result: String?): LLVMValueRef? { - return codegen.call(function, args, if (KotlinBuiltIns.isUnit(descriptor.returnType!!)) "" else result) + private fun call(descriptor: FunctionDescriptor, function: LLVMValueRef, args: List, result: String?): LLVMValueRef { + return currentCodeContext.genCall(function, args, + if (KotlinBuiltIns.isUnit(descriptor.returnType!!)) "" else result) } //-------------------------------------------------------------------------// - fun superCall(result:String, descriptor:ClassConstructorDescriptor, args:List ): LLVMValueRef? { + fun superCall(result:String, descriptor:ClassConstructorDescriptor, args:List ): LLVMValueRef { val tmp = codegen.load(codegen.thisVariable(), codegen.newVar()) - val rargs = - if (args.isNotEmpty()) - listOf(tmp, *args.toTypedArray()) - else - listOf(tmp) - return callDirect(descriptor, rargs!!, result) + val rargs = listOf(tmp, *args.toTypedArray()) + return callDirect(descriptor, rargs, result) } //-------------------------------------------------------------------------// diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt index effa55a0629..2e5c7311083 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt @@ -91,10 +91,13 @@ internal fun constValue(value: LLVMValueRef?) = object : ConstValue { override fun getLlvmValue() = value } -internal val int8Type = LLVMInt8Type() -internal val int32Type = LLVMInt32Type() +internal val int8Type = LLVMInt8Type()!! +internal val int32Type = LLVMInt32Type()!! +internal val int8TypePtr = pointerType(int8Type) -internal fun pointerType(pointeeType: LLVMTypeRef?) = LLVMPointerType(pointeeType, 0) +internal val voidType = LLVMVoidType() + +internal fun pointerType(pointeeType: LLVMTypeRef?) = LLVMPointerType(pointeeType, 0)!! internal fun structType(vararg types: LLVMTypeRef?): LLVMTypeRef = memScoped { LLVMStructType(allocArrayOf(*types)[0].ptr, types.size, 0)!! @@ -127,6 +130,35 @@ internal fun CArray.getBytes(size: Long) = (0 .. size-1).map { this[it].value }.toByteArray() internal fun getFunctionType(ptrToFunction: LLVMValueRef?): LLVMTypeRef { - val typeOfPtrToFunction = LLVMTypeOf(ptrToFunction) - return LLVMGetElementType(typeOfPtrToFunction)!! + return getGlobalType(ptrToFunction) } + +internal fun getGlobalType(ptrToGlobal: LLVMValueRef?): LLVMTypeRef { + return LLVMGetElementType(LLVMTypeOf(ptrToGlobal))!! +} + +internal fun ContextUtils.externalFunction(name: String, type: LLVMTypeRef): LLVMValueRef { + val found = LLVMGetNamedFunction(context.llvmModule, name) + if (found != null) { + assert (getFunctionType(found) == type) + return found + } else { + return LLVMAddFunction(context.llvmModule, name, type)!! + } +} + +internal fun ContextUtils.externalGlobal(name: String, type: LLVMTypeRef): LLVMValueRef { + val found = LLVMGetNamedGlobal(context.llvmModule, name) + if (found != null) { + assert (getGlobalType(found) == type) + return found + } else { + return LLVMAddGlobal(context.llvmModule, type, name)!! + } +} + +internal fun functionType(returnType: LLVMTypeRef?, isVarArg: Boolean = false, vararg paramTypes: LLVMTypeRef?) = + memScoped { + val paramTypesPtr = if (paramTypes.size != 0) allocArrayOf(*paramTypes)[0].ptr else null + LLVMFunctionType(returnType, paramTypesPtr, paramTypes.size, if (isVarArg) 1 else 0)!! + } \ No newline at end of file diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 39e5257d00d..d72339e9ca9 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -122,8 +122,8 @@ class UnitKonanTest extends KonanTest { void compileTest(File sourceS, File runtimeS, File stdlibKtBc, File exe) { def testC = sourceS.absolutePath.replace(".kt.S", "-test.c") project.execClang { - commandLine "clang", "${testC}", "${runtimeS.absolutePath}", "${stdlibKtBc.absolutePath}", "${sourceS.absolutePath}", "${mainC}", - "-o", "${exe.absolutePath}", linkDl(), linkM(), rdynamic() + commandLine "clang++", runtimeS.absolutePath, stdlibKtBc.absolutePath, sourceS.absolutePath, + "-o", exe.absolutePath, linkDl(), linkM(), rdynamic(), '-xc', testC, mainC } } } @@ -131,8 +131,8 @@ class UnitKonanTest extends KonanTest { class RunKonanTest extends KonanTest { void compileTest(File sourceS, File runtimeS, File libraryPath, File exe) { project.execClang { - commandLine "clang", "-DRUN_TEST", "${runtimeS.absolutePath}", "${stdlibKtBc.absolutePath}", "${sourceS.absolutePath}", "${mainC}", - "-o", "${exe.absolutePath}", linkDl(), linkM(), rdynamic() + commandLine "clang++", "-DRUN_TEST", runtimeS.absolutePath, stdlibKtBc.absolutePath, sourceS.absolutePath, + "-o", exe.absolutePath, linkDl(), linkM(), rdynamic(), '-xc', mainC } } } @@ -186,8 +186,8 @@ class LinkKonanTest extends KonanTest { void compileTest(File sourceS, File runtimeS, File stdlibKtBc, File exe) { def testC = sourceS.absolutePath.replace(".kt.S", "-test.c") project.execClang { - commandLine "clang", "${testC}", "${runtimeS.absolutePath}", "${stdlibKtBc.absolutePath}", "${sourceS.absolutePath}", "${mainC}", - "-o", "${exe.absolutePath}", linkDl(), rdynamic() + commandLine "clang++", runtimeS.absolutePath, stdlibKtBc.absolutePath, sourceS.absolutePath, + "-o", exe.absolutePath, linkDl(), rdynamic(), '-xc', testC, mainC } } @@ -432,3 +432,123 @@ task moderately_large_array(type: RunKonanTest) { } */ +task catch1(type: RunKonanTest) { + goldValue = "Before\nCaught Throwable\nDone\n" + source = "runtime/exceptions/catch1.kt" +} + +task catch2(type: RunKonanTest) { + goldValue = "Before\nCaught Error\nDone\n" + source = "runtime/exceptions/catch2.kt" +} + +task catch7(type: RunKonanTest) { + goldValue = "Error happens\n" + source = "runtime/exceptions/catch7.kt" +} + +task catch3(type: RunKonanTest) { + goldValue = "Before\nCaught Throwable\nDone\n" + source = "codegen/try/catch3.kt" +} + +task catch4(type: RunKonanTest) { + goldValue = "Before\nCaught Error\nDone\n" + source = "codegen/try/catch4.kt" +} + +task catch5(type: RunKonanTest) { + goldValue = "Before\nCaught Error\nDone\n" + source = "codegen/try/catch5.kt" +} + +task catch6(type: RunKonanTest) { + goldValue = "Before\nCaught Error\nDone\n" + source = "codegen/try/catch6.kt" +} + +task catch8(type: RunKonanTest) { + goldValue = "Error happens\n" + source = "codegen/try/catch8.kt" +} + +task finally1(type: RunKonanTest) { + goldValue = "Try\nFinally\nDone\n" + source = "codegen/try/finally1.kt" +} + +task finally2(type: RunKonanTest) { + goldValue = "Try\nCaught Error\nFinally\nDone\n" + source = "codegen/try/finally2.kt" +} + +task finally3(type: RunKonanTest) { + goldValue = "Try\nFinally\nCaught Error\nDone\n" + source = "codegen/try/finally3.kt" +} + +task finally4(type: RunKonanTest) { + goldValue = "Try\nCatch\nFinally\nCaught Exception\nDone\n" + source = "codegen/try/finally4.kt" +} + +task finally5(type: RunKonanTest) { + goldValue = "Done\nFinally\n0\n" + source = "codegen/try/finally5.kt" +} + +task finally6(type: RunKonanTest) { + goldValue = "Done\nFinally\n1\n" + source = "codegen/try/finally6.kt" +} + +task finally7(type: RunKonanTest) { + goldValue = "Done\nFinally\n1\n" + source = "codegen/try/finally7.kt" +} + +task finally8(type: RunKonanTest) { + goldValue = "Finally 1\nFinally 2\n42\n" + source = "codegen/try/finally8.kt" +} + +/* +TODO: enable after implementing break and continue +task finally9(type: RunKonanTest) { + goldValue = "Finally 1\nFinally 2\nAfter\n" + source = "codegen/try/finally9.kt" +} +*/ + +/* +TODO: enable after improving LLVM variable naming +task scope1(type: RunKonanTest) { + goldValue = "1\n" + source = "codegen/dataflow/scope1.kt" +} +*/ + +task try1(type: RunKonanTest) { + goldValue = "5\n" + source = "codegen/try/try1.kt" +} + +task try2(type: RunKonanTest) { + goldValue = "6\n" + source = "codegen/try/try2.kt" +} + +task try3(type: RunKonanTest) { + goldValue = "6\n" + source = "codegen/try/try3.kt" +} + +task try4(type: RunKonanTest) { + goldValue = "Try\n5\n" + source = "codegen/try/try4.kt" +} + +task unreachable1(type: RunKonanTest) { + goldValue = "1\n" + source = "codegen/controlflow/unreachable1.kt" +} diff --git a/backend.native/tests/codegen/controlflow/unreachable1.kt b/backend.native/tests/codegen/controlflow/unreachable1.kt new file mode 100644 index 00000000000..63032ff8950 --- /dev/null +++ b/backend.native/tests/codegen/controlflow/unreachable1.kt @@ -0,0 +1,8 @@ +fun main(args: Array) { + println(foo()) +} + +fun foo(): Int { + return 1 + println("After return") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/dataflow/scope1.kt b/backend.native/tests/codegen/dataflow/scope1.kt new file mode 100644 index 00000000000..f14eb27cf86 --- /dev/null +++ b/backend.native/tests/codegen/dataflow/scope1.kt @@ -0,0 +1,9 @@ +var b = true + +fun main(args: Array) { + var x = 1 + if (b) { + var x = 2 + } + println(x) +} diff --git a/backend.native/tests/codegen/try/catch3.kt b/backend.native/tests/codegen/try/catch3.kt new file mode 100644 index 00000000000..dd27ebe2672 --- /dev/null +++ b/backend.native/tests/codegen/try/catch3.kt @@ -0,0 +1,11 @@ +fun main(args : Array) { + try { + println("Before") + throw Error("Error happens") + println("After") + } catch (e: Throwable) { + println("Caught Throwable") + } + + println("Done") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/catch4.kt b/backend.native/tests/codegen/try/catch4.kt new file mode 100644 index 00000000000..f646e8e757c --- /dev/null +++ b/backend.native/tests/codegen/try/catch4.kt @@ -0,0 +1,15 @@ +fun main(args : Array) { + try { + println("Before") + throw Error("Error happens") + println("After") + } catch (e: Exception) { + println("Caught Exception") + } catch (e: Error) { + println("Caught Error") + } catch (e: Throwable) { + println("Caught Throwable") + } + + println("Done") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/catch5.kt b/backend.native/tests/codegen/try/catch5.kt new file mode 100644 index 00000000000..473734c8b49 --- /dev/null +++ b/backend.native/tests/codegen/try/catch5.kt @@ -0,0 +1,25 @@ +fun main(args : Array) { + try { + try { + println("Before") + foo() + println("After") + } catch (e: Exception) { + println("Caught Exception") + } + + println("After nested try") + + } catch (e: Error) { + println("Caught Error") + } catch (e: Throwable) { + println("Caught Throwable") + } + + println("Done") +} + +fun foo() { + throw Error("Error happens") + println("After in foo()") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/catch6.kt b/backend.native/tests/codegen/try/catch6.kt new file mode 100644 index 00000000000..e4711332f31 --- /dev/null +++ b/backend.native/tests/codegen/try/catch6.kt @@ -0,0 +1,22 @@ +fun main(args : Array) { + + try { + println("Before") + foo() + println("After") + } catch (e: Exception) { + println("Caught Exception") + } catch (e: Error) { + println("Caught Error") + } + + println("Done") +} + +fun foo() { + try { + throw Error("Error happens") + } catch (e: Exception) { + println("Caught Exception") + } +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/catch8.kt b/backend.native/tests/codegen/try/catch8.kt new file mode 100644 index 00000000000..49327992216 --- /dev/null +++ b/backend.native/tests/codegen/try/catch8.kt @@ -0,0 +1,10 @@ +fun main(args : Array) { + try { + throw Error("Error happens") + } catch (e: Throwable) { + val message = e.message + if (message != null) { + println(message) + } + } +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally1.kt b/backend.native/tests/codegen/try/finally1.kt new file mode 100644 index 00000000000..e2c1c257e39 --- /dev/null +++ b/backend.native/tests/codegen/try/finally1.kt @@ -0,0 +1,10 @@ +fun main(args : Array) { + + try { + println("Try") + } finally { + println("Finally") + } + + println("Done") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally2.kt b/backend.native/tests/codegen/try/finally2.kt new file mode 100644 index 00000000000..3edb2b73220 --- /dev/null +++ b/backend.native/tests/codegen/try/finally2.kt @@ -0,0 +1,14 @@ +fun main(args : Array) { + + try { + println("Try") + throw Error("Error happens") + println("After throw") + } catch (e: Error) { + println("Caught Error") + } finally { + println("Finally") + } + + println("Done") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally3.kt b/backend.native/tests/codegen/try/finally3.kt new file mode 100644 index 00000000000..7e6ab694d7d --- /dev/null +++ b/backend.native/tests/codegen/try/finally3.kt @@ -0,0 +1,19 @@ +fun main(args : Array) { + + try { + try { + println("Try") + throw Error("Error happens") + println("After throw") + } finally { + println("Finally") + } + + println("After nested try") + + } catch (e: Error) { + println("Caught Error") + } + + println("Done") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally4.kt b/backend.native/tests/codegen/try/finally4.kt new file mode 100644 index 00000000000..0a9a317c731 --- /dev/null +++ b/backend.native/tests/codegen/try/finally4.kt @@ -0,0 +1,25 @@ +fun main(args : Array) { + + try { + try { + println("Try") + throw Error("Error happens") + println("After throw") + } catch (e: Error) { + println("Catch") + throw Exception() + println("After throw") + } finally { + println("Finally") + } + + println("After nested try") + + } catch (e: Error) { + println("Caught Error") + } catch (e: Exception) { + println("Caught Exception") + } + + println("Done") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally5.kt b/backend.native/tests/codegen/try/finally5.kt new file mode 100644 index 00000000000..ebd72f51907 --- /dev/null +++ b/backend.native/tests/codegen/try/finally5.kt @@ -0,0 +1,15 @@ +fun main(args : Array) { + println(foo()) +} + +fun foo(): Int { + try { + println("Done") + return 0 + } finally { + println("Finally") + } + + println("After") + return 1 +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally6.kt b/backend.native/tests/codegen/try/finally6.kt new file mode 100644 index 00000000000..b60e13a0899 --- /dev/null +++ b/backend.native/tests/codegen/try/finally6.kt @@ -0,0 +1,16 @@ +fun main(args : Array) { + println(foo()) +} + +fun foo(): Int { + try { + println("Done") + return 0 + } finally { + println("Finally") + return 1 + } + + println("After") + return 2 +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally7.kt b/backend.native/tests/codegen/try/finally7.kt new file mode 100644 index 00000000000..b9d7e936ac3 --- /dev/null +++ b/backend.native/tests/codegen/try/finally7.kt @@ -0,0 +1,16 @@ +fun main(args : Array) { + println(foo()) +} + +fun foo(): Int { + try { + println("Done") + throw Error() + } finally { + println("Finally") + return 1 + } + + println("After") + return 2 +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally8.kt b/backend.native/tests/codegen/try/finally8.kt new file mode 100644 index 00000000000..d7b876e06d7 --- /dev/null +++ b/backend.native/tests/codegen/try/finally8.kt @@ -0,0 +1,18 @@ +fun main(args : Array) { + println(foo()) +} + +fun foo(): Int { + try { + try { + return 42 + } finally { + println("Finally 1") + } + } finally { + println("Finally 2") + } + + println("After") + return 2 +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/finally9.kt b/backend.native/tests/codegen/try/finally9.kt new file mode 100644 index 00000000000..46f75d4471d --- /dev/null +++ b/backend.native/tests/codegen/try/finally9.kt @@ -0,0 +1,21 @@ +fun main(args : Array) { + do { + try { + break + } finally { + println("Finally 1") + } + } while (false) + + var stop = false + while (!stop) { + try { + stop = true + continue + } finally { + println("Finally 2") + } + } + + println("After") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/try1.kt b/backend.native/tests/codegen/try/try1.kt new file mode 100644 index 00000000000..e4f54d70fb7 --- /dev/null +++ b/backend.native/tests/codegen/try/try1.kt @@ -0,0 +1,9 @@ +fun main(args : Array) { + val x = try { + 5 + } catch (e: Throwable) { + 6 + } + + println(x) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/try2.kt b/backend.native/tests/codegen/try/try2.kt new file mode 100644 index 00000000000..f40fc73e7a0 --- /dev/null +++ b/backend.native/tests/codegen/try/try2.kt @@ -0,0 +1,10 @@ +fun main(args : Array) { + val x = try { + throw Error() + 5 + } catch (e: Throwable) { + 6 + } + + println(x) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/try3.kt b/backend.native/tests/codegen/try/try3.kt new file mode 100644 index 00000000000..5a5c1a41c88 --- /dev/null +++ b/backend.native/tests/codegen/try/try3.kt @@ -0,0 +1,9 @@ +fun main(args : Array) { + val x = try { + throw Error() + } catch (e: Throwable) { + 6 + } + + println(x) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/try/try4.kt b/backend.native/tests/codegen/try/try4.kt new file mode 100644 index 00000000000..800ebade889 --- /dev/null +++ b/backend.native/tests/codegen/try/try4.kt @@ -0,0 +1,10 @@ +fun main(args : Array) { + val x = try { + println("Try") + 5 + } catch (e: Throwable) { + throw e + } + + println(x) +} \ No newline at end of file diff --git a/backend.native/tests/runtime/exceptions/catch1.kt b/backend.native/tests/runtime/exceptions/catch1.kt new file mode 100644 index 00000000000..53e8b65ab0d --- /dev/null +++ b/backend.native/tests/runtime/exceptions/catch1.kt @@ -0,0 +1,16 @@ +fun main(args : Array) { + try { + println("Before") + foo() + println("After") + } catch (e: Throwable) { + println("Caught Throwable") + } + + println("Done") +} + +fun foo() { + throw Error("Error happens") + println("After in foo()") +} \ No newline at end of file diff --git a/backend.native/tests/runtime/exceptions/catch2.kt b/backend.native/tests/runtime/exceptions/catch2.kt new file mode 100644 index 00000000000..86bf162ffde --- /dev/null +++ b/backend.native/tests/runtime/exceptions/catch2.kt @@ -0,0 +1,20 @@ +fun main(args : Array) { + try { + println("Before") + foo() + println("After") + } catch (e: Exception) { + println("Caught Exception") + } catch (e: Error) { + println("Caught Error") + } catch (e: Throwable) { + println("Caught Throwable") + } + + println("Done") +} + +fun foo() { + throw Error("Error happens") + println("After in foo()") +} \ No newline at end of file diff --git a/backend.native/tests/runtime/exceptions/catch7.kt b/backend.native/tests/runtime/exceptions/catch7.kt new file mode 100644 index 00000000000..766f17a369f --- /dev/null +++ b/backend.native/tests/runtime/exceptions/catch7.kt @@ -0,0 +1,14 @@ +fun main(args : Array) { + try { + foo() + } catch (e: Throwable) { + val message = e.message + if (message != null) { + println(message) + } + } +} + +fun foo() { + throw Error("Error happens") +} \ No newline at end of file diff --git a/runtime/src/main/cpp/Exceptions.cpp b/runtime/src/main/cpp/Exceptions.cpp index eb29b99a18e..183c6468d18 100644 --- a/runtime/src/main/cpp/Exceptions.cpp +++ b/runtime/src/main/cpp/Exceptions.cpp @@ -36,10 +36,25 @@ void ThrowClassCastException() { RuntimeAssert(false, "Throwing is unsupported"); } +class KotlinException { + public: + + KRef exception_; + + KotlinException(KRef exception) : exception_(exception) { + ::AddRef(exception_->container()); + }; + + ~KotlinException() { + ::Release(exception_->container()); + }; +}; + void ThrowException(KRef exception) { RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo), "Throwing something non-throwable"); - RuntimeAssert(false, "Throwing is unsupported"); + + throw KotlinException(exception); }