[EE-IR] Support Local Functions

This commit introduces support for calling and referencing local functions and
objects in evaluate expression on the IR backend.

The primary incision is a lowering inserted after Local Declaration Lowering,
that uses the intermediate data structures recorded by LDL to rewrite calls to
local functions to the appropriate function in the binary, instead of predicting
the compilation strategy. The required changes to the rest of the pipeline
facilitate piping the required data around.

The key to this transformation is that _captures by the local function_ must be
introduced as _captures by the fragment function_, such that the evaluator
infrastructure can find the appropriate values at run-time. This is necessary
due to the strategy of compiling local functions to static functions instead of
closures.

Additional test coverage of stepping behavior support the corresponding changes
in the Evaluator, part of the Kotlin Debugger plug-in.
This commit is contained in:
Kristoffer Andersen
2022-01-17 16:59:25 +01:00
committed by Nikita Nazarov
parent 5af9303a16
commit 4f8ef8c315
21 changed files with 575 additions and 56 deletions
@@ -22,9 +22,7 @@ import org.jetbrains.kotlin.codegen.optimization.OptimizationClassBuilderFactory
import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.config.LanguageVersion.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
@@ -423,6 +421,11 @@ class GenerationState private constructor(
private fun shouldOnlyCollectSignatures(origin: JvmDeclarationOrigin) =
classBuilderMode == ClassBuilderMode.LIGHT_CLASSES && origin.originKind in doNotGenerateInLightClassMode
val newFragmentCaptureParameters: MutableList<Triple<String, KotlinType, DeclarationDescriptor>> = mutableListOf()
fun recordNewFragmentCaptureParameter(string: String, type: KotlinType, descriptor: DeclarationDescriptor) {
newFragmentCaptureParameters.add(Triple(string, type, descriptor))
}
companion object {
val LANGUAGE_TO_METADATA_VERSION = EnumMap<LanguageVersion, JvmMetadataVersion>(LanguageVersion::class.java).apply {
val oldMetadataVersion = JvmMetadataVersion(1, 1, 18)
@@ -193,6 +193,28 @@ public class FirLocalVariableTestGenerated extends AbstractFirLocalVariableTest
runTest("compiler/testData/debug/localVariables/underscoreNames.kt");
}
@Nested
@TestMetadata("compiler/testData/debug/localVariables/constructors")
@TestDataPath("$PROJECT_ROOT")
public class Constructors {
@Test
public void testAllFilesPresentInConstructors() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/debug/localVariables/constructors"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("multipleConstructors.kt")
public void testMultipleConstructors() throws Exception {
runTest("compiler/testData/debug/localVariables/constructors/multipleConstructors.kt");
}
@Test
@TestMetadata("property.kt")
public void testProperty() throws Exception {
runTest("compiler/testData/debug/localVariables/constructors/property.kt");
}
}
@Nested
@TestMetadata("compiler/testData/debug/localVariables/receiverMangling")
@TestDataPath("$PROJECT_ROOT")
@@ -247,6 +247,18 @@ public class FirSteppingTestGenerated extends AbstractFirSteppingTest {
runTest("compiler/testData/debug/stepping/inlineSimpleCall.kt");
}
@Test
@TestMetadata("kt15259.kt")
public void testKt15259() throws Exception {
runTest("compiler/testData/debug/stepping/kt15259.kt");
}
@Test
@TestMetadata("kt29179.kt")
public void testKt29179() throws Exception {
runTest("compiler/testData/debug/stepping/kt29179.kt");
}
@Test
@TestMetadata("kt42208.kt")
public void testKt42208() throws Exception {
@@ -337,6 +349,12 @@ public class FirSteppingTestGenerated extends AbstractFirSteppingTest {
runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt");
}
@Test
@TestMetadata("nullcheck.kt")
public void testNullcheck() throws Exception {
runTest("compiler/testData/debug/stepping/nullcheck.kt");
}
@Test
@TestMetadata("primitiveNullChecks.kt")
public void testPrimitiveNullChecks() throws Exception {
@@ -11,9 +11,9 @@ import org.jetbrains.kotlin.backend.common.descriptors.synthesizedString
import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.common.lower.inline.isInlineParameter
import org.jetbrains.kotlin.backend.common.runOnFilePostfix
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.*
import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
@@ -71,7 +71,8 @@ class LocalDeclarationsLowering(
val localNameSanitizer: (String) -> String = { it },
val visibilityPolicy: VisibilityPolicy = VisibilityPolicy.DEFAULT,
val suggestUniqueNames: Boolean = true, // When `true` appends a `-#index` suffix to lifted declaration names
val forceFieldsForInlineCaptures: Boolean = false // See `LocalClassContext`
val forceFieldsForInlineCaptures: Boolean = false, // See `LocalClassContext`
private val postLocalDeclarationLoweringCallback: ((IntermediateDatastructures) -> Unit)? = null
) :
BodyLoweringPass {
@@ -109,7 +110,7 @@ class LocalDeclarationsLowering(
ScopeWithCounter(this)
}
private abstract class LocalContext {
abstract class LocalContext {
val capturedTypeParameterToTypeParameter: MutableMap<IrTypeParameter, IrTypeParameter> = mutableMapOf()
// By the time typeRemapper is used, the map will be already filled
@@ -121,7 +122,7 @@ class LocalDeclarationsLowering(
abstract fun irGet(startOffset: Int, endOffset: Int, valueDeclaration: IrValueDeclaration): IrExpression?
}
private abstract class LocalContextWithClosureAsParameters : LocalContext() {
abstract class LocalContextWithClosureAsParameters : LocalContext() {
abstract val declaration: IrFunction
abstract val transformedDeclaration: IrFunction
@@ -135,7 +136,7 @@ class LocalDeclarationsLowering(
}
}
private class LocalFunctionContext(
class LocalFunctionContext(
override val declaration: IrSimpleFunction,
val index: Int,
val ownerForLoweredDeclaration: IrDeclarationContainer
@@ -246,6 +247,10 @@ class LocalDeclarationsLowering(
rewriteDeclarations()
insertLoweredDeclarationForLocalFunctions()
postLocalDeclarationLoweringCallback?.invoke(
IntermediateDatastructures(localFunctions, newParameterToOld, newParameterToCaptured)
)
}
private fun insertLoweredDeclarationForLocalFunctions() {
@@ -967,6 +972,12 @@ class LocalDeclarationsLowering(
}, Data(null, false))
}
}
data class IntermediateDatastructures(
val localFunctions: Map<IrFunction, LocalFunctionContext>,
val newParameterToOld: Map<IrValueParameter, IrValueParameter>,
val newParameterToCaptured: Map<IrValueParameter, IrValueSymbol>
)
}
// Local inner classes capture anything through outer
@@ -56,7 +56,6 @@ open class JvmIrCodegenFactory(
private val externalMangler: JvmDescriptorMangler? = null,
private val externalSymbolTable: SymbolTable? = null,
private val jvmGeneratorExtensions: JvmGeneratorExtensionsImpl = JvmGeneratorExtensionsImpl(configuration),
private val prefixPhases: CompilerPhase<JvmBackendContext, IrModuleFragment, IrModuleFragment>? = null,
private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null,
private val shouldStubAndNotLinkUnboundSymbols: Boolean = false,
) : CodegenFactory {
@@ -258,11 +257,14 @@ open class JvmIrCodegenFactory(
)
JvmIrSerializerImpl(state.configuration)
else null
val phases = prefixPhases?.then(jvmLoweringPhases) ?: jvmLoweringPhases
val phases = if (evaluatorFragmentInfoForPsi2Ir != null) jvmFragmentLoweringPhases else jvmLoweringPhases
val phaseConfig = customPhaseConfig ?: PhaseConfig(phases)
val context = JvmBackendContext(
state, irModuleFragment.irBuiltins, irModuleFragment, symbolTable, phaseConfig, extensions, backendExtension, irSerializer,
)
if (evaluatorFragmentInfoForPsi2Ir != null) {
context.localDeclarationsLoweringData = mutableMapOf()
}
val intrinsics by lazy { IrIntrinsicMethods(irModuleFragment.irBuiltins, context.ir.symbols) }
context.getIntrinsic = { symbol: IrFunctionSymbol -> intrinsics.getIntrinsic(symbol) }
/* JvmBackendContext creates new unbound symbols, have to resolve them. */
@@ -90,7 +90,7 @@ private val arrayConstructorPhase = makeIrFilePhase(
description = "Transform `Array(size) { index -> value }` into a loop"
)
internal val expectDeclarationsRemovingPhase = makeIrModulePhase<CommonBackendContext>(
internal val expectDeclarationsRemovingPhase = makeIrModulePhase(
::ExpectDeclarationRemover,
name = "ExpectDeclarationsRemoving",
description = "Remove expect declaration from module fragment"
@@ -139,7 +139,15 @@ internal val localDeclarationsPhase = makeIrFilePhase(
private fun scopedVisibility(inInlineFunctionScope: Boolean): DescriptorVisibility =
if (inInlineFunctionScope) DescriptorVisibilities.PUBLIC else JavaDescriptorVisibilities.PACKAGE_VISIBILITY
},
forceFieldsForInlineCaptures = true
forceFieldsForInlineCaptures = true,
postLocalDeclarationLoweringCallback = context.localDeclarationsLoweringData?.let {
{ data ->
data.localFunctions.forEach { (localFunction, localContext) ->
it[localFunction] =
JvmBackendContext.LocalFunctionData(localContext, data.newParameterToOld, data.newParameterToCaptured)
}
}
}
)
},
name = "JvmLocalDeclarations",
@@ -383,23 +391,61 @@ private val jvmFilePhases = listOf(
// makePatchParentsPhase()
)
val jvmLoweringPhases = NamedCompilerPhase(
name = "IrLowering",
description = "IR lowering",
nlevels = 1,
actions = setOf(defaultDumper, validationAction),
lower = validateIrBeforeLowering then
processOptionalAnnotationsPhase then
expectDeclarationsRemovingPhase then
serializeIrPhase then
scriptsToClassesPhase then
fileClassPhase then
jvmStaticInObjectPhase then
repeatedAnnotationPhase then
performByIrFile(lower = jvmFilePhases) then
generateMultifileFacadesPhase then
resolveInlineCallsPhase then
// should be last transformation
prepareForBytecodeInlining then
validateIrAfterLowering
)
val jvmLoweringPhases = buildJvmLoweringPhases("IrLowering", listOf("PerformByIrFile" to jvmFilePhases))
private fun buildJvmLoweringPhases(
name: String,
phases: List<Pair<String, List<NamedCompilerPhase<JvmBackendContext, IrFile>>>>
): NamedCompilerPhase<JvmBackendContext, IrModuleFragment> {
return NamedCompilerPhase(
name = name,
description = "IR lowering",
nlevels = 1,
actions = setOf(defaultDumper, validationAction),
lower =
fragmentSharedVariablesLowering then
validateIrBeforeLowering then
processOptionalAnnotationsPhase then
expectDeclarationsRemovingPhase then
serializeIrPhase then
scriptsToClassesPhase then
fileClassPhase then
jvmStaticInObjectPhase then
repeatedAnnotationPhase then
buildLoweringsPhase(phases) then
generateMultifileFacadesPhase then
resolveInlineCallsPhase then
// should be last transformation
prepareForBytecodeInlining then
validateIrAfterLowering
)
}
// Build a compiler phase from a list of lowering sequences: each subsequence is run
// in parallel per file, and each parallel composition is run in sequence.
private fun buildLoweringsPhase(
perModuleLowerings: List<Pair<String, List<NamedCompilerPhase<JvmBackendContext, IrFile>>>>,
): CompilerPhase<JvmBackendContext, IrModuleFragment, IrModuleFragment> =
perModuleLowerings.map { (name, lowerings) -> performByIrFile(name, lower = lowerings) }
.reduce<
CompilerPhase<JvmBackendContext, IrModuleFragment, IrModuleFragment>,
CompilerPhase<JvmBackendContext, IrModuleFragment, IrModuleFragment>
> { result, phase -> result then phase }
val jvmFragmentLoweringPhases = run {
val localDeclarationsIndex = jvmFilePhases.indexOf(localDeclarationsPhase)
val loweringsUpToLocalDeclarations = jvmFilePhases.subList(0, localDeclarationsIndex + 1)
val remainingLowerings = jvmFilePhases.subList(localDeclarationsIndex + 1, jvmFilePhases.size)
buildJvmLoweringPhases(
"IrFragmentLowering",
listOf(
"PrefixOfIRPhases" to loweringsUpToLocalDeclarations,
"FragmentLowerings" to listOf(
fragmentLocalFunctionPatchLowering,
reflectiveAccessLowering,
),
"SuffixOfIRPhases" to remainingLowerings
)
)
}
@@ -0,0 +1,116 @@
/*
* Copyright 2010-2022 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.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.lower.LocalDeclarationsLowering
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
import org.jetbrains.kotlin.backend.jvm.localDeclarationsPhase
import org.jetbrains.kotlin.backend.jvm.lower.FragmentSharedVariablesLowering.Companion.GENERATED_FUNCTION_NAME
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.copyTypeArgumentsFrom
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
// Used from CodeFragmentCompiler for IDE Debugger Plug-In
@Suppress("unused")
val fragmentLocalFunctionPatchLowering = makeIrFilePhase(
::FragmentLocalFunctionPatchLowering,
name = "FragmentLocalFunctionPatching",
description = "Rewrite calls to local functions to the appropriate, lifted function created by local declarations lowering.",
prerequisite = setOf(localDeclarationsPhase)
)
// This lowering rewrites local function calls in code fragments to the
// corresponding lifted declaration. In the process, the lowering determines
// whether the captures of the local function are a subset of the captures of
// the fragment, and if not, introduces additional captures to the fragment
// wrapper. The captures are then supplied to the fragment wrapper as
// parameters supplied at evaluation time.
internal class FragmentLocalFunctionPatchLowering(
val context: JvmBackendContext
) : IrElementTransformerVoidWithContext(), FileLoweringPass {
lateinit var localDeclarationsData: Map<IrFunction, JvmBackendContext.LocalFunctionData>
override fun lower(irFile: IrFile) {
context.localDeclarationsLoweringData?.let {
localDeclarationsData = it
} ?: return
irFile.transformChildrenVoid(this)
}
override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement {
if (declaration.name.asString() != GENERATED_FUNCTION_NAME) return declaration
declaration.body!!.transformChildrenVoid(object : IrElementTransformerVoidWithContext() {
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this)
val localsData = localDeclarationsData[expression.symbol.owner] ?: return super.visitCall(expression)
val remappedTarget: LocalDeclarationsLowering.LocalFunctionContext = localsData.localContext
val irBuilder = context.createJvmIrBuilder(declaration.symbol)
return irBuilder.irCall(remappedTarget.transformedDeclaration).apply {
this.copyTypeArgumentsFrom(expression)
extensionReceiver = expression.extensionReceiver
dispatchReceiver = expression.dispatchReceiver
remappedTarget.transformedDeclaration.valueParameters.map { newValueParameterDeclaration ->
val oldParameter = localsData.newParameterToOld[newValueParameterDeclaration]
val getValue = if (oldParameter != null) {
// the parameter is an actual parameter to the local
// function, not a parameter corresponding to a
// capture introduced by a lowering: fetch
// the corresponding argument from the existing
// call and place at the appropriate slot in the
// call to the lowered function
expression.getValueArgument(oldParameter.index)!!
} else {
// The parameter is introduced by the lowering to
// private static function, so corresponds to a _capture_ by the local function
val capturedValueSymbol =
localsData.newParameterToCaptured[newValueParameterDeclaration]
?: error("Non-mapped parameter $newValueParameterDeclaration")
// We introduce a new parameter to the _fragment function_ surrounding the call to the
// lowered local function, and supply _that_ parameter to the corresponding _argument_ slot
// in the call to the lowered function.
val newParameter = declaration.addValueParameter {
type = capturedValueSymbol.owner.type
name = capturedValueSymbol.owner.name
}
context.state.recordNewFragmentCaptureParameter(
newParameter.name.asString(),
capturedValueSymbol.owner.type.toIrBasedKotlinType(),
capturedValueSymbol.owner.toIrBasedDescriptor()
)
irBuilder.irGet(newParameter)
}
putValueArgument(newValueParameterDeclaration.index, getValue)
}
}
}
})
return declaration
}
}
@@ -5,32 +5,35 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
import org.jetbrains.kotlin.backend.jvm.ir.findInlineCallSites
import org.jetbrains.kotlin.backend.jvm.ir.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.backend.jvm.lower.SyntheticAccessorLowering.Companion.isAccessible
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.starProjectedType
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
// Used from CodeFragmentCompiler for IDE Debugger Plug-In
@Suppress("unused")
val reflectiveAccessLowering = makeIrModulePhase(
val reflectiveAccessLowering = makeIrFilePhase(
::ReflectiveAccessLowering,
name = "ReflectiveCalls",
description = "Avoid the need for accessors by replacing direct access to inaccessible members with accesses via reflection",
@@ -80,14 +83,18 @@ internal class ReflectiveAccessLowering(
// Fragments are transformed in a post-order traversal: children first,
// then parent. This obscures, in particular, dispatch receivers, that go
// from `IrGetObjectValue` calls to blocks implementing the corresponding
// reflective access . We record these _before_ transformation, in order to
// reflective access. We record these _before_ transformation, in order to
// later predict the compilation strategy for fields. See the uses of
// `fieldLocationAndReceiver`.
val callsOnCompanionObjects: MutableMap<IrCall, IrClassSymbol> = mutableMapOf()
@OptIn(ObsoleteDescriptorBasedAPI::class)
private fun recordCompanionObjectAsDispatchReceiver(expression: IrCall) {
if ((expression.dispatchReceiver as? IrGetObjectValue)?.symbol?.owner?.isCompanion == true) {
callsOnCompanionObjects[expression] = (expression.dispatchReceiver!! as IrGetObjectValue).symbol
val dispatchReceiver = expression.dispatchReceiver as? IrGetField ?: return
val dispatchReceiverType = dispatchReceiver.symbol.owner.type as? IrSimpleType ?: return
val klass = dispatchReceiverType.classOrNull
if (klass != null && klass.owner.isCompanion) {
callsOnCompanionObjects[expression] = klass
}
}
@@ -207,6 +214,20 @@ internal class ReflectiveAccessLowering(
putValueArgument(0, receiver)
}
private fun createBuilder(startOffset: Int = UNDEFINED_OFFSET, endOffset: Int = UNDEFINED_OFFSET) =
context.createJvmIrBuilder(currentScope!!, startOffset, endOffset)
private fun IrBuilderWithScope.irVararg(
elementType: IrType,
values: List<IrExpression>
): IrExpression {
return IrArrayBuilder(createBuilder(), context.irBuiltIns.arrayClass.typeWith(elementType)).apply {
for (value in values) {
+value
}
}.build()
}
private fun IrBuilderWithScope.getDeclaredMethod(
declaringClass: IrExpression,
methodName: String,
@@ -293,13 +314,13 @@ internal class ReflectiveAccessLowering(
private fun generateReflectiveMethodInvocation(call: IrCall): IrExpression =
generateReflectiveMethodInvocation(
call.superQualifierSymbol?.defaultType ?: call.dispatchReceiver!!.type,
call.superQualifierSymbol?.defaultType ?: call.dispatchReceiver?.type ?: call.symbol.owner.parentAsClass.defaultType,
call.symbol.owner.name.asString(),
mutableListOf<IrType>().apply {
call.symbol.owner.extensionReceiverParameter?.let { add(it.type) }
addAll(call.valueParameterTypes())
},
call.dispatchReceiver!!,
call.dispatchReceiver,
mutableListOf<IrExpression>().apply {
call.extensionReceiver?.let { add(it) }
addAll(call.getValueArguments())
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.DefaultMapping
import org.jetbrains.kotlin.backend.common.Mapping
import org.jetbrains.kotlin.backend.common.ir.Ir
import org.jetbrains.kotlin.backend.common.lower.LocalDeclarationsLowering
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.jvm.caches.BridgeLoweringCache
import org.jetbrains.kotlin.backend.jvm.caches.CollectionStubComputer
@@ -47,6 +48,18 @@ class JvmBackendContext(
val backendExtension: JvmBackendExtension,
val irSerializer: JvmIrSerializer?,
) : CommonBackendContext {
data class LocalFunctionData(
val localContext: LocalDeclarationsLowering.LocalFunctionContext,
val newParameterToOld: Map<IrValueParameter, IrValueParameter>,
val newParameterToCaptured: Map<IrValueParameter, IrValueSymbol>
)
// If not-null, this is populated by LocalDeclarationsLowering with the intermediate data
// allowing mapping from local function captures to parameters and accurate transformation
// of calls to local functions from code fragments (i.e. the expression evaluator).
var localDeclarationsLoweringData: MutableMap<IrFunction, LocalFunctionData>? = null
// If the JVM fqname of a class differs from what is implied by its parent, e.g. if it's a file class
// annotated with @JvmPackageName, the correct name is recorded here.
val classNameOverride: MutableMap<IrClass, JvmClassName>
@@ -36,7 +36,7 @@ class GeneratorContext private constructor(
val typeTranslator: TypeTranslator,
override val irBuiltIns: IrBuiltIns,
internal val callToSubstitutedDescriptorMap: MutableMap<IrDeclarationReference, CallableDescriptor>,
internal val fragmentContext: FragmentContext?,
internal var fragmentContext: FragmentContext?,
) : IrGeneratorContext {
constructor(
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.psi2ir.generators.fragments
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueDescriptor
@@ -23,11 +22,13 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ThisClassReceiver
class FragmentCompilerSymbolTableDecorator(
signatureComposer: IdSignatureComposer,
irFactory: IrFactory,
private val fragmentInfo: EvaluatorFragmentInfo,
var fragmentInfo: EvaluatorFragmentInfo?,
nameProvider: NameProvider = NameProvider.DEFAULT,
) : SymbolTable(signatureComposer, irFactory, nameProvider) {
override fun referenceValueParameter(descriptor: ParameterDescriptor): IrValueParameterSymbol {
val fi = fragmentInfo ?: return super.referenceValueParameter(descriptor)
if (descriptor !is ReceiverParameterDescriptor) return super.referenceValueParameter(descriptor)
val finderPredicate = when (val receiverValue = descriptor.value) {
@@ -41,18 +42,20 @@ class FragmentCompilerSymbolTableDecorator(
}
val parameterPosition =
fragmentInfo.parameters.indexOfFirst(finderPredicate)
fi.parameters.indexOfFirst(finderPredicate)
if (parameterPosition > -1) {
return super.referenceValueParameter(fragmentInfo.methodDescriptor.valueParameters[parameterPosition])
return super.referenceValueParameter(fi.methodDescriptor.valueParameters[parameterPosition])
}
return super.referenceValueParameter(descriptor)
}
override fun referenceValue(value: ValueDescriptor): IrValueSymbol {
val fi = fragmentInfo ?: return super.referenceValue(value)
val parameterPosition =
fragmentInfo.parameters.indexOfFirst { it.descriptor == value }
fi.parameters.indexOfFirst { it.descriptor == value }
if (parameterPosition > -1) {
return super.referenceValueParameter(fragmentInfo.methodDescriptor.valueParameters[parameterPosition])
return super.referenceValueParameter(fi.methodDescriptor.valueParameters[parameterPosition])
}
return super.referenceValue(value)
@@ -38,14 +38,28 @@ class FragmentModuleGenerator(
patchDeclarationParents()
}
} else {
val fileContext = context.createFileScopeContext(ktFile)
generateSingleFile(DeclarationGenerator(fileContext), ktFile, irModule)
generateInContextWithoutFragmentInfo(ktFile) {
generateSingleFile(DeclarationGenerator(it), ktFile, irModule)
}
}
)
}
}
}
private fun <T> generateInContextWithoutFragmentInfo(ktFile: KtFile, block: (GeneratorContext) -> T): T {
val symbolTableDecorator = context.symbolTable as FragmentCompilerSymbolTableDecorator
val fragmentInfo = symbolTableDecorator.fragmentInfo
symbolTableDecorator.fragmentInfo = null
val fileContext = context.createFileScopeContext(ktFile)
fileContext.fragmentContext = null
return block(fileContext).also {
symbolTableDecorator.fragmentInfo = fragmentInfo
}
}
private fun createEmptyIrFile(ktFile: KtFile): IrFileImpl {
val fileEntry = PsiIrFileEntry(ktFile)
val packageFragmentDescriptor = context.moduleDescriptor.findPackageFragmentForFile(ktFile)!!
@@ -0,0 +1,38 @@
// FILE: test.kt
open class Base(i: Int)
class Derived(): Base(1) {
constructor(p: Int): this() {
val a = 2
}
constructor(p1: Int, p2: Int): this()
}
fun box() {
Derived(3)
Derived(4, 5)
}
// EXPECTATIONS
// test.kt:15 box:
// test.kt:7 <init>: p:int=3:int
// test.kt:6 <init>:
// test.kt:4 <init>: i:int=1:int
// test.kt:6 <init>:
// test.kt:8 <init>: p:int=3:int
// EXPECTATIONS JVM_IR
// test.kt:9 <init>: p:int=3:int, a:int=2:int
// EXPECTATIONS
// test.kt:15 box:
// test.kt:16 box:
// test.kt:11 <init>: p1:int=4:int, p2:int=5:int
// test.kt:6 <init>:
// test.kt:4 <init>: i:int=1:int
// test.kt:6 <init>:
// test.kt:11 <init>: p1:int=4:int, p2:int=5:int
// test.kt:16 box:
// test.kt:17 box:
@@ -0,0 +1,14 @@
// FILE: test.kt
class F(val a: String)
fun box() {
F("foo")
}
// EXPECTATIONS
// test.kt:7 box:
// test.kt:4 <init>: a:java.lang.String="foo":java.lang.String
// test.kt:7 box:
// test.kt:8 box:
+24
View File
@@ -0,0 +1,24 @@
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_FIR: JVM_IR
// FILE: test.kt
interface ObjectFace
private fun makeFace() = object : ObjectFace {
init { 5 }
}
fun box() {
makeFace()
}
// IR backend has additional steps on the way _out_ of the init block.
// EXPECTATIONS
// test.kt:12 box
// test.kt:6 makeFace
// test.kt:6 <init>
// test.kt:8 <init>
// test.kt:9 makeFace
// test.kt:12 box
// test.kt:13 box
+26
View File
@@ -0,0 +1,26 @@
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_FIR: JVM_IR
// FILE: test.kt
class A {
val a = 1
fun bar() = 2
fun foo() {
3
//Breakpoint! from the Evaluate Expression test suite.
.toString()
}
}
fun box() {
A().foo()
}
// EXPECTATIONS
// test.kt:15 box
// test.kt:4 <init>
// test.kt:5 <init>
// test.kt:15 box
// test.kt:8 foo
// test.kt:10 foo
// test.kt:11 foo
// test.kt:16 box
+68
View File
@@ -0,0 +1,68 @@
// FILE: test.kt
fun box() {
test("OK")
test(null)
testExpressionBody("OK")
testExpressionBody(null)
}
fun test(nullable: String?): Boolean {
return nullable != null &&
// Some comment
nullable.length == 2
}
fun testExpressionBody(nullable: String?) =
nullable != null &&
// Some comment
nullable.length == 2
// EXPECTATIONS
// EXPECTATIONS JVM
// test.kt:6 box
// test.kt:15 test
// test.kt:13 test
// test.kt:6 box
// test.kt:7 box
// test.kt:15 test
// test.kt:13 test
// test.kt:7 box
// test.kt:8 box
// test.kt:21 testExpressionBody
// test.kt:8 box
// test.kt:9 box
// test.kt:21 testExpressionBody
// test.kt:9 box
// test.kt:10 box
// EXPECTATIONS JVM_IR
// test.kt:6 box
// test.kt:13 test
// test.kt:15 test
// test.kt:13 test
// test.kt:6 box
// test.kt:7 box
// test.kt:13 test
// test.kt:15 test
// test.kt:13 test
// test.kt:7 box
// test.kt:8 box
// test.kt:19 testExpressionBody
// test.kt:21 testExpressionBody
// test.kt:8 box
// test.kt:9 box
// test.kt:19 testExpressionBody
// test.kt:21 testExpressionBody
// test.kt:9 box
// test.kt:10 box
@@ -193,6 +193,28 @@ public class IrLocalVariableTestGenerated extends AbstractIrLocalVariableTest {
runTest("compiler/testData/debug/localVariables/underscoreNames.kt");
}
@Nested
@TestMetadata("compiler/testData/debug/localVariables/constructors")
@TestDataPath("$PROJECT_ROOT")
public class Constructors {
@Test
public void testAllFilesPresentInConstructors() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/debug/localVariables/constructors"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("multipleConstructors.kt")
public void testMultipleConstructors() throws Exception {
runTest("compiler/testData/debug/localVariables/constructors/multipleConstructors.kt");
}
@Test
@TestMetadata("property.kt")
public void testProperty() throws Exception {
runTest("compiler/testData/debug/localVariables/constructors/property.kt");
}
}
@Nested
@TestMetadata("compiler/testData/debug/localVariables/receiverMangling")
@TestDataPath("$PROJECT_ROOT")
@@ -247,6 +247,18 @@ public class IrSteppingTestGenerated extends AbstractIrSteppingTest {
runTest("compiler/testData/debug/stepping/inlineSimpleCall.kt");
}
@Test
@TestMetadata("kt15259.kt")
public void testKt15259() throws Exception {
runTest("compiler/testData/debug/stepping/kt15259.kt");
}
@Test
@TestMetadata("kt29179.kt")
public void testKt29179() throws Exception {
runTest("compiler/testData/debug/stepping/kt29179.kt");
}
@Test
@TestMetadata("kt42208.kt")
public void testKt42208() throws Exception {
@@ -337,6 +349,12 @@ public class IrSteppingTestGenerated extends AbstractIrSteppingTest {
runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt");
}
@Test
@TestMetadata("nullcheck.kt")
public void testNullcheck() throws Exception {
runTest("compiler/testData/debug/stepping/nullcheck.kt");
}
@Test
@TestMetadata("primitiveNullChecks.kt")
public void testPrimitiveNullChecks() throws Exception {
@@ -193,6 +193,28 @@ public class LocalVariableTestGenerated extends AbstractLocalVariableTest {
runTest("compiler/testData/debug/localVariables/underscoreNames.kt");
}
@Nested
@TestMetadata("compiler/testData/debug/localVariables/constructors")
@TestDataPath("$PROJECT_ROOT")
public class Constructors {
@Test
public void testAllFilesPresentInConstructors() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/debug/localVariables/constructors"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@Test
@TestMetadata("multipleConstructors.kt")
public void testMultipleConstructors() throws Exception {
runTest("compiler/testData/debug/localVariables/constructors/multipleConstructors.kt");
}
@Test
@TestMetadata("property.kt")
public void testProperty() throws Exception {
runTest("compiler/testData/debug/localVariables/constructors/property.kt");
}
}
@Nested
@TestMetadata("compiler/testData/debug/localVariables/receiverMangling")
@TestDataPath("$PROJECT_ROOT")
@@ -247,6 +247,18 @@ public class SteppingTestGenerated extends AbstractSteppingTest {
runTest("compiler/testData/debug/stepping/inlineSimpleCall.kt");
}
@Test
@TestMetadata("kt15259.kt")
public void testKt15259() throws Exception {
runTest("compiler/testData/debug/stepping/kt15259.kt");
}
@Test
@TestMetadata("kt29179.kt")
public void testKt29179() throws Exception {
runTest("compiler/testData/debug/stepping/kt29179.kt");
}
@Test
@TestMetadata("kt42208.kt")
public void testKt42208() throws Exception {
@@ -337,6 +349,12 @@ public class SteppingTestGenerated extends AbstractSteppingTest {
runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt");
}
@Test
@TestMetadata("nullcheck.kt")
public void testNullcheck() throws Exception {
runTest("compiler/testData/debug/stepping/nullcheck.kt");
}
@Test
@TestMetadata("primitiveNullChecks.kt")
public void testPrimitiveNullChecks() throws Exception {