[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.codegen.serialization.JvmSerializationBindings
import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.config.LanguageVersion.* import org.jetbrains.kotlin.config.LanguageVersion.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
@@ -423,6 +421,11 @@ class GenerationState private constructor(
private fun shouldOnlyCollectSignatures(origin: JvmDeclarationOrigin) = private fun shouldOnlyCollectSignatures(origin: JvmDeclarationOrigin) =
classBuilderMode == ClassBuilderMode.LIGHT_CLASSES && origin.originKind in doNotGenerateInLightClassMode 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 { companion object {
val LANGUAGE_TO_METADATA_VERSION = EnumMap<LanguageVersion, JvmMetadataVersion>(LanguageVersion::class.java).apply { val LANGUAGE_TO_METADATA_VERSION = EnumMap<LanguageVersion, JvmMetadataVersion>(LanguageVersion::class.java).apply {
val oldMetadataVersion = JvmMetadataVersion(1, 1, 18) val oldMetadataVersion = JvmMetadataVersion(1, 1, 18)
@@ -193,6 +193,28 @@ public class FirLocalVariableTestGenerated extends AbstractFirLocalVariableTest
runTest("compiler/testData/debug/localVariables/underscoreNames.kt"); 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 @Nested
@TestMetadata("compiler/testData/debug/localVariables/receiverMangling") @TestMetadata("compiler/testData/debug/localVariables/receiverMangling")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@@ -247,6 +247,18 @@ public class FirSteppingTestGenerated extends AbstractFirSteppingTest {
runTest("compiler/testData/debug/stepping/inlineSimpleCall.kt"); 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 @Test
@TestMetadata("kt42208.kt") @TestMetadata("kt42208.kt")
public void testKt42208() throws Exception { public void testKt42208() throws Exception {
@@ -337,6 +349,12 @@ public class FirSteppingTestGenerated extends AbstractFirSteppingTest {
runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt"); runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt");
} }
@Test
@TestMetadata("nullcheck.kt")
public void testNullcheck() throws Exception {
runTest("compiler/testData/debug/stepping/nullcheck.kt");
}
@Test @Test
@TestMetadata("primitiveNullChecks.kt") @TestMetadata("primitiveNullChecks.kt")
public void testPrimitiveNullChecks() throws Exception { 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.ir.*
import org.jetbrains.kotlin.backend.common.lower.inline.isInlineParameter import org.jetbrains.kotlin.backend.common.lower.inline.isInlineParameter
import org.jetbrains.kotlin.backend.common.runOnFilePostfix import org.jetbrains.kotlin.backend.common.runOnFilePostfix
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.DescriptorVisibility import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.* import org.jetbrains.kotlin.ir.*
import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor
import org.jetbrains.kotlin.ir.builders.declarations.buildFun import org.jetbrains.kotlin.ir.builders.declarations.buildFun
@@ -71,7 +71,8 @@ class LocalDeclarationsLowering(
val localNameSanitizer: (String) -> String = { it }, val localNameSanitizer: (String) -> String = { it },
val visibilityPolicy: VisibilityPolicy = VisibilityPolicy.DEFAULT, val visibilityPolicy: VisibilityPolicy = VisibilityPolicy.DEFAULT,
val suggestUniqueNames: Boolean = true, // When `true` appends a `-#index` suffix to lifted declaration names 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 { BodyLoweringPass {
@@ -109,7 +110,7 @@ class LocalDeclarationsLowering(
ScopeWithCounter(this) ScopeWithCounter(this)
} }
private abstract class LocalContext { abstract class LocalContext {
val capturedTypeParameterToTypeParameter: MutableMap<IrTypeParameter, IrTypeParameter> = mutableMapOf() val capturedTypeParameterToTypeParameter: MutableMap<IrTypeParameter, IrTypeParameter> = mutableMapOf()
// By the time typeRemapper is used, the map will be already filled // 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? 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 declaration: IrFunction
abstract val transformedDeclaration: IrFunction abstract val transformedDeclaration: IrFunction
@@ -135,7 +136,7 @@ class LocalDeclarationsLowering(
} }
} }
private class LocalFunctionContext( class LocalFunctionContext(
override val declaration: IrSimpleFunction, override val declaration: IrSimpleFunction,
val index: Int, val index: Int,
val ownerForLoweredDeclaration: IrDeclarationContainer val ownerForLoweredDeclaration: IrDeclarationContainer
@@ -246,6 +247,10 @@ class LocalDeclarationsLowering(
rewriteDeclarations() rewriteDeclarations()
insertLoweredDeclarationForLocalFunctions() insertLoweredDeclarationForLocalFunctions()
postLocalDeclarationLoweringCallback?.invoke(
IntermediateDatastructures(localFunctions, newParameterToOld, newParameterToCaptured)
)
} }
private fun insertLoweredDeclarationForLocalFunctions() { private fun insertLoweredDeclarationForLocalFunctions() {
@@ -967,6 +972,12 @@ class LocalDeclarationsLowering(
}, Data(null, false)) }, 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 // Local inner classes capture anything through outer
@@ -56,7 +56,6 @@ open class JvmIrCodegenFactory(
private val externalMangler: JvmDescriptorMangler? = null, private val externalMangler: JvmDescriptorMangler? = null,
private val externalSymbolTable: SymbolTable? = null, private val externalSymbolTable: SymbolTable? = null,
private val jvmGeneratorExtensions: JvmGeneratorExtensionsImpl = JvmGeneratorExtensionsImpl(configuration), private val jvmGeneratorExtensions: JvmGeneratorExtensionsImpl = JvmGeneratorExtensionsImpl(configuration),
private val prefixPhases: CompilerPhase<JvmBackendContext, IrModuleFragment, IrModuleFragment>? = null,
private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null, private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null,
private val shouldStubAndNotLinkUnboundSymbols: Boolean = false, private val shouldStubAndNotLinkUnboundSymbols: Boolean = false,
) : CodegenFactory { ) : CodegenFactory {
@@ -258,11 +257,14 @@ open class JvmIrCodegenFactory(
) )
JvmIrSerializerImpl(state.configuration) JvmIrSerializerImpl(state.configuration)
else null else null
val phases = prefixPhases?.then(jvmLoweringPhases) ?: jvmLoweringPhases val phases = if (evaluatorFragmentInfoForPsi2Ir != null) jvmFragmentLoweringPhases else jvmLoweringPhases
val phaseConfig = customPhaseConfig ?: PhaseConfig(phases) val phaseConfig = customPhaseConfig ?: PhaseConfig(phases)
val context = JvmBackendContext( val context = JvmBackendContext(
state, irModuleFragment.irBuiltins, irModuleFragment, symbolTable, phaseConfig, extensions, backendExtension, irSerializer, 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) } val intrinsics by lazy { IrIntrinsicMethods(irModuleFragment.irBuiltins, context.ir.symbols) }
context.getIntrinsic = { symbol: IrFunctionSymbol -> intrinsics.getIntrinsic(symbol) } context.getIntrinsic = { symbol: IrFunctionSymbol -> intrinsics.getIntrinsic(symbol) }
/* JvmBackendContext creates new unbound symbols, have to resolve them. */ /* 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" description = "Transform `Array(size) { index -> value }` into a loop"
) )
internal val expectDeclarationsRemovingPhase = makeIrModulePhase<CommonBackendContext>( internal val expectDeclarationsRemovingPhase = makeIrModulePhase(
::ExpectDeclarationRemover, ::ExpectDeclarationRemover,
name = "ExpectDeclarationsRemoving", name = "ExpectDeclarationsRemoving",
description = "Remove expect declaration from module fragment" description = "Remove expect declaration from module fragment"
@@ -139,7 +139,15 @@ internal val localDeclarationsPhase = makeIrFilePhase(
private fun scopedVisibility(inInlineFunctionScope: Boolean): DescriptorVisibility = private fun scopedVisibility(inInlineFunctionScope: Boolean): DescriptorVisibility =
if (inInlineFunctionScope) DescriptorVisibilities.PUBLIC else JavaDescriptorVisibilities.PACKAGE_VISIBILITY 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", name = "JvmLocalDeclarations",
@@ -383,23 +391,61 @@ private val jvmFilePhases = listOf(
// makePatchParentsPhase() // makePatchParentsPhase()
) )
val jvmLoweringPhases = NamedCompilerPhase( val jvmLoweringPhases = buildJvmLoweringPhases("IrLowering", listOf("PerformByIrFile" to jvmFilePhases))
name = "IrLowering",
description = "IR lowering", private fun buildJvmLoweringPhases(
nlevels = 1, name: String,
actions = setOf(defaultDumper, validationAction), phases: List<Pair<String, List<NamedCompilerPhase<JvmBackendContext, IrFile>>>>
lower = validateIrBeforeLowering then ): NamedCompilerPhase<JvmBackendContext, IrModuleFragment> {
processOptionalAnnotationsPhase then return NamedCompilerPhase(
expectDeclarationsRemovingPhase then name = name,
serializeIrPhase then description = "IR lowering",
scriptsToClassesPhase then nlevels = 1,
fileClassPhase then actions = setOf(defaultDumper, validationAction),
jvmStaticInObjectPhase then lower =
repeatedAnnotationPhase then fragmentSharedVariablesLowering then
performByIrFile(lower = jvmFilePhases) then validateIrBeforeLowering then
generateMultifileFacadesPhase then processOptionalAnnotationsPhase then
resolveInlineCallsPhase then expectDeclarationsRemovingPhase then
// should be last transformation serializeIrPhase then
prepareForBytecodeInlining then scriptsToClassesPhase then
validateIrAfterLowering 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 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.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext 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.common.phaser.makeIrModulePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver import org.jetbrains.kotlin.backend.jvm.ir.*
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.lower.SyntheticAccessorLowering.Companion.isAccessible 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.builders.*
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.defaultType import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.starProjectedType
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.load.java.JvmAbi 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 // Used from CodeFragmentCompiler for IDE Debugger Plug-In
@Suppress("unused") @Suppress("unused")
val reflectiveAccessLowering = makeIrModulePhase( val reflectiveAccessLowering = makeIrFilePhase(
::ReflectiveAccessLowering, ::ReflectiveAccessLowering,
name = "ReflectiveCalls", name = "ReflectiveCalls",
description = "Avoid the need for accessors by replacing direct access to inaccessible members with accesses via reflection", 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, // Fragments are transformed in a post-order traversal: children first,
// then parent. This obscures, in particular, dispatch receivers, that go // then parent. This obscures, in particular, dispatch receivers, that go
// from `IrGetObjectValue` calls to blocks implementing the corresponding // 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 // later predict the compilation strategy for fields. See the uses of
// `fieldLocationAndReceiver`. // `fieldLocationAndReceiver`.
val callsOnCompanionObjects: MutableMap<IrCall, IrClassSymbol> = mutableMapOf() val callsOnCompanionObjects: MutableMap<IrCall, IrClassSymbol> = mutableMapOf()
@OptIn(ObsoleteDescriptorBasedAPI::class)
private fun recordCompanionObjectAsDispatchReceiver(expression: IrCall) { private fun recordCompanionObjectAsDispatchReceiver(expression: IrCall) {
if ((expression.dispatchReceiver as? IrGetObjectValue)?.symbol?.owner?.isCompanion == true) { val dispatchReceiver = expression.dispatchReceiver as? IrGetField ?: return
callsOnCompanionObjects[expression] = (expression.dispatchReceiver!! as IrGetObjectValue).symbol 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) 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( private fun IrBuilderWithScope.getDeclaredMethod(
declaringClass: IrExpression, declaringClass: IrExpression,
methodName: String, methodName: String,
@@ -293,13 +314,13 @@ internal class ReflectiveAccessLowering(
private fun generateReflectiveMethodInvocation(call: IrCall): IrExpression = private fun generateReflectiveMethodInvocation(call: IrCall): IrExpression =
generateReflectiveMethodInvocation( generateReflectiveMethodInvocation(
call.superQualifierSymbol?.defaultType ?: call.dispatchReceiver!!.type, call.superQualifierSymbol?.defaultType ?: call.dispatchReceiver?.type ?: call.symbol.owner.parentAsClass.defaultType,
call.symbol.owner.name.asString(), call.symbol.owner.name.asString(),
mutableListOf<IrType>().apply { mutableListOf<IrType>().apply {
call.symbol.owner.extensionReceiverParameter?.let { add(it.type) } call.symbol.owner.extensionReceiverParameter?.let { add(it.type) }
addAll(call.valueParameterTypes()) addAll(call.valueParameterTypes())
}, },
call.dispatchReceiver!!, call.dispatchReceiver,
mutableListOf<IrExpression>().apply { mutableListOf<IrExpression>().apply {
call.extensionReceiver?.let { add(it) } call.extensionReceiver?.let { add(it) }
addAll(call.getValueArguments()) 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.DefaultMapping
import org.jetbrains.kotlin.backend.common.Mapping import org.jetbrains.kotlin.backend.common.Mapping
import org.jetbrains.kotlin.backend.common.ir.Ir 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.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.jvm.caches.BridgeLoweringCache import org.jetbrains.kotlin.backend.jvm.caches.BridgeLoweringCache
import org.jetbrains.kotlin.backend.jvm.caches.CollectionStubComputer import org.jetbrains.kotlin.backend.jvm.caches.CollectionStubComputer
@@ -47,6 +48,18 @@ class JvmBackendContext(
val backendExtension: JvmBackendExtension, val backendExtension: JvmBackendExtension,
val irSerializer: JvmIrSerializer?, val irSerializer: JvmIrSerializer?,
) : CommonBackendContext { ) : 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 // 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. // annotated with @JvmPackageName, the correct name is recorded here.
val classNameOverride: MutableMap<IrClass, JvmClassName> val classNameOverride: MutableMap<IrClass, JvmClassName>
@@ -36,7 +36,7 @@ class GeneratorContext private constructor(
val typeTranslator: TypeTranslator, val typeTranslator: TypeTranslator,
override val irBuiltIns: IrBuiltIns, override val irBuiltIns: IrBuiltIns,
internal val callToSubstitutedDescriptorMap: MutableMap<IrDeclarationReference, CallableDescriptor>, internal val callToSubstitutedDescriptorMap: MutableMap<IrDeclarationReference, CallableDescriptor>,
internal val fragmentContext: FragmentContext?, internal var fragmentContext: FragmentContext?,
) : IrGeneratorContext { ) : IrGeneratorContext {
constructor( constructor(
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.psi2ir.generators.fragments package org.jetbrains.kotlin.psi2ir.generators.fragments
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueDescriptor import org.jetbrains.kotlin.descriptors.ValueDescriptor
@@ -23,11 +22,13 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ThisClassReceiver
class FragmentCompilerSymbolTableDecorator( class FragmentCompilerSymbolTableDecorator(
signatureComposer: IdSignatureComposer, signatureComposer: IdSignatureComposer,
irFactory: IrFactory, irFactory: IrFactory,
private val fragmentInfo: EvaluatorFragmentInfo, var fragmentInfo: EvaluatorFragmentInfo?,
nameProvider: NameProvider = NameProvider.DEFAULT, nameProvider: NameProvider = NameProvider.DEFAULT,
) : SymbolTable(signatureComposer, irFactory, nameProvider) { ) : SymbolTable(signatureComposer, irFactory, nameProvider) {
override fun referenceValueParameter(descriptor: ParameterDescriptor): IrValueParameterSymbol { override fun referenceValueParameter(descriptor: ParameterDescriptor): IrValueParameterSymbol {
val fi = fragmentInfo ?: return super.referenceValueParameter(descriptor)
if (descriptor !is ReceiverParameterDescriptor) return super.referenceValueParameter(descriptor) if (descriptor !is ReceiverParameterDescriptor) return super.referenceValueParameter(descriptor)
val finderPredicate = when (val receiverValue = descriptor.value) { val finderPredicate = when (val receiverValue = descriptor.value) {
@@ -41,18 +42,20 @@ class FragmentCompilerSymbolTableDecorator(
} }
val parameterPosition = val parameterPosition =
fragmentInfo.parameters.indexOfFirst(finderPredicate) fi.parameters.indexOfFirst(finderPredicate)
if (parameterPosition > -1) { if (parameterPosition > -1) {
return super.referenceValueParameter(fragmentInfo.methodDescriptor.valueParameters[parameterPosition]) return super.referenceValueParameter(fi.methodDescriptor.valueParameters[parameterPosition])
} }
return super.referenceValueParameter(descriptor) return super.referenceValueParameter(descriptor)
} }
override fun referenceValue(value: ValueDescriptor): IrValueSymbol { override fun referenceValue(value: ValueDescriptor): IrValueSymbol {
val fi = fragmentInfo ?: return super.referenceValue(value)
val parameterPosition = val parameterPosition =
fragmentInfo.parameters.indexOfFirst { it.descriptor == value } fi.parameters.indexOfFirst { it.descriptor == value }
if (parameterPosition > -1) { if (parameterPosition > -1) {
return super.referenceValueParameter(fragmentInfo.methodDescriptor.valueParameters[parameterPosition]) return super.referenceValueParameter(fi.methodDescriptor.valueParameters[parameterPosition])
} }
return super.referenceValue(value) return super.referenceValue(value)
@@ -38,14 +38,28 @@ class FragmentModuleGenerator(
patchDeclarationParents() patchDeclarationParents()
} }
} else { } else {
val fileContext = context.createFileScopeContext(ktFile) generateInContextWithoutFragmentInfo(ktFile) {
generateSingleFile(DeclarationGenerator(fileContext), ktFile, irModule) 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 { private fun createEmptyIrFile(ktFile: KtFile): IrFileImpl {
val fileEntry = PsiIrFileEntry(ktFile) val fileEntry = PsiIrFileEntry(ktFile)
val packageFragmentDescriptor = context.moduleDescriptor.findPackageFragmentForFile(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"); 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 @Nested
@TestMetadata("compiler/testData/debug/localVariables/receiverMangling") @TestMetadata("compiler/testData/debug/localVariables/receiverMangling")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@@ -247,6 +247,18 @@ public class IrSteppingTestGenerated extends AbstractIrSteppingTest {
runTest("compiler/testData/debug/stepping/inlineSimpleCall.kt"); 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 @Test
@TestMetadata("kt42208.kt") @TestMetadata("kt42208.kt")
public void testKt42208() throws Exception { public void testKt42208() throws Exception {
@@ -337,6 +349,12 @@ public class IrSteppingTestGenerated extends AbstractIrSteppingTest {
runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt"); runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt");
} }
@Test
@TestMetadata("nullcheck.kt")
public void testNullcheck() throws Exception {
runTest("compiler/testData/debug/stepping/nullcheck.kt");
}
@Test @Test
@TestMetadata("primitiveNullChecks.kt") @TestMetadata("primitiveNullChecks.kt")
public void testPrimitiveNullChecks() throws Exception { public void testPrimitiveNullChecks() throws Exception {
@@ -193,6 +193,28 @@ public class LocalVariableTestGenerated extends AbstractLocalVariableTest {
runTest("compiler/testData/debug/localVariables/underscoreNames.kt"); 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 @Nested
@TestMetadata("compiler/testData/debug/localVariables/receiverMangling") @TestMetadata("compiler/testData/debug/localVariables/receiverMangling")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@@ -247,6 +247,18 @@ public class SteppingTestGenerated extends AbstractSteppingTest {
runTest("compiler/testData/debug/stepping/inlineSimpleCall.kt"); 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 @Test
@TestMetadata("kt42208.kt") @TestMetadata("kt42208.kt")
public void testKt42208() throws Exception { public void testKt42208() throws Exception {
@@ -337,6 +349,12 @@ public class SteppingTestGenerated extends AbstractSteppingTest {
runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt"); runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt");
} }
@Test
@TestMetadata("nullcheck.kt")
public void testNullcheck() throws Exception {
runTest("compiler/testData/debug/stepping/nullcheck.kt");
}
@Test @Test
@TestMetadata("primitiveNullChecks.kt") @TestMetadata("primitiveNullChecks.kt")
public void testPrimitiveNullChecks() throws Exception { public void testPrimitiveNullChecks() throws Exception {