Refine stack frames markup calculation

It fixes VerifyError with coroutines on Dalvik happening because of
variables spilling before/after suspension point

BasicInterpreter from ASM does not distinct 'int' types from other
int-like types like 'byte' or 'boolean', neither do HotSpot and JVM spec.
But it seems like Dalvik does not follow it, and spilling
boolean value into an 'int' field fails with VerifyError on Android 4,
so it's necessary to distinct int types for variables spilling

 #KT-13289 Fixed
This commit is contained in:
Denis Zharkov
2016-08-09 11:53:43 +03:00
parent d8c3cefc7c
commit 1df9724c0c
24 changed files with 1284 additions and 5 deletions
@@ -20,13 +20,12 @@ import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
import org.jetbrains.kotlin.codegen.optimization.common.analyzeLiveness
import org.jetbrains.kotlin.codegen.optimization.common.insnListOf
import org.jetbrains.kotlin.codegen.optimization.common.removeEmptyCatchBlocks
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
@@ -191,7 +190,7 @@ class CoroutineTransformerMethodVisitor(
private fun spillVariables(suspensionPoints: List<SuspensionPoint>, methodNode: MethodNode) {
val instructions = methodNode.instructions
val frames = MethodTransformer.analyze(classBuilder.thisName, methodNode, OptimizationBasicInterpreter())
val frames = performRefinedTypeAnalysis(methodNode, classBuilder.thisName)
fun AbstractInsnNode.index() = instructions.indexOf(this)
// We postpone these actions because they change instruction indices that we use when obtaining frames
@@ -202,11 +201,11 @@ class CoroutineTransformerMethodVisitor(
for (suspension in suspensionPoints) {
val suspensionCallBegin = suspension.suspensionCallBegin
val suspensionCallEnd = suspension.suspensionCallEnd
assert(frames[suspensionCallEnd.next.index()].stackSize == (if (suspension.returnType.sort == Type.VOID) 0 else 1)) {
assert(frames[suspensionCallEnd.next.index()]?.stackSize == (if (suspension.returnType.sort == Type.VOID) 0 else 1)) {
"Stack should be spilled before suspension call"
}
val frame = frames[suspensionCallBegin.index()]
val frame = frames[suspensionCallBegin.index()].sure { "Suspension points containing in dead code must be removed" }
val localsCount = frame.locals
val varsCountByType = mutableMapOf<Type, Int>()
@@ -0,0 +1,232 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.codegen.optimization.common.*
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceInterpreter
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue
import java.util.*
// BasicValue interpreter from ASM does not distinct 'int' types from other int-like types like 'byte' or 'boolean',
// neither do HotSpot and JVM spec.
// But it seems like Dalvik does not follow it, and spilling boolean value into an 'int' field fails with VerifyError on Android 4,
// so this function calculates refined frames' markup.
// Note that type of some values is only possible to determine by their usages (e.g. ICONST_1, BALOAD both may push boolean or byte on stack)
internal fun performRefinedTypeAnalysis(methodNode: MethodNode, thisName: String): Array<out Frame<out BasicValue>?> {
val insnList = methodNode.instructions
val basicFrames = MethodTransformer.analyze(thisName, methodNode, OptimizationBasicInterpreter())
val sourceValueFrames = MethodTransformer.analyze(thisName, methodNode, MySourceInterpreter())
val expectedTypeAndSourcesByInsnIndex: Array<Pair<Type, List<SourceValue>>?> = arrayOfNulls(insnList.size())
fun AbstractInsnNode.index() = insnList.indexOf(this)
fun saveExpectedType(value: SourceValue?, expectedType: Type) {
if (value == null) return
if (expectedType.sort !in REFINED_INT_SORTS) return
value.insns.forEach {
val index = insnList.indexOf(it)
checkUpdatedExpectedType(expectedTypeAndSourcesByInsnIndex[index]?.first, expectedType)
expectedTypeAndSourcesByInsnIndex[index] =
Pair(expectedType,
expectedTypeAndSourcesByInsnIndex[index]?.second.orEmpty() + value)
}
}
fun saveExpectedTypeForArrayStore(insn: AbstractInsnNode, sourceValueFrame: Frame<SourceValue>) {
val arrayStoreType =
when (insn.opcode) {
Opcodes.BASTORE -> Type.BYTE_TYPE
Opcodes.CASTORE -> Type.CHAR_TYPE
Opcodes.SASTORE -> Type.SHORT_TYPE
else -> return
}
val insnIndex = insnList.indexOf(insn)
val arrayArg = basicFrames[insnIndex].peek(2)
// may be different from 'arrayStoreType' in case of boolean arrays (BASTORE opcode is also used for them)
val expectedType =
if (arrayArg?.type?.sort == Type.ARRAY)
arrayArg.type.elementType
else
arrayStoreType
saveExpectedType(sourceValueFrame.top(), expectedType)
}
fun saveExpectedTypeForFieldOrMethod(insn: AbstractInsnNode, sourceValueFrame: Frame<SourceValue>) {
when (insn.opcode) {
Opcodes.PUTFIELD, Opcodes.PUTSTATIC ->
saveExpectedType(sourceValueFrame.top(), Type.getType((insn as FieldInsnNode).desc))
Opcodes.INVOKESTATIC, Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL -> {
val argumentTypes = Type.getArgumentTypes((insn as MethodInsnNode).desc)
argumentTypes.withIndex().forEach {
val (argIndex, type) = it
saveExpectedType(sourceValueFrame.peek(argumentTypes.size - argIndex - 1), type)
}
}
}
}
fun saveExpectedTypeForVarStore(insn: AbstractInsnNode, sourceValueFrame: Frame<SourceValue>) {
if (insn.isIntStore()) {
val varIndex = (insn as VarInsnNode).`var`
// Considering next insn is important because variable initializer is emitted just before
// the beginning of variable
val nextInsn = InsnSequence(insn.next, insnList.last).firstOrNull(AbstractInsnNode::isMeaningful)
val variableNode =
methodNode.findContainingVariableFromTable(insn, varIndex)
?: methodNode.findContainingVariableFromTable(nextInsn ?: return, varIndex)
?: return
saveExpectedType(sourceValueFrame.top(), Type.getType(variableNode.desc))
}
}
for ((insnIndex, insn) in insnList.toArray().withIndex()) {
assert(insn.opcode != Opcodes.IRETURN) {
"Coroutine body must not contain IRETURN instructions because 'doResume' is always void"
}
val sourceValueFrame = sourceValueFrames[insnIndex] ?: continue
saveExpectedTypeForArrayStore(insn, sourceValueFrame)
saveExpectedTypeForFieldOrMethod(insn, sourceValueFrame)
saveExpectedTypeForVarStore(insn, sourceValueFrame)
}
val refinedVarFrames = analyze(methodNode, object : BackwardAnalysisInterpreter<VarExpectedTypeFrame> {
override fun newFrame(maxLocals: Int): VarExpectedTypeFrame = VarExpectedTypeFrame(maxLocals)
override fun def(frame: VarExpectedTypeFrame, insn: AbstractInsnNode) {
if (insn.isIntStore()) {
frame.expectedTypeByVarIndex[(insn as VarInsnNode).`var`] = null
}
}
override fun use(frame: VarExpectedTypeFrame, insn: AbstractInsnNode) {
val (expectedType, sources) = expectedTypeAndSourcesByInsnIndex[insn.index()] ?: return
sources.flatMap(SourceValue::insns).forEach {
insn ->
if (insn.isIntLoad()) {
frame.updateExpectedType((insn as VarInsnNode).`var`, expectedType)
}
}
}
})
val refinedFrames = Array(basicFrames.size) {
insnIndex ->
val current = Frame(basicFrames[insnIndex] ?: return@Array null)
refinedVarFrames[insnIndex].expectedTypeByVarIndex.withIndex().filter { it.value != null }.forEach {
assert(current.getLocal(it.index)?.type == Type.INT_TYPE) {
"int type expected, but ${current.getLocal(it.index)?.type} was found in basic frames"
}
current.setLocal(it.index, BasicValue(it.value))
}
current
}
return refinedFrames
}
private fun AbstractInsnNode.isIntLoad() = opcode == Opcodes.ILOAD
private fun AbstractInsnNode.isIntStore() = opcode == Opcodes.ISTORE
private fun checkUpdatedExpectedType(was: Type?, new: Type) {
assert(was == null || was == new) {
"Conflicting expected types: $was/$new"
}
}
private class MySourceInterpreter : SourceInterpreter() {
override fun copyOperation(insn: AbstractInsnNode, value: SourceValue) =
when {
insn.isStoreOperation() || insn.isLoadOperation() -> SourceValue(value.size, insn)
// For DUP* instructions return the same value (effectively ignore DUP's)
else -> value
}
}
private val REFINED_INT_SORTS = setOf(Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT)
private fun MethodNode.findContainingVariableFromTable(insn: AbstractInsnNode, varIndex: Int): LocalVariableNode? {
val insnIndex = instructions.indexOf(insn)
return localVariables.firstOrNull {
it.index == varIndex && it.rangeContainsInsn(insnIndex, instructions)
}
}
private fun LocalVariableNode.rangeContainsInsn(insnIndex: Int, insnList: InsnList) =
insnList.indexOf(start) < insnIndex && insnIndex < insnList.indexOf(end)
private class VarExpectedTypeFrame(maxLocals: Int) : VarFrame<VarExpectedTypeFrame> {
val expectedTypeByVarIndex = arrayOfNulls<Type>(maxLocals)
override fun mergeFrom(other: VarExpectedTypeFrame) {
assert(expectedTypeByVarIndex.size == other.expectedTypeByVarIndex.size) {
"Other VarExpectedTypeFrame has different size: ${expectedTypeByVarIndex.size} / ${other.expectedTypeByVarIndex.size}"
}
for ((varIndex, type) in other.expectedTypeByVarIndex.withIndex()) {
updateExpectedType(varIndex, type ?: continue)
}
}
fun updateExpectedType(varIndex: Int, new: Type) {
val was = expectedTypeByVarIndex[varIndex]
// Widening to int is always allowed
if (new == Type.INT_TYPE) return
checkUpdatedExpectedType(was, new)
expectedTypeByVarIndex[varIndex] = new
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as VarExpectedTypeFrame
if (!Arrays.equals(expectedTypeByVarIndex, other.expectedTypeByVarIndex)) return false
return true
}
override fun hashCode(): Int {
return Arrays.hashCode(expectedTypeByVarIndex)
}
}
@@ -0,0 +1,29 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
fun foo() = true
private var booleanResult = false
fun setBooleanRes(x: Boolean) {
booleanResult = x
}
fun box(): String {
builder {
val x = true
val y = false
suspendHere()
setBooleanRes(if (foo()) x else y)
}
if (!booleanResult) return "fail 1"
return "OK"
}
@@ -0,0 +1,28 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var byteResult: Byte = 0
fun setByteRes(x: Byte) {
byteResult = x
}
fun foo(): Int = 1
fun box(): String {
builder {
val x: Byte = foo().toByte()
suspendHere()
setByteRes(x)
}
if (byteResult != 1.toByte()) return "fail 1"
return "OK"
}
@@ -0,0 +1,27 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var booleanResult = false
fun setBooleanRes(x: Boolean) {
booleanResult = x
}
fun box(): String {
builder {
val a = booleanArrayOf(true)
val x = a[0]
suspendHere()
setBooleanRes(x)
}
if (!booleanResult) return "fail 1"
return "OK"
}
@@ -0,0 +1,27 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var byteResult: Byte = 0
fun setByteRes(x: Byte) {
byteResult = x
}
fun box(): String {
builder {
val a = byteArrayOf(1)
val x = a[0]
suspendHere()
setByteRes(x)
}
if (byteResult != 1.toByte()) return "fail 1"
return "OK"
}
@@ -0,0 +1,27 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var booleanResult = false
fun setBooleanRes(x: Boolean, ignored: Unit) {
booleanResult = x
}
fun box(): String {
builder {
// 'true' value is spilled into variable and saved to field before suspension point
// It's important that there is no type info about this variable in local var table,
// so we should infer that ICONST_1 is a boolean value from it's usage
setBooleanRes(true, suspendHere())
}
if (!booleanResult) return "fail 1"
return "OK"
}
@@ -0,0 +1,30 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var result: String = ""
fun setRes(x: Byte, y: Int) {
result = "$x#$y"
}
fun foo(): Int = 1
fun box(): String {
builder {
val x: Byte = 1
// No actual cast happens here
val y: Int = x.toInt()
suspendHere()
setRes(x, y)
}
if (result != "1#1") return "fail 1"
return "OK"
}
@@ -0,0 +1,74 @@
// WITH_RUNTIME
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
@JvmField
var booleanResult = booleanArrayOf()
@JvmField
var charResult = charArrayOf()
@JvmField
var byteResult = byteArrayOf()
@JvmField
var shortResult = shortArrayOf()
@JvmField
var intResult = intArrayOf()
fun box(): String {
builder {
val x = true
suspendHere()
val a = BooleanArray(1)
a[0] = x
booleanResult = a
}
if (!booleanResult[0]) return "fail 1"
builder {
val x = '1'
suspendHere()
val a = CharArray(1)
a[0] = x
charResult = a
}
if (charResult[0] != '1') return "fail 2"
builder {
val x: Byte = 1
suspendHere()
val a = ByteArray(1)
a[0] = x
byteResult = a
}
if (byteResult[0] != 1.toByte()) return "fail 3"
builder {
val x: Short = 1
suspendHere()
val a = ShortArray(1)
a[0] = x
shortResult = a
}
if (shortResult[0] != 1.toShort()) return "fail 4"
builder {
val x: Int = 1
suspendHere()
val a = IntArray(1)
a[0] = x
intResult = a
}
if (intResult[0] != 1) return "fail 5"
return "OK"
}
@@ -0,0 +1,77 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var booleanResult = false
fun setBooleanRes(x: Boolean) {
booleanResult = x
}
private var charResult: Char = '0'
fun setCharRes(x: Char) {
charResult = x
}
private var byteResult: Byte = 0
fun setByteRes(x: Byte) {
byteResult = x
}
private var shortResult: Short = 0
fun setShortRes(x: Short) {
shortResult = x
}
private var intResult: Int = 0
fun setIntRes(x: Int) {
intResult = x
}
fun box(): String {
builder {
val x = true
suspendHere()
setBooleanRes(x)
}
if (!booleanResult) return "fail 1"
builder {
val x = '1'
suspendHere()
setCharRes(x)
}
if (charResult != '1') return "fail 2"
builder {
val x: Byte = 1
suspendHere()
setByteRes(x)
}
if (byteResult != 1.toByte()) return "fail 3"
builder {
val x: Short = 1
suspendHere()
setShortRes(x)
}
if (shortResult != 1.toShort()) return "fail 4"
builder {
val x: Int = 1
suspendHere()
setIntRes(x)
}
if (intResult != 1) return "fail 5"
return "OK"
}
@@ -0,0 +1,64 @@
// WITH_RUNTIME
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
@JvmField
var booleanResult = false
@JvmField
var charResult: Char = '0'
@JvmField
var byteResult: Byte = 0
@JvmField
var shortResult: Short = 0
@JvmField
var intResult: Int = 0
fun box(): String {
builder {
val x = true
suspendHere()
booleanResult = x
}
if (!booleanResult) return "fail 1"
builder {
val x = '1'
suspendHere()
charResult = x
}
if (charResult != '1') return "fail 2"
builder {
val x: Byte = 1
suspendHere()
byteResult = x
}
if (byteResult != 1.toByte()) return "fail 3"
builder {
val x: Short = 1
suspendHere()
shortResult = x
}
if (shortResult != 1.toShort()) return "fail 4"
builder {
val x: Int = 1
suspendHere()
intResult = x
}
if (intResult != 1) return "fail 5"
return "OK"
}
@@ -0,0 +1,53 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
fun box(): String {
builder {
val x = true
suspendHere()
val y: Boolean = x
if (!y) throw IllegalStateException("fail 1")
}
builder {
val x = '1'
suspendHere()
val y: Char = x
if (y != '1') throw IllegalStateException("fail 2")
}
builder {
val x: Byte = 1
suspendHere()
val y: Byte = x
if (y != 1.toByte()) throw IllegalStateException("fail 3")
}
builder {
val x: Short = 1
suspendHere()
val y: Short = x
if (y != 1.toShort()) throw IllegalStateException("fail 4")
}
builder {
val x: Int = 1
suspendHere()
val y: Int = x
if (y != 1) throw IllegalStateException("fail 5")
}
return "OK"
}
@@ -0,0 +1,32 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
fun foo() = true
private var booleanResult = false
fun setBooleanRes(x: Boolean) {
booleanResult = x
}
fun box(): String {
builder {
val x = true
val y = false
suspendHere()
setBooleanRes(if (foo()) x else y)
}
if (!booleanResult) return "fail 1"
return "OK"
}
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.Z\$1 : Z
@@ -0,0 +1,30 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var byteResult: Byte = 0
fun setByteRes(x: Byte) {
byteResult = x
}
fun foo(): Int = 1
fun box(): String {
builder {
val x: Byte = foo().toByte()
suspendHere()
setByteRes(x)
}
if (byteResult != 1.toByte()) return "fail 1"
return "OK"
}
// 1 PUTFIELD .*\.B\$0 : B
@@ -0,0 +1,29 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var booleanResult = false
fun setBooleanRes(x: Boolean) {
booleanResult = x
}
fun box(): String {
builder {
val a = booleanArrayOf(true)
val x = a[0]
suspendHere()
setBooleanRes(x)
}
if (!booleanResult) return "fail 1"
return "OK"
}
// 1 PUTFIELD .*\.Z\$0 : Z
@@ -0,0 +1,29 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var byteResult: Byte = 0
fun setByteRes(x: Byte) {
byteResult = x
}
fun box(): String {
builder {
val a = byteArrayOf(1)
val x = a[0]
suspendHere()
setByteRes(x)
}
if (byteResult != 1.toByte()) return "fail 1"
return "OK"
}
// 1 PUTFIELD .*\.B\$0 : B
@@ -0,0 +1,29 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var booleanResult = false
fun setBooleanRes(x: Boolean, ignored: Unit) {
booleanResult = x
}
fun box(): String {
builder {
// 'true' value is spilled into variable and saved to field before suspension point
// It's important that there is no type info about this variable in local var table,
// so we should infer that ICONST_1 is a boolean value from it's usage
setBooleanRes(true, suspendHere())
}
if (!booleanResult) return "fail 1"
return "OK"
}
// 1 PUTFIELD .*\.Z\$0 : Z
@@ -0,0 +1,33 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var result: String = ""
fun setRes(x: Byte, y: Int) {
result = "$x#$y"
}
fun foo(): Int = 1
fun box(): String {
builder {
val x: Byte = 1
// No actual cast happens here
val y: Int = x.toInt()
suspendHere()
setRes(x, y)
}
if (result != "1#1") return "fail 1"
return "OK"
}
// 1 PUTFIELD .*\.B\$0 : B
// 1 PUTFIELD .*\.I\$0 : I
@@ -0,0 +1,80 @@
// WITH_RUNTIME
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
@JvmField
var booleanResult = booleanArrayOf()
@JvmField
var charResult = charArrayOf()
@JvmField
var byteResult = byteArrayOf()
@JvmField
var shortResult = shortArrayOf()
@JvmField
var intResult = intArrayOf()
fun box(): String {
builder {
val x = true
suspendHere()
val a = BooleanArray(1)
a[0] = x
booleanResult = a
}
if (!booleanResult[0]) return "fail 1"
builder {
val x = '1'
suspendHere()
val a = CharArray(1)
a[0] = x
charResult = a
}
if (charResult[0] != '1') return "fail 2"
builder {
val x: Byte = 1
suspendHere()
val a = ByteArray(1)
a[0] = x
byteResult = a
}
if (byteResult[0] != 1.toByte()) return "fail 3"
builder {
val x: Short = 1
suspendHere()
val a = ShortArray(1)
a[0] = x
shortResult = a
}
if (shortResult[0] != 1.toShort()) return "fail 4"
builder {
val x: Int = 1
suspendHere()
val a = IntArray(1)
a[0] = x
intResult = a
}
if (intResult[0] != 1) return "fail 5"
return "OK"
}
// 1 PUTFIELD .*\.B\$0 : B
// 1 PUTFIELD .*\.C\$0 : C
// 1 PUTFIELD .*\.S\$0 : S
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.I\$0 : I
@@ -0,0 +1,83 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
private var booleanResult = false
fun setBooleanRes(x: Boolean) {
booleanResult = x
}
private var charResult: Char = '0'
fun setCharRes(x: Char) {
charResult = x
}
private var byteResult: Byte = 0
fun setByteRes(x: Byte) {
byteResult = x
}
private var shortResult: Short = 0
fun setShortRes(x: Short) {
shortResult = x
}
private var intResult: Int = 0
fun setIntRes(x: Int) {
intResult = x
}
fun box(): String {
builder {
val x = true
suspendHere()
setBooleanRes(x)
}
if (!booleanResult) return "fail 1"
builder {
val x = '1'
suspendHere()
setCharRes(x)
}
if (charResult != '1') return "fail 2"
builder {
val x: Byte = 1
suspendHere()
setByteRes(x)
}
if (byteResult != 1.toByte()) return "fail 3"
builder {
val x: Short = 1
suspendHere()
setShortRes(x)
}
if (shortResult != 1.toShort()) return "fail 4"
builder {
val x: Int = 1
suspendHere()
setIntRes(x)
}
if (intResult != 1) return "fail 5"
return "OK"
}
// 1 PUTFIELD .*\.B\$0 : B
// 1 PUTFIELD .*\.C\$0 : C
// 1 PUTFIELD .*\.S\$0 : S
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.I\$0 : I
@@ -0,0 +1,70 @@
// WITH_RUNTIME
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
@JvmField
var booleanResult = false
@JvmField
var charResult: Char = '0'
@JvmField
var byteResult: Byte = 0
@JvmField
var shortResult: Short = 0
@JvmField
var intResult: Int = 0
fun box(): String {
builder {
val x = true
suspendHere()
booleanResult = x
}
if (!booleanResult) return "fail 1"
builder {
val x = '1'
suspendHere()
charResult = x
}
if (charResult != '1') return "fail 2"
builder {
val x: Byte = 1
suspendHere()
byteResult = x
}
if (byteResult != 1.toByte()) return "fail 3"
builder {
val x: Short = 1
suspendHere()
shortResult = x
}
if (shortResult != 1.toShort()) return "fail 4"
builder {
val x: Int = 1
suspendHere()
intResult = x
}
if (intResult != 1) return "fail 5"
return "OK"
}
// 1 PUTFIELD .*\.B\$0 : B
// 1 PUTFIELD .*\.C\$0 : C
// 1 PUTFIELD .*\.S\$0 : S
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.I\$0 : I
@@ -0,0 +1,59 @@
class Controller {
suspend fun suspendHere(x: Continuation<Unit>) {
x.resume(Unit)
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
fun box(): String {
builder {
val x = true
suspendHere()
val y: Boolean = x
if (!y) throw IllegalStateException("fail 1")
}
builder {
val x = '1'
suspendHere()
val y: Char = x
if (y != '1') throw IllegalStateException("fail 2")
}
builder {
val x: Byte = 1
suspendHere()
val y: Byte = x
if (y != 1.toByte()) throw IllegalStateException("fail 3")
}
builder {
val x: Short = 1
suspendHere()
val y: Short = x
if (y != 1.toShort()) throw IllegalStateException("fail 4")
}
builder {
val x: Int = 1
suspendHere()
val y: Int = x
if (y != 1) throw IllegalStateException("fail 5")
}
return "OK"
}
// 1 PUTFIELD .*\.B\$0 : B
// 1 PUTFIELD .*\.C\$0 : C
// 1 PUTFIELD .*\.S\$0 : S
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.I\$0 : I
@@ -4557,6 +4557,75 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/varValueConflictsWithTableSameSort.kt");
doTest(fileName);
}
@TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class IntLikeVarSpilling extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInIntLikeVarSpilling() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/intLikeVarSpilling"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("complicatedMerge.kt")
public void testComplicatedMerge() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/complicatedMerge.kt");
doTest(fileName);
}
@TestMetadata("i2bResult.kt")
public void testI2bResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/i2bResult.kt");
doTest(fileName);
}
@TestMetadata("loadFromBooleanArray.kt")
public void testLoadFromBooleanArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/loadFromBooleanArray.kt");
doTest(fileName);
}
@TestMetadata("loadFromByteArray.kt")
public void testLoadFromByteArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/loadFromByteArray.kt");
doTest(fileName);
}
@TestMetadata("noVariableInTable.kt")
public void testNoVariableInTable() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/noVariableInTable.kt");
doTest(fileName);
}
@TestMetadata("sameIconst1ManyVars.kt")
public void testSameIconst1ManyVars() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/sameIconst1ManyVars.kt");
doTest(fileName);
}
@TestMetadata("usedInArrayStore.kt")
public void testUsedInArrayStore() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/usedInArrayStore.kt");
doTest(fileName);
}
@TestMetadata("usedInMethodCall.kt")
public void testUsedInMethodCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/usedInMethodCall.kt");
doTest(fileName);
}
@TestMetadata("usedInPutfield.kt")
public void testUsedInPutfield() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/usedInPutfield.kt");
doTest(fileName);
}
@TestMetadata("usedInVarStore.kt")
public void testUsedInVarStore() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling/usedInVarStore.kt");
doTest(fileName);
}
}
}
@TestMetadata("compiler/testData/codegen/box/dataClasses")
@@ -792,6 +792,75 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/varValueConflictsWithTableSameSort.kt");
doTest(fileName);
}
@TestMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class IntLikeVarSpilling extends AbstractBytecodeTextTest {
public void testAllFilesPresentInIntLikeVarSpilling() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("complicatedMerge.kt")
public void testComplicatedMerge() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/complicatedMerge.kt");
doTest(fileName);
}
@TestMetadata("i2bResult.kt")
public void testI2bResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/i2bResult.kt");
doTest(fileName);
}
@TestMetadata("loadFromBooleanArray.kt")
public void testLoadFromBooleanArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/loadFromBooleanArray.kt");
doTest(fileName);
}
@TestMetadata("loadFromByteArray.kt")
public void testLoadFromByteArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/loadFromByteArray.kt");
doTest(fileName);
}
@TestMetadata("noVariableInTable.kt")
public void testNoVariableInTable() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/noVariableInTable.kt");
doTest(fileName);
}
@TestMetadata("sameIconst1ManyVars.kt")
public void testSameIconst1ManyVars() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/sameIconst1ManyVars.kt");
doTest(fileName);
}
@TestMetadata("usedInArrayStore.kt")
public void testUsedInArrayStore() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/usedInArrayStore.kt");
doTest(fileName);
}
@TestMetadata("usedInMethodCall.kt")
public void testUsedInMethodCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/usedInMethodCall.kt");
doTest(fileName);
}
@TestMetadata("usedInPutfield.kt")
public void testUsedInPutfield() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/usedInPutfield.kt");
doTest(fileName);
}
@TestMetadata("usedInVarStore.kt")
public void testUsedInVarStore() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/usedInVarStore.kt");
doTest(fileName);
}
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/deadCodeElimination")