Implement lowering of some builtin operators. (#154)

* EQEQ
* THROW_NPE

Add test for (un)boxing `==` operands.
This commit is contained in:
SvyatoslavScherbina
2016-12-22 13:02:20 +07:00
committed by GitHub
parent 0632a30eca
commit b6561b13a4
11 changed files with 189 additions and 120 deletions
@@ -17,6 +17,10 @@ internal class KonanLower(val context: Context) {
fun lower(irFile: IrFile) {
val phaser = PhaseManager(context)
phaser.phase("Lower_builtin_operators") {
BuiltinOperatorLowering(context).runOnFilePostfix(irFile)
}
phaser.phase("Lower_shared_variables") {
SharedVariablesLowering(context).runOnFilePostfix(irFile)
}
@@ -10,6 +10,7 @@ object KonanPhases {
val phases = mapOf<String, KonanPhase> (
"Backend" to KonanPhase("All backend"),
"Optimizer" to KonanPhase("IR Optimizer"),
"Lower_builtin_operators" to KonanPhase("BuiltIn Operators Lowering"),
"Lower_shared_variables" to KonanPhase("Shared Variable Lowering"),
"Lower_local_functions" to KonanPhase("Local Function Lowering"),
"Lower_callables" to KonanPhase("Callable references Lowering"),
@@ -43,6 +43,12 @@ internal val FunctionDescriptor.implementation: FunctionDescriptor
}
}
private val intrinsicAnnotation = FqName("konan.internal.Intrinsic")
// TODO: check it is external?
internal val FunctionDescriptor.isIntrinsic: Boolean
get() = this.annotations.findAnnotation(intrinsicAnnotation) != null
private val intrinsicTypes = setOf(
"kotlin.Unit",
"kotlin.Boolean", "kotlin.Char",
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.isNothing
@@ -1723,6 +1724,10 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
return evaluateFunctionInvoke(descriptor, args)
}
if (descriptor.isIntrinsic) {
return evaluateIntrinsicCall(callee, args)
}
when (descriptor) {
is IrBuiltinOperatorDescriptorBase -> return evaluateOperatorCall (callee, args)
is ClassConstructorDescriptor -> return evaluateConstructorCall (callee, args)
@@ -1808,14 +1813,31 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
}
//-------------------------------------------------------------------------//
private val kEqeq = Name.identifier("EQEQ")
private val ktEqeqeq = Name.identifier("EQEQEQ")
private val kGt0 = Name.identifier("GT0")
private val kGteq0 = Name.identifier("GTEQ0")
private val kLt0 = Name.identifier("LT0")
private val kLteq0 = Name.identifier("LTEQ0")
private val kNot = Name.identifier("NOT")
private val kThrowNpe = Name.identifier("THROW_NPE")
private fun evaluateIntrinsicCall(callee: IrCall, args: List<LLVMValueRef?>): LLVMValueRef {
val descriptor = callee.descriptor
val name = descriptor.fqNameUnsafe.asString()
return when (name) {
"konan.internal.areEqualByValue" -> {
val arg0 = args[0]!!
val arg1 = args[1]!!
assert (arg0.type == arg1.type)
when (LLVMGetTypeKind(arg0.type)) {
LLVMTypeKind.LLVMFloatTypeKind, LLVMTypeKind.LLVMDoubleTypeKind ->
codegen.fcmpEq(arg0, arg1)
else ->
codegen.icmpEq(arg0, arg1)
}
}
else -> TODO(name)
}
}
//-------------------------------------------------------------------------//
private val kImmZero = LLVMConstInt(LLVMInt32Type(), 0, 1)!!
private val kImmOne = LLVMConstInt(LLVMInt32Type(), 1, 1)!!
private val kTrue = LLVMConstInt(LLVMInt1Type(), 1, 1)!!
@@ -1825,16 +1847,14 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
private fun evaluateOperatorCall(callee: IrCall, args: List<LLVMValueRef?>): LLVMValueRef {
context.log("evaluateCall : origin:${ir2string(callee)}")
val descriptor = callee.descriptor
when (descriptor.name) {
kEqeq -> return evaluateOperatorEqeq (callee as IrBinaryPrimitiveImpl, args[0]!!, args[1]!!)
ktEqeqeq -> return evaluateOperatorEqeqeq(callee as IrBinaryPrimitiveImpl, args[0]!!, args[1]!!)
kGt0 -> return codegen.icmpGt(args[0]!!, kImmZero)
kGteq0 -> return codegen.icmpGe(args[0]!!, kImmZero)
kLt0 -> return codegen.icmpLt(args[0]!!, kImmZero)
kLteq0 -> return codegen.icmpLe(args[0]!!, kImmZero)
kNot -> return codegen.icmpNe(args[0]!!, kTrue)
// TODO: reconsider.
kThrowNpe -> return evaluateOperatorThrowNpe()
val ib = context.irModule!!.irBuiltins
when (descriptor) {
ib.eqeqeq -> return codegen.icmpEq(args[0]!!, args[1]!!)
ib.gt0 -> return codegen.icmpGt(args[0]!!, kImmZero)
ib.gteq0 -> return codegen.icmpGe(args[0]!!, kImmZero)
ib.lt0 -> return codegen.icmpLt(args[0]!!, kImmZero)
ib.lteq0 -> return codegen.icmpLe(args[0]!!, kImmZero)
ib.booleanNot -> return codegen.icmpNe(args[0]!!, kTrue)
else -> {
TODO(descriptor.name.toString())
}
@@ -1843,19 +1863,6 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
//-------------------------------------------------------------------------//
private fun getEquals(type: KotlinType) : SimpleFunctionDescriptor {
val name = Name.identifier("equals")
val descriptors = type.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).filter {
it.valueParameters.size == 1 && KotlinBuiltIns.isAnyOrNullableAny(it.valueParameters[0].type)
}
assert(descriptors.size == 1)
val descriptor = descriptors.first()
return descriptor
}
//-------------------------------------------------------------------------//
private fun getToString(type: KotlinType): SimpleFunctionDescriptor {
val descriptor = type.memberScope.getContributedFunctions(kNameToString, NoLookupLocation.FROM_BACKEND).first()
return descriptor
@@ -1863,81 +1870,6 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
//-------------------------------------------------------------------------//
private fun getThrowNpe(): SimpleFunctionDescriptor {
val name = Name.identifier("ThrowNullPointerException")
val moduleDescriptor = KonanPlatform.builtIns.builtInsModule
val packageDescriptor = moduleDescriptor.getPackage(FqName("konan.internal"))
val descriptor = packageDescriptor.memberScope.getContributedFunctions(
name, NoLookupLocation.FROM_BACKEND).first()
return descriptor
}
//-------------------------------------------------------------------------//
private fun evaluateOperatorEqeq(callee: IrBinaryPrimitiveImpl, arg0: LLVMValueRef, arg1: LLVMValueRef): LLVMValueRef {
val arg0Type = callee.argument0.type
val isFp = KotlinBuiltIns.isDouble(arg0Type) || KotlinBuiltIns.isFloat(arg0Type)
val isInt = KotlinBuiltIns.isPrimitiveType(arg0Type) && !isFp
when {
isFp -> return codegen.fcmpEq(arg0, arg1)
isInt -> return codegen.icmpEq(arg0, arg1)
else -> return generateEqeqForObjects(callee, arg0, arg1)
}
}
//-------------------------------------------------------------------------//
private fun generateEqeqForObjects(callee: IrBinaryPrimitiveImpl, arg0: LLVMValueRef, arg1: LLVMValueRef): LLVMValueRef {
val arg0Type = callee.argument0.type
val descriptor = getEquals(arg0Type) // 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 result = codegen.alloca(callee.type)
val condition = codegen.icmpEq(arg0, codegen.kNullObjHeaderPtr) // Compare arg0 with "null".
codegen.condBr(condition, bbEq3, bbEq2) // If (arg0 == null) bbEq3 else bbEq2.
codegen.positionAtEnd(bbEq3) // Get generation to bbEq3.
val resultIdentityEq = evaluateOperatorEqeqeq(callee, arg0, arg1)
codegen.store(resultIdentityEq, result) // Store "eqeq" result in "result".
codegen.br(bbExit) //
codegen.positionAtEnd(bbEq2) // Get generation to bbEq2.
val resultEquals = evaluateSimpleFunctionCall(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) // Load result.
} else {
return evaluateSimpleFunctionCall(descriptor, listOf(arg0, arg1))
}
}
//-------------------------------------------------------------------------//
private fun evaluateOperatorEqeqeq(callee: IrBinaryPrimitiveImpl,
arg0: LLVMValueRef, arg1: LLVMValueRef): LLVMValueRef {
val arg0Type = callee.argument0.type
return when {
KotlinBuiltIns.isPrimitiveType(arg0Type) -> TODO("${ir2string(callee)}")
else -> codegen.icmpEq(arg0, arg1)
}
}
private fun evaluateOperatorThrowNpe(): LLVMValueRef {
val result = evaluateSimpleFunctionCall(getThrowNpe(), listOf())
codegen.unreachable()
return result
}
//-------------------------------------------------------------------------//
private fun generateWhenCase(isUnit:Boolean, isNothing:Boolean, resultPtr: LLVMValueRef?, branch: IrBranch, bbNext: LLVMBasicBlockRef?, bbExit: LLVMBasicBlockRef?) {
val neitherUnitNorNothing = !isNothing && !isUnit // If branches doesn't end with 'return' either result hasn't got 'unit' type.
val branchResult = branch.result
@@ -2001,7 +1933,11 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
// In such case it would be possible to check that all args are available and in the correct order.
// However, it currently requires some refactoring to be performed.
private fun call(descriptor: FunctionDescriptor, function: LLVMValueRef, args: List<LLVMValueRef>): LLVMValueRef {
return currentCodeContext.genCall(function, args)
val result = currentCodeContext.genCall(function, args)
if (descriptor.returnType?.isNothing() == true) {
codegen.unreachable()
}
return result
}
//-------------------------------------------------------------------------//
@@ -109,15 +109,6 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
}
}
override fun IrExpression.useAsArgument(parameter: ParameterDescriptor): IrExpression {
return if (parameter.containingDeclaration == irBuiltins.eqeq) {
this // Do not box arguments of `==` because codegen expects them to be unboxed.
// TODO: implement `==` lowering at IR level and remove this hack.
} else {
this.useAsValue(parameter)
}
}
/**
* @return the element of [primitiveTypes] if given type is represented as primitive type in generated code,
* or `null` if represented as object reference.
@@ -168,7 +159,8 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
private fun IrExpression.box(primitiveType: KotlinType): IrExpression {
val primitiveTypeClass = TypeUtils.getClassDescriptor(primitiveType)!!
val boxFunction = context.builtIns.getKonanInternalFunctions("box${primitiveTypeClass.name}").single()
val boxFunction = context.builtIns.getKonanInternalFunctions("box${primitiveTypeClass.name}").singleOrNull() ?:
TODO(primitiveType.toString())
return IrCallImpl(startOffset, endOffset, boxFunction).apply {
putValueArgument(0, this@box)
@@ -0,0 +1,91 @@
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions
import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptor
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrTry
import org.jetbrains.kotlin.ir.expressions.impl.IrBinaryPrimitiveImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTryImpl
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
/**
* This lowering pass lowers some calls to [IrBuiltinOperatorDescriptor]s.
*/
internal class BuiltinOperatorLowering(val context: Context) : BodyLoweringPass {
private val transformer = BuiltinOperatorTransformer(context)
override fun lower(irBody: IrBody) {
irBody.transformChildrenVoid(transformer)
}
}
private class BuiltinOperatorTransformer(val context: Context) : IrElementTransformerVoid() {
private val builtIns = context.builtIns
private val irBuiltins = context.irModule!!.irBuiltins
override fun visitTry(aTry: IrTry): IrExpression {
// Workaround for the bug in IrTryImpl.transformChildren: transform `finallyExpression` too.
// TODO: fix the bug and remove it.
val transformer = this
return (aTry as IrTryImpl).apply {
tryResult = tryResult.transform(transformer, null)
catches.forEachIndexed { i, irCatch ->
catches[i] = irCatch.transform(transformer, null)
}
finallyExpression = finallyExpression?.transform(transformer, null)
}
}
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this)
if (expression.descriptor is IrBuiltinOperatorDescriptor) {
return transformBuiltinOperator(expression)
}
return expression
}
private fun transformBuiltinOperator(expression: IrCall): IrExpression {
val descriptor = expression.descriptor
return when (descriptor) {
irBuiltins.eqeq -> {
val binary = expression as IrBinaryPrimitiveImpl
lowerEqeq(binary.argument0, binary.argument1, expression.startOffset, expression.endOffset)
}
irBuiltins.throwNpe -> IrCallImpl(expression.startOffset, expression.endOffset,
builtIns.getKonanInternalFunctions("ThrowNullPointerException").single())
else -> expression
}
}
private fun lowerEqeq(lhs: IrExpression, rhs: IrExpression, startOffset: Int, endOffset: Int): IrExpression {
// TODO: optimize boxing?
// TODO: areEqualByValue intrinsics are specially treated by code generator
// and thus can be declared synthetically in the compiler instead of explicitly in the runtime.
// Find a type-compatible `konan.internal.areEqualByValue` intrinsic:
val equals = builtIns.getKonanInternalFunctions("areEqualByValue").firstOrNull {
lhs.type.isSubtypeOf(it.valueParameters[0].type) && rhs.type.isSubtypeOf(it.valueParameters[1].type)
} ?: builtIns.getKonanInternalFunctions("areEqual").single() // or use the general implementation.
return IrCallImpl(startOffset, endOffset, equals).apply {
putValueArgument(0, lhs)
putValueArgument(1, rhs)
}
}
}
+5
View File
@@ -528,6 +528,11 @@ task boxing12(type: RunKonanTest) {
source = "codegen/boxing/boxing12.kt"
}
task boxing13(type: RunKonanTest) {
goldValue = "false\nfalse\ntrue\ntrue\nfalse\nfalse\n"
source = "codegen/boxing/boxing13.kt"
}
task interface0(type: RunKonanTest) {
goldValue = "PASSED\n"
source = "runtime/basic/interface0.kt"
@@ -0,0 +1,10 @@
fun is42(x: Any?) {
println(x == 42)
println(42 == x)
}
fun main(args: Array<String>) {
is42(16)
is42(42)
is42("42")
}
@@ -16,3 +16,9 @@ annotation class ExportForCppRuntime(val name: String = "")
// in the absence of IR.
annotation class HasBackingField
/**
* This annotation denotes that the element is intrinsic and its usages require special handling in compiler.
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class Intrinsic
@@ -0,0 +1,18 @@
package konan.internal
@Intrinsic external fun areEqualByValue(first: Boolean, second: Boolean): Boolean
@Intrinsic external fun areEqualByValue(first: Char, second: Char): Boolean
@Intrinsic external fun areEqualByValue(first: Byte, second: Byte): Boolean
@Intrinsic external fun areEqualByValue(first: Short, second: Short): Boolean
@Intrinsic external fun areEqualByValue(first: Int, second: Int): Boolean
@Intrinsic external fun areEqualByValue(first: Long, second: Long): Boolean
@Intrinsic external fun areEqualByValue(first: Float, second: Float): Boolean
@Intrinsic external fun areEqualByValue(first: Double, second: Double): Boolean
// For comparing with `null`:
@Intrinsic external fun areEqualByValue(first: Nothing?, second: Any?): Boolean
@Intrinsic external fun areEqualByValue(first: Any?, second: Nothing?): Boolean
inline fun areEqual(first: Any?, second: Any?): Boolean {
return if (first == null) second == null else first.equals(second)
}
@@ -1,17 +1,17 @@
package konan.internal
@ExportForCppRuntime
internal fun ThrowNullPointerException() {
internal fun ThrowNullPointerException(): Nothing {
throw NullPointerException()
}
@ExportForCppRuntime
internal fun ThrowArrayIndexOutOfBoundsException() {
internal fun ThrowArrayIndexOutOfBoundsException(): Nothing {
throw IndexOutOfBoundsException()
}
@ExportForCppRuntime
internal fun ThrowClassCastException() {
internal fun ThrowClassCastException(): Nothing {
throw ClassCastException()
}