JVM_IR KT-47984 inplace arguments inlining for @InlineOnly functions

This commit is contained in:
Dmitry Petrov
2021-08-05 15:43:03 +03:00
committed by TeamCityServer
parent ec90649854
commit b01c13a4df
27 changed files with 1013 additions and 15 deletions

View File

@@ -0,0 +1,258 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.InsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
class InplaceArgumentsMethodTransformer : MethodTransformer() {
override fun transform(internalClassName: String, methodNode: MethodNode) {
val methodContext = parseMethodOrNull(methodNode)
if (methodContext != null) {
if (methodContext.calls.isEmpty()) return
collectStartToEnd(methodContext)
transformMethod(methodContext)
val stackSizeAfter = StackSizeCalculator(internalClassName, methodNode).calculateStackSize()
methodNode.maxStack = stackSizeAfter
}
stripMarkers(methodNode)
}
private class MethodContext(
val methodNode: MethodNode,
val calls: List<CallContext>
) {
val startArgToEndArg = HashMap<AbstractInsnNode, AbstractInsnNode>()
}
private class CallContext(
val callStartMarker: AbstractInsnNode,
val callEndMarker: AbstractInsnNode,
val args: List<ArgContext>,
val calls: List<CallContext>
)
private class ArgContext(
val argStartMarker: AbstractInsnNode,
val argEndMarker: AbstractInsnNode,
val calls: List<CallContext>,
val storeInsn: VarInsnNode
) {
val loadOpcode = storeInsn.opcode - Opcodes.ISTORE + Opcodes.ILOAD
val varIndex = storeInsn.`var`
}
private fun parseMethodOrNull(methodNode: MethodNode): MethodContext? {
// We assume that the method body structure follows this grammar:
// METHOD ::= insn* (CALL insn*)*
// CALL ::= callStartMarker insn* (ARG insn*)* (CALL insn*)* callEndMarker
// ARG ::= argStartMarker insn* (CALL insn*)* argEndMarker storeInsn
val iter = methodNode.instructions.iterator()
val calls = ArrayList<CallContext>()
try {
while (iter.hasNext()) {
val insn = iter.next()
when {
insn.isInplaceCallStartMarker() ->
calls.add(parseCall(insn, iter))
insn.isInplaceCallEndMarker() || insn.isInplaceArgumentStartMarker() || insn.isInplaceArgumentEndMarker() ->
throw ParseErrorException()
}
}
} catch (e: ParseErrorException) {
return null
}
return MethodContext(methodNode, calls)
}
private fun parseCall(start: AbstractInsnNode, iter: ListIterator<AbstractInsnNode>): CallContext {
// CALL ::= callStartMarker insn* (ARG insn*)* (CALL insn*)* callEndMarker
val args = ArrayList<ArgContext>()
val calls = ArrayList<CallContext>()
while (iter.hasNext()) {
val insn = iter.next()
when {
insn.isInplaceCallStartMarker() ->
calls.add(parseCall(insn, iter))
insn.isInplaceCallEndMarker() ->
return CallContext(start, insn, args, calls)
insn.isInplaceArgumentStartMarker() ->
args.add(parseArg(insn, iter))
insn.isInplaceArgumentEndMarker() ->
throw ParseErrorException()
}
}
// Reached instruction list end, didn't find inplace-call-end marker
throw ParseErrorException()
}
private fun parseArg(start: AbstractInsnNode, iter: ListIterator<AbstractInsnNode>): ArgContext {
// ARG ::= argStartMarker insn* (CALL insn*)* argEndMarker storeInsn
val calls = ArrayList<CallContext>()
while (iter.hasNext()) {
val insn = iter.next()
when {
insn.isInplaceCallStartMarker() ->
calls.add(parseCall(insn, iter))
insn.isInplaceArgumentEndMarker() -> {
val next = insn.next
if (next is VarInsnNode && next.opcode in Opcodes.ISTORE..Opcodes.ASTORE) {
iter.next()
return ArgContext(start, insn, calls, next)
} else {
throw ParseErrorException()
}
}
insn.isInplaceCallEndMarker() || insn.isInplaceArgumentStartMarker() ->
throw ParseErrorException()
}
}
// Reached instruction list end, didn't find inplace-argument-end marker
throw ParseErrorException()
}
private class ParseErrorException : RuntimeException() {
override fun fillInStackTrace(): Throwable = this
}
private fun collectStartToEnd(methodContext: MethodContext) {
for (call in methodContext.calls) {
collectStartToEnd(methodContext, call)
}
}
private fun collectStartToEnd(methodContext: MethodContext, callContext: CallContext) {
for (arg in callContext.args) {
collectStartToEnd(methodContext, arg)
}
for (call in callContext.calls) {
collectStartToEnd(methodContext, call)
}
}
private fun collectStartToEnd(methodContext: MethodContext, argContext: ArgContext) {
methodContext.startArgToEndArg[argContext.argStartMarker] = argContext.argEndMarker
for (call in argContext.calls) {
collectStartToEnd(methodContext, call)
}
}
private fun transformMethod(methodContext: MethodContext) {
for (call in methodContext.calls) {
transformCall(methodContext, call)
}
}
private fun transformCall(methodContext: MethodContext, callContext: CallContext) {
for (arg in callContext.args) {
transformArg(methodContext, arg)
}
for (call in callContext.calls) {
transformCall(methodContext, call)
}
val insnList = methodContext.methodNode.instructions
val args = callContext.args.associateBy { it.varIndex }
var argsProcessed = 0
var insn: AbstractInsnNode = callContext.callStartMarker
while (insn != callContext.callEndMarker) {
when {
insn.isInplaceArgumentStartMarker() -> {
// Skip argument body
insn = methodContext.startArgToEndArg[insn]!!
}
insn.opcode in Opcodes.ILOAD..Opcodes.ALOAD -> {
// Load instruction
val loadInsn = insn as VarInsnNode
val varIndex = loadInsn.`var`
val arg = args[varIndex]
if (arg == null || arg.loadOpcode != insn.opcode) {
// Not an argument load
insn = insn.next
} else {
// Replace argument load with argument body
var argInsn = arg.argStartMarker.next
while (argInsn != arg.argEndMarker) {
val argInsnNext = argInsn.next
insnList.remove(argInsn)
insnList.insertBefore(loadInsn, argInsn)
argInsn = argInsnNext
}
// Remove argument load and corresponding argument store instructions
insnList.remove(arg.storeInsn)
insn = loadInsn.next
insnList.remove(loadInsn)
// Replace subsequent argument loads with DUP instructions of appropriate size
while (insn.opcode == loadInsn.opcode && (insn as VarInsnNode).`var` == varIndex) {
if (insn.opcode == Opcodes.LLOAD || insn.opcode == Opcodes.DLOAD) {
insnList.insertBefore(insn, InsnNode(Opcodes.DUP2))
} else {
insnList.insertBefore(insn, InsnNode(Opcodes.DUP))
}
val next = insn.next
insnList.remove(insn)
insn = next
}
// Remove argument markers
insnList.remove(arg.argStartMarker)
insnList.remove(arg.argEndMarker)
// If there are no more inplace arguments left to process, we are done
++argsProcessed
if (argsProcessed >= callContext.args.size)
break
}
}
else ->
insn = insn.next
}
}
// Remove call start and call end markers
insnList.remove(callContext.callStartMarker)
insnList.remove(callContext.callEndMarker)
}
private fun transformArg(methodContext: MethodContext, argContext: ArgContext) {
// Transform nested calls inside argument
for (call in argContext.calls) {
transformCall(methodContext, call)
}
}
private fun stripMarkers(methodNode: MethodNode) {
var insn = methodNode.instructions.first
while (insn != null) {
if (insn.isInplaceCallStartMarker() ||
insn.isInplaceCallEndMarker() ||
insn.isInplaceArgumentStartMarker() ||
insn.isInplaceArgumentEndMarker()
) {
val next = insn.next
methodNode.instructions.remove(insn)
insn = next
continue
}
insn = insn.next
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen.inline
import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.codegen.optimization.fixStack.FastStackAnalyzer
import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackInterpreter
import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackValue
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
internal class StackSizeCalculator(owner: String, method: MethodNode) :
FastStackAnalyzer<FixStackValue>(owner, method, FixStackInterpreter()) {
fun calculateStackSize(): Int {
val frames = analyze()
return frames.maxOf { frame ->
if (frame is ExpandableStackFrame) frame.getActualStackSize() else method.maxStack
}
}
override fun newFrame(nLocals: Int, nStack: Int): Frame<FixStackValue> =
ExpandableStackFrame(nLocals, nStack)
class ExpandableStackFrame(nLocals: Int, nStack: Int) : Frame<FixStackValue>(nLocals, nStack) {
private val extraStack = Stack<FixStackValue>()
override fun init(src: Frame<out FixStackValue>): Frame<FixStackValue> {
extraStack.clear()
extraStack.addAll((src as ExpandableStackFrame).extraStack)
return super.init(src)
}
override fun clearStack() {
extraStack.clear()
super.clearStack()
}
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<FixStackValue>) {
if (insn.opcode == Opcodes.RETURN) return
super.execute(insn, interpreter)
}
override fun push(value: FixStackValue) {
if (super.getStackSize() < maxStackSize) {
super.push(value)
} else {
extraStack.add(value)
}
}
override fun pop(): FixStackValue =
if (extraStack.isNotEmpty()) {
extraStack.pop()
} else {
super.pop()
}
override fun setStack(i: Int, value: FixStackValue) {
if (i < maxStackSize) {
super.setStack(i, value)
} else {
extraStack[i - maxStackSize] = value
}
}
override fun merge(frame: Frame<out FixStackValue>, interpreter: Interpreter<FixStackValue>): Boolean {
throw UnsupportedOperationException("Stack normalization should not merge frames")
}
fun getActualStackSize() = super.getStackSize() + extraStack.sumOf { it.size }
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isParameterCheckedForNull
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.IincInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
fun canInlineArgumentsInPlace(methodNode: MethodNode): Boolean {
// Usual inline functions are inlined in the following way:
// <evaluate argument #1>
// <store argument to an argument variable V1>
// ...
// <evaluate argument #N>
// <store argument to an argument variable VN>
// <inline function method body with parameter variables Pi remapped to argument variables Vi>
// If an argument #k is already stored in a local variable W, this variable W is reused.
// When inlining arguments in-place, we instead replace corresponding variable load instructions in the inline function method body
// with bytecode for evaluating a given argument.
// We can do so if such transformation keeps the evaluation order intact, possibly disregarding class initialization.
//
// This is true for many simple @InlineOnly functions from Kotlin standard library.
// For example, bytecode for 'inline fun println(message: Any?)' is:
// GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
// ALOAD 0
// INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
// Basic inlining for 'println("Hello, world!")' would produce (skipping labels and line numbers):
// // evaluate arguments, storing them to local variables
// LDC "Hello, world!"
// ASTORE 0
// GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
// ALOAD 0
// INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
// With argument "Hello, world!" inlined in-place it would be:
// GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
// LDC "Hello, world!"
// INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
// Such inlining is possible because we consider it OK to reorder GETSTATIC instruction with any argument evaluation instructions
// ('LDC "Hello, world!"' in this case).
val tcbStartLabels = methodNode.tryCatchBlocks.mapTo(HashSet()) { it.start }
val methodParameterTypes = Type.getArgumentTypes(methodNode.desc)
val jvmArgumentTypes = ArrayList<Type>(methodParameterTypes.size + 1)
if (methodNode.access and Opcodes.ACC_STATIC == 0) {
// Here we don't care much about the exact 'this' type,
// it's only important to remember that variable slot #0 holds an object reference.
jvmArgumentTypes.add(AsmTypes.OBJECT_TYPE)
}
jvmArgumentTypes.addAll(methodParameterTypes)
val argumentVarEnd = jvmArgumentTypes.sumOf { it.size }
var expectedArgumentVar = 0
var lastArgIndex = 0
var insn = methodNode.instructions.first
// During arguments evaluation, make sure that all arguments are loaded in expected order
// and there are no unexpected side effects in-between.
while (insn != null && expectedArgumentVar < argumentVarEnd) {
// Entering a try-catch block before all arguments are loaded breaks evaluation order.
if (insn in tcbStartLabels)
return false
// Some instructions break evaluation order.
if (insn.isProhibitedDuringArgumentsEvaluation())
return false
// Writing to or incrementing an argument variable forbids in-place argument inlining.
if (insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE && (insn as VarInsnNode).`var` < argumentVarEnd)
return false
if (insn.opcode == Opcodes.IINC && (insn as IincInsnNode).`var` < argumentVarEnd)
return false
// Analyze variable loads.
if (insn.opcode in Opcodes.ILOAD..Opcodes.ALOAD) {
// Skip parameter null check: 'aload x; ldc "..."; invokestatic <check>'
if (insn.opcode == Opcodes.ALOAD && insn.isParameterCheckedForNull()) {
// Go directly to the instruction after 'invokestatic <check>'
insn = insn.next.next.next
continue
}
val varInsn = insn as VarInsnNode
val varIndex = (varInsn).`var`
if (varIndex == expectedArgumentVar) {
// Expected argument variable loaded.
expectedArgumentVar += jvmArgumentTypes[lastArgIndex].size
++lastArgIndex
// Skip a sequence of load instructions referring to the same argument variable
// (such sequence is present in functions like 'Array.copyOf' and can be replaced with DUP instructions).
do {
insn = insn.next
} while (insn != null && insn.opcode == varInsn.opcode && (insn as VarInsnNode).`var` == varIndex)
continue
} else if (varIndex < argumentVarEnd) {
// Loaded an argument variable, but not an expected one => broken evaluation order
return false
} else {
// It's OK to load any non-argument variable during argument evaluation.
insn = insn.next
continue
}
}
// Anything else is fine.
insn = insn.next
}
// Method body is over, but not all arguments were loaded on stack.
if (expectedArgumentVar < argumentVarEnd)
return false
// After arguments evaluation make sure that argument variables are no longer accessed
// (we are not going to store anything to those variables anyway).
while (insn != null) {
if (insn.opcode in Opcodes.ILOAD..Opcodes.ALOAD || insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE) {
if ((insn as VarInsnNode).`var` < argumentVarEnd)
return false
} else if (insn.opcode == Opcodes.IINC) {
if ((insn as IincInsnNode).`var` < argumentVarEnd)
return false
}
insn = insn.next
}
// Didn't encounter anything suspicious.
return true
}
private fun AbstractInsnNode.isProhibitedDuringArgumentsEvaluation() =
opcode in opcodeProhibitedDuringArgumentsEvaluation.indices &&
opcodeProhibitedDuringArgumentsEvaluation[opcode]
private val opcodeProhibitedDuringArgumentsEvaluation = BooleanArray(256).also { a ->
// Any kind of jump during arguments evaluation is a hazard.
// This includes all conditional jump instructions, switch instructions, return and throw instructions.
// Very conservative, but enough for practical cases.
for (i in Opcodes.IFEQ..Opcodes.RETURN) a[i] = true
a[Opcodes.IFNULL] = true
a[Opcodes.IFNONNULL] = true
a[Opcodes.ATHROW] = true
// Instruction with non-trivial side effects is a hazard.
// NB here we don't care about possible class initialization caused by GETSTATIC.
a[Opcodes.PUTSTATIC] = true
a[Opcodes.PUTFIELD] = true
a[Opcodes.INVOKEVIRTUAL] = true
a[Opcodes.INVOKESPECIAL] = true
a[Opcodes.INVOKESTATIC] = true
a[Opcodes.INVOKEINTERFACE] = true
a[Opcodes.INVOKEDYNAMIC] = true
a[Opcodes.MONITORENTER] = true
a[Opcodes.MONITOREXIT] = true
// Integer division instructions can throw exception
a[Opcodes.IDIV] = true
a[Opcodes.LDIV] = true
a[Opcodes.IREM] = true
a[Opcodes.LREM] = true
// CHECKCAST can throw exception
a[Opcodes.CHECKCAST] = true
// Array creation can throw exception (in case of negative array size)
a[Opcodes.NEWARRAY] = true
a[Opcodes.ANEWARRAY] = true
a[Opcodes.MULTIANEWARRAY] = true
// Array access instructions can throw exception
for (i in Opcodes.IALOAD..Opcodes.SALOAD) a[i] = true
for (i in Opcodes.IASTORE..Opcodes.SASTORE) a[i] = true
}
private const val MARKER_INPLACE_CALL_START = "<INPLACE-CALL-START>"
private const val MARKER_INPLACE_ARGUMENT_START = "<INPLACE-ARGUMENT-START>"
private const val MARKER_INPLACE_ARGUMENT_END = "<INPLACE-ARGUMENT-END>"
private const val MARKER_INPLACE_CALL_END = "<INPLACE-CALL-END>"
private fun InstructionAdapter.addMarker(name: String) {
visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME, name, "()V", false)
}
fun InstructionAdapter.addInplaceCallStartMarker() = addMarker(MARKER_INPLACE_CALL_START)
fun InstructionAdapter.addInplaceCallEndMarker() = addMarker(MARKER_INPLACE_CALL_END)
fun InstructionAdapter.addInplaceArgumentStartMarker() = addMarker(MARKER_INPLACE_ARGUMENT_START)
fun InstructionAdapter.addInplaceArgumentEndMarker() = addMarker(MARKER_INPLACE_ARGUMENT_END)
internal fun AbstractInsnNode.isInplaceCallStartMarker() = isInlineMarker(this, MARKER_INPLACE_CALL_START)
internal fun AbstractInsnNode.isInplaceCallEndMarker() = isInlineMarker(this, MARKER_INPLACE_CALL_END)
internal fun AbstractInsnNode.isInplaceArgumentStartMarker() = isInlineMarker(this, MARKER_INPLACE_ARGUMENT_START)
internal fun AbstractInsnNode.isInplaceArgumentEndMarker() = isInlineMarker(this, MARKER_INPLACE_ARGUMENT_END)

View File

@@ -60,7 +60,7 @@ const val INLINE_FUN_VAR_SUFFIX = "\$iv"
internal const val FIRST_FUN_LABEL = "$$$$\$ROOT$$$$$"
internal const val SPECIAL_TRANSFORMATION_NAME = "\$special"
const val INLINE_TRANSFORMATION_SUFFIX = "\$inlined"
internal const val INLINE_CALL_TRANSFORMATION_SUFFIX = "$" + INLINE_TRANSFORMATION_SUFFIX
internal const val INLINE_CALL_TRANSFORMATION_SUFFIX = "$$INLINE_TRANSFORMATION_SUFFIX"
internal const val INLINE_FUN_THIS_0_SUFFIX = "\$inline_fun"
internal const val DEFAULT_LAMBDA_FAKE_CALL = "$$\$DEFAULT_LAMBDA_FAKE_CALL$$$"
internal const val CAPTURED_FIELD_FOLD_PREFIX = "$$$"
@@ -68,11 +68,10 @@ internal const val CAPTURED_FIELD_FOLD_PREFIX = "$$$"
private const val NON_LOCAL_RETURN = "$$$$\$NON_LOCAL_RETURN$$$$$"
const val CAPTURED_FIELD_PREFIX = "$"
private const val NON_CAPTURED_FIELD_PREFIX = "$$"
private const val INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"
internal const val INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"
private const val INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall"
private const val INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall"
private const val INLINE_MARKER_FINALLY_START = "finallyStart"
private const val INLINE_MARKER_FINALLY_END = "finallyEnd"
private const val INLINE_MARKER_BEFORE_SUSPEND_ID = 0
private const val INLINE_MARKER_AFTER_SUSPEND_ID = 1
@@ -535,17 +534,15 @@ internal fun isInlineMarker(insn: AbstractInsnNode): Boolean {
return isInlineMarker(insn, null)
}
private fun isInlineMarker(insn: AbstractInsnNode, name: String?): Boolean {
if (insn !is MethodInsnNode) {
return false
}
internal fun isInlineMarker(insn: AbstractInsnNode, name: String?): Boolean {
if (insn.opcode != Opcodes.INVOKESTATIC) return false
return insn.getOpcode() == Opcodes.INVOKESTATIC &&
insn.owner == INLINE_MARKER_CLASS_NAME &&
val methodInsn = insn as MethodInsnNode
return methodInsn.owner == INLINE_MARKER_CLASS_NAME &&
if (name != null)
insn.name == name
methodInsn.name == name
else
insn.name == INLINE_MARKER_BEFORE_METHOD_NAME || insn.name == INLINE_MARKER_AFTER_METHOD_NAME
methodInsn.name == INLINE_MARKER_BEFORE_METHOD_NAME || methodInsn.name == INLINE_MARKER_AFTER_METHOD_NAME
}
internal fun isBeforeInlineMarker(insn: AbstractInsnNode): Boolean {

View File

@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.codegen.optimization
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor
import org.jetbrains.kotlin.codegen.inline.InplaceArgumentsMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.boxing.PopBackwardPropagationTransformer
import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.boxing.StackPeepholeOptimizationsTransformer
@@ -40,6 +41,7 @@ class OptimizationMethodVisitor(
UninitializedStoresMethodTransformer(generationState.constructorCallNormalizationMode)
val normalizationMethodTransformer = CompositeMethodTransformer(
InplaceArgumentsMethodTransformer(),
FixStackWithLabelNormalizationMethodTransformer(),
MethodVerifier("AFTER mandatory stack transformations", generationState)
)

View File

@@ -441,7 +441,7 @@ fun MethodNode.usesLocalExceptParameterNullCheck(index: Int): Boolean =
it is VarInsnNode && it.opcode == Opcodes.ALOAD && it.`var` == index && !it.isParameterCheckedForNull()
}
internal fun AbstractInsnNode.isParameterCheckedForNull(): Boolean =
fun AbstractInsnNode.isParameterCheckedForNull(): Boolean =
next?.takeIf { it.opcode == Opcodes.LDC }?.next?.isCheckParameterIsNotNull() == true
internal fun AbstractInsnNode.isCheckParameterIsNotNull() =

View File

@@ -18219,6 +18219,40 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
public class InlineArgsInPlace {
@Test
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("breakInArgumentExpression.kt")
public void testBreakInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt");
}
@Test
@TestMetadata("continueInArgumentExpression.kt")
public void testContinueInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt");
}
@Test
@TestMetadata("mapSet.kt")
public void testMapSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt");
}
@Test
@TestMetadata("mutableCollectionPlusAssign.kt")
public void testMutableCollectionPlusAssign() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/inlineClasses")
@TestDataPath("$PROJECT_ROOT")

View File

@@ -3395,6 +3395,28 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
}
}
@Nested
@TestMetadata("compiler/testData/codegen/bytecodeText/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
public class InlineArgsInPlace {
@Test
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeText/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("println.kt")
public void testPrintln() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt");
}
@Test
@TestMetadata("sin.kt")
public void testSin() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/bytecodeText/inlineClasses")
@TestDataPath("$PROJECT_ROOT")

View File

@@ -467,6 +467,8 @@ class ExpressionCodegen(
addInlineMarker(mv, isStartNotEnd = true)
}
callGenerator.beforeCallStart()
expression.dispatchReceiver?.let { receiver ->
val type = if (expression.superQualifierSymbol != null) receiver.asmType else callable.owner
callGenerator.genValueAndPut(callee.dispatchReceiverParameter!!, receiver, type, this, data)
@@ -506,6 +508,8 @@ class ExpressionCodegen(
addInlineMarker(mv, isStartNotEnd = false)
}
callGenerator.afterCallEnd()
return when {
(expression.type.isNothing() || expression.type.isUnit()) && irFunction.shouldContainSuspendMarkers() -> {
// NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail.

View File

@@ -33,9 +33,11 @@ interface IrCallGenerator {
}
}
fun beforeValueParametersStart() {
fun beforeCallStart() {}
}
fun beforeValueParametersStart() {}
fun afterCallEnd() {}
fun genValueAndPut(
irValueParameter: IrValueParameter,

View File

@@ -39,6 +39,32 @@ class IrInlineCodegen(
InlineCodegen<ExpressionCodegen>(codegen, state, signature, typeParameterMappings, sourceCompiler, reifiedTypeInliner),
IrInlineCallGenerator {
private val inlineArgumentsInPlace = run {
if (function.isInlineOnly() &&
!function.isSuspend &&
function.valueParameters.isNotEmpty() &&
function.valueParameters.none { it.type.isFunction() || it.type.isSuspendFunction() }
) {
val methodNode = sourceCompiler.compileInlineFunction(signature).node
if (canInlineArgumentsInPlace(methodNode)) {
return@run true
}
}
false
}
override fun beforeCallStart() {
if (inlineArgumentsInPlace) {
codegen.visitor.addInplaceCallStartMarker()
}
}
override fun afterCallEnd() {
if (inlineArgumentsInPlace) {
codegen.visitor.addInplaceCallEndMarker()
}
}
override fun generateAssertField() {
// May be inlining code into `<clinit>`, in which case it's too late to modify the IR and
// `generateAssertFieldIfNeeded` will return a statement for which we need to emit bytecode.
@@ -90,13 +116,20 @@ class IrInlineCodegen(
ValueKind.DEFAULT_PARAMETER, ValueKind.DEFAULT_INLINE_PARAMETER ->
StackValue.createDefaultValue(parameterType)
else -> {
if (inlineArgumentsInPlace) {
codegen.visitor.addInplaceArgumentStartMarker()
}
// Here we replicate the old backend: reusing the locals for everything except extension receivers.
// TODO when stopping at a breakpoint placed in an inline function, arguments which reuse an existing
// local will not be visible in the debugger, so this needs to be reconsidered.
if (irValueParameter.index >= 0)
val argValue = if (irValueParameter.index >= 0)
codegen.genOrGetLocal(argumentExpression, parameterType, irValueParameter.type, blockInfo)
else
codegen.gen(argumentExpression, parameterType, irValueParameter.type, blockInfo)
if (inlineArgumentsInPlace) {
codegen.visitor.addInplaceArgumentEndMarker()
}
argValue
}
}

View File

@@ -67,7 +67,19 @@ class IrSourceCompilerForInline(
return FunctionCodegen(lambdaInfo.function, codegen.classCodegen).generate(codegen, reifiedTypeParameters)
}
private var cachedJvmSignature: JvmMethodSignature? = null
private var cachedCompiledInlineFunction: SMAPAndMethodNode? = null
override fun compileInlineFunction(jvmSignature: JvmMethodSignature): SMAPAndMethodNode {
if (jvmSignature == cachedJvmSignature) {
return cachedCompiledInlineFunction!!
}
cachedJvmSignature = jvmSignature
return doCompileInlineFunction(jvmSignature)
.also { cachedCompiledInlineFunction = it }
}
private fun doCompileInlineFunction(jvmSignature: JvmMethodSignature): SMAPAndMethodNode {
generateInlineIntrinsicForIr(state.languageVersionSettings, callee.toIrBasedDescriptor())?.let {
return it
}

View File

@@ -0,0 +1,18 @@
// FULL_JDK
// WITH_RUNTIME
fun test(xs: List<String>): Map<String, String> {
val result = linkedMapOf<String, String>()
for (x in xs) {
result[x] = x.zap("OK") ?: break
}
return result
}
fun String.zap(y: String): String? {
return if (this == "x") y else null
}
fun box(): String {
return test(listOf("x", "bcde", "a"))["x"]!!
}

View File

@@ -0,0 +1,19 @@
// FULL_JDK
// WITH_RUNTIME
fun test(xs: List<String>, flag: Boolean = false): Map<String, String> {
val result = linkedMapOf<String, String>()
for (x in xs) {
if (x.length > 3) continue
result[x] = x.zap("OK", flag) ?: continue
}
return result
}
fun String.zap(y: String, flag: Boolean = false): String? {
return if (flag || this == "x") y else null
}
fun box(): String {
return test(listOf("a", "bcde", "x"))["x"]!!
}

View File

@@ -0,0 +1,8 @@
// FULL_JDK
// WITH_RUNTIME
fun box(): String {
val m = HashMap<String, String>()
m["ok"] = "OK"
return m["ok"]!!
}

View File

@@ -0,0 +1,10 @@
// FULL_JDK
// WITH_RUNTIME
class C(val xs: MutableList<String>)
fun box(): String {
val c = C(ArrayList<String>())
c.xs += listOf("OK")
return c.xs[0]
}

View File

@@ -0,0 +1,8 @@
fun test() {
println("Hello, world!")
}
// JVM_IR_TEMPLATES:
// 0 ALOAD
// 0 ASTORE

View File

@@ -0,0 +1,9 @@
// WITH_RUNTIME
import kotlin.math.sin
fun test(x: Double) = sin(2 * x)
// JVM_IR_TEMPLATES:
// 0 DSTORE
// 1 DLOAD

View File

@@ -18075,6 +18075,40 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
public class InlineArgsInPlace {
@Test
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@Test
@TestMetadata("breakInArgumentExpression.kt")
public void testBreakInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt");
}
@Test
@TestMetadata("continueInArgumentExpression.kt")
public void testContinueInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt");
}
@Test
@TestMetadata("mapSet.kt")
public void testMapSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt");
}
@Test
@TestMetadata("mutableCollectionPlusAssign.kt")
public void testMutableCollectionPlusAssign() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/inlineClasses")
@TestDataPath("$PROJECT_ROOT")

View File

@@ -3251,6 +3251,28 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
}
}
@Nested
@TestMetadata("compiler/testData/codegen/bytecodeText/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
public class InlineArgsInPlace {
@Test
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeText/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@Test
@TestMetadata("println.kt")
public void testPrintln() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt");
}
@Test
@TestMetadata("sin.kt")
public void testSin() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/bytecodeText/inlineClasses")
@TestDataPath("$PROJECT_ROOT")

View File

@@ -18219,6 +18219,40 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
public class InlineArgsInPlace {
@Test
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("breakInArgumentExpression.kt")
public void testBreakInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt");
}
@Test
@TestMetadata("continueInArgumentExpression.kt")
public void testContinueInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt");
}
@Test
@TestMetadata("mapSet.kt")
public void testMapSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt");
}
@Test
@TestMetadata("mutableCollectionPlusAssign.kt")
public void testMutableCollectionPlusAssign() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/inlineClasses")
@TestDataPath("$PROJECT_ROOT")

View File

@@ -3395,6 +3395,28 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
}
}
@Nested
@TestMetadata("compiler/testData/codegen/bytecodeText/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
public class InlineArgsInPlace {
@Test
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeText/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("println.kt")
public void testPrintln() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt");
}
@Test
@TestMetadata("sin.kt")
public void testSin() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/bytecodeText/inlineClasses")
@TestDataPath("$PROJECT_ROOT")

View File

@@ -14977,6 +14977,39 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
}
}
@TestMetadata("compiler/testData/codegen/box/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class InlineArgsInPlace extends AbstractLightAnalysisModeTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@TestMetadata("breakInArgumentExpression.kt")
public void testBreakInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt");
}
@TestMetadata("continueInArgumentExpression.kt")
public void testContinueInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt");
}
@TestMetadata("mapSet.kt")
public void testMapSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt");
}
@TestMetadata("mutableCollectionPlusAssign.kt")
public void testMutableCollectionPlusAssign() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/inlineClasses")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)

