[JVM] Implement extension point to avoid excess reflective calls
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KaFirCompilerFacility.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KaFirCompilerFacility.kt
index 64440fb..a7d3c59 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KaFirCompilerFacility.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KaFirCompilerFacility.kt
@@ -48,6 +48,7 @@
 import org.jetbrains.kotlin.builtins.DefaultBuiltIns
 import org.jetbrains.kotlin.builtins.StandardNames
 import org.jetbrains.kotlin.codegen.ClassBuilderFactories
+import org.jetbrains.kotlin.codegen.JvmMemberAccessOracleBE
 import org.jetbrains.kotlin.codegen.state.CompiledCodeProvider
 import org.jetbrains.kotlin.codegen.state.GenerationState
 import org.jetbrains.kotlin.config.CommonConfigurationKeys
@@ -118,6 +119,7 @@
 import org.jetbrains.kotlin.psi.*
 import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
 import org.jetbrains.kotlin.psi2ir.generators.fragments.EvaluatorFragmentInfo
+import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
 import org.jetbrains.kotlin.resolve.source.PsiSourceFile
 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource
 import org.jetbrains.kotlin.utils.addIfNotNull
@@ -127,6 +129,7 @@
 import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
 import org.jetbrains.kotlin.utils.yieldIfNotNull
 import java.util.*
+import org.jetbrains.org.objectweb.asm.Type
 
 /**
  * A source file to be compiled as a part of some [ChunkToCompile].
@@ -247,7 +250,8 @@
                 jvmIrDeserializer,
                 codeFragmentMappings?.takeIf { chunk.hasCodeFragments },
                 generateClassFilter,
-                KaFirDelegatingCompiledCodeProvider(registeredCodeProviders)
+                KaFirDelegatingCompiledCodeProvider(registeredCodeProviders),
+                target.debuggerExtension?.jvmMemberAccessOracle
             )
 
             when (result) {
@@ -819,7 +823,8 @@
         jvmIrDeserializer: JvmIrDeserializer,
         codeFragmentMappings: CodeFragmentMappings?,
         generateClassFilter: GenerationState.GenerateClassFilter,
-        compiledCodeProvider: CompiledCodeProvider
+        compiledCodeProvider: CompiledCodeProvider,
+        jvmMemberAccessOracle: JvmMemberAccessOracle?,
     ): KaCompilationResult {
         val session = resolutionFacade.sessionProvider.getResolvableSession(module)
         val configuration = baseConfiguration.copy().apply {
@@ -877,7 +882,8 @@
             configuration = configuration,
             isCodeFragment = codeFragmentMappings != null,
             irModuleFragment = fir2IrResult.irModuleFragment,
-            typeArgumentsMap = convertedMapping
+            typeArgumentsMap = convertedMapping,
+            jvmMemberAccessOracle = jvmMemberAccessOracle
         )
 
         val result = runJvmIrCodeGen(
@@ -1270,6 +1276,7 @@
         isCodeFragment: Boolean,
         irModuleFragment: IrModuleFragment,
         typeArgumentsMap: Map<IrTypeParameterSymbol, IrType>,
+        jvmMemberAccessOracle: JvmMemberAccessOracle?,
     ): JvmIrCodegenFactory {
         val jvmGeneratorExtensions = object : JvmGeneratorExtensionsImpl(configuration) {
             override fun getContainerSource(descriptor: DeclarationDescriptor): DeserializedContainerSource? {
@@ -1310,6 +1317,17 @@
             jvmGeneratorExtensions = jvmGeneratorExtensions,
             evaluatorFragmentInfoForPsi2Ir = evaluatorFragmentInfoForPsi2Ir,
             ideCodegenSettings = ideCodegenSettings,
+            jvmMemberAccessOracle = if (jvmMemberAccessOracle != null) {
+                object : JvmMemberAccessOracleBE {
+                    override fun tryMakeFieldAccessible(declaringClass: Type, name: String) =
+                        jvmMemberAccessOracle.tryMakeFieldAccessible(declaringClass, name)
+
+                    override fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature) =
+                        jvmMemberAccessOracle.tryMakeMethodAccessible(declaringClass, signature)
+                }
+            } else {
+                null
+            }
         )
     }
 }
diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt
index 20ec96b..1d84d91 100644
--- a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt
+++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt
@@ -22,6 +22,7 @@
 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
 import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
 import org.jetbrains.kotlin.codegen.BytecodeListingTextCollectingVisitor
+import org.jetbrains.kotlin.codegen.JvmMemberAccessOracleBE
 import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
 import org.jetbrains.kotlin.config.CommonConfigurationKeys
 import org.jetbrains.kotlin.config.CompilerConfiguration
@@ -141,7 +142,7 @@
             val target = KaCompilerTarget.Jvm(
                 isTestMode = true,
                 compiledClassHandler = null,
-                debuggerExtension = DebuggerExtension(callStack.asSequence())
+                debuggerExtension = DebuggerExtension(callStack.asSequence(), JvmMemberAccessOracle.EMPTY)
             )
             val allowedErrorFilter: (KaDiagnostic) -> Boolean = { it.factoryName in ALLOWED_ERRORS }
 
diff --git a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KaCompilerFacility.kt b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KaCompilerFacility.kt
index f70d167..2fae1cf 100644
--- a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KaCompilerFacility.kt
+++ b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KaCompilerFacility.kt
@@ -15,6 +15,8 @@
 import org.jetbrains.kotlin.config.CompilerConfigurationKey
 import org.jetbrains.kotlin.psi.KtCodeFragment
 import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
+import org.jetbrains.org.objectweb.asm.Type
 import java.io.File
 
 /**
@@ -179,6 +181,29 @@
  *
  * @property stack A sequence of PSI elements of the expressions (function calls or property accesses) in the current execution stack,
  * listed from the top to the bottom.
+ *
+ * @property jvmMemberAccessOracle is a compiler extension point using which debugger can avoid generation of reflective calls for private
+ * members
  */
 @KaExperimentalApi
