JVM IR: support multi-file classes

Without the `-Xmultifile-parts-inherit` mode for now.

This is implemented as follows: FileClassLowering collects information
about multifile parts and the corresponding facades, which a later
GenerateMultifileFacades phase uses to generate new IrFile instances and
add it to the module fragment that's being compiled.

Note that GenerateMultifileFacades is in the end of lowering phases
because delegates in the facade should be generated for all additional
functions generated by certain lowerings (default arguments,
JvmOverloads, etc.). If GenerateMultifileFacades was right after
FileClassLowering, they would still be generated, but we'd then process
them in lowerings mentioned above, which would result in duplicated
logic in the bytecode. There's a new bytecode text test which checks
that this doesn't happen for functions with default arguments.
This commit is contained in:
Alexander Udalov
2019-07-19 17:33:47 +02:00
parent 73a671af7c
commit 4be0e00071
40 changed files with 239 additions and 48 deletions
@@ -26,6 +26,7 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin {
object DEFAULT_IMPLS : IrDeclarationOriginImpl("DEFAULT_IMPLS")
object DEFAULT_IMPLS_BRIDGE : IrDeclarationOriginImpl("DEFAULT_IMPLS_BRIDGE")
object DEFAULT_IMPLS_BRIDGE_TO_SYNTHETIC : IrDeclarationOriginImpl("DEFAULT_IMPLS_BRIDGE_TO_SYNTHETIC", isSynthetic = true)
object MULTIFILE_BRIDGE : IrDeclarationOriginImpl("MULTIFILE_BRIDGE")
object FIELD_FOR_OUTER_THIS : IrDeclarationOriginImpl("FIELD_FOR_OUTER_THIS")
object LAMBDA_IMPL : IrDeclarationOriginImpl("LAMBDA_IMPL")
object FUNCTION_REFERENCE_IMPL : IrDeclarationOriginImpl("FUNCTION_REFERENCE_IMPL", isSynthetic = true)
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.util.ReferenceSymbolTable
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi2ir.PsiSourceManager
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
class JvmBackendContext(
val state: GenerationState,
@@ -63,6 +64,8 @@ class JvmBackendContext(
internal val localDelegatedProperties = mutableMapOf<IrClass, List<IrLocalDelegatedPropertySymbol>>()
internal val multifileFacadesToAdd = mutableMapOf<JvmClassName, MutableList<IrClass>>()
override var inVerbosePhase: Boolean = false
override val configuration get() = state.configuration
@@ -184,7 +184,8 @@ val jvmPhases = namedIrModulePhase(
description = "IR lowering",
lower = expectDeclarationsRemovingPhase then
fileClassPhase then
performByIrFile(lower = jvmFilePhases)
performByIrFile(lower = jvmFilePhases) then
generateMultifileFacadesPhase
)
class JvmLower(val context: JvmBackendContext) {
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.backend.jvm.codegen
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry
import org.jetbrains.kotlin.backend.jvm.lower.constantValue
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
@@ -50,14 +51,12 @@ open class ClassCodegen protected constructor(
val type: Type = typeMapper.mapClass(irClass)
private val fileEntry = context.psiSourceManager.getFileEntry(irClass.fileParent)
val visitor: ClassBuilder = createClassBuilder()
open fun createClassBuilder() = state.factory.newVisitor(
OtherOrigin(descriptor.psiElement, descriptor),
type,
listOf(File(fileEntry.name))
irClass.fileParent.loadSourceFilesInfo()
)
private var sourceMapper: DefaultSourceMapper? = null
@@ -97,9 +96,12 @@ open class ClassCodegen protected constructor(
continuationCodegen.generate()
}
/* TODO: Temporary workaround: ClassBuilder needs a pathless name. */
val shortName = File(fileEntry.name).name
visitor.visitSource(shortName, null)
val fileEntry = context.psiSourceManager.getFileEntry(irClass.fileParent)
if (fileEntry != null) {
/* TODO: Temporary workaround: ClassBuilder needs a pathless name. */
val shortName = File(fileEntry.name).name
visitor.visitSource(shortName, null)
}
for (declaration in irClass.declarations) {
generateDeclaration(declaration)
@@ -164,6 +166,14 @@ open class ClassCodegen protected constructor(
visitor.done()
}
private fun IrFile.loadSourceFilesInfo(): List<File> {
val entry = fileEntry
if (entry is MultifileFacadeFileEntry) {
return entry.partFiles.flatMap { it.loadSourceFilesInfo() }
}
return listOf(File(context.psiSourceManager.getFileEntry(this)!!.name))
}
companion object {
fun generate(irClass: IrClass, context: JvmBackendContext) {
val state = context.state
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
import org.jetbrains.kotlin.backend.jvm.intrinsics.JavaClassProperty
import org.jetbrains.kotlin.backend.jvm.intrinsics.Not
import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry
import org.jetbrains.kotlin.backend.jvm.lower.constantValue
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.codegen.*
@@ -140,11 +141,13 @@ class ExpressionCodegen(
if (offset < 0) {
return
}
val lineNumber = fileEntry.getLineNumber(offset) + 1
assert(lineNumber > 0)
if (lastLineNumber != lineNumber) {
lastLineNumber = lineNumber
mv.visitLineNumber(lineNumber, markNewLabel())
if (fileEntry != null) {
val lineNumber = fileEntry.getLineNumber(offset) + 1
assert(lineNumber > 0)
if (lastLineNumber != lineNumber) {
lastLineNumber = lineNumber
mv.visitLineNumber(lineNumber, markNewLabel())
}
}
}
@@ -188,7 +191,8 @@ class ExpressionCodegen(
// Although these are accessible from Java, the functions they bridge to already have the assertions.
irFunction.origin == IrDeclarationOrigin.BRIDGE_SPECIAL ||
irFunction.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE ||
irFunction.origin == JvmLoweredDeclarationOrigin.JVM_STATIC_WRAPPER
irFunction.origin == JvmLoweredDeclarationOrigin.JVM_STATIC_WRAPPER ||
irFunction.origin == JvmLoweredDeclarationOrigin.MULTIFILE_BRIDGE
if (!isInlineLambda && !isSyntheticOrBridge && !Visibilities.isPrivate(irFunction.visibility)) {
irFunction.extensionReceiverParameter?.let { generateNonNullAssertion(it) }
irFunction.valueParameters.forEach(::generateNonNullAssertion)
@@ -1086,7 +1090,9 @@ class ExpressionCodegen(
typeMapper.mapToCallableMethod(irCall.symbol.owner, isSuper)
private fun getOrCreateCallGenerator(element: IrFunctionAccessExpression, data: BlockInfo): IrCallGenerator {
if (!element.symbol.owner.isInlineFunctionCall(context)) {
if (!element.symbol.owner.isInlineFunctionCall(context) ||
classCodegen.irClass.fileParent.fileEntry is MultifileFacadeFileEntry
) {
return IrCallGenerator.DefaultCallGenerator
}
@@ -85,6 +85,7 @@ internal val DeclarationDescriptorWithSource.psiElement: PsiElement?
fun JvmBackendContext.getSourceMapper(declaration: IrClass): DefaultSourceMapper {
val sourceManager = this.psiSourceManager
val fileEntry = sourceManager.getFileEntry(declaration.fileParent)
check(fileEntry != null) { "No PSI file entry found for class: ${declaration.dump()}" }
// NOTE: apparently inliner requires the source range to cover the
// whole file the class is declared in rather than the class only.
// TODO: revise
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl
import org.jetbrains.kotlin.psi2ir.PsiSourceManager
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import java.util.*
@@ -107,6 +108,11 @@ private class FileClassLowering(val context: JvmBackendContext) : FileLoweringPa
if (fileClassInfo.withJvmMultifileClass) AsmUtil.asmTypeByFqNameWithoutInnerClasses(fileClassInfo.facadeClassFqName)
else null
context.state.factory.packagePartRegistry.addPart(irFile.fqName, partClassType.internalName, facadeClassType?.internalName)
if (facadeClassType != null) {
val jvmClassName = JvmClassName.byInternalName(facadeClassType.internalName)
context.multifileFacadesToAdd.getOrPut(jvmClassName) { ArrayList() }.add(this)
}
}
// TODO file annotations
}
@@ -0,0 +1,135 @@
/*
* Copyright 2010-2019 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.deepCopyWithWrappedDescriptors
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.backend.common.lower.InitializersLowering
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.PhaserState
import org.jetbrains.kotlin.backend.common.phaser.SameTypeCompilerPhase
import org.jetbrains.kotlin.backend.common.phaser.namedIrModulePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.fileParent
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
import org.jetbrains.kotlin.ir.SourceManager
import org.jetbrains.kotlin.ir.SourceRangeInfo
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
internal val generateMultifileFacadesPhase = namedIrModulePhase(
name = "GenerateMultifileFacades",
description = "Generate JvmMultifileClass facades, based on the information provided by FileClassLowering",
prerequisite = setOf(fileClassPhase),
lower = object : SameTypeCompilerPhase<JvmBackendContext, IrModuleFragment> {
override fun invoke(
phaseConfig: PhaseConfig,
phaserState: PhaserState<IrModuleFragment>,
context: JvmBackendContext,
input: IrModuleFragment
): IrModuleFragment {
input.files.addAll(generateMultifileFacades(input.descriptor, context))
context.multifileFacadesToAdd.clear()
return input
}
}
)
internal class MultifileFacadeFileEntry(
private val className: JvmClassName,
val partFiles: List<IrFile>
) : SourceManager.FileEntry {
override val name: String
get() = "<multi-file facade $className>"
override val maxOffset: Int
get() = UNDEFINED_OFFSET
override fun getSourceRangeInfo(beginOffset: Int, endOffset: Int): SourceRangeInfo =
error("Multifile facade doesn't support debug info: $className")
override fun getLineNumber(offset: Int): Int =
error("Multifile facade doesn't support debug info: $className")
override fun getColumnNumber(offset: Int): Int =
error("Multifile facade doesn't support debug info: $className")
}
private fun generateMultifileFacades(module: ModuleDescriptor, context: JvmBackendContext): List<IrFile> =
context.multifileFacadesToAdd.map { (jvmClassName, partClasses) ->
val fileEntry = MultifileFacadeFileEntry(jvmClassName, partClasses.map(IrClass::fileParent))
val file = IrFileImpl(fileEntry, EmptyPackageFragmentDescriptor(module, jvmClassName.packageFqName))
val facadeClass = buildClass {
name = jvmClassName.fqNameForTopLevelClassMaybeWithDollars.shortName()
}.apply {
parent = file
createImplicitParameterDeclarationWithWrappedDescriptor()
}
file.declarations.add(facadeClass)
for (partClass in partClasses) {
for (member in partClass.declarations) {
member.createMultifileDelegateIfNeeded(context, facadeClass)
}
}
file
}
private fun IrDeclaration.createMultifileDelegateIfNeeded(context: JvmBackendContext, facadeClass: IrClass) {
if (this !is IrFunction ||
Visibilities.isPrivate(visibility) ||
name == InitializersLowering.clinitName ||
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR
) return
// TODO: perform copy of the signature only, without body
val function = deepCopyWithSymbols(facadeClass)
function.body = context.createIrBuilder(symbol).irBlockBody {
val functionForCall = computeFunctionForCall()
+irReturn(irCall(functionForCall).also { call ->
function.extensionReceiverParameter?.let { parameter ->
call.extensionReceiver = irGet(parameter)
}
for (parameter in function.valueParameters) {
call.putValueArgument(parameter.index, irGet(parameter))
}
})
}
function.origin = JvmLoweredDeclarationOrigin.MULTIFILE_BRIDGE
facadeClass.declarations.add(function)
}
// This deep copy is needed while we still use KotlinTypeMapper to map signatures in method calls. Without it, KotlinTypeMapper takes
// the descriptor and assumes that a call to that function must go through the public facade (see mapOwner call in mapToCallableMethod),
// which results in endless recursion here. With this copy, we trick it into thinking that the function is actually a static function
// in a class whose name is the name of the multi-file part, as opposed to being top level.
private fun IrFunction.computeFunctionForCall(): IrFunction {
val property = (this as? IrSimpleFunction)?.correspondingPropertySymbol?.owner
?: return deepCopyWithWrappedDescriptors(parent)
val propertyCopy = property.deepCopyWithWrappedDescriptors(property.parent)
return when (this) {
property.getter -> propertyCopy.getter!!
property.setter -> propertyCopy.setter!!
else -> error("Property accessor must be getter or setter: ${dump()}")
}
}
@@ -95,6 +95,6 @@ class PsiSourceManager : SourceManager {
fun getKtFile(irFile: IrFile): KtFile? =
(irFile.fileEntry as? PsiFileEntry)?.let { ktFileByFileEntry[it] }
override fun getFileEntry(irFile: IrFile): SourceManager.FileEntry =
fileEntriesByIrFile[irFile]!!
override fun getFileEntry(irFile: IrFile): SourceManager.FileEntry? =
fileEntriesByIrFile[irFile]
}
@@ -1,6 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@file:JvmName("Test")
@@ -1,6 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@file:JvmName("Test")
@@ -0,0 +1,16 @@
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: facade.kt
@file:JvmName("Facade")
@file:JvmMultifileClass
package test
@JvmOverloads
fun foo(o: String = "O", k: String = "K"): String = o + k
// FILE: test.kt
fun box(): String =
Class.forName("test.Facade").getMethod("foo").invoke(null) as String
@@ -1,6 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: box.kt
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: 1.kt
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: box.kt
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: box.kt
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: Baz.java
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: Baz.java
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: Baz.java
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_LIGHT_ANALYSIS
// WITH_RUNTIME
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_LIGHT_ANALYSIS
// WITH_RUNTIME
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_LIGHT_ANALYSIS
// WITH_RUNTIME
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: foo.kt
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: 1/part.kt
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@@ -1,5 +1,4 @@
// !LANGUAGE: +MultiPlatformProjects
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: common.kt
@@ -1,6 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_REFLECT
// FILE: Test1.kt
@@ -1,7 +1,5 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_REFLECT
// FILE: 1.kt
@file:JvmMultifileClass
@@ -1,6 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@file:kotlin.jvm.JvmMultifileClass
@@ -1,6 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@file:kotlin.jvm.JvmMultifileClass
@@ -1,6 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@file:kotlin.jvm.JvmMultifileClass
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// FILE: 1.kt
// WITH_RUNTIME
@@ -0,0 +1,19 @@
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: part1.kt
@file:JvmName("Facade")
@file:JvmMultifileClass
fun foo(o: String = "O", k: String = "K"): String = o + k
// FILE: part2.kt
@file:JvmName("Facade")
@file:JvmMultifileClass
val bar = ""
// Default argument handling should not happen in the facade, only in parts.
// @Facade.class:
// 0 LDC
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// FILE: A.kt
@@ -14899,6 +14899,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/jvmOverloads/manyParameters.kt");
}
@TestMetadata("multifileClass.kt")
public void testMultifileClass() throws Exception {
runTest("compiler/testData/codegen/box/jvmOverloads/multifileClass.kt");
}
@TestMetadata("multipleDefaultParameters.kt")
public void testMultipleDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/jvmOverloads/multipleDefaultParameters.kt");
@@ -3120,6 +3120,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("defaultFunctionInMultifileClass.kt")
public void testDefaultFunctionInMultifileClass() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/multifileClasses/defaultFunctionInMultifileClass.kt");
}
@TestMetadata("optimizedMultifileClassFacadeMethods.kt")
public void testOptimizedMultifileClassFacadeMethods() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/multifileClasses/optimizedMultifileClassFacadeMethods.kt");
@@ -14899,6 +14899,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/jvmOverloads/manyParameters.kt");
}
@TestMetadata("multifileClass.kt")
public void testMultifileClass() throws Exception {
runTest("compiler/testData/codegen/box/jvmOverloads/multifileClass.kt");
}
@TestMetadata("multipleDefaultParameters.kt")
public void testMultipleDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/jvmOverloads/multipleDefaultParameters.kt");
@@ -13789,6 +13789,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/jvmOverloads/manyParameters.kt");
}
@TestMetadata("multifileClass.kt")
public void testMultifileClass() throws Exception {
runTest("compiler/testData/codegen/box/jvmOverloads/multifileClass.kt");
}
@TestMetadata("multipleDefaultParameters.kt")
public void testMultipleDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/jvmOverloads/multipleDefaultParameters.kt");
@@ -3090,6 +3090,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("defaultFunctionInMultifileClass.kt")
public void testDefaultFunctionInMultifileClass() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/multifileClasses/defaultFunctionInMultifileClass.kt");
}
@TestMetadata("optimizedMultifileClassFacadeMethods.kt")
public void testOptimizedMultifileClassFacadeMethods() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/multifileClasses/optimizedMultifileClassFacadeMethods.kt");
@@ -1 +0,0 @@
// IGNORE_BACKEND: JVM_IR