[klib] Add an option to write out header klibs

The header klib is supposed to only contain the public abi of the module
similar to jvm-abi-gen. It is intended to be used as a dependency for other
klib compilations instead of the full klib for compilation avoidance.

^KT-60807
diff --git a/compiler/cli/cli-common/gen/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArgumentsCopyGenerated.kt b/compiler/cli/cli-common/gen/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArgumentsCopyGenerated.kt
index 7bbacf1..e5aa6d4 100644
--- a/compiler/cli/cli-common/gen/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArgumentsCopyGenerated.kt
+++ b/compiler/cli/cli-common/gen/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArgumentsCopyGenerated.kt
@@ -45,6 +45,7 @@
     to.generateNoExitTestRunner = from.generateNoExitTestRunner
     to.generateTestRunner = from.generateTestRunner
     to.generateWorkerTestRunner = from.generateWorkerTestRunner
+    to.headerKlibPath = from.headerKlibPath
     to.includeBinaries = from.includeBinaries?.copyOf()
     to.includes = from.includes?.copyOf()
     to.incrementalCacheDir = from.incrementalCacheDir
@@ -104,4 +105,4 @@
     to.workerExceptionHandling = from.workerExceptionHandling
 
     return to
-}
+}
\ No newline at end of file
diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt
index 6c3f5f3..e52bc25 100644
--- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt
+++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt
@@ -372,6 +372,12 @@
     @Argument(value = "-Xmetadata-klib", description = "Produce a klib that only contains the declarations metadata")
     var metadataKlib: Boolean = false
 
+    @Argument(
+        value = "-Xheader-klib-path",
+        description = "Save a klib that only contains the public abi to the given path"
+    )
+    var headerKlibPath: String? = null
+
     @Argument(value = "-Xdebug-prefix-map", valueDescription = "<old1=new1,old2=new2,...>", description = "Remap file source directory paths in debug info")
     var debugPrefixMap: Array<String>? = null
 
diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataKlibSerializer.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataKlibSerializer.kt
index 956c092..1c0e718 100644
--- a/compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataKlibSerializer.kt
+++ b/compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataKlibSerializer.kt
@@ -58,7 +58,8 @@
             project,
             exportKDoc = false,
             skipExpects = false,
-            includeOnlyModuleContent = true
+            includeOnlyModuleContent = true,
+            produceHeaderKlib = false,
         ).serializeModule(module)
 
         buildKotlinMetadataLibrary(configuration, serializedMetadata, destDir)
diff --git a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementSerializer.kt b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementSerializer.kt
index 76353fa..d649992 100644
--- a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementSerializer.kt
+++ b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementSerializer.kt
@@ -74,6 +74,7 @@
     private val serializeTypeTableToFunction: Boolean,
     private val typeApproximator: AbstractTypeApproximator,
     private val languageVersionSettings: LanguageVersionSettings,