-public class DebuggerExtension(public val stack: Sequence<PsiElement?>)
\ No newline at end of file
+public class DebuggerExtension(
+    public val stack: Sequence<PsiElement?>,
+    public val jvmMemberAccessOracle: JvmMemberAccessOracle,
+)
+
+@KaExperimentalApi
+public interface JvmMemberAccessOracle {
+    public fun tryMakeFieldAccessible(declaringClass: Type, name: String): Boolean
+    public fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature): Boolean
+
+    public companion object {
+        public val EMPTY: JvmMemberAccessOracle = object : JvmMemberAccessOracle {
+            override fun tryMakeFieldAccessible(declaringClass: Type, name: String) = false
+            override fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature) = false
+        }
+        public val ALWAYS_SAY_YES: JvmMemberAccessOracle = object : JvmMemberAccessOracle {
+            override fun tryMakeFieldAccessible(declaringClass: Type, name: String) = true
+            override fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature) = true
+        }
+    }
+}
\ No newline at end of file
diff --git a/compiler/backend.common.jvm/src/org/jetbrains/kotlin/codegen/JvmMemberAccessOracleBE.kt b/compiler/backend.common.jvm/src/org/jetbrains/kotlin/codegen/JvmMemberAccessOracleBE.kt
new file mode 100644
index 0000000..23fcdd0
--- /dev/null
+++ b/compiler/backend.common.jvm/src/org/jetbrains/kotlin/codegen/JvmMemberAccessOracleBE.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.codegen
+
+import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
+import org.jetbrains.org.objectweb.asm.Type
+
+interface JvmMemberAccessOracleBE {
+    fun tryMakeFieldAccessible(declaringClass: Type, name: String): Boolean
+    fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature): Boolean
+}
\ No newline at end of file
diff --git a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt
index ee62a75..f29ebdf 100644
--- a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt
+++ b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt
@@ -24,6 +24,7 @@
 import org.jetbrains.kotlin.backend.jvm.serialization.DisabledIdSignatureDescriptor
 import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor
 import org.jetbrains.kotlin.builtins.StandardNames.BUILT_INS_PACKAGE_FQ_NAMES
+import org.jetbrains.kotlin.codegen.JvmMemberAccessOracleBE
 import org.jetbrains.kotlin.codegen.addCompiledPartsAndSort
 import org.jetbrains.kotlin.codegen.loadCompiledModule
 import org.jetbrains.kotlin.codegen.state.GenerationState
