[JVM_IR] Refactor and add bytecode text tests for compose-like code.
Tests that the default argument mask is not in the local variable table.
This commit is contained in:
committed by
Alexander Udalov
parent
83588e9f22
commit
fadedc84db
@@ -2,3 +2,7 @@ fun foo(s: String = "O") = s
|
|||||||
|
|
||||||
fun box() = foo() + foo("K")
|
fun box() = foo() + foo("K")
|
||||||
|
|
||||||
|
// For Compose special default arugment handling, we still do not want
|
||||||
|
// the default argument mask in the local variable table.
|
||||||
|
|
||||||
|
// 0 \$default
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
inline fun foo(s: String = "O") = s
|
inline fun foo(s: String = "O") = s
|
||||||
|
|
||||||
fun box() = foo() + foo("K")
|
fun box() = foo() + foo("K")
|
||||||
|
|
||||||
|
// For Compose special default arugment handling, we still do not want
|
||||||
|
// the default argument mask in the local variable table.
|
||||||
|
|
||||||
|
// 0 \$default
|
||||||
@@ -2,3 +2,8 @@ fun box(): String {
|
|||||||
fun foo(s: String = "O") = s
|
fun foo(s: String = "O") = s
|
||||||
return foo() + foo("K")
|
return foo() + foo("K")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For Compose special default arugment handling, we still do not want
|
||||||
|
// the default argument mask in the local variable table.
|
||||||
|
|
||||||
|
// 0 \$default
|
||||||
@@ -110,6 +110,8 @@ public abstract class CodegenTestCase extends KotlinBaseTest<KotlinBaseTest.Test
|
|||||||
myEnvironment = KotlinCoreEnvironment.createForTests(
|
myEnvironment = KotlinCoreEnvironment.createForTests(
|
||||||
getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
|
getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setupEnvironment(myEnvironment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
-317
@@ -5,35 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.ir
|
package org.jetbrains.kotlin.codegen.ir
|
||||||
|
|
||||||
import com.intellij.mock.MockProject
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
|
||||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
import org.jetbrains.kotlin.codegen.AbstractBlackBoxCodegenTest
|
import org.jetbrains.kotlin.codegen.AbstractBlackBoxCodegenTest
|
||||||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
|
||||||
import org.jetbrains.kotlin.ir.IrStatement
|
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
|
|
||||||
import org.jetbrains.kotlin.ir.descriptors.WrappedValueParameterDescriptor
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
|
|
||||||
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
|
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
|
||||||
import org.jetbrains.kotlin.ir.types.isPrimitiveType
|
|
||||||
import org.jetbrains.kotlin.ir.types.makeNullable
|
|
||||||
import org.jetbrains.kotlin.ir.util.hasDefaultValue
|
|
||||||
import org.jetbrains.kotlin.ir.util.isInlined
|
|
||||||
import org.jetbrains.kotlin.ir.util.statements
|
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
|
||||||
import org.jetbrains.kotlin.name.Name
|
|
||||||
import org.jetbrains.kotlin.test.TargetBackend
|
import org.jetbrains.kotlin.test.TargetBackend
|
||||||
|
|
||||||
abstract class AbstractComposeLikeIrBlackBoxCodegenTest : AbstractBlackBoxCodegenTest() {
|
abstract class AbstractComposeLikeIrBlackBoxCodegenTest : AbstractBlackBoxCodegenTest() {
|
||||||
@@ -43,294 +16,4 @@ abstract class AbstractComposeLikeIrBlackBoxCodegenTest : AbstractBlackBoxCodege
|
|||||||
ComposeLikeExtensionRegistrar.registerComponents(environment.project)
|
ComposeLikeExtensionRegistrar.registerComponents(environment.project)
|
||||||
super.setupEnvironment(environment)
|
super.setupEnvironment(environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComposeLikeExtensionRegistrar : ComponentRegistrar {
|
|
||||||
companion object {
|
|
||||||
fun registerComponents(project: Project) {
|
|
||||||
IrGenerationExtension.registerExtension(project, ComposeLikeGenerationExtension())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
|
|
||||||
registerComponents(project)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComposeLikeGenerationExtension : IrGenerationExtension {
|
|
||||||
private val rewrittenFunctions = mutableSetOf<IrFunction>()
|
|
||||||
|
|
||||||
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
|
|
||||||
moduleFragment.transformChildrenVoid(ComposeLikeDefaultArgumentRewriter(pluginContext, rewrittenFunctions))
|
|
||||||
moduleFragment.transformChildrenVoid(ComposeLikeDefaultMethodCallRewriter(pluginContext, rewrittenFunctions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComposeLikeDefaultMethodCallRewriter(private val context: IrPluginContext, private val rewrittenFunctions: Set<IrFunction>) :
|
|
||||||
IrElementTransformerVoid() {
|
|
||||||
override fun visitCall(expression: IrCall): IrExpression {
|
|
||||||
val function = expression.symbol.owner
|
|
||||||
return if (rewrittenFunctions.contains(function)) {
|
|
||||||
IrCallImpl(
|
|
||||||
expression.startOffset,
|
|
||||||
expression.endOffset,
|
|
||||||
expression.type,
|
|
||||||
expression.symbol,
|
|
||||||
function.typeParameters.size,
|
|
||||||
function.valueParameters.size,
|
|
||||||
expression.origin,
|
|
||||||
expression.superQualifierSymbol
|
|
||||||
).also {
|
|
||||||
it.dispatchReceiver = expression.dispatchReceiver?.transform(this, null)
|
|
||||||
it.extensionReceiver = expression.extensionReceiver?.transform(this, null)
|
|
||||||
var bitmap = 0
|
|
||||||
for (i in function.valueParameters.indices) {
|
|
||||||
if (i < expression.valueArgumentsCount) {
|
|
||||||
if (expression.getValueArgument(i) != null) {
|
|
||||||
it.putValueArgument(i, expression.getValueArgument(i))
|
|
||||||
} else {
|
|
||||||
bitmap = bitmap or (1.shl(i))
|
|
||||||
it.putValueArgument(
|
|
||||||
i,
|
|
||||||
IrConstImpl.defaultValueForType(
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
function.valueParameters[i].type
|
|
||||||
).let { defaultValue ->
|
|
||||||
IrCompositeImpl(
|
|
||||||
defaultValue.startOffset,
|
|
||||||
defaultValue.endOffset,
|
|
||||||
defaultValue.type,
|
|
||||||
IrStatementOrigin.DEFAULT_VALUE,
|
|
||||||
listOf(defaultValue)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
it.putValueArgument(
|
|
||||||
function.valueParameters.size - 1,
|
|
||||||
IrConstImpl.int(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.intType, bitmap)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.visitCall(expression)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComposeLikeDefaultArgumentRewriter(
|
|
||||||
private val context: IrPluginContext,
|
|
||||||
private val rewrittenFunctions: MutableSet<IrFunction>
|
|
||||||
) : IrElementTransformerVoid() {
|
|
||||||
private val parameterMapping = mutableMapOf<IrValueParameter, IrValueParameter>()
|
|
||||||
|
|
||||||
override fun visitGetValue(expression: IrGetValue): IrExpression {
|
|
||||||
parameterMapping[expression.symbol.owner]?.let {
|
|
||||||
return IrGetValueImpl(
|
|
||||||
expression.startOffset,
|
|
||||||
expression.endOffset,
|
|
||||||
expression.type,
|
|
||||||
it.symbol,
|
|
||||||
expression.origin
|
|
||||||
)
|
|
||||||
} ?: return super.visitGetValue(expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitFunction(declaration: IrFunction): IrStatement {
|
|
||||||
val hasDefaultArguments = declaration.valueParameters.any { it.defaultValue != null }
|
|
||||||
if (!hasDefaultArguments) return super.visitFunction(declaration)
|
|
||||||
rewrittenFunctions.add(declaration)
|
|
||||||
val newParameters = mutableListOf<IrValueParameter>()
|
|
||||||
declaration.valueParameters.forEach { param ->
|
|
||||||
newParameters.add(
|
|
||||||
if (param.defaultValue != null) {
|
|
||||||
val result = IrValueParameterImpl(
|
|
||||||
param.startOffset,
|
|
||||||
param.endOffset,
|
|
||||||
param.origin,
|
|
||||||
IrValueParameterSymbolImpl(WrappedValueParameterDescriptor()),
|
|
||||||
param.name,
|
|
||||||
index = param.index,
|
|
||||||
type = defaultParameterType(param),
|
|
||||||
varargElementType = param.varargElementType,
|
|
||||||
isCrossinline = param.isCrossinline,
|
|
||||||
isNoinline = param.isNoinline,
|
|
||||||
isAssignable = param.defaultValue != null
|
|
||||||
).also {
|
|
||||||
it.defaultValue = param.defaultValue
|
|
||||||
it.parent = declaration
|
|
||||||
}
|
|
||||||
parameterMapping[param] = result
|
|
||||||
result
|
|
||||||
} else param
|
|
||||||
)
|
|
||||||
}
|
|
||||||
declaration.valueParameters = newParameters
|
|
||||||
val defaultParam = declaration.addValueParameter(
|
|
||||||
"\$default",
|
|
||||||
context.irBuiltIns.intType,
|
|
||||||
IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION
|
|
||||||
)
|
|
||||||
declaration.transformChildrenVoid()
|
|
||||||
val body = declaration.body!!
|
|
||||||
val defaultSelection = mutableListOf<IrStatement>()
|
|
||||||
declaration.valueParameters.forEach {
|
|
||||||
if (it.hasDefaultValue()) {
|
|
||||||
val index = defaultSelection.size
|
|
||||||
defaultSelection.add(
|
|
||||||
irIf(
|
|
||||||
condition = irGetBit(defaultParam, index),
|
|
||||||
body = irSet(it, it.defaultValue!!.expression)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
it.defaultValue = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declaration.body = IrBlockBodyImpl(
|
|
||||||
body.startOffset,
|
|
||||||
body.endOffset,
|
|
||||||
listOf(
|
|
||||||
*defaultSelection.toTypedArray(),
|
|
||||||
*body.statements.toTypedArray()
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return declaration
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun defaultParameterType(param: IrValueParameter): IrType {
|
|
||||||
val type = param.type
|
|
||||||
return when {
|
|
||||||
type.isPrimitiveType() -> type
|
|
||||||
type.isInlined() -> type
|
|
||||||
else -> type.makeNullable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irIf(condition: IrExpression, body: IrExpression): IrExpression {
|
|
||||||
return IrIfThenElseImpl(
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
context.irBuiltIns.unitType,
|
|
||||||
origin = IrStatementOrigin.IF
|
|
||||||
).also {
|
|
||||||
it.branches.add(
|
|
||||||
IrBranchImpl(condition, body)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irSet(variable: IrValueDeclaration, value: IrExpression): IrExpression {
|
|
||||||
return IrSetValueImpl(
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
context.irBuiltIns.unitType,
|
|
||||||
variable.symbol,
|
|
||||||
value = value,
|
|
||||||
origin = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irNotEqual(lhs: IrExpression, rhs: IrExpression): IrExpression {
|
|
||||||
return irNot(irEqual(lhs, rhs))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irGetBit(param: IrValueParameter, index: Int): IrExpression {
|
|
||||||
// value and (1 shl index) != 0
|
|
||||||
return irNotEqual(
|
|
||||||
irAnd(
|
|
||||||
// a value of 1 in default means it was NOT provided
|
|
||||||
irGet(param),
|
|
||||||
irConst(0b1 shl index)
|
|
||||||
),
|
|
||||||
irConst(0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irConst(value: Int): IrConst<Int> = IrConstImpl(
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
context.irBuiltIns.intType,
|
|
||||||
IrConstKind.Int,
|
|
||||||
value
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun irConst(value: Boolean) = IrConstImpl(
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
context.irBuiltIns.booleanType,
|
|
||||||
IrConstKind.Boolean,
|
|
||||||
value
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun irGet(type: IrType, symbol: IrValueSymbol): IrExpression {
|
|
||||||
return IrGetValueImpl(
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
type,
|
|
||||||
symbol
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irGet(variable: IrValueDeclaration): IrExpression {
|
|
||||||
return irGet(variable.type, variable.symbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrType.binaryOperator(name: Name, paramType: IrType): IrFunctionSymbol =
|
|
||||||
context.symbols.getBinaryOperator(name, this, paramType)
|
|
||||||
|
|
||||||
private fun irAnd(lhs: IrExpression, rhs: IrExpression): IrCallImpl {
|
|
||||||
return irCall(
|
|
||||||
lhs.type.binaryOperator(Name.identifier("and"), rhs.type),
|
|
||||||
null,
|
|
||||||
lhs,
|
|
||||||
null,
|
|
||||||
rhs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irCall(
|
|
||||||
symbol: IrFunctionSymbol,
|
|
||||||
origin: IrStatementOrigin? = null,
|
|
||||||
dispatchReceiver: IrExpression? = null,
|
|
||||||
extensionReceiver: IrExpression? = null,
|
|
||||||
vararg args: IrExpression
|
|
||||||
): IrCallImpl {
|
|
||||||
return IrCallImpl(
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
symbol.owner.returnType,
|
|
||||||
symbol as IrSimpleFunctionSymbol,
|
|
||||||
symbol.owner.typeParameters.size,
|
|
||||||
symbol.owner.valueParameters.size,
|
|
||||||
origin
|
|
||||||
).also {
|
|
||||||
if (dispatchReceiver != null) it.dispatchReceiver = dispatchReceiver
|
|
||||||
if (extensionReceiver != null) it.extensionReceiver = extensionReceiver
|
|
||||||
args.forEachIndexed { index, arg ->
|
|
||||||
it.putValueArgument(index, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irNot(value: IrExpression): IrExpression {
|
|
||||||
return irCall(
|
|
||||||
context.irBuiltIns.booleanNotSymbol,
|
|
||||||
dispatchReceiver = value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun irEqual(lhs: IrExpression, rhs: IrExpression): IrExpression {
|
|
||||||
return irCall(
|
|
||||||
context.irBuiltIns.eqeqeqSymbol,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
lhs,
|
|
||||||
rhs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -5,9 +5,15 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.ir
|
package org.jetbrains.kotlin.codegen.ir
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
import org.jetbrains.kotlin.codegen.AbstractBytecodeTextTest
|
import org.jetbrains.kotlin.codegen.AbstractBytecodeTextTest
|
||||||
import org.jetbrains.kotlin.test.TargetBackend
|
import org.jetbrains.kotlin.test.TargetBackend
|
||||||
|
|
||||||
abstract class AbstractComposeLikeIrBytecodeTextTest : AbstractBytecodeTextTest() {
|
abstract class AbstractComposeLikeIrBytecodeTextTest : AbstractBytecodeTextTest() {
|
||||||
override val backend = TargetBackend.JVM_IR
|
override val backend = TargetBackend.JVM_IR
|
||||||
|
|
||||||
|
override fun setupEnvironment(environment: KotlinCoreEnvironment) {
|
||||||
|
ComposeLikeExtensionRegistrar.registerComponents(environment.project)
|
||||||
|
super.setupEnvironment(environment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+326
@@ -1,7 +1,333 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
* Copyright 2010-2020 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.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.ir
|
package org.jetbrains.kotlin.codegen.ir
|
||||||
|
|
||||||
|
import com.intellij.mock.MockProject
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||||
|
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||||
|
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||||
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
|
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
|
||||||
|
import org.jetbrains.kotlin.ir.descriptors.WrappedValueParameterDescriptor
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
|
||||||
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
|
import org.jetbrains.kotlin.ir.types.isPrimitiveType
|
||||||
|
import org.jetbrains.kotlin.ir.types.makeNullable
|
||||||
|
import org.jetbrains.kotlin.ir.util.hasDefaultValue
|
||||||
|
import org.jetbrains.kotlin.ir.util.isInlined
|
||||||
|
import org.jetbrains.kotlin.ir.util.statements
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
|
||||||
|
class ComposeLikeExtensionRegistrar : ComponentRegistrar {
|
||||||
|
companion object {
|
||||||
|
fun registerComponents(project: Project) {
|
||||||
|
IrGenerationExtension.registerExtension(project, ComposeLikeGenerationExtension())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
|
||||||
|
registerComponents(project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComposeLikeGenerationExtension : IrGenerationExtension {
|
||||||
|
private val rewrittenFunctions = mutableSetOf<IrFunction>()
|
||||||
|
|
||||||
|
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
|
||||||
|
moduleFragment.transformChildrenVoid(ComposeLikeDefaultArgumentRewriter(pluginContext, rewrittenFunctions))
|
||||||
|
moduleFragment.transformChildrenVoid(ComposeLikeDefaultMethodCallRewriter(pluginContext, rewrittenFunctions))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComposeLikeDefaultMethodCallRewriter(private val context: IrPluginContext, private val rewrittenFunctions: Set<IrFunction>) :
|
||||||
|
IrElementTransformerVoid() {
|
||||||
|
override fun visitCall(expression: IrCall): IrExpression {
|
||||||
|
val function = expression.symbol.owner
|
||||||
|
return if (rewrittenFunctions.contains(function)) {
|
||||||
|
IrCallImpl(
|
||||||
|
expression.startOffset,
|
||||||
|
expression.endOffset,
|
||||||
|
expression.type,
|
||||||
|
expression.symbol,
|
||||||
|
function.typeParameters.size,
|
||||||
|
function.valueParameters.size,
|
||||||
|
expression.origin,
|
||||||
|
expression.superQualifierSymbol
|
||||||
|
).also {
|
||||||
|
it.dispatchReceiver = expression.dispatchReceiver?.transform(this, null)
|
||||||
|
it.extensionReceiver = expression.extensionReceiver?.transform(this, null)
|
||||||
|
var bitmap = 0
|
||||||
|
for (i in function.valueParameters.indices) {
|
||||||
|
if (i < expression.valueArgumentsCount) {
|
||||||
|
if (expression.getValueArgument(i) != null) {
|
||||||
|
it.putValueArgument(i, expression.getValueArgument(i))
|
||||||
|
} else {
|
||||||
|
bitmap = bitmap or (1.shl(i))
|
||||||
|
it.putValueArgument(
|
||||||
|
i,
|
||||||
|
IrConstImpl.defaultValueForType(
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
function.valueParameters[i].type
|
||||||
|
).let { defaultValue ->
|
||||||
|
IrCompositeImpl(
|
||||||
|
defaultValue.startOffset,
|
||||||
|
defaultValue.endOffset,
|
||||||
|
defaultValue.type,
|
||||||
|
IrStatementOrigin.DEFAULT_VALUE,
|
||||||
|
listOf(defaultValue)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.putValueArgument(
|
||||||
|
function.valueParameters.size - 1,
|
||||||
|
IrConstImpl.int(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.intType, bitmap)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.visitCall(expression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComposeLikeDefaultArgumentRewriter(
|
||||||
|
private val context: IrPluginContext,
|
||||||
|
private val rewrittenFunctions: MutableSet<IrFunction>
|
||||||
|
) : IrElementTransformerVoid() {
|
||||||
|
private val parameterMapping = mutableMapOf<IrValueParameter, IrValueParameter>()
|
||||||
|
|
||||||
|
override fun visitGetValue(expression: IrGetValue): IrExpression {
|
||||||
|
parameterMapping[expression.symbol.owner]?.let {
|
||||||
|
return IrGetValueImpl(
|
||||||
|
expression.startOffset,
|
||||||
|
expression.endOffset,
|
||||||
|
expression.type,
|
||||||
|
it.symbol,
|
||||||
|
expression.origin
|
||||||
|
)
|
||||||
|
} ?: return super.visitGetValue(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunction(declaration: IrFunction): IrStatement {
|
||||||
|
val hasDefaultArguments = declaration.valueParameters.any { it.defaultValue != null }
|
||||||
|
if (!hasDefaultArguments) return super.visitFunction(declaration)
|
||||||
|
rewrittenFunctions.add(declaration)
|
||||||
|
val newParameters = mutableListOf<IrValueParameter>()
|
||||||
|
declaration.valueParameters.forEach { param ->
|
||||||
|
newParameters.add(
|
||||||
|
if (param.defaultValue != null) {
|
||||||
|
val descriptor = WrappedValueParameterDescriptor()
|
||||||
|
val result = IrValueParameterImpl(
|
||||||
|
param.startOffset,
|
||||||
|
param.endOffset,
|
||||||
|
param.origin,
|
||||||
|
IrValueParameterSymbolImpl(descriptor),
|
||||||
|
param.name,
|
||||||
|
index = param.index,
|
||||||
|
type = defaultParameterType(param),
|
||||||
|
varargElementType = param.varargElementType,
|
||||||
|
isCrossinline = param.isCrossinline,
|
||||||
|
isNoinline = param.isNoinline,
|
||||||
|
isHidden = false,
|
||||||
|
isAssignable = param.defaultValue != null
|
||||||
|
).also {
|
||||||
|
it.defaultValue = param.defaultValue
|
||||||
|
it.parent = declaration
|
||||||
|
}
|
||||||
|
descriptor.bind(result)
|
||||||
|
parameterMapping[param] = result
|
||||||
|
result
|
||||||
|
} else param
|
||||||
|
)
|
||||||
|
}
|
||||||
|
declaration.valueParameters = newParameters
|
||||||
|
val defaultParam = declaration.addValueParameter(
|
||||||
|
"\$default",
|
||||||
|
context.irBuiltIns.intType,
|
||||||
|
IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION
|
||||||
|
)
|
||||||
|
declaration.transformChildrenVoid()
|
||||||
|
val body = declaration.body!!
|
||||||
|
val defaultSelection = mutableListOf<IrStatement>()
|
||||||
|
declaration.valueParameters.forEach {
|
||||||
|
if (it.hasDefaultValue()) {
|
||||||
|
val index = defaultSelection.size
|
||||||
|
defaultSelection.add(
|
||||||
|
irIf(
|
||||||
|
condition = irGetBit(defaultParam, index),
|
||||||
|
body = irSet(it, it.defaultValue!!.expression)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
it.defaultValue = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declaration.body = IrBlockBodyImpl(
|
||||||
|
body.startOffset,
|
||||||
|
body.endOffset,
|
||||||
|
listOf(
|
||||||
|
*defaultSelection.toTypedArray(),
|
||||||
|
*body.statements.toTypedArray()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return declaration
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun defaultParameterType(param: IrValueParameter): IrType {
|
||||||
|
val type = param.type
|
||||||
|
return when {
|
||||||
|
type.isPrimitiveType() -> type
|
||||||
|
type.isInlined() -> type
|
||||||
|
else -> type.makeNullable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irIf(condition: IrExpression, body: IrExpression): IrExpression {
|
||||||
|
return IrIfThenElseImpl(
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
context.irBuiltIns.unitType,
|
||||||
|
origin = IrStatementOrigin.IF
|
||||||
|
).also {
|
||||||
|
it.branches.add(
|
||||||
|
IrBranchImpl(condition, body)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irSet(variable: IrValueDeclaration, value: IrExpression): IrExpression {
|
||||||
|
return IrSetValueImpl(
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
context.irBuiltIns.unitType,
|
||||||
|
variable.symbol,
|
||||||
|
value = value,
|
||||||
|
origin = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irNotEqual(lhs: IrExpression, rhs: IrExpression): IrExpression {
|
||||||
|
return irNot(irEqual(lhs, rhs))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irGetBit(param: IrValueParameter, index: Int): IrExpression {
|
||||||
|
// value and (1 shl index) != 0
|
||||||
|
return irNotEqual(
|
||||||
|
irAnd(
|
||||||
|
// a value of 1 in default means it was NOT provided
|
||||||
|
irGet(param),
|
||||||
|
irConst(0b1 shl index)
|
||||||
|
),
|
||||||
|
irConst(0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irConst(value: Int): IrConst<Int> = IrConstImpl(
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
context.irBuiltIns.intType,
|
||||||
|
IrConstKind.Int,
|
||||||
|
value
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun irGet(type: IrType, symbol: IrValueSymbol): IrExpression {
|
||||||
|
return IrGetValueImpl(
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
type,
|
||||||
|
symbol
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irGet(variable: IrValueDeclaration): IrExpression {
|
||||||
|
return irGet(variable.type, variable.symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrType.binaryOperator(name: Name, paramType: IrType): IrFunctionSymbol =
|
||||||
|
context.symbols.getBinaryOperator(name, this, paramType)
|
||||||
|
|
||||||
|
private fun irAnd(lhs: IrExpression, rhs: IrExpression): IrCallImpl {
|
||||||
|
return irCall(
|
||||||
|
lhs.type.binaryOperator(Name.identifier("and"), rhs.type),
|
||||||
|
null,
|
||||||
|
lhs,
|
||||||
|
null,
|
||||||
|
rhs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irCall(
|
||||||
|
symbol: IrFunctionSymbol,
|
||||||
|
origin: IrStatementOrigin? = null,
|
||||||
|
dispatchReceiver: IrExpression? = null,
|
||||||
|
extensionReceiver: IrExpression? = null,
|
||||||
|
vararg args: IrExpression
|
||||||
|
): IrCallImpl {
|
||||||
|
return IrCallImpl(
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
UNDEFINED_OFFSET,
|
||||||
|
symbol.owner.returnType,
|
||||||
|
symbol as IrSimpleFunctionSymbol,
|
||||||
|
symbol.owner.typeParameters.size,
|
||||||
|
symbol.owner.valueParameters.size,
|
||||||
|
origin
|
||||||
|
).also {
|
||||||
|
if (dispatchReceiver != null) it.dispatchReceiver = dispatchReceiver
|
||||||
|
if (extensionReceiver != null) it.extensionReceiver = extensionReceiver
|
||||||
|
args.forEachIndexed { index, arg ->
|
||||||
|
it.putValueArgument(index, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irNot(value: IrExpression): IrExpression {
|
||||||
|
return irCall(
|
||||||
|
context.irBuiltIns.booleanNotSymbol,
|
||||||
|
dispatchReceiver = value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irEqual(lhs: IrExpression, rhs: IrExpression): IrExpression {
|
||||||
|
return irCall(
|
||||||
|
context.irBuiltIns.eqeqeqSymbol,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
lhs,
|
||||||
|
rhs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Generated
+6
-6
@@ -17,7 +17,7 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
@TestMetadata("compiler/testData/codegen/composeLike")
|
@TestMetadata("compiler/testData/codegen/composeLikeBytecodeText")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public class ComposeLikeIrBytecodeTextTestGenerated extends AbstractComposeLikeIrBytecodeTextTest {
|
public class ComposeLikeIrBytecodeTextTestGenerated extends AbstractComposeLikeIrBytecodeTextTest {
|
||||||
@@ -25,22 +25,22 @@ public class ComposeLikeIrBytecodeTextTestGenerated extends AbstractComposeLikeI
|
|||||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAllFilesPresentInComposeLike() throws Exception {
|
public void testAllFilesPresentInComposeLikeBytecodeText() throws Exception {
|
||||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/composeLike"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/composeLikeBytecodeText"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("default.kt")
|
@TestMetadata("default.kt")
|
||||||
public void testDefault() throws Exception {
|
public void testDefault() throws Exception {
|
||||||
runTest("compiler/testData/codegen/composeLike/default.kt");
|
runTest("compiler/testData/codegen/composeLikeBytecodeText/default.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("defaultInline.kt")
|
@TestMetadata("defaultInline.kt")
|
||||||
public void testDefaultInline() throws Exception {
|
public void testDefaultInline() throws Exception {
|
||||||
runTest("compiler/testData/codegen/composeLike/defaultInline.kt");
|
runTest("compiler/testData/codegen/composeLikeBytecodeText/defaultInline.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("defaultLocal.kt")
|
@TestMetadata("defaultLocal.kt")
|
||||||
public void testDefaultLocal() throws Exception {
|
public void testDefaultLocal() throws Exception {
|
||||||
runTest("compiler/testData/codegen/composeLike/defaultLocal.kt");
|
runTest("compiler/testData/codegen/composeLikeBytecodeText/defaultLocal.kt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -464,6 +464,10 @@ fun main(args: Array<String>) {
|
|||||||
model("codegen/composeLike", targetBackend = TargetBackend.JVM_IR)
|
model("codegen/composeLike", targetBackend = TargetBackend.JVM_IR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testClass<AbstractComposeLikeIrBytecodeTextTest> {
|
||||||
|
model("codegen/composeLikeBytecodeText", targetBackend = TargetBackend.JVM_IR)
|
||||||
|
}
|
||||||
|
|
||||||
testClass<AbstractIrBlackBoxCodegenTest> {
|
testClass<AbstractIrBlackBoxCodegenTest> {
|
||||||
model("codegen/box", targetBackend = TargetBackend.JVM_IR, excludeDirs = listOf("oldLanguageVersions"))
|
model("codegen/box", targetBackend = TargetBackend.JVM_IR, excludeDirs = listOf("oldLanguageVersions"))
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -92,7 +92,10 @@ the Kotlin IntelliJ IDEA plugin:
|
|||||||
- Path: wasm/ir/src/org/jetbrains/kotlin/wasm/ir/convertors
|
- Path: wasm/ir/src/org/jetbrains/kotlin/wasm/ir/convertors
|
||||||
- License: MIT ([license/third_party/asmble_license.txt][asmble])
|
- License: MIT ([license/third_party/asmble_license.txt][asmble])
|
||||||
- Origin: Copyright (C) 2018 Chad Retz
|
- Origin: Copyright (C) 2018 Chad Retz
|
||||||
|
|
||||||
|
- Path: compiler/tests-common/tests/org/jetbrains/kotlin/codegen/ir/ComposeLikeGenerationExtension.kt
|
||||||
|
- License: Apache 2 ([license/third_party/aosp_license.txt][aosp])
|
||||||
|
- Origin: Derived from JetPack Compose compiler plugin code, Copyright 2019 The Android Open Source Project
|
||||||
|
|
||||||
## Kotlin Test Data
|
## Kotlin Test Data
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user