+    private val produceHeaderKlib: Boolean,
 ) {
     private val contractSerializer = FirContractSerializer()
     private val providedDeclarationsService = session.providedDeclarationsForMetadataService
@@ -89,7 +90,8 @@
 
         fun addDeclaration(declaration: FirDeclaration, onUnsupportedDeclaration: (FirDeclaration) -> Unit) {
             if (declaration is FirMemberDeclaration) {
-                if (!declaration.shouldBeSerialized(actualizedExpectDeclarations)) return
+                if (!declaration.isNotExpectOrShouldBeSerialized(actualizedExpectDeclarations)) return
+                if (!declaration.isNotPrivateOrShouldBeSerialized(produceHeaderKlib)) return
                 when (declaration) {
                     is FirProperty -> propertyProto(declaration)?.let { builder.addProperty(it) }
                     is FirSimpleFunction -> functionProto(declaration)?.let { builder.addFunction(it) }
@@ -178,6 +180,7 @@
          */
         if (regularClass != null && regularClass.classKind != ClassKind.ENUM_ENTRY) {
             for (constructor in regularClass.constructors()) {
+                if (!constructor.isNotPrivateOrShouldBeSerialized(produceHeaderKlib)) continue
                 builder.addConstructor(constructorProto(constructor))
             }
 
@@ -203,6 +206,7 @@
 
         for (declaration in callableMembers) {
             if (declaration !is FirEnumEntry && declaration.isStatic) continue // ??? Miss values() & valueOf()
+            if (!declaration.isNotPrivateOrShouldBeSerialized(produceHeaderKlib)) continue
             when (declaration) {
                 is FirProperty -> propertyProto(declaration)?.let { builder.addProperty(it) }
                 is FirSimpleFunction -> functionProto(declaration)?.let { builder.addFunction(it) }
@@ -1042,7 +1046,7 @@
         FirElementSerializer(
             session, scopeSession, declaration, Interner(typeParameters), extension,
             typeTable, versionRequirementTable, serializeTypeTableToFunction = false,
-            typeApproximator, languageVersionSettings
+            typeApproximator, languageVersionSettings, produceHeaderKlib
         )
 
     val stringTable: FirElementAwareStringTable
@@ -1148,6 +1152,7 @@
             extension: FirSerializerExtension,
             typeApproximator: AbstractTypeApproximator,
             languageVersionSettings: LanguageVersionSettings,
+            produceHeaderKlib: Boolean = false,
         ): FirElementSerializer =
             FirElementSerializer(
                 session, scopeSession, null,
@@ -1155,6 +1160,7 @@
                 serializeTypeTableToFunction = false,
                 typeApproximator,
                 languageVersionSettings,
+                produceHeaderKlib,
             )
 
         @JvmStatic
@@ -1171,6 +1177,7 @@
                 versionRequirementTable = null, serializeTypeTableToFunction = true,
                 typeApproximator,
                 languageVersionSettings,
+                produceHeaderKlib = false,
             )
 
         @JvmStatic
@@ -1182,16 +1189,17 @@
             parentSerializer: FirElementSerializer?,
             typeApproximator: AbstractTypeApproximator,
             languageVersionSettings: LanguageVersionSettings,
+            produceHeaderKlib: Boolean = false,
         ): FirElementSerializer {
             val parentClassId = klass.symbol.classId.outerClassId
             val parent = if (parentClassId != null && !parentClassId.isLocal) {
                 val parentClass = session.symbolProvider.getClassLikeSymbolByClassId(parentClassId)!!.fir as FirRegularClass
                 parentSerializer ?: create(
                     session, scopeSession, parentClass, extension, null, typeApproximator,
-                    languageVersionSettings,
+                    languageVersionSettings, produceHeaderKlib
                 )
             } else {
-                createTopLevel(session, scopeSession, extension, typeApproximator, languageVersionSettings)
+                createTopLevel(session, scopeSession, extension, typeApproximator, languageVersionSettings, produceHeaderKlib)
             }
 
             // Calculate type parameter ids for the outer class beforehand, as it would've had happened if we were always
@@ -1212,6 +1220,7 @@
                 serializeTypeTableToFunction = false,
                 typeApproximator,
                 languageVersionSettings,
+                produceHeaderKlib,
             )
             for (typeParameter in klass.typeParameters) {
                 if (typeParameter !is FirTypeParameter) continue
diff --git a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/firKlibSerialization.kt b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/firKlibSerialization.kt
index 345628f..0d8a166 100644
--- a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/firKlibSerialization.kt
+++ b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/firKlibSerialization.kt
@@ -22,12 +22,14 @@
     actualizedExpectDeclarations: Set<FirDeclaration>?,
     serializerExtension: FirKLibSerializerExtension,
     languageVersionSettings: LanguageVersionSettings,
+    produceHeaderKlib: Boolean = false,
 ): ProtoBuf.PackageFragment {
     val approximator = TypeApproximatorForMetadataSerializer(session)
     val packageSerializer = FirElementSerializer.createTopLevel(
         session, scopeSession, serializerExtension,
         approximator,
-        languageVersionSettings
+        languageVersionSettings,
+        produceHeaderKlib
     )
 
     // TODO: typealiases (see klib serializer)
@@ -40,13 +42,16 @@
 
     fun List<FirClassSymbol<*>>.makeClassesProtoWithNested() {
         val classSymbols = this
-            .filter { it.fir.shouldBeSerialized(actualizedExpectDeclarations) }
+            .filter {
+                it.fir.isNotExpectOrShouldBeSerialized(actualizedExpectDeclarations) &&
+                        it.fir.isNotPrivateOrShouldBeSerialized(produceHeaderKlib)
+            }
             .sortedBy { it.classId.asFqNameString() }
         for (symbol in classSymbols) {
             val klass = symbol.fir
             val classSerializer = FirElementSerializer.create(
                 session, scopeSession, klass, serializerExtension, null,
-                approximator, languageVersionSettings
+                approximator, languageVersionSettings, produceHeaderKlib
             )
             val index = classSerializer.stringTable.getFqNameIndex(klass)
 
@@ -60,7 +65,8 @@
     }
 
     val hasTopLevelDeclarations = file.declarations.any {
-        it is FirMemberDeclaration && it.shouldBeSerialized(actualizedExpectDeclarations) &&
+        it is FirMemberDeclaration && it.isNotExpectOrShouldBeSerialized(actualizedExpectDeclarations) &&
+                it.isNotPrivateOrShouldBeSerialized(produceHeaderKlib) &&
                 (it is FirProperty || it is FirSimpleFunction || it is FirTypeAlias)
     }
 
diff --git a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/serializationUtil.kt b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/serializationUtil.kt
index f88099d..ef0b7ff 100644
--- a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/serializationUtil.kt
+++ b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/serializationUtil.kt
@@ -10,6 +10,7 @@
 import org.jetbrains.kotlin.fir.declarations.FirDeclaration
 import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
 import org.jetbrains.kotlin.fir.declarations.utils.isExpect
+import org.jetbrains.kotlin.fir.declarations.utils.visibility
 import org.jetbrains.kotlin.fir.diagnostics.ConeIntermediateDiagnostic
 import org.jetbrains.kotlin.fir.languageVersionSettings
 import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
@@ -47,6 +48,10 @@
     )
 }
 
-fun FirMemberDeclaration.shouldBeSerialized(actualizedExpectDeclaration: Set<FirDeclaration>?): Boolean {
+fun FirMemberDeclaration.isNotExpectOrShouldBeSerialized(actualizedExpectDeclaration: Set<FirDeclaration>?): Boolean {
     return !isExpect || actualizedExpectDeclaration == null || this !in actualizedExpectDeclaration
 }
+
+fun FirMemberDeclaration.isNotPrivateOrShouldBeSerialized(produceHeaderKlib: Boolean): Boolean {
+    return !produceHeaderKlib || visibility.isPublicAPI
+}
diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileSerializer.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileSerializer.kt
index 777cb28..43c283c 100644
--- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileSerializer.kt
+++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileSerializer.kt
@@ -27,6 +27,7 @@
 import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.types.Variance
 import org.jetbrains.kotlin.utils.filterIsInstanceAnd
+import org.jetbrains.kotlin.utils.addToStdlib.applyIf
 import java.io.File
 import org.jetbrains.kotlin.backend.common.serialization.proto.AccessorIdSignature as ProtoAccessorIdSignature
 import org.jetbrains.kotlin.backend.common.serialization.proto.CommonIdSignature as ProtoCommonIdSignature
@@ -118,6 +119,7 @@
     private val languageVersionSettings: LanguageVersionSettings,
     private val bodiesOnlyForInlines: Boolean = false,
     private val normalizeAbsolutePaths: Boolean = false,
+    private val skipPrivateApi: Boolean = false,
     private val sourceBaseDirs: Collection<String>
 ) {
     private val loopIndex = hashMapOf<IrLoop, Int>()
@@ -183,7 +185,11 @@
     private fun serializeIrStatementOrigin(origin: IrStatementOrigin): Int =
         serializeString((origin as? IrStatementOriginImpl)?.debugName ?: error("Unable to serialize origin ${origin.javaClass.name}"))
 
-    private fun serializeCoordinates(start: Int, end: Int): Long = BinaryCoordinates.encode(start, end)
+    private fun serializeCoordinates(start: Int, end: Int): Long = if (skipPrivateApi) {
+        0L
+    } else {
+        BinaryCoordinates.encode(start, end)
+    }
 
     /* ------- Strings ---------------------------------------------------------- */
 
@@ -1340,9 +1346,9 @@
 
 // ---------- Top level ------------------------------------------------------
 
-    private fun serializeFileEntry(entry: IrFileEntry): ProtoFileEntry = ProtoFileEntry.newBuilder()
+    private fun serializeFileEntry(entry: IrFileEntry, includeLineStartOffsets: Boolean = true): ProtoFileEntry = ProtoFileEntry.newBuilder()
         .setName(entry.matchAndNormalizeFilePath())
-        .addAllLineStartOffset(entry.lineStartOffsets.asIterable())
+        .applyIf(includeLineStartOffsets) { addAllLineStartOffset(entry.lineStartOffsets.asIterable()) }
         .build()
 
     open fun backendSpecificExplicitRoot(node: IrAnnotationContainer): Boolean = false
@@ -1351,12 +1357,18 @@
     open fun backendSpecificSerializeAllMembers(irClass: IrClass) = false
     open fun backendSpecificMetadata(irFile: IrFile): FileBackendSpecificMetadata? = null
 
+    private fun skipIfPrivate(declaration: IrDeclaration) =
+        skipPrivateApi && (declaration as? IrDeclarationWithVisibility)?.visibility?.isPublicAPI != true
+
     open fun memberNeedsSerialization(member: IrDeclaration): Boolean {
         val parent = member.parent
         require(parent is IrClass)
         if (backendSpecificSerializeAllMembers(parent)) return true
         if (bodiesOnlyForInlines && member is IrAnonymousInitializer && parent.visibility != DescriptorVisibilities.LOCAL)
             return false
+        if (skipIfPrivate(member)) {
+            return false
+        }
 
         return (!member.isFakeOverride)
     }
@@ -1414,11 +1426,15 @@
         val topLevelDeclarations = mutableListOf<SerializedDeclaration>()
 
         val proto = ProtoFile.newBuilder()
-            .setFileEntry(serializeFileEntry(file.fileEntry))
+            .setFileEntry(serializeFileEntry(file.fileEntry, includeLineStartOffsets = !skipPrivateApi))
             .addAllFqName(serializeFqName(file.packageFqName.asString()))
             .addAllAnnotation(serializeAnnotations(file.annotations))
 
         file.declarations.forEach {
+            if (skipIfPrivate(it)) {
+                // Skip the declaration if producing header klib and the declaration is not public.
+                return@forEach
+            }
             if (it.descriptor.isExpectMember && !it.descriptor.isSerializableExpectClass) {
                 // Skip the declaration unless it is `expect annotation class` marked with `OptionalExpectation`
                 // without the corresponding `actual` counterpart for the current leaf target.
@@ -1441,7 +1457,7 @@
 
         // Make sure that all top level properties are initialized on library's load.
         file.declarations
-            .filterIsInstanceAnd<IrProperty> { it.backingField?.initializer != null && keepOrderOfProperties(it) }
+            .filterIsInstanceAnd<IrProperty> { it.backingField?.initializer != null && keepOrderOfProperties(it) && !skipIfPrivate(it) }
             .forEach {
                 val fieldSymbol = it.backingField?.symbol ?: error("Not found ID ${it.render()}")
                 proto.addExplicitlyExportedToCompiler(serializeIrSymbol(fieldSymbol))
diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataMonolithicSerializer.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataMonolithicSerializer.kt
index c46c445..02a6b8b 100644
--- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataMonolithicSerializer.kt
+++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataMonolithicSerializer.kt
@@ -27,8 +27,9 @@
     exportKDoc: Boolean,
     skipExpects: Boolean,
     includeOnlyModuleContent: Boolean = false,
-    allowErrorTypes: Boolean = false
-) : KlibMetadataSerializer(languageVersionSettings, metadataVersion, project, exportKDoc, skipExpects, includeOnlyModuleContent, allowErrorTypes) {
+    allowErrorTypes: Boolean = false,
+    produceHeaderKlib: Boolean = false,
+) : KlibMetadataSerializer(languageVersionSettings, metadataVersion, project, exportKDoc, skipExpects, includeOnlyModuleContent, allowErrorTypes, produceHeaderKlib) {
 
     private fun serializePackageFragment(fqName: FqName, module: ModuleDescriptor): List<ProtoBuf.PackageFragment> {
 
diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataSerializer.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataSerializer.kt
index 8a1bbc7..10db46b 100644
--- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataSerializer.kt
+++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataSerializer.kt
@@ -18,7 +18,7 @@
 import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
 import org.jetbrains.kotlin.name.ClassId
 import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.*
 import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
 import org.jetbrains.kotlin.serialization.ApproximatingStringTable
 import org.jetbrains.kotlin.serialization.DescriptorSerializer
@@ -36,7 +36,8 @@
     val exportKDoc: Boolean = false,
     val skipExpects: Boolean = false,
     val includeOnlyModuleContent: Boolean = false,
-    private val allowErrorTypes: Boolean
+    private val allowErrorTypes: Boolean,
+    val produceHeaderKlib: Boolean = false,
 ) {
 
     lateinit var serializerContext: SerializerContext
@@ -54,7 +55,8 @@
             metadataVersion,
             ApproximatingStringTable(),
             allowErrorTypes,
-            exportKDoc
+            exportKDoc,
+            produceHeaderKlib
         )
         return SerializerContext(
             extension,
@@ -94,8 +96,8 @@
     // TODO: we filter out expects with present actuals.
     // This is done because deserialized member scope doesn't give us actuals
     // when it has a choice
-    private fun List<DeclarationDescriptor>.filterOutExpectsWithActuals(): List<DeclarationDescriptor> {
-        val actualClassIds = this.filter{ !it.isExpectMember }.map { ClassId.topLevel(it.fqNameSafe) }
+    private fun Sequence<DeclarationDescriptor>.filterOutExpectsWithActuals(): Sequence<DeclarationDescriptor> {
+        val actualClassIds = this.filter { !it.isExpectMember }.map { ClassId.topLevel(it.fqNameSafe) }
         return this.filterNot {
             // TODO: this only filters classes for now.
             // Need to do the same for functions etc
@@ -103,12 +105,20 @@
         }
     }
 
-    protected fun List<DeclarationDescriptor>.filterOutExpects(): List<DeclarationDescriptor> =
+    private fun Sequence<DeclarationDescriptor>.filterOutExpects(): Sequence<DeclarationDescriptor> =
         if (skipExpects)
             this.filterNot { it.isExpectMember && !it.isSerializableExpectClass }
         else
             this.filterOutExpectsWithActuals()
 
+    private fun Sequence<DeclarationDescriptor>.filterPrivate(): Sequence<DeclarationDescriptor> =
+        if (produceHeaderKlib) {
+            // We keep all interfaces since publicly accessible classes can inherit from private interfaces.
+            this.filter {
+                it is ClassDescriptor && it.kind.isInterface || it is DeclarationDescriptorWithVisibility && it.effectiveVisibility().publicApi
+            }
+        } else this
+
     private fun serializeClasses(packageName: FqName,
                                  //builder: ProtoBuf.PackageFragment.Builder,
                                  descriptors: Collection<DeclarationDescriptor>): List<Pair<ProtoBuf.Class, Int>> {
@@ -131,8 +141,8 @@
         allTopLevelDescriptors: List<DeclarationDescriptor>
     ): List<ProtoBuf.PackageFragment> {
 
-        val classifierDescriptors = allClassifierDescriptors.filterOutExpects()
-        val topLevelDescriptors = allTopLevelDescriptors.filterOutExpects()
+        val classifierDescriptors = allClassifierDescriptors.asSequence().filterOutExpects().filterPrivate().toList()
+        val topLevelDescriptors = allTopLevelDescriptors.asSequence().filterOutExpects().filterPrivate().toList()
 
         if (TOP_LEVEL_CLASS_DECLARATION_COUNT_PER_FILE == null &&
             TOP_LEVEL_DECLARATION_COUNT_PER_FILE == null) {
diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataSerializerExtension.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataSerializerExtension.kt
index 298b371..4bc9ca7 100644
--- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataSerializerExtension.kt
+++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataSerializerExtension.kt
@@ -5,7 +5,6 @@
 
 package org.jetbrains.kotlin.backend.common.serialization.metadata
 
-import org.jetbrains.kotlin.config.LanguageFeature
 import org.jetbrains.kotlin.config.LanguageVersionSettings
 import org.jetbrains.kotlin.descriptors.*
 import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
@@ -15,8 +14,10 @@
 import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable
 import org.jetbrains.kotlin.psi.KtDeclaration
 import org.jetbrains.kotlin.psi.KtPrimaryConstructor
+import org.jetbrains.kotlin.resolve.DescriptorUtils
 import org.jetbrains.kotlin.resolve.source.getPsi
 import org.jetbrains.kotlin.serialization.DescriptorSerializer
+import org.jetbrains.kotlin.serialization.DescriptorSerializer.Companion.sort
 import org.jetbrains.kotlin.serialization.KotlinSerializerExtensionBase
 import org.jetbrains.kotlin.serialization.StringTableImpl
 import org.jetbrains.kotlin.serialization.deserialization.DYNAMIC_TYPE_DESERIALIZER_ID
@@ -28,9 +29,22 @@
     override val metadataVersion: BinaryVersion,
     override val stringTable: StringTableImpl,
     private val allowErrorTypes: Boolean,
-    private val exportKDoc: Boolean
+    private val exportKDoc: Boolean,
+    private val produceHeaderKlib: Boolean
 ) : KotlinSerializerExtensionBase(KlibMetadataSerializerProtocol) {
     override fun shouldUseTypeTable(): Boolean = true
+    override val customClassMembersProducer: ClassMembersProducer?
+        get() = if (produceHeaderKlib)
+            object : ClassMembersProducer {
+                override fun getCallableMembers(classDescriptor: ClassDescriptor) =
+                    sort(
+                        DescriptorUtils.getAllDescriptors(classDescriptor.defaultType.memberScope)
+                            .filterIsInstance<CallableMemberDescriptor>()
+                            .filter { it.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
+                            .filter { it.visibility.isPublicAPI }
+                    )
+            }
+        else super.customClassMembersProducer
 
     private fun descriptorFileId(descriptor: DeclarationDescriptorWithSource): Int? {
         val fileName = descriptor.source.containingFile.name ?: return null
diff --git a/compiler/serialization/src/org/jetbrains/kotlin/serialization/DescriptorSerializer.kt b/compiler/serialization/src/org/jetbrains/kotlin/serialization/DescriptorSerializer.kt
index f218c38..e7c4d55 100644
--- a/compiler/serialization/src/org/jetbrains/kotlin/serialization/DescriptorSerializer.kt
+++ b/compiler/serialization/src/org/jetbrains/kotlin/serialization/DescriptorSerializer.kt
@@ -163,10 +163,15 @@
         classDescriptor.inlineClassRepresentation?.let { inlineClassRepresentation ->
             builder.inlineClassUnderlyingPropertyName = getSimpleNameIndex(inlineClassRepresentation.underlyingPropertyName)
 
-            val property = callableMembers.single {
-                it is PropertyDescriptor && it.extensionReceiverParameter == null && it.name == inlineClassRepresentation.underlyingPropertyName
-            }
-            if (!property.visibility.isPublicAPI) {
+            // The underlying property might be missing from `callableMembers` if we are producing a header klib and
+            // the inline class is a part of the public API but the underlying property is not.
+            val property = callableMembers.singleOrNull { candidate ->
+                candidate is PropertyDescriptor
+                        && candidate.extensionReceiverParameter == null
+                        && candidate.name == inlineClassRepresentation.underlyingPropertyName
+            } ?: return@let
+
+            if (property.visibility.isPublicAPI) {
                 if (useTypeTable()) {
                     builder.inlineClassUnderlyingTypeId = typeId(inlineClassRepresentation.underlyingType)
                 } else {
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/FirNativeSerializer.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/FirNativeSerializer.kt
index bb1ce70..8e1ad7e 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/FirNativeSerializer.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/FirNativeSerializer.kt
@@ -7,6 +7,7 @@
 import org.jetbrains.kotlin.backend.konan.driver.PhaseContext
 import org.jetbrains.kotlin.backend.konan.driver.phases.Fir2IrOutput
 import org.jetbrains.kotlin.backend.konan.driver.phases.FirOutput
+import org.jetbrains.kotlin.backend.konan.driver.phases.FirSerializerInput
 import org.jetbrains.kotlin.backend.konan.driver.phases.SerializerOutput
 import org.jetbrains.kotlin.backend.konan.serialization.KonanIrModuleSerializer
 import org.jetbrains.kotlin.config.CommonConfigurationKeys
@@ -34,11 +35,13 @@
     else -> firSerializerBase(input.firResult, null)
 }
 
-internal fun PhaseContext.fir2IrSerializer(input: Fir2IrOutput) = firSerializerBase(input.firResult, input)
+internal fun PhaseContext.fir2IrSerializer(input: FirSerializerInput) =
+    firSerializerBase(input.firToIrOutput.firResult, input.firToIrOutput, produceHeaderKlib = input.produceHeaderKlib)
 
 internal fun PhaseContext.firSerializerBase(
         firResult: FirResult,
         fir2IrInput: Fir2IrOutput?,
+        produceHeaderKlib: Boolean = false,
 ): SerializerOutput {
     val configuration = config.configuration
     val sourceFiles = mutableListOf<KtSourceFile>()
@@ -71,6 +74,8 @@
             moduleName = fir2IrInput?.irModuleFragment?.descriptor?.name?.asString()
                     ?: firResult.outputs.last().session.moduleData.name.asString(),
             firFilesAndSessionsBySourceFile,
+            bodiesOnlyForInlines = produceHeaderKlib,
+            skipPrivateApi = produceHeaderKlib,
     ) { firFile, session, scopeSession ->
         serializeSingleFirFile(
                 firFile,
@@ -87,6 +92,7 @@
                         additionalAnnotationsProvider = fir2IrInput?.components?.annotationsFromPluginRegistrar?.createMetadataAnnotationsProvider()
                 ),
                 configuration.languageVersionSettings,
+                produceHeaderKlib,
         )
     }
 }
@@ -102,14 +108,16 @@
 }
 
 internal fun PhaseContext.serializeNativeModule(
-        configuration: CompilerConfiguration,
-        messageLogger: IrMessageLogger,
-        files: List<KtSourceFile>,
-        dependencies: List<KonanLibrary>?,
-        moduleFragment: IrModuleFragment?,
-        moduleName: String,
-        firFilesAndSessionsBySourceFile: Map<KtSourceFile, Triple<FirFile, FirSession, ScopeSession>>,
-        serializeSingleFile: (FirFile, FirSession, ScopeSession) -> ProtoBuf.PackageFragment
+    configuration: CompilerConfiguration,
+    messageLogger: IrMessageLogger,
+    files: List<KtSourceFile>,
+    dependencies: List<KonanLibrary>?,
+    moduleFragment: IrModuleFragment?,
+    moduleName: String,
+    firFilesAndSessionsBySourceFile: Map<KtSourceFile, Triple<FirFile, FirSession, ScopeSession>>,
+    bodiesOnlyForInlines: Boolean = false,
+    skipPrivateApi: Boolean = false,
+    serializeSingleFile: (FirFile, FirSession, ScopeSession) -> ProtoBuf.PackageFragment
 ): SerializerOutput {
     if (moduleFragment != null) {
         assert(files.size == moduleFragment.files.size)
@@ -126,6 +134,8 @@
                 normalizeAbsolutePaths = absolutePathNormalization,
                 sourceBaseDirs = sourceBaseDirs,
                 languageVersionSettings = configuration.languageVersionSettings,
+                bodiesOnlyForInlines = bodiesOnlyForInlines,
+                skipPrivateApi = skipPrivateApi
         ).serializedIrModule(moduleFragment)
     }
 
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt
index ccee532..0d376e8 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt
@@ -225,6 +225,8 @@
 
     internal val metadataKlib get() = configuration.get(KonanConfigKeys.METADATA_KLIB)!!
 
+    internal val headerKlibPath get() = configuration.get(KonanConfigKeys.HEADER_KLIB)
+
     internal val produceStaticFramework get() = configuration.getBoolean(KonanConfigKeys.STATIC_FRAMEWORK)
 
     internal val purgeUserLibs: Boolean
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt
index 0fdab57..651f71a 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt
@@ -75,6 +75,8 @@
                 = CompilerConfigurationKey.create("provide manifest addend file")
         val METADATA_KLIB: CompilerConfigurationKey<Boolean>
                 = CompilerConfigurationKey.create("metadata klib")
+        val HEADER_KLIB: CompilerConfigurationKey<String?>
+                = CompilerConfigurationKey.create("path to file where header klib should be produced")
         val MODULE_NAME: CompilerConfigurationKey<String?>
                 = CompilerConfigurationKey.create("module name")
         val NATIVE_LIBRARY_FILES: CompilerConfigurationKey<List<String>>
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt
index bbbcfb7..2778107 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt
@@ -56,6 +56,7 @@
             (arguments.produce ?: "program").uppercase())
     put(PRODUCE, outputKind)
     put(METADATA_KLIB, arguments.metadataKlib)
+    putIfNotNull(HEADER_KLIB, arguments.headerKlibPath)
 
     arguments.libraryVersion?.let { put(LIBRARY_VERSION, it) }
 
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/DynamicCompilerDriver.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/DynamicCompilerDriver.kt
index e7e949f..f9f6aaa 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/DynamicCompilerDriver.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/DynamicCompilerDriver.kt
@@ -108,8 +108,15 @@
             engine.runFirSerializer(frontendOutput)
         } else {
             val fir2IrOutput = engine.runFir2Ir(frontendOutput)
+
+            val headerKlibPath = environment.configuration.get(KonanConfigKeys.HEADER_KLIB)
+            if (!headerKlibPath.isNullOrEmpty()) {
+                val headerKlib = engine.runFir2IrSerializer(FirSerializerInput(fir2IrOutput, produceHeaderKlib = true))
+                engine.writeKlib(headerKlib, headerKlibPath)
+            }
+
             engine.runK2SpecialBackendChecks(fir2IrOutput)
-            engine.runFir2IrSerializer(fir2IrOutput)
+            engine.runFir2IrSerializer(FirSerializerInput(fir2IrOutput))
         }
     }
 
@@ -124,6 +131,10 @@
         } else {
             engine.runPsiToIr(frontendOutput, isProducingLibrary = true) as PsiToIrOutput.ForKlib
         }
+        if (!config.headerKlibPath.isNullOrEmpty()) {
+            val headerKlib = engine.runSerializer(frontendOutput.moduleDescriptor, psiToIrOutput, produceHeaderKlib = true)
+            engine.writeKlib(headerKlib, config.headerKlibPath)
+        }
         return engine.runSerializer(frontendOutput.moduleDescriptor, psiToIrOutput)
     }
 
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/FirSerializer.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/FirSerializer.kt
index 5624604..e6c7e84 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/FirSerializer.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/FirSerializer.kt
@@ -10,6 +10,12 @@
 import org.jetbrains.kotlin.backend.konan.firSerializer
 import org.jetbrains.kotlin.backend.konan.fir2IrSerializer
 
+
+internal data class FirSerializerInput(
+    val firToIrOutput: Fir2IrOutput,
+    val produceHeaderKlib: Boolean = false,
+)
+
 internal val FirSerializerPhase = createSimpleNamedCompilerPhase<PhaseContext, FirOutput, SerializerOutput?>(
         "FirSerializer", "Fir serializer",
         outputIfNotEnabled = { _, _, _, _ -> SerializerOutput(null, null, null, listOf()) }
@@ -17,10 +23,10 @@
     context.firSerializer(input)
 }
 
-internal val Fir2IrSerializerPhase = createSimpleNamedCompilerPhase<PhaseContext, Fir2IrOutput, SerializerOutput>(
+internal val Fir2IrSerializerPhase = createSimpleNamedCompilerPhase<PhaseContext, FirSerializerInput, SerializerOutput>(
         "Fir2IrSerializer", "Fir2Ir serializer",
         outputIfNotEnabled = { _, _, _, _ -> SerializerOutput(null, null, null, listOf()) }
-) { context: PhaseContext, input: Fir2IrOutput ->
+) { context: PhaseContext, input: FirSerializerInput ->
     context.fir2IrSerializer(input)
 }
 
@@ -31,7 +37,7 @@
 }
 
 internal fun <T : PhaseContext> PhaseEngine<T>.runFir2IrSerializer(
-        fir2irOutput: Fir2IrOutput
+        firSerializerInput: FirSerializerInput
 ): SerializerOutput {
-    return this.runPhase(Fir2IrSerializerPhase, fir2irOutput)
+    return this.runPhase(Fir2IrSerializerPhase, firSerializerInput)
 }
\ No newline at end of file
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Serializer.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Serializer.kt
index f2ba8bd..02ac7da 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Serializer.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Serializer.kt
@@ -21,6 +21,7 @@
 internal data class SerializerInput(
         val moduleDescriptor: ModuleDescriptor,
         val psiToIrOutput: PsiToIrOutput.ForKlib?,
+        val produceHeaderKlib: Boolean,
 )
 
 data class SerializerOutput(
@@ -48,24 +49,27 @@
                 normalizeAbsolutePaths = normalizeAbsolutePaths,
                 sourceBaseDirs = relativePathBase,
                 languageVersionSettings = config.languageVersionSettings,
+                bodiesOnlyForInlines = input.produceHeaderKlib,
+                skipPrivateApi = input.produceHeaderKlib,
         ).serializedIrModule(ir)
     }
 
     val serializer = KlibMetadataMonolithicSerializer(
-            config.configuration.languageVersionSettings,
-            config.configuration.get(CommonConfigurationKeys.METADATA_VERSION)!!,
-            config.project,
-            exportKDoc = context.shouldExportKDoc(),
-            !expectActualLinker, includeOnlyModuleContent = true)
+        config.configuration.languageVersionSettings,
+        config.configuration.get(CommonConfigurationKeys.METADATA_VERSION)!!,
+        config.project,
+        exportKDoc = context.shouldExportKDoc(),
+        !expectActualLinker, includeOnlyModuleContent = true, produceHeaderKlib = input.produceHeaderKlib)
     val serializedMetadata = serializer.serializeModule(input.moduleDescriptor)
     val neededLibraries = config.librariesWithDependencies()
     SerializerOutput(serializedMetadata, serializedIr, null, neededLibraries)
 }
 
 internal fun <T : PhaseContext> PhaseEngine<T>.runSerializer(
-        moduleDescriptor: ModuleDescriptor,
-        psiToIrResult: PsiToIrOutput.ForKlib?,
+    moduleDescriptor: ModuleDescriptor,
+    psiToIrResult: PsiToIrOutput.ForKlib?,
+    produceHeaderKlib: Boolean = false,
 ): SerializerOutput {
-    val input = SerializerInput(moduleDescriptor, psiToIrResult)
+    val input = SerializerInput(moduleDescriptor, psiToIrResult, produceHeaderKlib)
     return this.runPhase(SerializerPhase, input)
 }
\ No newline at end of file
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/WriteKlib.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/WriteKlib.kt
index b337820..645f1c7 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/WriteKlib.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/WriteKlib.kt
@@ -14,13 +14,19 @@
 import org.jetbrains.kotlin.library.KotlinAbiVersion
 import org.jetbrains.kotlin.library.KotlinLibraryVersioning
 import org.jetbrains.kotlin.library.metadata.KlibMetadataVersion
+import org.jetbrains.kotlin.util.removeSuffixIfPresent
 
-internal val WriteKlibPhase = createSimpleNamedCompilerPhase<PhaseContext, SerializerOutput>(
+internal data class KlibWriterInput(
+        val serializerOutput: SerializerOutput,
+        val customOutputPath: String?
+)
+internal val WriteKlibPhase = createSimpleNamedCompilerPhase<PhaseContext, KlibWriterInput>(
         "WriteKlib", "Write klib output",
 ) { context, input ->
     val config = context.config
     val configuration = config.configuration
-    val outputFiles = OutputFiles(config.outputPath, config.target, config.produce)
+    val outputFiles = OutputFiles(input.customOutputPath?.removeSuffixIfPresent(".klib")
+            ?: config.outputPath, config.target, config.produce)
     val nopack = configuration.getBoolean(KonanConfigKeys.NOPACK)
     val output = outputFiles.klibOutputFileName(!nopack)
     val libraryName = config.moduleId
@@ -51,14 +57,14 @@
     (e.g. commonized cinterops, host vs client environment differences).
     */
     val linkDependencies = if (context.config.metadataKlib) emptyList()
-    else input.neededLibraries
+    else input.serializerOutput.neededLibraries
 
     buildLibrary(
             natives = config.nativeLibraries,
             included = config.includeBinaries,
             linkDependencies = linkDependencies,
-            metadata = input.serializedMetadata!!,
-            ir = input.serializedIr,
+            metadata = input.serializerOutput.serializedMetadata!!,
+            ir = input.serializerOutput.serializedIr,
             versions = versions,
             target = target,
             output = output,
@@ -66,12 +72,13 @@
             nopack = nopack,
             shortName = shortLibraryName,
             manifestProperties = manifestProperties,
-            dataFlowGraph = input.dataFlowGraph
+            dataFlowGraph = input.serializerOutput.dataFlowGraph
     )
 }
 
 internal fun <T : PhaseContext> PhaseEngine<T>.writeKlib(
         serializationOutput: SerializerOutput,
+        customOutputPath: String? = null,
 ) {
-    this.runPhase(WriteKlibPhase, serializationOutput)
+    this.runPhase(WriteKlibPhase, KlibWriterInput(serializationOutput, customOutputPath))
 }
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrFileSerializer.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrFileSerializer.kt
index 70a6aac..123c986 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrFileSerializer.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrFileSerializer.kt
@@ -12,21 +12,23 @@
 import org.jetbrains.kotlin.ir.util.hasAnnotation
 
 class KonanIrFileSerializer(
-        messageLogger: IrMessageLogger,
-        declarationTable: DeclarationTable,
-        languageVersionSettings: LanguageVersionSettings,
-        bodiesOnlyForInlines: Boolean = false,
-        compatibilityMode: CompatibilityMode,
-        normalizeAbsolutePaths: Boolean,
-        sourceBaseDirs: Collection<String>
+    messageLogger: IrMessageLogger,
+    declarationTable: DeclarationTable,
+    languageVersionSettings: LanguageVersionSettings,
+    bodiesOnlyForInlines: Boolean = false,
+    compatibilityMode: CompatibilityMode,
+    normalizeAbsolutePaths: Boolean,
+    sourceBaseDirs: Collection<String>,
+    skipPrivateApi: Boolean = false,
 ) : IrFileSerializer(
-        messageLogger = messageLogger,
-        declarationTable = declarationTable,
-        compatibilityMode = compatibilityMode,
-        languageVersionSettings = languageVersionSettings,
-        bodiesOnlyForInlines = bodiesOnlyForInlines,
-        normalizeAbsolutePaths = normalizeAbsolutePaths,
-        sourceBaseDirs = sourceBaseDirs
+    messageLogger,
+    declarationTable,
+    compatibilityMode,
+    languageVersionSettings,
+    skipPrivateApi = skipPrivateApi,
+    bodiesOnlyForInlines = bodiesOnlyForInlines,
+    normalizeAbsolutePaths = normalizeAbsolutePaths,
+    sourceBaseDirs = sourceBaseDirs
 ) {
 
     override fun backendSpecificExplicitRoot(node: IrAnnotationContainer): Boolean {
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrModuleSerializer.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrModuleSerializer.kt
index 253c1a2..dd57c47 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrModuleSerializer.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrModuleSerializer.kt
@@ -15,6 +15,8 @@
         normalizeAbsolutePaths: Boolean,
         sourceBaseDirs: Collection<String>,
         private val languageVersionSettings: LanguageVersionSettings,
+        private val bodiesOnlyForInlines: Boolean = false,
+        private val skipPrivateApi: Boolean = false,
 ) : IrModuleSerializer<KonanIrFileSerializer>(messageLogger, compatibilityMode, normalizeAbsolutePaths, sourceBaseDirs) {
 
     private val globalDeclarationTable = KonanGlobalDeclarationTable(irBuiltIns)
@@ -33,5 +35,7 @@
                     compatibilityMode = compatibilityMode,
                     normalizeAbsolutePaths = normalizeAbsolutePaths,
                     sourceBaseDirs = sourceBaseDirs,
-                    languageVersionSettings = languageVersionSettings)
+                    languageVersionSettings = languageVersionSettings,
+                    bodiesOnlyForInlines = bodiesOnlyForInlines,
+                    skipPrivateApi = skipPrivateApi)
 }
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/anonymousObjects/base/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/anonymousObjects/base/test.kt
new file mode 100644
index 0000000..bc74a64
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/anonymousObjects/base/test.kt
@@ -0,0 +1,7 @@
+package test
+
+private val x = object { fun f() = 1 }
+
+fun g() =
+    x.f() +
+        object { fun f() = 0 }.f()
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/anonymousObjects/sameAbi/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/anonymousObjects/sameAbi/test.kt
new file mode 100644
index 0000000..043f671
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/anonymousObjects/sameAbi/test.kt
@@ -0,0 +1,3 @@
+package test
+
+fun g() = 1
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/classFlags/base/Class.kt b/native/native.tests/testData/klib/header-klibs/comparison/classFlags/base/Class.kt
new file mode 100644
index 0000000..6671676
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/classFlags/base/Class.kt
@@ -0,0 +1,3 @@
+package test
+
+class Class
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/classFlags/differentAbi/Class.kt b/native/native.tests/testData/klib/header-klibs/comparison/classFlags/differentAbi/Class.kt
new file mode 100644
index 0000000..bf877a0f
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/classFlags/differentAbi/Class.kt
@@ -0,0 +1,3 @@
+package test
+
+open class Class
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/classPrivateMembers/base/A.kt b/native/native.tests/testData/klib/header-klibs/comparison/classPrivateMembers/base/A.kt
new file mode 100644
index 0000000..e24b9db
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/classPrivateMembers/base/A.kt
@@ -0,0 +1,18 @@
+package test
+
+class A {
+    val publicVal = 0
+    fun publicMethod() = 0
+
+    internal val internalVal = 0
+    internal fun internalMethod() = 0
+
+    protected val protectedVal = 0
+    protected fun protectedMethod() = 0
+
+    private val privateVal = 0
+    private fun privateMethod() = 0
+
+    val publicValBody = internalMethod() + privateMethod() + 2
+    val publicMethodBody = publicValBody + 2
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/classPrivateMembers/sameAbi/A.kt b/native/native.tests/testData/klib/header-klibs/comparison/classPrivateMembers/sameAbi/A.kt
new file mode 100644
index 0000000..8cadf0f
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/classPrivateMembers/sameAbi/A.kt
@@ -0,0 +1,15 @@
+package test
+
+class A {
+    val publicVal = 0
+    fun publicMethod() = 0
+
+    internal val internalVal = 42
+    internal fun internalMethod() = 42
+
+    protected val protectedVal = 0
+    protected fun protectedMethod() = 0
+
+    val publicValBody = internalMethod() + 1
+    val publicMethodBody = publicValBody + 1
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/constant/base/const.kt b/native/native.tests/testData/klib/header-klibs/comparison/constant/base/const.kt
new file mode 100644
index 0000000..de62917
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/constant/base/const.kt
@@ -0,0 +1,3 @@
+package test
+
+const val x = 0
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/constant/differentAbi/const.kt b/native/native.tests/testData/klib/header-klibs/comparison/constant/differentAbi/const.kt
new file mode 100644
index 0000000..56faca1
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/constant/differentAbi/const.kt
@@ -0,0 +1,3 @@
+package test
+
+const val x = 1
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/declarationOrderInline/base/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/declarationOrderInline/base/test.kt
new file mode 100644
index 0000000..e166d32
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/declarationOrderInline/base/test.kt
@@ -0,0 +1,4 @@
+package test
+
+inline fun f() = 1
+inline fun g() = 2
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/declarationOrderInline/differentAbi/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/declarationOrderInline/differentAbi/test.kt
new file mode 100644
index 0000000..a333a46
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/declarationOrderInline/differentAbi/test.kt
@@ -0,0 +1,7 @@
+package test
+
+inline fun g() = 2
+
+inline fun f() = 1
+
+// This changes the line numbers in f, g
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/functionBody/base/function.kt b/native/native.tests/testData/klib/header-klibs/comparison/functionBody/base/function.kt
new file mode 100644
index 0000000..46c201a
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/functionBody/base/function.kt
@@ -0,0 +1,10 @@
+package test
+
+fun sum(x: Int, y: Int): Int =
+    try {
+        var result = x
+        result += y
+        result
+    } finally {
+        // do nothing
+    }
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/functionBody/sameAbi/function.kt b/native/native.tests/testData/klib/header-klibs/comparison/functionBody/sameAbi/function.kt
new file mode 100644
index 0000000..56079d7
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/functionBody/sameAbi/function.kt
@@ -0,0 +1,3 @@
+package test
+
+fun sum(x: Int, y: Int): Int = y + x
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateClass/base/anonymousObject.kt b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateClass/base/anonymousObject.kt
new file mode 100644
index 0000000..fdd7de5
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateClass/base/anonymousObject.kt
@@ -0,0 +1,9 @@
+private class A {
+    inline fun test(crossinline s: () -> Unit) {
+        object {
+            fun run() {
+                s()
+            }
+        }.run()
+    }
+}
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateClass/sameAbi/anonymousObject.kt b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateClass/sameAbi/anonymousObject.kt
new file mode 100644
index 0000000..7fb3b19
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateClass/sameAbi/anonymousObject.kt
@@ -0,0 +1,9 @@
+private class A {
+    inline fun test(crossinline s: () -> Unit) {
+        object {
+            fun run() {
+                //s()
+            }
+        }.run()
+    }
+}
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateNestedClass/base/anonymousObject.kt b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateNestedClass/base/anonymousObject.kt
new file mode 100644
index 0000000..6493cee
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateNestedClass/base/anonymousObject.kt
@@ -0,0 +1,11 @@
+class A {
+    private class B {
+        inline fun test(crossinline s: () -> Unit) {
+            object {
+                fun run() {
+                    s()
+                }
+            }.run()
+        }
+    }
+}
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateNestedClass/sameAbi/anonymousObject.kt b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateNestedClass/sameAbi/anonymousObject.kt
new file mode 100644
index 0000000..1b7d476
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateNestedClass/sameAbi/anonymousObject.kt
@@ -0,0 +1,11 @@
+class A {
+    private class B {
+        inline fun test(crossinline s: () -> Unit) {
+            object {
+                fun run() {
+                    //s()
+                }
+            }.run()
+        }
+    }
+}
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/inlineFunctionBody/base/function.kt b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunctionBody/base/function.kt
new file mode 100644
index 0000000..855baa9
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunctionBody/base/function.kt
@@ -0,0 +1,3 @@
+package test
+
+inline fun sum(x: Int, y: Int): Int = x + y
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/inlineFunctionBody/differentAbi/function.kt b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunctionBody/differentAbi/function.kt
new file mode 100644
index 0000000..aec93d3
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/inlineFunctionBody/differentAbi/function.kt
@@ -0,0 +1,3 @@
+package test
+
+inline fun sum(x: Int, y: Int): Int = y + x
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/lambdas/base/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/lambdas/base/test.kt
new file mode 100644
index 0000000..b83f585
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/lambdas/base/test.kt
@@ -0,0 +1,4 @@
+package test
+
+private inline fun f() = { 1 }()
+fun g() = f()
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/lambdas/sameAbi/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/lambdas/sameAbi/test.kt
new file mode 100644
index 0000000..043f671
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/lambdas/sameAbi/test.kt
@@ -0,0 +1,3 @@
+package test
+
+fun g() = 1
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/parameterName/base/function.kt b/native/native.tests/testData/klib/header-klibs/comparison/parameterName/base/function.kt
new file mode 100644
index 0000000..2d75715
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/parameterName/base/function.kt
@@ -0,0 +1,3 @@
+package test
+
+fun id(x: Int): Int = x
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/parameterName/differentAbi/function.kt b/native/native.tests/testData/klib/header-klibs/comparison/parameterName/differentAbi/function.kt
new file mode 100644
index 0000000..f83fa59
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/parameterName/differentAbi/function.kt
@@ -0,0 +1,3 @@
+package test
+
+fun id(y: Int): Int = y
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/privateInterface/base/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/privateInterface/base/test.kt
new file mode 100644
index 0000000..6398d40
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/privateInterface/base/test.kt
@@ -0,0 +1,10 @@
+package test
+
+private interface A {
+    fun foo() = 0
+    fun bar(): String
+}
+
+class B: A {
+    override fun bar() = "test${foo()}"
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/privateInterface/sameAbi/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/privateInterface/sameAbi/test.kt
new file mode 100644
index 0000000..ffde831
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/privateInterface/sameAbi/test.kt
@@ -0,0 +1,10 @@
+package test
+
+private interface A {
+    fun foo() = 42
+    fun bar(): String
+}
+
+class B: A {
+    override fun bar() = "test${foo()}"
+}
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/privateTypealias/base/typealiases.kt b/native/native.tests/testData/klib/header-klibs/comparison/privateTypealias/base/typealiases.kt
new file mode 100644
index 0000000..8654b9c
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/privateTypealias/base/typealiases.kt
@@ -0,0 +1,13 @@
+package test
+
+class PublicClass1
+class PublicClass2
+
+typealias PublicTypeAlias1 = PublicClass1
+typealias PublicTypeAlias2 = PublicClass1
+
+internal typealias InternalTypeAlias1 = PublicClass1
+internal typealias InternalTypeAlias2 = PublicClass1
+
+private typealias PrivateTypeAlias1 = PublicClass1
+private typealias PrivateTypeAlias2 = PublicClass1
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/privateTypealias/sameAbi/typealiases.kt b/native/native.tests/testData/klib/header-klibs/comparison/privateTypealias/sameAbi/typealiases.kt
new file mode 100644
index 0000000..b9cf0d124
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/privateTypealias/sameAbi/typealiases.kt
@@ -0,0 +1,12 @@
+package test
+
+class PublicClass1
+class PublicClass2
+
+typealias PublicTypeAlias1 = PublicClass1
+typealias PublicTypeAlias2 = PublicClass1
+
+internal typealias InternalTypeAlias1 = PublicClass1
+internal typealias InternalTypeAlias2 = PublicClass1
+
+private typealias PrivateTypeAlias1 = PublicClass2
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/returnType/base/function.kt b/native/native.tests/testData/klib/header-klibs/comparison/returnType/base/function.kt
new file mode 100644
index 0000000..78c05ec
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/returnType/base/function.kt
@@ -0,0 +1,3 @@
+package test
+
+fun foo() = 0
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/returnType/differentAbi/function.kt b/native/native.tests/testData/klib/header-klibs/comparison/returnType/differentAbi/function.kt
new file mode 100644
index 0000000..df46051
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/returnType/differentAbi/function.kt
@@ -0,0 +1,3 @@
+package test
+
+fun foo() = "0"
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/superClass/base/classes.kt b/native/native.tests/testData/klib/header-klibs/comparison/superClass/base/classes.kt
new file mode 100644
index 0000000..70c2dd4
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/superClass/base/classes.kt
@@ -0,0 +1,4 @@
+package test
+
+open class A
+class B : A()
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/superClass/differentAbi/classes.kt b/native/native.tests/testData/klib/header-klibs/comparison/superClass/differentAbi/classes.kt
new file mode 100644
index 0000000..9c2f6ab
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/superClass/differentAbi/classes.kt
@@ -0,0 +1,4 @@
+package test
+
+open class A
+class B
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/syntheticAccessors/base/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/syntheticAccessors/base/test.kt
new file mode 100644
index 0000000..7f35cef
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/syntheticAccessors/base/test.kt
@@ -0,0 +1,7 @@
+package test
+
+private val x = 1
+
+object A { fun f() = x }
+
+val y = 2
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/syntheticAccessors/sameAbi/test.kt b/native/native.tests/testData/klib/header-klibs/comparison/syntheticAccessors/sameAbi/test.kt
new file mode 100644
index 0000000..f7917a1
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/syntheticAccessors/sameAbi/test.kt
@@ -0,0 +1,5 @@
+package test
+
+object A { fun f() = 1 }
+
+val y = 2
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/topLevelPrivateMembers/base/utils.kt b/native/native.tests/testData/klib/header-klibs/comparison/topLevelPrivateMembers/base/utils.kt
new file mode 100644
index 0000000..9a80bf3
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/topLevelPrivateMembers/base/utils.kt
@@ -0,0 +1,13 @@
+package test
+
+val publicVal = 0
+const val publicConst = 0
+fun publicFun() = 0
+
+internal val internalVal = 0
+internal const val internalConst = 0
+internal fun internalFun() = 0
+
+private val privateVal = 0
+private const val privateConst = 0
+private fun privateFun() = 0
diff --git a/native/native.tests/testData/klib/header-klibs/comparison/topLevelPrivateMembers/sameAbi/utils.kt b/native/native.tests/testData/klib/header-klibs/comparison/topLevelPrivateMembers/sameAbi/utils.kt
new file mode 100644
index 0000000..323c16d
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/comparison/topLevelPrivateMembers/sameAbi/utils.kt
@@ -0,0 +1,9 @@
+package test
+
+val publicVal = 0
+const val publicConst = 0
+fun publicFun() = 0
+
+internal val internalVal = 0
+internal const val internalConst = 0
+internal fun internalFun() = 0
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/anonymousObject/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/anonymousObject/lib/lib.kt
new file mode 100644
index 0000000..68f19cb
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/anonymousObject/lib/lib.kt
@@ -0,0 +1,10 @@
+package lib
+
+interface Interface {
+    fun getInt(): Int
+}
+
+fun getInterface(): Interface =
+    object : Interface {
+        override fun getInt(): Int = 10
+    }
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/anonymousObject/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/anonymousObject/main/main.kt
new file mode 100644
index 0000000..de1ba31
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/anonymousObject/main/main.kt
@@ -0,0 +1,11 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    val i = getInterface()
+    val value = i.getInt()
+    if (value != 10) error("getInterface().getInt() is '$value', but is expected to be '10'")
+
+    return "OK"
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/classes/lib/classes.kt b/native/native.tests/testData/klib/header-klibs/compilation/classes/lib/classes.kt
new file mode 100644
index 0000000..c26a85c
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/classes/lib/classes.kt
@@ -0,0 +1,31 @@
+package lib
+
+interface I {
+    val iProperty: Int
+    fun iMethod(): Int
+}
+
+open class A : I {
+    override val iProperty: Int = 0
+    override fun iMethod(): Int = 10
+
+    val aProperty: Int = 20
+    fun aMethod(): Int = 30
+    inline fun aInlineMethod(): Int = 40
+
+    private class AB {}
+
+    companion object {
+        const val aConst: Int = 50
+    }
+}
+
+class B : A() {
+    val bProperty: Int = 60
+    fun bMethod(): Int = 70
+    inline fun bInlineMethod(): Int = 80
+
+    companion object {
+        const val bConst: Int = 90
+    }
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/classes/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/classes/main/main.kt
new file mode 100644
index 0000000..a0c7bd9
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/classes/main/main.kt
@@ -0,0 +1,40 @@
+package app
+
+import lib.*
+
+fun useI(i: I) {
+    i.iProperty
+    i.iMethod()
+}
+
+fun useA(a: A) {
+    a.iProperty
+    a.iMethod()
+
+    a.aProperty
+    a.aMethod()
+    a.aInlineMethod()
+    A.aConst
+}
+
+fun useB(b: B) {
+    b.iProperty
+    b.iMethod()
+
+    b.aProperty
+    b.aMethod()
+    b.aInlineMethod()
+
+    b.bProperty
+    b.bMethod()
+    b.bInlineMethod()
+    B.bConst
+}
+
+fun runAppAndReturnOk(): String {
+    useI(A())
+    useA(A())
+    useB(B())
+
+    return "OK"
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/clinit/lib/object.kt b/native/native.tests/testData/klib/header-klibs/compilation/clinit/lib/object.kt
new file mode 100644
index 0000000..2e13c86
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/clinit/lib/object.kt
@@ -0,0 +1,7 @@
+package lib
+
+object Object {
+    val x = 1
+    val y = 2
+    val z = x + y
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/clinit/lib/topLevel.kt b/native/native.tests/testData/klib/header-klibs/compilation/clinit/lib/topLevel.kt
new file mode 100644
index 0000000..9dfe37e
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/clinit/lib/topLevel.kt
@@ -0,0 +1,5 @@
+package lib
+
+val x = 1
+val y = 2
+val z = x + y
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/clinit/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/clinit/main/main.kt
new file mode 100644
index 0000000..acdac19
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/clinit/main/main.kt
@@ -0,0 +1,10 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    if (Object.z != 3) error("lib.Object.z is ${Object.z}, but '3' was expected")
+    if (z != 3) error("lib.z is $z, but '3' was expected")
+
+    return "OK"
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineAnnotationInstantiation/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineAnnotationInstantiation/lib/lib.kt
new file mode 100644
index 0000000..320eb9d
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineAnnotationInstantiation/lib/lib.kt
@@ -0,0 +1,5 @@
+package lib
+
+annotation class A(val value: String)
+
+inline fun a() = A("OK")
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineAnnotationInstantiation/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineAnnotationInstantiation/main/main.kt
new file mode 100644
index 0000000..b4ce580
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineAnnotationInstantiation/main/main.kt
@@ -0,0 +1,7 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    return a().value
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineAnonymousObject/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineAnonymousObject/lib/lib.kt
new file mode 100644
index 0000000..70413b3
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineAnonymousObject/lib/lib.kt
@@ -0,0 +1,11 @@
+package lib
+
+interface Interface {
+    fun getInt(): Int
+}
+
+inline fun getCounter(crossinline init: () -> Int): Interface =
+    object : Interface {
+        var value = init()
+        override fun getInt(): Int = value++
+    }
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineAnonymousObject/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineAnonymousObject/main/main.kt
new file mode 100644
index 0000000..b392da6
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineAnonymousObject/main/main.kt
@@ -0,0 +1,13 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    val a = lib.getCounter { 100 }
+    val x = a.getInt()
+    if (x != 100) error("a returned $x but expected '100'")
+    val y = a.getInt()
+    if (y != 101) error("a returned $y but expected '101'")
+
+    return "OK"
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineCapture/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineCapture/lib/lib.kt
new file mode 100644
index 0000000..05c9bb3
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineCapture/lib/lib.kt
@@ -0,0 +1,10 @@
+package lib
+
+fun inlineCapture(s: String): String {
+    return with(StringBuilder()) {
+        val o = object {
+            override fun toString() = s
+        }
+        append(o)
+    }.toString()
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineCapture/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineCapture/main/main.kt
new file mode 100644
index 0000000..ed1c0c0
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineCapture/main/main.kt
@@ -0,0 +1,7 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    return inlineCapture("OK")
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineNoRegeneration/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineNoRegeneration/lib/lib.kt
new file mode 100644
index 0000000..d1e07a7
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineNoRegeneration/lib/lib.kt
@@ -0,0 +1,19 @@
+package lib
+
+var result = "fail"
+
+inline fun foo(crossinline s: () -> String) {
+    object {
+        private inline fun test(crossinline z: () -> String) {
+            result = object { //should be marked as public abi as there is no regenerated abject on inline
+                fun run(): String {
+                    return "O"
+                }
+            }.run() + z()
+        }
+
+        fun foo() {
+            test { s() }
+        }
+    }.foo()
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineNoRegeneration/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineNoRegeneration/main/main.kt
new file mode 100644
index 0000000..ef5e12a
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineNoRegeneration/main/main.kt
@@ -0,0 +1,10 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    foo {
+        "K"
+    }
+    return result
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineReifiedFunction/lib/utils.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineReifiedFunction/lib/utils.kt
new file mode 100644
index 0000000..1d71238
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineReifiedFunction/lib/utils.kt
@@ -0,0 +1,4 @@
+package lib
+
+inline fun <reified T> safeCall(x: Any?, fn: (T) -> T): T? =
+    if (x is T) fn(x) else null
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineReifiedFunction/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineReifiedFunction/main/main.kt
new file mode 100644
index 0000000..292d620
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineReifiedFunction/main/main.kt
@@ -0,0 +1,13 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    val a = safeCall<Int>(10) { it * it }
+    if (a != 100) error("a is '$a', but is expected to be '100'")
+
+    val b = safeCall<Int>(null) { it * it }
+    if (b != null) error("b is '$b', but is expected to be 'null'")
+
+    return "OK"
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineWhenMappings/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineWhenMappings/lib/lib.kt
new file mode 100644
index 0000000..170ec9d
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineWhenMappings/lib/lib.kt
@@ -0,0 +1,10 @@
+package lib
+
+enum class E {
+    A, B
+}
+
+inline fun value(x: E) = when (x) {
+    E.A -> "OK"
+    E.B -> "Fail"
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/inlineWhenMappings/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/inlineWhenMappings/main/main.kt
new file mode 100644
index 0000000..ed5a53b
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/inlineWhenMappings/main/main.kt
@@ -0,0 +1,5 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String = value(E.A)
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/innerObjectRegeneration/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/innerObjectRegeneration/lib/lib.kt
new file mode 100644
index 0000000..c1b0495
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/innerObjectRegeneration/lib/lib.kt
@@ -0,0 +1,19 @@
+package lib
+
+var result = "fail"
+
+inline fun foo(crossinline s: () -> String) {
+    object {
+        private inline fun test(crossinline z: () -> String) {
+            object {
+                fun run() {
+                    result = z()
+                }
+            }.run()
+        }
+
+        fun foo() {
+            test { s() } // regenerated object should be marked as public abi
+        }
+    }.foo()
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/innerObjectRegeneration/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/innerObjectRegeneration/main/main.kt
new file mode 100644
index 0000000..b176501
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/innerObjectRegeneration/main/main.kt
@@ -0,0 +1,10 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    foo {
+        "OK"
+    }
+    return result
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/kt-40133/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/kt-40133/lib/lib.kt
new file mode 100644
index 0000000..963c101
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/kt-40133/lib/lib.kt
@@ -0,0 +1,11 @@
+package lib
+
+inline fun anInlineFunction(crossinline crossInlineLamba: () -> Unit) {
+    Foo().apply {
+        barMethod { crossInlineLamba() }
+    }
+}
+
+class Foo {
+    fun barMethod(aLambda: () -> Unit) { aLambda() }
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/kt-40133/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/kt-40133/main/main.kt
new file mode 100644
index 0000000..c0e3e50
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/kt-40133/main/main.kt
@@ -0,0 +1,9 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    var result = "Fail"
+    anInlineFunction { result = "OK" }
+    return result
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/privateOnlyConstructors/lib/classes.kt b/native/native.tests/testData/klib/header-klibs/compilation/privateOnlyConstructors/lib/classes.kt
new file mode 100644
index 0000000..1ad66a4
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/privateOnlyConstructors/lib/classes.kt
@@ -0,0 +1,7 @@
+package lib
+
+class A private constructor(val x: Int) {
+    companion object {
+        fun create(x: Int): A = A(x * 2)
+    }
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/privateOnlyConstructors/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/privateOnlyConstructors/main/main.kt
new file mode 100644
index 0000000..99092a0
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/privateOnlyConstructors/main/main.kt
@@ -0,0 +1,10 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    val a = A.create(10)
+    if (a.x != 20) error("a.x is '${a.x}', but is expected to be '20'")
+
+    return "OK"
+}
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/privateValueClassConstructor/lib/lib.kt b/native/native.tests/testData/klib/header-klibs/compilation/privateValueClassConstructor/lib/lib.kt
new file mode 100644
index 0000000..4ab6e09
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/privateValueClassConstructor/lib/lib.kt
@@ -0,0 +1,6 @@
+package lib
+
+value class A private constructor(val value: String) {
+    companion object { fun a() = A("OK") }
+    inline fun b() = value
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/privateValueClassConstructor/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/privateValueClassConstructor/main/main.kt
new file mode 100644
index 0000000..40ec932
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/privateValueClassConstructor/main/main.kt
@@ -0,0 +1,7 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    return A.a().b()
+}
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/topLevel/lib/utils.kt b/native/native.tests/testData/klib/header-klibs/compilation/topLevel/lib/utils.kt
new file mode 100644
index 0000000..c3cd59c
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/topLevel/lib/utils.kt
@@ -0,0 +1,6 @@
+package lib
+
+val prop: Int = 1
+fun func(): Int = 2
+inline fun inlineFunc(): Int = 3
+const val constant: Int = 4
\ No newline at end of file
diff --git a/native/native.tests/testData/klib/header-klibs/compilation/topLevel/main/main.kt b/native/native.tests/testData/klib/header-klibs/compilation/topLevel/main/main.kt
new file mode 100644
index 0000000..4741983
--- /dev/null
+++ b/native/native.tests/testData/klib/header-klibs/compilation/topLevel/main/main.kt
@@ -0,0 +1,14 @@
+package app
+
+import lib.*
+
+fun runAppAndReturnOk(): String {
+    if (prop != 1) error("prop is '$prop', but is expected to be '1'")
+    val funcValue = func()
+    if (funcValue != 2) error("func() is '$funcValue', but is expected to be '2'")
+    val inlineFuncValue = inlineFunc()
+    if (inlineFuncValue != 3) error("inlineFunc() is '$inlineFuncValue', but is expected to be '3'")
+    if (constant != 4) error("constant is '$constant', but is expected to be '4'")
+
+    return "OK"
+}
\ No newline at end of file
diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirNativeHeaderKlibComparisonTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirNativeHeaderKlibComparisonTestGenerated.java
new file mode 100644
index 0000000..cc3878c
--- /dev/null
+++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirNativeHeaderKlibComparisonTestGenerated.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2010-2023 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.konan.blackboxtest;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.junit.jupiter.api.Tag;
+import org.jetbrains.kotlin.konan.blackboxtest.support.group.FirPipeline;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateNativeTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("native/native.tests/testData/klib/header-klibs/comparison")
+@TestDataPath("$PROJECT_ROOT")
+@Tag("frontend-fir")
+@FirPipeline()
+public class FirNativeHeaderKlibComparisonTestGenerated extends AbstractNativeHeaderKlibComparisonTest {
+    @Test
+    public void testAllFilesPresentInComparison() throws Exception {
+        KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/klib/header-klibs/comparison"), Pattern.compile("^([^\\.]+)$"), null, false);
+    }
+
+    @Test
+    @TestMetadata("anonymousObjects")
+    public void testAnonymousObjects() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/anonymousObjects/");
+    }
+
+    @Test
+    @TestMetadata("classFlags")
+    public void testClassFlags() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/classFlags/");
+    }
+
+    @Test
+    @TestMetadata("classPrivateMembers")
+    public void testClassPrivateMembers() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/classPrivateMembers/");
+    }
+
+    @Test
+    @TestMetadata("constant")
+    public void testConstant() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/constant/");
+    }
+
+    @Test
+    @TestMetadata("declarationOrderInline")
+    public void testDeclarationOrderInline() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/declarationOrderInline/");
+    }
+
+    @Test
+    @TestMetadata("functionBody")
+    public void testFunctionBody() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/functionBody/");
+    }
+
+    @Test
+    @TestMetadata("inlineFunInPrivateClass")
+    public void testInlineFunInPrivateClass() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateClass/");
+    }
+
+    @Test
+    @TestMetadata("inlineFunInPrivateNestedClass")
+    public void testInlineFunInPrivateNestedClass() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateNestedClass/");
+    }
+
+    @Test
+    @TestMetadata("inlineFunctionBody")
+    public void testInlineFunctionBody() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/inlineFunctionBody/");
+    }
+
+    @Test
+    @TestMetadata("lambdas")
+    public void testLambdas() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/lambdas/");
+    }
+
+    @Test
+    @TestMetadata("parameterName")
+    public void testParameterName() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/parameterName/");
+    }
+
+    @Test
+    @TestMetadata("privateInterface")
+    public void testPrivateInterface() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/privateInterface/");
+    }
+
+    @Test
+    @TestMetadata("privateTypealias")
+    public void testPrivateTypealias() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/privateTypealias/");
+    }
+
+    @Test
+    @TestMetadata("returnType")
+    public void testReturnType() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/returnType/");
+    }
+
+    @Test
+    @TestMetadata("superClass")
+    public void testSuperClass() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/superClass/");
+    }
+
+    @Test
+    @TestMetadata("syntheticAccessors")
+    public void testSyntheticAccessors() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/syntheticAccessors/");
+    }
+
+    @Test
+    @TestMetadata("topLevelPrivateMembers")
+    public void testTopLevelPrivateMembers() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/topLevelPrivateMembers/");
+    }
+}
diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirNativeHeaderKlibCompilationTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirNativeHeaderKlibCompilationTestGenerated.java
new file mode 100644
index 0000000..5c567fa
--- /dev/null
+++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirNativeHeaderKlibCompilationTestGenerated.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2010-2023 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.konan.blackboxtest;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.junit.jupiter.api.Tag;
+import org.jetbrains.kotlin.konan.blackboxtest.support.group.FirPipeline;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateNativeTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("native/native.tests/testData/klib/header-klibs/compilation")
+@TestDataPath("$PROJECT_ROOT")
+@Tag("frontend-fir")
+@FirPipeline()
+public class FirNativeHeaderKlibCompilationTestGenerated extends AbstractNativeHeaderKlibCompilationTest {
+    @Test
+    public void testAllFilesPresentInCompilation() throws Exception {
+        KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/klib/header-klibs/compilation"), Pattern.compile("^([^\\.]+)$"), null, false);
+    }
+
+    @Test
+    @TestMetadata("anonymousObject")
+    public void testAnonymousObject() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/anonymousObject/");
+    }
+
+    @Test
+    @TestMetadata("classes")
+    public void testClasses() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/classes/");
+    }
+
+    @Test
+    @TestMetadata("clinit")
+    public void testClinit() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/clinit/");
+    }
+
+    @Test
+    @TestMetadata("inlineAnnotationInstantiation")
+    public void testInlineAnnotationInstantiation() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineAnnotationInstantiation/");
+    }
+
+    @Test
+    @TestMetadata("inlineAnonymousObject")
+    public void testInlineAnonymousObject() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineAnonymousObject/");
+    }
+
+    @Test
+    @TestMetadata("inlineCapture")
+    public void testInlineCapture() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineCapture/");
+    }
+
+    @Test
+    @TestMetadata("inlineNoRegeneration")
+    public void testInlineNoRegeneration() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineNoRegeneration/");
+    }
+
+    @Test
+    @TestMetadata("inlineReifiedFunction")
+    public void testInlineReifiedFunction() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineReifiedFunction/");
+    }
+
+    @Test
+    @TestMetadata("inlineWhenMappings")
+    public void testInlineWhenMappings() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineWhenMappings/");
+    }
+
+    @Test
+    @TestMetadata("innerObjectRegeneration")
+    public void testInnerObjectRegeneration() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/innerObjectRegeneration/");
+    }
+
+    @Test
+    @TestMetadata("kt-40133")
+    public void testKt_40133() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/kt-40133/");
+    }
+
+    @Test
+    @TestMetadata("privateOnlyConstructors")
+    public void testPrivateOnlyConstructors() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/privateOnlyConstructors/");
+    }
+
+    @Test
+    @TestMetadata("privateValueClassConstructor")
+    public void testPrivateValueClassConstructor() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/privateValueClassConstructor/");
+    }
+
+    @Test
+    @TestMetadata("topLevel")
+    public void testTopLevel() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/topLevel/");
+    }
+}
diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeHeaderKlibComparisonTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeHeaderKlibComparisonTestGenerated.java
new file mode 100644
index 0000000..4c97fe7
--- /dev/null
+++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeHeaderKlibComparisonTestGenerated.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2010-2023 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.konan.blackboxtest;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateNativeTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("native/native.tests/testData/klib/header-klibs/comparison")
+@TestDataPath("$PROJECT_ROOT")
+public class NativeHeaderKlibComparisonTestGenerated extends AbstractNativeHeaderKlibComparisonTest {
+    @Test
+    public void testAllFilesPresentInComparison() throws Exception {
+        KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/klib/header-klibs/comparison"), Pattern.compile("^([^\\.]+)$"), null, false);
+    }
+
+    @Test
+    @TestMetadata("anonymousObjects")
+    public void testAnonymousObjects() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/anonymousObjects/");
+    }
+
+    @Test
+    @TestMetadata("classFlags")
+    public void testClassFlags() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/classFlags/");
+    }
+
+    @Test
+    @TestMetadata("classPrivateMembers")
+    public void testClassPrivateMembers() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/classPrivateMembers/");
+    }
+
+    @Test
+    @TestMetadata("constant")
+    public void testConstant() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/constant/");
+    }
+
+    @Test
+    @TestMetadata("declarationOrderInline")
+    public void testDeclarationOrderInline() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/declarationOrderInline/");
+    }
+
+    @Test
+    @TestMetadata("functionBody")
+    public void testFunctionBody() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/functionBody/");
+    }
+
+    @Test
+    @TestMetadata("inlineFunInPrivateClass")
+    public void testInlineFunInPrivateClass() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateClass/");
+    }
+
+    @Test
+    @TestMetadata("inlineFunInPrivateNestedClass")
+    public void testInlineFunInPrivateNestedClass() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/inlineFunInPrivateNestedClass/");
+    }
+
+    @Test
+    @TestMetadata("inlineFunctionBody")
+    public void testInlineFunctionBody() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/inlineFunctionBody/");
+    }
+
+    @Test
+    @TestMetadata("lambdas")
+    public void testLambdas() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/lambdas/");
+    }
+
+    @Test
+    @TestMetadata("parameterName")
+    public void testParameterName() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/parameterName/");
+    }
+
+    @Test
+    @TestMetadata("privateInterface")
+    public void testPrivateInterface() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/privateInterface/");
+    }
+
+    @Test
+    @TestMetadata("privateTypealias")
+    public void testPrivateTypealias() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/privateTypealias/");
+    }
+
+    @Test
+    @TestMetadata("returnType")
+    public void testReturnType() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/returnType/");
+    }
+
+    @Test
+    @TestMetadata("superClass")
+    public void testSuperClass() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/superClass/");
+    }
+
+    @Test
+    @TestMetadata("syntheticAccessors")
+    public void testSyntheticAccessors() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/syntheticAccessors/");
+    }
+
+    @Test
+    @TestMetadata("topLevelPrivateMembers")
+    public void testTopLevelPrivateMembers() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/comparison/topLevelPrivateMembers/");
+    }
+}
diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeHeaderKlibCompilationTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeHeaderKlibCompilationTestGenerated.java
new file mode 100644
index 0000000..489083b
--- /dev/null
+++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeHeaderKlibCompilationTestGenerated.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2010-2023 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.konan.blackboxtest;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.util.KtTestUtil;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateNativeTestsKt}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("native/native.tests/testData/klib/header-klibs/compilation")
+@TestDataPath("$PROJECT_ROOT")
+public class NativeHeaderKlibCompilationTestGenerated extends AbstractNativeHeaderKlibCompilationTest {
+    @Test
+    public void testAllFilesPresentInCompilation() throws Exception {
+        KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/klib/header-klibs/compilation"), Pattern.compile("^([^\\.]+)$"), null, false);
+    }
+
+    @Test
+    @TestMetadata("anonymousObject")
+    public void testAnonymousObject() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/anonymousObject/");
+    }
+
+    @Test
+    @TestMetadata("classes")
+    public void testClasses() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/classes/");
+    }
+
+    @Test
+    @TestMetadata("clinit")
+    public void testClinit() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/clinit/");
+    }
+
+    @Test
+    @TestMetadata("inlineAnnotationInstantiation")
+    public void testInlineAnnotationInstantiation() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineAnnotationInstantiation/");
+    }
+
+    @Test
+    @TestMetadata("inlineAnonymousObject")
+    public void testInlineAnonymousObject() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineAnonymousObject/");
+    }
+
+    @Test
+    @TestMetadata("inlineCapture")
+    public void testInlineCapture() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineCapture/");
+    }
+
+    @Test
+    @TestMetadata("inlineNoRegeneration")
+    public void testInlineNoRegeneration() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineNoRegeneration/");
+    }
+
+    @Test
+    @TestMetadata("inlineReifiedFunction")
+    public void testInlineReifiedFunction() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineReifiedFunction/");
+    }
+
+    @Test
+    @TestMetadata("inlineWhenMappings")
+    public void testInlineWhenMappings() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/inlineWhenMappings/");
+    }
+
+    @Test
+    @TestMetadata("innerObjectRegeneration")
+    public void testInnerObjectRegeneration() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/innerObjectRegeneration/");
+    }
+
+    @Test
+    @TestMetadata("kt-40133")
+    public void testKt_40133() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/kt-40133/");
+    }
+
+    @Test
+    @TestMetadata("privateOnlyConstructors")
+    public void testPrivateOnlyConstructors() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/privateOnlyConstructors/");
+    }
+
+    @Test
+    @TestMetadata("privateValueClassConstructor")
+    public void testPrivateValueClassConstructor() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/privateValueClassConstructor/");
+    }
+
+    @Test
+    @TestMetadata("topLevel")
+    public void testTopLevel() throws Exception {
+        runTest("native/native.tests/testData/klib/header-klibs/compilation/topLevel/");
+    }
+}
diff --git a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt
index 3ce72e8..56097a4 100644
--- a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt
+++ b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt
@@ -314,6 +314,36 @@
                 }
             }
         }
