JVM_IR. Generate additional checkcast for when/try epressions to avoid frame map problems on reification

This commit is contained in:
Mikhael Bogdanov
2021-01-26 16:12:54 +01:00
parent 16928d6e3f
commit 5b64ceceb3
4 changed files with 43 additions and 20 deletions
@@ -629,7 +629,11 @@ public abstract class StackValue {
} }
public static void coerce(@NotNull Type fromType, @NotNull Type toType, @NotNull InstructionAdapter v) { public static void coerce(@NotNull Type fromType, @NotNull Type toType, @NotNull InstructionAdapter v) {
if (toType.equals(fromType)) return; coerce(fromType, toType, v, false);
}
public static void coerce(@NotNull Type fromType, @NotNull Type toType, @NotNull InstructionAdapter v, boolean forceSelfCast) {
if (toType.equals(fromType) && !forceSelfCast) return;
if (toType.getSort() == Type.VOID) { if (toType.getSort() == Type.VOID) {
pop(v, fromType); pop(v, fromType);
@@ -504,10 +504,10 @@ class ExpressionCodegen(
} }
unboxedInlineClassIrType != null && !irFunction.isInvokeSuspendOfContinuation() -> unboxedInlineClassIrType != null && !irFunction.isInvokeSuspendOfContinuation() ->
object : PromisedValue(this, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType) { object : PromisedValue(this, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType) {
override fun materializeAt(target: Type, irTarget: IrType) { override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
mv.checkcast(unboxedInlineClassIrType.asmType) mv.checkcast(unboxedInlineClassIrType.asmType)
MaterialValue(this@ExpressionCodegen, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType) MaterialValue(this@ExpressionCodegen, unboxedInlineClassIrType.asmType, unboxedInlineClassIrType)
.materializeAt(target, irTarget) .materializeAt(target, irTarget, castForReified)
} }
override fun discard() { override fun discard() {
@@ -951,7 +951,7 @@ class ExpressionCodegen(
if (!exhaustive) { if (!exhaustive) {
result.discard() result.discard()
} else { } else {
val materializedResult = result.materializedAt(expression.type) val materializedResult = result.materializedAt(typeMapper.mapType(expression.type), expression.type, true)
if (branch.condition.isTrueConst()) { if (branch.condition.isTrueConst()) {
// The rest of the expression is dead code. // The rest of the expression is dead code.
mv.mark(endLabel) mv.mark(endLabel)
@@ -1098,7 +1098,7 @@ class ExpressionCodegen(
val isExpression = !aTry.type.isUnit() val isExpression = !aTry.type.isUnit()
var savedValue: Int? = null var savedValue: Int? = null
if (isExpression) { if (isExpression) {
tryResult.materializeAt(tryAsmType, aTry.type) tryResult.materializeAt(tryAsmType, aTry.type, true)
savedValue = frameMap.enterTemp(tryAsmType) savedValue = frameMap.enterTemp(tryAsmType)
mv.store(savedValue, tryAsmType) mv.store(savedValue, tryAsmType)
} else { } else {
@@ -1127,7 +1127,7 @@ class ExpressionCodegen(
val catchBody = clause.result val catchBody = clause.result
val catchResult = catchBody.accept(this, data) val catchResult = catchBody.accept(this, data)
if (savedValue != null) { if (savedValue != null) {
catchResult.materializeAt(tryAsmType, aTry.type) catchResult.materializeAt(tryAsmType, aTry.type, true)
mv.store(savedValue, tryAsmType) mv.store(savedValue, tryAsmType)
} else { } else {
catchResult.discard() catchResult.discard()
@@ -1170,11 +1170,11 @@ class ExpressionCodegen(
mv.mark(tryCatchBlockEnd) mv.mark(tryCatchBlockEnd)
// TODO: generate a common `finally` for try & catch blocks here? Right now this breaks the inliner. // TODO: generate a common `finally` for try & catch blocks here? Right now this breaks the inliner.
return object : PromisedValue(this, tryAsmType, aTry.type) { return object : PromisedValue(this, tryAsmType, aTry.type) {
override fun materializeAt(target: Type, irTarget: IrType) { override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
if (savedValue != null) { if (savedValue != null) {
mv.load(savedValue, tryAsmType) mv.load(savedValue, tryAsmType)
frameMap.leaveTemp(tryAsmType) frameMap.leaveTemp(tryAsmType)
super.materializeAt(target, irTarget) super.materializeAt(target, irTarget, castForReified)
} else { } else {
unitValue.materializeAt(target, irTarget) unitValue.materializeAt(target, irTarget)
} }
@@ -11,10 +11,16 @@ import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi
import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.isTypeParameter
import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.substitute
import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.types.model.SimpleTypeMarker
import org.jetbrains.kotlin.types.model.typeConstructor
import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
@@ -24,7 +30,7 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val irType: IrType) { abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val irType: IrType) {
// If this value is immaterial, construct an object on the top of the stack. This // If this value is immaterial, construct an object on the top of the stack. This
// must always be done before generating other values or emitting raw bytecode. // must always be done before generating other values or emitting raw bytecode.
open fun materializeAt(target: Type, irTarget: IrType) { open fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
val erasedSourceType = irType.eraseTypeParameters() val erasedSourceType = irType.eraseTypeParameters()
val erasedTargetType = irTarget.eraseTypeParameters() val erasedTargetType = irTarget.eraseTypeParameters()
val isFromTypeInlineClass = erasedSourceType.classOrNull!!.owner.isInline val isFromTypeInlineClass = erasedSourceType.classOrNull!!.owner.isInline
@@ -54,9 +60,10 @@ abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val
} }
} }
if (type != target) { if (type != target || (castForReified && irType.anyTypeArgument { it.isReified })) {
StackValue.coerce(type, target, mv) StackValue.coerce(type, target, mv, type == target)
} }
} }
abstract fun discard() abstract fun discard()
@@ -68,6 +75,15 @@ abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val
get() = codegen.typeMapper get() = codegen.typeMapper
} }
fun IrType.anyTypeArgument(check: (IrTypeParameter) -> Boolean): Boolean {
when {
isTypeParameter() -> if (check((classifierOrNull as IrTypeParameterSymbol).owner)) return true
this is IrSimpleType -> return arguments.any { it.typeOrNull?.anyTypeArgument(check) ?: false }
}
return false
}
// A value that *has* been fully constructed. // A value that *has* been fully constructed.
class MaterialValue(codegen: ExpressionCodegen, type: Type, irType: IrType) : PromisedValue(codegen, type, irType) { class MaterialValue(codegen: ExpressionCodegen, type: Type, irType: IrType) : PromisedValue(codegen, type, irType) {
override fun discard() { override fun discard() {
@@ -83,7 +99,7 @@ abstract class BooleanValue(codegen: ExpressionCodegen) :
abstract fun jumpIfFalse(target: Label) abstract fun jumpIfFalse(target: Label)
abstract fun jumpIfTrue(target: Label) abstract fun jumpIfTrue(target: Label)
override fun materializeAt(target: Type, irTarget: IrType) { override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
val const0 = Label() val const0 = Label()
val end = Label() val end = Label()
jumpIfFalse(const0) jumpIfFalse(const0)
@@ -101,7 +117,7 @@ abstract class BooleanValue(codegen: ExpressionCodegen) :
class BooleanConstant(codegen: ExpressionCodegen, val value: Boolean) : BooleanValue(codegen) { class BooleanConstant(codegen: ExpressionCodegen, val value: Boolean) : BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) = if (value) Unit else mv.goTo(target) override fun jumpIfFalse(target: Label) = if (value) Unit else mv.goTo(target)
override fun jumpIfTrue(target: Label) = if (value) mv.goTo(target) else Unit override fun jumpIfTrue(target: Label) = if (value) mv.goTo(target) else Unit
override fun materializeAt(target: Type, irTarget: IrType) { override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
mv.iconst(if (value) 1 else 0) mv.iconst(if (value) 1 else 0)
if (Type.BOOLEAN_TYPE != target) { if (Type.BOOLEAN_TYPE != target) {
StackValue.coerce(Type.BOOLEAN_TYPE, target, mv) StackValue.coerce(Type.BOOLEAN_TYPE, target, mv)
@@ -116,10 +132,10 @@ fun PromisedValue.coerceToBoolean(): BooleanValue =
is BooleanValue -> this is BooleanValue -> this
else -> object : BooleanValue(codegen) { else -> object : BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) = override fun jumpIfFalse(target: Label) =
this@coerceToBoolean.materializeAt(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType).also { mv.ifeq(target) } this@coerceToBoolean.materializeAt(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType, false).also { mv.ifeq(target) }
override fun jumpIfTrue(target: Label) = override fun jumpIfTrue(target: Label) =
this@coerceToBoolean.materializeAt(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType).also { mv.ifne(target) } this@coerceToBoolean.materializeAt(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType, false).also { mv.ifne(target) }
override fun discard() { override fun discard() {
this@coerceToBoolean.discard() this@coerceToBoolean.discard()
@@ -127,9 +143,12 @@ fun PromisedValue.coerceToBoolean(): BooleanValue =
} }
} }
fun PromisedValue.materializeAt(target: Type, irTarget: IrType) {
materializeAt(target, irTarget, false)
}
fun PromisedValue.materializedAt(target: Type, irTarget: IrType): MaterialValue { fun PromisedValue.materializedAt(target: Type, irTarget: IrType, castForReified: Boolean = false): MaterialValue {
materializeAt(target, irTarget) materializeAt(target, irTarget, castForReified)
return MaterialValue(codegen, target, irTarget) return MaterialValue(codegen, target, irTarget)
} }
@@ -163,7 +182,7 @@ val ExpressionCodegen.unitValue: PromisedValue
val ExpressionCodegen.nullConstant: PromisedValue val ExpressionCodegen.nullConstant: PromisedValue
get() = object : PromisedValue(this, AsmTypes.OBJECT_TYPE, context.irBuiltIns.nothingNType) { get() = object : PromisedValue(this, AsmTypes.OBJECT_TYPE, context.irBuiltIns.nothingNType) {
override fun materializeAt(target: Type, irTarget: IrType) { override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
mv.aconst(null) mv.aconst(null)
} }
@@ -29,9 +29,9 @@ object UnsafeCoerce : IntrinsicMethod() {
val arg = expression.getValueArgument(0)!! val arg = expression.getValueArgument(0)!!
val result = arg.accept(codegen, data) val result = arg.accept(codegen, data)
return object : PromisedValue(codegen, toType, to) { return object : PromisedValue(codegen, toType, to) {
override fun materializeAt(target: Type, irTarget: IrType) { override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
result.materializeAt(fromType, from) result.materializeAt(fromType, from)
super.materializeAt(target, irTarget) super.materializeAt(target, irTarget, castForReified)
} }
override fun discard() { override fun discard() {