Implement lowering of some builtin operators. (#154)
* EQEQ * THROW_NPE Add test for (un)boxing `==` operands.
This commit is contained in:
committed by
GitHub
parent
0632a30eca
commit
b6561b13a4
+4
@@ -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)
|
||||
}
|
||||
|
||||
+1
@@ -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"),
|
||||
|
||||
+6
@@ -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",
|
||||
|
||||
+43
-107
@@ -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
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
+2
-10
@@ -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)
|
||||
|
||||
+91
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user