+
+        // Header klib comparison tests
+        testGroup("native/native.tests/tests-gen", "native/native.tests/testData") {
+            testClass<AbstractNativeHeaderKlibComparisonTest>(
+                suiteTestClassName = "NativeHeaderKlibComparisonTestGenerated",
+            ) {
+                model("klib/header-klibs/comparison", extension = null, recursive = false)
+            }
+            testClass<AbstractNativeHeaderKlibComparisonTest>(
+                suiteTestClassName = "FirNativeHeaderKlibComparisonTestGenerated",
+                annotations = listOf(*frontendFir()),
+            ) {
+                model("klib/header-klibs/comparison", extension = null, recursive = false)
+            }
+        }
+
+        // Header klib compilation tests
+        testGroup("native/native.tests/tests-gen", "native/native.tests/testData") {
+            testClass<AbstractNativeHeaderKlibCompilationTest>(
+                suiteTestClassName = "NativeHeaderKlibCompilationTestGenerated",
+            ) {
+                model("klib/header-klibs/compilation", extension = null, recursive = false)
+            }
+            testClass<AbstractNativeHeaderKlibCompilationTest>(
+                suiteTestClassName = "FirNativeHeaderKlibCompilationTestGenerated",
+                annotations = listOf(*frontendFir()),
+            ) {
+                model("klib/header-klibs/compilation", extension = null, recursive = false)
+            }
+        }
     }
 }
 
diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeHeaderKlibTest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeHeaderKlibTest.kt
new file mode 100644
index 0000000..3890e4a
--- /dev/null
+++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeHeaderKlibTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.konan.blackboxtest
+
+import com.intellij.testFramework.TestDataFile
+import org.jetbrains.kotlin.konan.blackboxtest.support.*
+import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact
+import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
+import org.jetbrains.kotlin.konan.blackboxtest.support.group.UsePartialLinkage
+import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunChecks
+import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Timeouts
+import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile
+import org.junit.jupiter.api.Tag
+import java.io.File
+import kotlin.test.assertContentEquals
+import kotlin.test.assertFailsWith
+
+@Tag("klib")
+@UsePartialLinkage(UsePartialLinkage.Mode.ENABLED_WITH_ERROR)
+abstract class AbstractNativeHeaderKlibComparisonTest : AbstractNativeSimpleTest() {
+
+    protected fun runTest(@TestDataFile testPath: String) {
+        val testPathFull = getAbsoluteFile(testPath)
+
+        val testCaseBase: TestCase = generateTestcaseFromDirectory(testPathFull, "base", listOf())
+        compileToLibrary(testCaseBase).assertSuccess()
+        val headerKlibBase = File(getHeaderPath("base"))
+        assert(headerKlibBase.exists())
+
+        val sameAbiDir = testPathFull.resolve("sameAbi")
+        val differentAbiDir = testPathFull.resolve("differentAbi")
+
+        assert(sameAbiDir.exists() || differentAbiDir.exists()) { "Nothing to compare" }
+
+        if (sameAbiDir.exists()) {
+            val testCaseSameAbi: TestCase = generateTestcaseFromDirectory(testPathFull, "sameAbi", listOf())
+            compileToLibrary(testCaseSameAbi).assertSuccess()
+            val headerKlibSameAbi = File(getHeaderPath("sameAbi"))
+            assert(headerKlibSameAbi.exists())
+            assertContentEquals(headerKlibBase.readBytes(), headerKlibSameAbi.readBytes())
+        }
+
+        if (differentAbiDir.exists()) {
+            val testCaseDifferentAbi: TestCase = generateTestcaseFromDirectory(testPathFull, "differentAbi", listOf())
+            compileToLibrary(testCaseDifferentAbi).assertSuccess()
+            val headerKlibDifferentAbi = File(getHeaderPath("differentAbi"))
+            assert(headerKlibDifferentAbi.exists())
+            assertFailsWith<AssertionError>("base and differentAbi header klib are equal") {
+                assertContentEquals(headerKlibBase.readBytes(), headerKlibDifferentAbi.readBytes())
+            }
+        }
+    }
+
+}
+
+@Tag("klib")
+@UsePartialLinkage(UsePartialLinkage.Mode.ENABLED_WITH_ERROR)
+abstract class AbstractNativeHeaderKlibCompilationTest : AbstractNativeSimpleTest() {
+
+    protected fun runTest(@TestDataFile testPath: String) {
+        val testPathFull = getAbsoluteFile(testPath)
+        assert(testPathFull.exists())
+        val testCaseLib: TestCase = generateTestcaseFromDirectory(testPathFull, "lib", listOf())
+        val klibLib = compileToLibrary(testCaseLib)
+        val headerKlibLib = File(getHeaderPath("lib"))
+        assert(headerKlibLib.exists())
+        val testPathApp = testPathFull.resolve("main")
+        val klibAppFromHeader = compileToLibrary(testPathApp, TestCompilationArtifact.KLIB(headerKlibLib))
+        val klibAppFromFull = compileToLibrary(testPathApp, klibLib.resultingArtifact)
+        assertContentEquals(
+            klibAppFromHeader.klibFile.readBytes(),
+            klibAppFromFull.klibFile.readBytes()
+        )
+    }
+}
+
+private fun AbstractNativeSimpleTest.getHeaderPath(rev: String) = buildDir.absolutePath + "/header.$rev.klib"
+private fun AbstractNativeSimpleTest.generateTestcaseFromDirectory(source: File, rev: String, extraArgs: List<String>): TestCase {
+    val moduleName: String = source.name
+    val module = TestModule.Exclusive(moduleName, emptySet(), emptySet(), emptySet())
+    source.resolve(rev).listFiles()?.forEach {
+        muteTestIfNecessary(it)
+        module.files += TestFile.createCommitted(it, module)
+    }
+
+    val headerKlibPath = "-Xheader-klib-path=" + getHeaderPath(rev)
+    val relativeBasePath = "-Xklib-relative-path-base=$source/$rev"
+
+    return TestCase(
+        id = TestCaseId.Named(moduleName),
+        kind = TestKind.STANDALONE,
+        modules = setOf(module),
+        freeCompilerArgs = TestCompilerArgs(extraArgs + relativeBasePath + headerKlibPath),
+        nominalPackageName = PackageName.EMPTY,
+        checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
+        extras = TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT)
+    ).apply {
+        initialize(null, null)
+    }
+}