mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
JVM_IR KT-47984 inplace arguments inlining for @InlineOnly functions
This commit is contained in:
committed by
TeamCityServer
parent
ec90649854
commit
b01c13a4df
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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() =
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -33,9 +33,11 @@ interface IrCallGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
fun beforeValueParametersStart() {
|
||||
fun beforeCallStart() {}
|
||||
|
||||
}
|
||||
fun beforeValueParametersStart() {}
|
||||
|
||||
fun afterCallEnd() {}
|
||||
|
||||
fun genValueAndPut(
|
||||
irValueParameter: IrValueParameter,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
18
compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt
vendored
Normal file
18
compiler/testData/codegen/box/inlineArgsInPlace/breakInArgumentExpression.kt
vendored
Normal 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"]!!
|
||||
}
|
||||
19
compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt
vendored
Normal file
19
compiler/testData/codegen/box/inlineArgsInPlace/continueInArgumentExpression.kt
vendored
Normal 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"]!!
|
||||
}
|
||||
8
compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt
vendored
Normal file
8
compiler/testData/codegen/box/inlineArgsInPlace/mapSet.kt
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// FULL_JDK
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun box(): String {
|
||||
val m = HashMap<String, String>()
|
||||
m["ok"] = "OK"
|
||||
return m["ok"]!!
|
||||
}
|
||||
10
compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt
vendored
Normal file
10
compiler/testData/codegen/box/inlineArgsInPlace/mutableCollectionPlusAssign.kt
vendored
Normal 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]
|
||||
}
|
||||
8
compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt
vendored
Normal file
8
compiler/testData/codegen/bytecodeText/inlineArgsInPlace/println.kt
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
fun test() {
|
||||
println("Hello, world!")
|
||||
}
|
||||
|
||||
// JVM_IR_TEMPLATES:
|
||||
// 0 ALOAD
|
||||
// 0 ASTORE
|
||||
9
compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt
vendored
Normal file
9
compiler/testData/codegen/bytecodeText/inlineArgsInPlace/sin.kt
vendored
Normal 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
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user