View File

@@ -13066,6 +13066,39 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
}
}
@TestMetadata("compiler/testData/codegen/box/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class InlineArgsInPlace extends AbstractIrJsCodegenBoxES6Test {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath);
}
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
}
@TestMetadata("breakInArgumentExpression.kt")
public void testBreakInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt");
}
@TestMetadata("continueInArgumentExpression.kt")
public void testContinueInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt");
}
@TestMetadata("mapSet.kt")
public void testMapSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt");
}
@TestMetadata("mutableCollectionPlusAssign.kt")
public void testMutableCollectionPlusAssign() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/inlineClasses")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)

View File

@@ -12472,6 +12472,39 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
}
}
@TestMetadata("compiler/testData/codegen/box/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class InlineArgsInPlace extends AbstractIrJsCodegenBoxTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
}
@TestMetadata("breakInArgumentExpression.kt")
public void testBreakInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt");
}
@TestMetadata("continueInArgumentExpression.kt")
public void testContinueInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt");
}
@TestMetadata("mapSet.kt")
public void testMapSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt");
}
@TestMetadata("mutableCollectionPlusAssign.kt")
public void testMutableCollectionPlusAssign() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/inlineClasses")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)

View File

@@ -12537,6 +12537,39 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
}
}
@TestMetadata("compiler/testData/codegen/box/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class InlineArgsInPlace extends AbstractJsCodegenBoxTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
}
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineArgsInPlace"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true);
}
@TestMetadata("breakInArgumentExpression.kt")
public void testBreakInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt");
}
@TestMetadata("continueInArgumentExpression.kt")
public void testContinueInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt");
}
@TestMetadata("mapSet.kt")
public void testMapSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt");
}
@TestMetadata("mutableCollectionPlusAssign.kt")
public void testMutableCollectionPlusAssign() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/inlineClasses")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)

View File

@@ -6627,6 +6627,39 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
}
}
@TestMetadata("compiler/testData/codegen/box/inlineArgsInPlace")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class InlineArgsInPlace extends AbstractIrCodegenBoxWasmTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.WASM, testDataFilePath);
}
public void testAllFilesPresentInInlineArgsInPlace() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineArgsInPlace"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true);
}
@TestMetadata("breakInArgumentExpression.kt")
public void testBreakInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt");
}
@TestMetadata("continueInArgumentExpression.kt")
public void testContinueInArgumentExpression() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt");
}
@TestMetadata("mapSet.kt")
public void testMapSet() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt");
}
@TestMetadata("mutableCollectionPlusAssign.kt")
public void testMutableCollectionPlusAssign() throws Exception {
runTest("compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/inlineClasses")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)