@@ -75,6 +76,7 @@
     private val externalSymbolTable: SymbolTable? = null,
     private val jvmGeneratorExtensions: JvmGeneratorExtensionsImpl = JvmGeneratorExtensionsImpl(configuration),
     private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null,
+    private val jvmMemberAccessOracle: JvmMemberAccessOracleBE? = null,
     private val ideCodegenSettings: IdeCodegenSettings = IdeCodegenSettings(),
 ) {
     /**
@@ -325,8 +327,7 @@
     }
 
     fun invokeLowerings(state: GenerationState, input: BackendInput): CodegenInput {
-        val (irModuleFragment, irBuiltIns, symbolTable, irProviders, extensions, backendExtension, irPluginContext) =
-            input
+        val (irModuleFragment, irBuiltIns, symbolTable, irProviders, extensions, backendExtension, irPluginContext) = input
         val irSerializer = if (
             state.configuration.get(JVMConfigurationKeys.SERIALIZE_IR, JvmSerializeIrMode.NONE) != JvmSerializeIrMode.NONE
         )
@@ -338,7 +339,12 @@
         )
         if (evaluatorFragmentInfoForPsi2Ir != null) {
             context.evaluatorData =
-                JvmEvaluatorData(mutableMapOf(), evaluatorFragmentInfoForPsi2Ir.methodIR, evaluatorFragmentInfoForPsi2Ir.typeArgumentsMap)
+                JvmEvaluatorData(
+                    mutableMapOf(),
+                    evaluatorFragmentInfoForPsi2Ir.methodIR,
+                    evaluatorFragmentInfoForPsi2Ir.typeArgumentsMap,
+                    jvmMemberAccessOracle
+                )
         }
         val generationExtensions = state.project.filteredExtensions
             .mapNotNull { it.getPlatformIntrinsicExtension(context) as? JvmIrIntrinsicExtension }
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SpecialAccess.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SpecialAccess.kt
index 6f464fb..a1e2e32 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SpecialAccess.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SpecialAccess.kt
@@ -17,6 +17,7 @@
 import org.jetbrains.kotlin.descriptors.Modality
 import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
 import org.jetbrains.kotlin.ir.builders.*
+import org.jetbrains.kotlin.ir.declarations.IrClass
 import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
 import org.jetbrains.kotlin.ir.declarations.IrFile
 import org.jetbrains.kotlin.ir.declarations.IrParameterKind
@@ -24,18 +25,24 @@
 import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
 import org.jetbrains.kotlin.ir.expressions.*
 import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
+import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
+import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl
 import org.jetbrains.kotlin.ir.overrides.isNonPrivate
 import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
 import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
 import org.jetbrains.kotlin.ir.symbols.IrSymbol
 import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl
+import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl
+import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
 import org.jetbrains.kotlin.ir.types.*
 import org.jetbrains.kotlin.ir.util.*
 import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
 import org.jetbrains.kotlin.load.kotlin.FacadeClassSource
+import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
+import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
 import org.jetbrains.org.objectweb.asm.Type
-import org.jetbrains.org.objectweb.asm.commons.Method
 
 /**
  * This lowering replaces member accesses that are illegal according to JVM accessibility rules with corresponding calls to the
@@ -66,6 +73,7 @@
 ) : IrElementTransformerVoidWithContext(), FileLoweringPass {
 
     private lateinit var inlineScopeResolver: IrInlineScopeResolver
+    private val jvmMemberAccessOracle = context?.evaluatorData?.jvmMemberAccessOracle
 
     override fun lower(irFile: IrFile) {
         if (context.evaluatorData == null) return
@@ -111,11 +119,11 @@
         }
 
         return when {
-            expression.symbol.owner.isGetter -> generateReflectiveAccessForGetter(expression)
-            expression.symbol.owner.isSetter -> generateReflectiveAccessForSetter(expression)
-            expression.dispatchReceiver == null -> generateReflectiveStaticCall(expression)
+            expression.symbol.owner.isGetter -> generateSuitableAccessForGetter(expression)
+            expression.symbol.owner.isSetter -> generateSuitableAccessForSetter(expression)
+            expression.dispatchReceiver == null -> generateSuitableStaticCall(expression)
             superQualifier != null -> generateInvokeSpecialForCall(expression, superQualifier)
-            else -> generateReflectiveMethodInvocation(expression)
+            else -> generateSuitableMethodInvocation(expression)
         }
     }
 
@@ -126,7 +134,7 @@
         return if (field.isAccessible()) {
             expression
         } else {
-            generateReflectiveFieldGet(expression)
+            generateSuitableFieldGet(expression)
         }
     }
 
@@ -139,7 +147,7 @@
         } else if (field.owner.correspondingPropertySymbol?.owner?.isConst == true || (field.owner.isFromJava() && field.owner.isFinal)) {
             generateThrowIllegalAccessException(expression)
         } else {
-            generateReflectiveFieldSet(expression)
+            generateSuitableFieldSet(expression)
         }
     }
 
@@ -150,7 +158,7 @@
         return if (callee.isAccessible()) {
             expression
         } else {
-            generateReflectiveConstructorInvocation(expression)
+            generateReflectiveConstructorInvocationOrNullIfAccessible(expression) ?: expression
         }
     }
 
@@ -161,7 +169,7 @@
         return if (callee.isAccessible()) {
             expression
         } else {
-            generateReflectiveAccessForCompanion(expression)
+            generateSuitableAccessForCompanion(expression)
         }
     }
 
@@ -247,7 +255,7 @@
     private fun IrBuilderWithScope.methodInvoke(
         method: IrExpression,
         receiver: IrExpression,
-        arguments: List<IrExpression>
+        arguments: List<IrExpression>,
     ): IrExpression =
         irCall(reflectSymbols.javaLangReflectMethodInvoke).apply {
             this.arguments[0] = method
@@ -277,34 +285,54 @@
             this.arguments[1] = irVararg(context.irBuiltIns.anyNType, arguments.map { coerceToUnboxed(it) })
         }
 
+    private val IrType.asmType: Type get() = context.defaultTypeMapper.mapType(this, TypeMappingMode.CLASS_DECLARATION)
+
     /**
      * Specific reflective "patches"
      */
 
-    private fun generateReflectiveMethodInvocation(
+    private fun redirectCallIfPrivateMultifileMember(call: IrCall, functionName: String) {
+        val declaringClass = getDeclaredClassType(call)
+        val function = call.symbol.owner
+        val classPartStubOrThis = declaringClass.classPartForMultifileFacadeOrThis
+        if (classPartStubOrThis != declaringClass && DescriptorVisibilities.isPrivate(call.symbol.owner.visibility)) {
+            val newSymbol = IrFactoryImpl.createSimpleFunction(
+                UNDEFINED_OFFSET,
+                UNDEFINED_OFFSET,
+                IrDeclarationOrigin.SYNTHETIC_ACCESSOR,
+                Name.identifier("$functionName$${classPartStubOrThis.classOrFail.owner.name}"),
+                DescriptorVisibilities.PRIVATE,
+                function.isInline,
+                function.isExpect,
+                function.returnType,
+                function.modality,
+                IrSimpleFunctionSymbolImpl(),
+                function.isTailrec,
+                function.isSuspend,
+                function.isOperator,
+                function.isInfix
+            ).apply {
+                parent = classPartStubOrThis.classOrFail.owner
+            }.symbol
+            call.symbol = newSymbol
+        }
+    }
+
+    private fun generateReflectiveMethodInvocationOrNullIfAccessible(
         declaringClass: IrType,
         signature: JvmMethodSignature,
         receiver: IrExpression?, // null => static method on `declaringClass`
         arguments: List<IrExpression>,
         returnType: IrType,
         symbol: IrSimpleFunctionSymbol,
-    ): IrExpression {
-        val classPartStubOrThis = declaringClass.classPartForMultifileFacadeOrThis
-        val signatureToLookup = if (classPartStubOrThis != declaringClass && DescriptorVisibilities.isPrivate(symbol.owner.visibility)) {
-            JvmMethodSignature(
-                Method(
-                    "${signature.asmMethod.name}$${classPartStubOrThis.classOrFail.owner.name}",
-                    signature.asmMethod.descriptor
-                ),
-                signature.parameters
-            )
-        } else {
-            signature
+    ): IrExpression? {
+        if (jvmMemberAccessOracle?.tryMakeMethodAccessible(declaringClass.asmType, signature) == true) {
+            return null
         }
         return context.createJvmIrBuilder(symbol).irBlock(resultType = returnType) {
             val methodVar =
                 createTmpVariable(
-                    getDeclaredMethod(javaClassObject(classPartStubOrThis), signatureToLookup),
+                    getDeclaredMethod(javaClassObject(declaringClass), signature),
                     nameHint = "method",
                     irType = reflectSymbols.javaLangReflectMethod.defaultType
                 )
@@ -323,7 +351,7 @@
             arguments[0] = expression
         }
 
-    private fun generateReflectiveMethodInvocation(call: IrCall): IrExpression {
+    private fun generateSuitableMethodInvocation(call: IrCall): IrExpression {
         val targetFunction = call.symbol.owner
         val arguments = (targetFunction.parameters zip call.arguments).mapNotNull { (param, arg) ->
             when {
@@ -332,37 +360,44 @@
                 else -> null
             }
         }
-
-        return generateReflectiveMethodInvocation(
+        redirectCallIfPrivateMultifileMember(call, context.defaultMethodSignatureMapper.mapFunctionName(targetFunction))
+        val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(targetFunction)
+        return generateReflectiveMethodInvocationOrNullIfAccessible(
             getDeclaredClassType(call),
-            context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(targetFunction),
+            signature,
             call.dispatchReceiver,
             arguments,
             call.type,
             call.symbol
-        )
+        ) ?: call
     }
 
-    private fun generateReflectiveStaticCall(call: IrCall): IrExpression {
+    private fun generateSuitableStaticCall(call: IrCall): IrExpression {
         assert(call.dispatchReceiver == null) { "Assumed-to-be static call with a dispatch receiver" }
-        return generateReflectiveMethodInvocation(
+        redirectCallIfPrivateMultifileMember(call, context.defaultMethodSignatureMapper.mapFunctionName(call.symbol.owner))
+        val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(call.symbol.owner)
+        return generateReflectiveMethodInvocationOrNullIfAccessible(
             call.symbol.owner.parentAsClass.defaultType,
-            context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(call.symbol.owner),
+            signature,
             null, // static call
             call.nonDispatchArguments.filterNotNull(),
             call.type,
             call.symbol
-        )
+        ) ?: call
     }
 
-    private fun generateReflectiveConstructorInvocation(call: IrConstructorCall): IrExpression =
-        context.createJvmIrBuilder(call.symbol)
+    private fun generateReflectiveConstructorInvocationOrNullIfAccessible(call: IrConstructorCall): IrExpression? {
+        val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(call.symbol.owner)
+        if (jvmMemberAccessOracle?.tryMakeMethodAccessible(call.symbol.owner.parentAsClass.defaultType.asmType, signature) == true) {
+            return null
+        }
+        return context.createJvmIrBuilder(call.symbol)
             .irBlock(resultType = call.type) {
                 val constructorVar =
                     createTmpVariable(
                         getDeclaredConstructor(
                             javaClassObject(call.symbol.owner.parentAsClass.defaultType),
-                            this@SpecialAccessLowering.context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(call.symbol.owner)
+                            signature
                         ),
                         nameHint = "constructor",
                         irType = reflectSymbols.javaLangReflectConstructor.defaultType
@@ -370,18 +405,20 @@
                 +constructorSetAccessible(irGet(constructorVar))
                 +constructorNewInstance(irGet(constructorVar), call.nonDispatchArguments.filterNotNull())
             }
+    }
 
-    private fun generateReflectiveFieldGet(
+    private fun generateReflectiveFieldGetOrNullIfAccessible(
         declaringClass: IrType,
         fieldName: String,
         fieldType: IrType,
         instance: IrExpression?, // null ==> static field on `declaringClass`
         symbol: IrSymbol,
-    ): IrExpression =
-        context.createJvmIrBuilder(symbol)
+    ): IrExpression? {
+        if (jvmMemberAccessOracle?.tryMakeFieldAccessible(declaringClass.asmType, fieldName) == true) return null
+        return context.createJvmIrBuilder(symbol)
             .irBlock(resultType = fieldType) {
                 val classVar = createTmpVariable(
-                    javaClassObject(declaringClass.classPartForMultifileFacadeOrThis),
+                    javaClassObject(declaringClass),
                     nameHint = "klass",
                     irType = symbols.kClassJavaPropertyGetter.returnType
                 )
@@ -393,6 +430,7 @@
                 +fieldSetAccessible(irGet(fieldVar))
                 +coerceResult(fieldGet(irGet(fieldVar), instance ?: irGet(classVar)), fieldType)
             }
+    }
 
     private val IrType.classPartForMultifileFacadeOrThis: IrType
         get() {
@@ -420,29 +458,30 @@
         }
 
 
-    private fun generateReflectiveFieldGet(getField: IrGetField): IrExpression =
-        generateReflectiveFieldGet(
+    private fun generateSuitableFieldGet(getField: IrGetField): IrExpression =
+        generateReflectiveFieldGetOrNullIfAccessible(
             getField.symbol.owner.parentClassOrNull!!.defaultType,
             getField.symbol.owner.name.asString(),
             getField.type,
             getField.receiver,
             getField.symbol
-        )
+        ) ?: getField
 
-    private fun generateReflectiveFieldSet(
+    private fun generateSuitableFieldSet(
         declaringClass: IrType,
         fieldName: String,
         value: IrExpression,
         type: IrType,
         instance: IrExpression?,
-        symbol: IrSymbol
-    ): IrExpression {
+        symbol: IrSymbol,
+    ): IrExpression? {
+        if (jvmMemberAccessOracle?.tryMakeFieldAccessible(declaringClass.asmType, fieldName) == true) return null
         return context.createJvmIrBuilder(symbol)
             .irBlock(resultType = type) {
                 val fieldVar =
                     createTmpVariable(
                         getDeclaredField(
-                            javaClassObject(declaringClass.classPartForMultifileFacadeOrThis),
+                            javaClassObject(declaringClass),
                             fieldName
                         ),
                         nameHint = "field",
@@ -453,15 +492,15 @@
             }
     }
 
-    private fun generateReflectiveFieldSet(setField: IrSetField): IrExpression =
-        generateReflectiveFieldSet(
+    private fun generateSuitableFieldSet(setField: IrSetField): IrExpression =
+        generateSuitableFieldSet(
             setField.symbol.owner.parentClassOrNull!!.defaultType,
             setField.symbol.owner.name.asString(),
             setField.value,
             setField.type,
             setField.receiver,
             setField.symbol,
-        )
+        ) ?: setField
 
     private fun isPresentInBytecode(accessor: IrSimpleFunction): Boolean {
         val property = accessor.correspondingPropertySymbol!!.owner
@@ -483,63 +522,97 @@
         callsOnCompanionObjects[call]?.let {
             val parentAsClass = it.owner.parentAsClass
             if (!parentAsClass.isJvmInterface) {
-                return parentAsClass.defaultType to null
+                return parentAsClass.defaultType.classPartForMultifileFacadeOrThis to null
             }
         }
 
         val type = getDeclaredClassType(call)
-        return type to call.dispatchReceiver
+        return type.classPartForMultifileFacadeOrThis to call.dispatchReceiver
     }
 
     private fun getDeclaredClassType(call: IrCall) =
         call.superQualifierSymbol?.defaultType ?: call.symbol.owner.resolveFakeOverrideOrFail().parentAsClass.defaultType
 
-    private fun generateReflectiveAccessForGetter(call: IrCall): IrExpression {
+    private fun generateSuitableAccessForGetter(call: IrCall): IrExpression {
         val realGetter = call.symbol.owner.resolveFakeOverrideOrFail()
 
         if (isPresentInBytecode(realGetter)) {
-            return generateReflectiveMethodInvocation(
+            redirectCallIfPrivateMultifileMember(call, context.defaultMethodSignatureMapper.mapFunctionName(realGetter))
+            val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(realGetter)
+            return generateReflectiveMethodInvocationOrNullIfAccessible(
                 realGetter.parentAsClass.defaultType,
-                context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(realGetter),
+                signature,
                 call.dispatchReceiver,
                 call.nonDispatchArguments.filterNotNull(),
                 realGetter.returnType,
                 realGetter.symbol
-            )
+            ) ?: call
         }
-
         val (fieldLocation, instance) = fieldLocationAndReceiver(call)
-        return generateReflectiveFieldGet(
+        return generateReflectiveFieldGetOrNullIfAccessible(
             fieldLocation,
             realGetter.correspondingPropertySymbol!!.owner.name.asString(),
             realGetter.returnType,
             instance,
             call.symbol,
+        ) ?: IrGetFieldImpl(
+            call.startOffset,
+            call.endOffset,
+            getOrCreateSymbolForField(instance, fieldLocation, realGetter),
+            realGetter.returnType,
+            instance
         )
     }
 
-    private fun generateReflectiveAccessForSetter(call: IrCall): IrExpression {
+    private fun getOrCreateSymbolForField(accessReceiver: IrExpression?, fieldLocation: IrType, accessor: IrSimpleFunction): IrFieldSymbol {
+        val field = accessor.correspondingPropertySymbol!!.owner.backingField!!
+        if (field.parent as IrClass? == fieldLocation.classOrNull) return field.symbol
+        return IrFactoryImpl.createField(
+            UNDEFINED_OFFSET, UNDEFINED_OFFSET,
+            IrDeclarationOrigin.SYNTHETIC_ACCESSOR,
+            field.name,
+            DescriptorVisibilities.PRIVATE,
+            IrFieldSymbolImpl(),
+            field.type,
+            field.isFinal,
+            accessReceiver == null,
+            field.isExternal
+        ).apply {
+            parent = fieldLocation.classOrFail.owner
+        }.symbol
+    }
+
+    private fun generateSuitableAccessForSetter(call: IrCall): IrExpression {
         val realSetter = call.symbol.owner.resolveFakeOverrideOrFail()
 
         if (isPresentInBytecode(realSetter)) {
-            return generateReflectiveMethodInvocation(
+            redirectCallIfPrivateMultifileMember(call, context.defaultMethodSignatureMapper.mapFunctionName(realSetter))
+            val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(realSetter)
+            return generateReflectiveMethodInvocationOrNullIfAccessible(
                 realSetter.parentAsClass.defaultType,
-                context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(realSetter),
+                signature,
                 call.dispatchReceiver,
                 call.nonDispatchArguments.filterNotNull(),
                 realSetter.returnType,
                 call.symbol
-            )
+            ) ?: call
         }
 
         val (fieldLocation, receiver) = fieldLocationAndReceiver(call)
-        return generateReflectiveFieldSet(
+        return generateSuitableFieldSet(
             fieldLocation,
             realSetter.correspondingPropertySymbol!!.owner.name.asString(),
             call.arguments.last()!!,
             call.type,
             receiver,
             call.symbol
+        ) ?: IrSetFieldImpl(
+            call.startOffset,
+            call.endOffset,
+            getOrCreateSymbolForField(receiver, fieldLocation, realSetter),
+            receiver,
+            call.arguments.last()!!,
+            call.type,
         )
     }
 
@@ -583,12 +656,12 @@
         }
     }
 
-    private fun generateReflectiveAccessForCompanion(call: IrGetObjectValue): IrExpression =
-        generateReflectiveFieldGet(
+    private fun generateSuitableAccessForCompanion(call: IrGetObjectValue): IrExpression =
+        generateReflectiveFieldGetOrNullIfAccessible(
             call.symbol.owner.parentAsClass.defaultType,
             "Companion",
             call.type,
             null,
             call.symbol
-        )
+        ) ?: call
 }
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmEvaluatorData.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmEvaluatorData.kt
index e06ca6ad..f9c18d7 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmEvaluatorData.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmEvaluatorData.kt
@@ -5,6 +5,7 @@
 
 package org.jetbrains.kotlin.backend.jvm
 
+import org.jetbrains.kotlin.codegen.JvmMemberAccessOracleBE
 import org.jetbrains.kotlin.ir.declarations.IrFunction
 import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
 import org.jetbrains.kotlin.ir.types.IrType
@@ -18,5 +19,7 @@
     val evaluatorGeneratedFunction: IrFunction?,
     // If the code fragment captures some reified type parameters, we will need the corresponding arguments for the proper codegen
     // Bytecode might not contain all the required data, so we extract it from the debugger API and store here
-    val capturedTypeParametersMapping: Map<IrTypeParameterSymbol, IrType>
+    val capturedTypeParametersMapping: Map<IrTypeParameterSymbol, IrType>,
+    // `SpecialAccessLowering` uses this to avoid generating of excess reflective accesses to private members
+    val jvmMemberAccessOracle: JvmMemberAccessOracleBE?
 )