From b01c13a4dfef7ddbe43eed74bfefa71c95d61c01 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Thu, 5 Aug 2021 15:43:03 +0300 Subject: [PATCH] JVM_IR KT-47984 inplace arguments inlining for @InlineOnly functions --- .../InplaceArgumentsMethodTransformer.kt | 258 ++++++++++++++++++ .../codegen/inline/StackSizeCalculator.kt | 79 ++++++ .../codegen/inline/inlineArgumentsInPlace.kt | 206 ++++++++++++++ .../codegen/inline/inlineCodegenUtils.kt | 19 +- .../optimization/OptimizationMethodVisitor.kt | 2 + .../RedundantNullCheckMethodTransformer.kt | 2 +- .../FirBlackBoxCodegenTestGenerated.java | 34 +++ .../codegen/FirBytecodeTextTestGenerated.java | 22 ++ .../backend/jvm/codegen/ExpressionCodegen.kt | 4 + .../backend/jvm/codegen/IrCallGenerator.kt | 6 +- .../backend/jvm/codegen/IrInlineCodegen.kt | 35 ++- .../jvm/codegen/IrSourceCompilerForInline.kt | 12 + .../breakInArgumentExpression.kt | 18 ++ .../continueInArgumentExpression.kt | 19 ++ .../codegen/box/inlineArgsInPlace/mapSet.kt | 8 + .../mutableCollectionPlusAssign.kt | 10 + .../bytecodeText/inlineArgsInPlace/println.kt | 8 + .../bytecodeText/inlineArgsInPlace/sin.kt | 9 + .../codegen/BlackBoxCodegenTestGenerated.java | 34 +++ .../codegen/BytecodeTextTestGenerated.java | 22 ++ .../IrBlackBoxCodegenTestGenerated.java | 34 +++ .../codegen/IrBytecodeTextTestGenerated.java | 22 ++ .../LightAnalysisModeTestGenerated.java | 33 +++ .../IrJsCodegenBoxES6TestGenerated.java | 33 +++ .../IrJsCodegenBoxTestGenerated.java | 33 +++ .../semantics/JsCodegenBoxTestGenerated.java | 33 +++ .../IrCodegenBoxWasmTestGenerated.java | 33 +++ 27 files changed, 1013 insertions(+), 15 deletions(-) create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InplaceArgumentsMethodTransformer.kt create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/inline/StackSizeCalculator.kt create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineArgumentsInPlace.kt create mode 100644 compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt create mode 100644 compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt create mode 100644 compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt create mode 100644 compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt create mode 100644 compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt create mode 100644 compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InplaceArgumentsMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InplaceArgumentsMethodTransformer.kt new file mode 100644 index 00000000000..e3b7b42459e --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InplaceArgumentsMethodTransformer.kt @@ -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 + ) { + val startArgToEndArg = HashMap() + } + + private class CallContext( + val callStartMarker: AbstractInsnNode, + val callEndMarker: AbstractInsnNode, + val args: List, + val calls: List + ) + + private class ArgContext( + val argStartMarker: AbstractInsnNode, + val argEndMarker: AbstractInsnNode, + val calls: List, + 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() + 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): CallContext { + // CALL ::= callStartMarker insn* (ARG insn*)* (CALL insn*)* callEndMarker + val args = ArrayList() + val calls = ArrayList() + 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): ArgContext { + // ARG ::= argStartMarker insn* (CALL insn*)* argEndMarker storeInsn + val calls = ArrayList() + 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 + } + } + +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/StackSizeCalculator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/StackSizeCalculator.kt new file mode 100644 index 00000000000..9a938a55e84 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/StackSizeCalculator.kt @@ -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(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 = + ExpandableStackFrame(nLocals, nStack) + + class ExpandableStackFrame(nLocals: Int, nStack: Int) : Frame(nLocals, nStack) { + private val extraStack = Stack() + + override fun init(src: Frame): Frame { + 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) { + 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, interpreter: Interpreter): Boolean { + throw UnsupportedOperationException("Stack normalization should not merge frames") + } + + fun getActualStackSize() = super.getStackSize() + extraStack.sumOf { it.size } + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineArgumentsInPlace.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineArgumentsInPlace.kt new file mode 100644 index 00000000000..e8182c91a24 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineArgumentsInPlace.kt @@ -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: + // + // + // ... + // + // + // + // 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(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 ' + if (insn.opcode == Opcodes.ALOAD && insn.isParameterCheckedForNull()) { + // Go directly to the instruction after 'invokestatic ' + 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 = "" +private const val MARKER_INPLACE_ARGUMENT_START = "" +private const val MARKER_INPLACE_ARGUMENT_END = "" +private const val MARKER_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) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt index 07094cf1a1a..72bfbb2313e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt @@ -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 { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt index 4cc6898f7bf..8da95c4a1ed 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt @@ -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) ) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt index a66f3a012b3..7df1917106f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt @@ -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() = diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index d67531aaafa..52be5678e11 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -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") diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java index 46b3d4d16c0..c6e588d76ad 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java @@ -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") diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 062b86e99d2..5aa7c8886f7 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -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. diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrCallGenerator.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrCallGenerator.kt index 3749e3dcebf..f42fa89e886 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrCallGenerator.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrCallGenerator.kt @@ -33,9 +33,11 @@ interface IrCallGenerator { } } - fun beforeValueParametersStart() { + fun beforeCallStart() {} - } + fun beforeValueParametersStart() {} + + fun afterCallEnd() {} fun genValueAndPut( irValueParameter: IrValueParameter, diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt index f62733e2466..53a53ff2673 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt @@ -39,6 +39,32 @@ class IrInlineCodegen( InlineCodegen(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 ``, 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 } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt index 38079cd5224..ee41278509a 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt @@ -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 } diff --git a/compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt b/compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt new file mode 100644 index 00000000000..5ebb65705a2 --- /dev/null +++ b/compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt @@ -0,0 +1,18 @@ +// FULL_JDK +// WITH_RUNTIME + +fun test(xs: List): Map { + val result = linkedMapOf() + 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"]!! +} diff --git a/compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt b/compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt new file mode 100644 index 00000000000..31b198bc73d --- /dev/null +++ b/compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt @@ -0,0 +1,19 @@ +// FULL_JDK +// WITH_RUNTIME + +fun test(xs: List, flag: Boolean = false): Map { + val result = linkedMapOf() + 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"]!! +} diff --git a/compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt b/compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt new file mode 100644 index 00000000000..2b371889631 --- /dev/null +++ b/compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt @@ -0,0 +1,8 @@ +// FULL_JDK +// WITH_RUNTIME + +fun box(): String { + val m = HashMap() + m["ok"] = "OK" + return m["ok"]!! +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt b/compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt new file mode 100644 index 00000000000..e52062c2c04 --- /dev/null +++ b/compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt @@ -0,0 +1,10 @@ +// FULL_JDK +// WITH_RUNTIME + +class C(val xs: MutableList) + +fun box(): String { + val c = C(ArrayList()) + c.xs += listOf("OK") + return c.xs[0] +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt b/compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt new file mode 100644 index 00000000000..b91fefc51b6 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt @@ -0,0 +1,8 @@ + +fun test() { + println("Hello, world!") +} + +// JVM_IR_TEMPLATES: +// 0 ALOAD +// 0 ASTORE diff --git a/compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt b/compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt new file mode 100644 index 00000000000..398874c5158 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +import kotlin.math.sin + +fun test(x: Double) = sin(2 * x) + +// JVM_IR_TEMPLATES: +// 0 DSTORE +// 1 DLOAD diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java index 21d311c3914..4021ccb2278 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java @@ -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") diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BytecodeTextTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BytecodeTextTestGenerated.java index a5fafcaaf03..49e87fc7492 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BytecodeTextTestGenerated.java @@ -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") diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 79c1fca84c4..d342861910f 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -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") diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java index d05d5e0d6ba..e20027db1b5 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java @@ -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") diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index c5d5f53cfc0..d303301e314 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -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) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java index 04825cc2336..3a84558c51b 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java @@ -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) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index dba2bb7227e..05534670581 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -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) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index d7de67abab0..f942fc206a3 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -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) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenBoxWasmTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenBoxWasmTestGenerated.java index c088d0ec527..9f6e634e2cd 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenBoxWasmTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenBoxWasmTestGenerated.java @@ -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)