in progress
diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseFactories.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseFactories.kt
index c3a6bc3..e01918f 100644
--- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseFactories.kt
+++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseFactories.kt
@@ -19,7 +19,7 @@
 
 annotation class PhaseDescription(
     val name: String,
-    val prerequisite: Array<KClass<out FileLoweringPass>> = [],
+    val prerequisite: Array<KClass<out ModuleLoweringPass>> = [],
 )
 
 fun <Context : LoweringContext> createFilePhases(
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmIrInliner.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmIrInliner.kt
index c3c3fa7..38dea2a 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmIrInliner.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmIrInliner.kt
@@ -10,6 +10,8 @@
 import org.jetbrains.kotlin.backend.jvm.ir.isInlineFunctionCall
 import org.jetbrains.kotlin.ir.declarations.IrFile
 import org.jetbrains.kotlin.ir.declarations.IrFunction
+import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
+import org.jetbrains.kotlin.ir.inline.AbstractInlineFunctionResolver
 import org.jetbrains.kotlin.ir.inline.FunctionInlining
 import org.jetbrains.kotlin.ir.inline.InlineFunctionResolver
 import org.jetbrains.kotlin.ir.inline.InlineMode
@@ -27,13 +29,13 @@
 ) {
     private val enabled = context.config.enableIrInliner
 
-    override fun lower(irFile: IrFile) {
+    override fun lower(irModule: IrModuleFragment) {
         if (enabled) {
-            super.lower(irFile)
+            super.lower(irModule)
         }
     }
 }
 
-class JvmInlineFunctionResolver(private val context: JvmBackendContext) : InlineFunctionResolver(InlineMode.ALL_INLINE_FUNCTIONS) {
+class JvmInlineFunctionResolver(private val context: JvmBackendContext) : AbstractInlineFunctionResolver(InlineMode.ALL_INLINE_FUNCTIONS) {
     override fun needsInlining(symbol: IrFunctionSymbol): Boolean = symbol.owner.isInlineFunctionCall(context)
 }
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmFunctionInlining.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmFunctionInlining.kt
index 1eb2ae7..ca0bbbb 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmFunctionInlining.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmFunctionInlining.kt
@@ -21,7 +21,7 @@
             context = context,
             inlineFunctionResolver = WasmInlineFunctionResolver(context, inlineMode),
             produceOuterThisFields = false,
-        ).inline(irModule)
+        ).lower(irModule)
 
         irModule.patchDeclarationParents()
     }
diff --git a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt
index af37b9d..3d18db4 100644
--- a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt
+++ b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt
@@ -21,7 +21,6 @@
 import org.jetbrains.kotlin.ir.expressions.impl.*
 import org.jetbrains.kotlin.ir.originalBeforeInline
 import org.jetbrains.kotlin.ir.symbols.*
-import org.jetbrains.kotlin.ir.symbols.impl.IrReturnableBlockSymbolImpl
 import org.jetbrains.kotlin.ir.types.*
 import org.jetbrains.kotlin.ir.util.*
 import org.jetbrains.kotlin.ir.visitors.*
@@ -35,22 +34,108 @@
     private val insertAdditionalImplicitCasts: Boolean = true,
     private val regenerateInlinedAnonymousObjects: Boolean = false,
     private val produceOuterThisFields: Boolean = true,
-) : IrElementTransformerVoidWithContext(), BodyLoweringPass {
+) : ModuleLoweringPass, FileLoweringPass {
+    class WrapperResolver(
+        val cached: Map<IrFunctionSymbol, IrFunction>,
+        val delegate: InlineFunctionResolver,
+    ) : InlineFunctionResolver by delegate {
+        override fun getFunctionDeclaration(symbol: IrFunctionSymbol): IrFunction? {
+            return cached[symbol] ?: delegate.getFunctionDeclaration(symbol)
+        }
+    }
+
+    private fun lower(container: IrElement) {
+        val dependencies = mutableMapOf<IrFunctionSymbol, MutableList<IrFunctionSymbol>>()
+        container.acceptChildrenVoid(object : IrVisitorVoid() {
+            val insideInlineFunction = mutableListOf<IrFunctionSymbol>()
+            override fun visitFunction(declaration: IrFunction) {
+                if (inlineFunctionResolver.needsInlining(declaration.symbol)) {
+                    insideInlineFunction.add(declaration.symbol)
+                    super.visitFunction(declaration)
+                    insideInlineFunction.removeLast()
+                } else {
+                    super.visitFunction(declaration)
+                }
+            }
+
+            override fun visitFunctionAccess(expression: IrFunctionAccessExpression) {
+                super.visitFunctionAccess(expression)
+                val callee = expression.symbol as? IrSimpleFunctionSymbol ?: return
+                if (inlineFunctionResolver.needsInlining(callee)) {
+                    for (callSite in insideInlineFunction) {
+                        dependencies.getOrPut(callSite) { mutableListOf() }.add(callee)
+                    }
+                }
+            }
+        })
+        val cache = mutableMapOf<IrFunctionSymbol, IrFunction>()
+        val inProgress = mutableSetOf<IrFunctionSymbol>()
+        val resolver = WrapperResolver(cache, inlineFunctionResolver)
+        val inlineTransformer = FunctionInliningTransformer(
+            context,
+            resolver,
+            insertAdditionalImplicitCasts,
+            regenerateInlinedAnonymousObjects,
+            produceOuterThisFields,
+        )
+
+        val inliningOrder = mutableListOf<IrFunctionSymbol>()
+
+        fun computeOrder(callee: IrFunctionSymbol) {
+            if (cache.containsKey(callee) || callee !in dependencies.keys) return
+            if (!inProgress.add(callee)) {
+                TODO("Report recursive inlining")
+            }
+            for (dep in dependencies[callee] ?: emptyList()) {
+                computeOrder(dep)
+            }
+            inliningOrder.add(callee)
+            inProgress.remove(callee)
+        }
+
+        for (callee in dependencies.keys) {
+            computeOrder(callee)
+        }
+
+        for (callee in inliningOrder) {
+            val result = callee.owner.deepCopyWithSymbols(callee.owner.parent)
+            result.transformChildrenVoid(inlineTransformer)
+            // TODO: run erasure on result
+            cache[callee] = result // visible to future inlines in this loop
+        }
+
+        container.transformChildrenVoid(inlineTransformer)
+    }
+
+    override fun lower(irModule: IrModuleFragment) {
+        lower(irModule as IrElement)
+    }
+
+    override fun lower(irFile: IrFile) {
+        lower(irFile as IrElement)
+    }
+
+    fun lower(irFunction: IrFunction) {
+        lower(irFunction.body as IrElement)
+        for (parameter in irFunction.parameters) {
+            parameter.defaultValue?.let(::lower)
+        }
+    }
+}
+
+class FunctionInliningTransformer(
+    val context: LoweringContext,
+    private val inlineFunctionResolver: InlineFunctionResolver,
+    private val insertAdditionalImplicitCasts: Boolean = true,
+    private val regenerateInlinedAnonymousObjects: Boolean = false,
+    private val produceOuterThisFields: Boolean = true,
+) : IrElementTransformerVoidWithContext() {
     init {
         require(!produceOuterThisFields || context is CommonBackendContext) {
             "The inliner can generate outer fields only with param `context` of type `CommonBackendContext`"
         }
     }
 
-    override fun lower(irBody: IrBody, container: IrDeclaration) {
-        // TODO container: IrSymbolDeclaration
-        withinScope(container) {
-            irBody.accept(this, null)
-        }
-
-        irBody.patchDeclarationParents(container as? IrDeclarationParent ?: container.parent)
-    }
-
     override fun visitDeclaration(declaration: IrDeclarationBase): IrStatement {
         return when (declaration) {
             is IrFunction, is IrClass, is IrProperty -> context.irFactory.stageController.restrictTo(declaration) {
@@ -60,8 +145,6 @@
         }
     }
 
-    fun inline(irModule: IrModuleFragment) = irModule.accept(this, data = null)
-
     override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
         expression.transformChildrenVoid(this)
 
@@ -82,16 +165,6 @@
             return expression
         }
 
-        withinScope(actualCallee) {
-            actualCallee.body?.transformChildrenVoid()
-            actualCallee.parameters.forEachIndexed { index, param ->
-                if (expression.arguments[index] == null) {
-                    // Default values can recursively reference [callee] - transform only needed.
-                    param.defaultValue = param.defaultValue?.transform(this@FunctionInlining, null)
-                }
-            }
-        }
-
         val parent = allScopes.map { it.irElement }.filterIsInstance<IrDeclarationParent>().lastOrNull()
             ?: allScopes.map { it.irElement }.filterIsInstance<IrDeclaration>().lastOrNull()?.parent
 
diff --git a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineFunctionResolver.kt b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineFunctionResolver.kt
index 6a8be92..7bce198 100644
--- a/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineFunctionResolver.kt
+++ b/compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineFunctionResolver.kt
@@ -54,18 +54,27 @@
     ALL_FUNCTIONS,
 }
 
-abstract class InlineFunctionResolver(val inlineMode: InlineMode) {
-    open val callInlinerStrategy: CallInlinerStrategy
+interface InlineFunctionResolver {
+    val inlineMode: InlineMode
+    val callInlinerStrategy: CallInlinerStrategy
+    val allowExternalInlining: Boolean
+    fun needsInlining(symbol: IrFunctionSymbol): Boolean
+    fun needsInlining(expression: IrFunctionAccessExpression): Boolean
+    fun getFunctionDeclaration(symbol: IrFunctionSymbol): IrFunction?
+}
+
+abstract class AbstractInlineFunctionResolver(override val inlineMode: InlineMode) : InlineFunctionResolver {
+    override val callInlinerStrategy: CallInlinerStrategy
         get() = CallInlinerStrategy.DEFAULT
-    open val allowExternalInlining: Boolean
+    override val allowExternalInlining: Boolean
         get() = false
 
-    open fun needsInlining(symbol: IrFunctionSymbol) =
+    override fun needsInlining(symbol: IrFunctionSymbol) =
         symbol.isBound && symbol.owner.isInline && (allowExternalInlining || !symbol.owner.isExternal)
 
-    open fun needsInlining(expression: IrFunctionAccessExpression) = needsInlining(expression.symbol)
+    override fun needsInlining(expression: IrFunctionAccessExpression) = needsInlining(expression.symbol)
 
-    open fun getFunctionDeclaration(symbol: IrFunctionSymbol): IrFunction? {
+    override fun getFunctionDeclaration(symbol: IrFunctionSymbol): IrFunction? {
         if (shouldExcludeFunctionFromInlining(symbol)) return null
 
         val owner = symbol.owner
@@ -80,7 +89,7 @@
 abstract class InlineFunctionResolverReplacingCoroutineIntrinsics<Ctx : LoweringContext>(
     protected val context: Ctx,
     inlineMode: InlineMode,
-) : InlineFunctionResolver(inlineMode) {
+) : AbstractInlineFunctionResolver(inlineMode) {
     final override val allowExternalInlining: Boolean
         get() = context.allowExternalInlining
 
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt
index 0f59c41..633bc52 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt
@@ -363,11 +363,11 @@
 /**
  * The second phase of inlining (inline all functions).
  */
-internal val inlineAllFunctionsPhase = createFileLoweringPhase(
-        lowering = { context: Context ->
-            NativeIrInliner(context, inlineMode = InlineMode.ALL_INLINE_FUNCTIONS)
-        },
+internal val inlineAllFunctionsPhase = createSimpleNamedCompilerPhase<NativeGenerationState, IrModuleFragment>(
         name = "InlineAllFunctions",
+        op = { context: NativeGenerationState, module ->
+            NativeIrInliner(context.context, inlineMode = InlineMode.ALL_INLINE_FUNCTIONS).lower(module)
+        }
 )
 
 private val interopPhase = createFileLoweringPhase(
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt
index 434066c..a70d854 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt
@@ -160,7 +160,7 @@
                 // invariant, we would like to put a synchronization point immediately before "InlineAllFunctions".
                 fragmentWithState.forEach { (fragment, state) -> state.runSpecifiedLowerings(fragment, getLoweringsUpToAndIncludingSyntheticAccessors()) }
                 fragmentWithState.forEach { (fragment, state) -> state.runSpecifiedLowerings(fragment, validateIrAfterInliningOnlyPrivateFunctions) }
-                fragmentWithState.forEach { (fragment, state) -> state.runSpecifiedLowerings(fragment, listOf(inlineAllFunctionsPhase)) }
+                fragmentWithState.forEach { (fragment, state) -> state.runSpecifiedLowerings(fragment, inlineAllFunctionsPhase) }
                 if (context.config.configuration[KlibConfigurationKeys.SYNTHETIC_ACCESSORS_DUMP_DIR] != null) {
                     fragmentWithState.forEach { (fragment, state) -> state.runSpecifiedLowerings(fragment, dumpSyntheticAccessorsPhase) }
                 }
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeInlineFunctionResolver.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeInlineFunctionResolver.kt
index 9e066a4..27ff0dca 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeInlineFunctionResolver.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeInlineFunctionResolver.kt
@@ -61,7 +61,7 @@
 
         ArrayConstructorLowering(context).lower(body, function)
 
-        NativeIrInliner(context, inlineMode = InlineMode.PRIVATE_INLINE_FUNCTIONS).lower(body, function)
+        NativeIrInliner(context, inlineMode = InlineMode.PRIVATE_INLINE_FUNCTIONS).lower(function)
         OuterThisInInlineFunctionsSpecialAccessorLowering(context).lowerWithoutAddingAccessorsToParents(function)
         SyntheticAccessorLowering(context).lowerWithoutAddingAccessorsToParents(function)
     }
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/PreCodegenInliner.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/PreCodegenInliner.kt
index f6030e2..96b639a 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/PreCodegenInliner.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/PreCodegenInliner.kt
@@ -20,12 +20,15 @@
 import org.jetbrains.kotlin.ir.expressions.IrCall
 import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
 import org.jetbrains.kotlin.ir.expressions.IrSuspensionPoint
+import org.jetbrains.kotlin.ir.inline.AbstractInlineFunctionResolver
 import org.jetbrains.kotlin.ir.inline.FunctionInlining
+import org.jetbrains.kotlin.ir.inline.FunctionInliningTransformer
 import org.jetbrains.kotlin.ir.inline.InlineFunctionResolver
 import org.jetbrains.kotlin.ir.inline.InlineMode
 import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
 import org.jetbrains.kotlin.ir.types.classOrNull
 import org.jetbrains.kotlin.ir.util.*
+import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
 import org.jetbrains.kotlin.library.metadata.isCInteropLibrary
 import kotlin.collections.*
 
@@ -146,9 +149,9 @@
                     }
 
                     if (functionsToInline.isNotEmpty()) {
-                        val inliner = FunctionInlining(
+                        val inliner = FunctionInliningTransformer(
                                 context,
-                                inlineFunctionResolver = object : InlineFunctionResolver(inlineMode = InlineMode.ALL_FUNCTIONS) {
+                                inlineFunctionResolver = object : AbstractInlineFunctionResolver(inlineMode = InlineMode.ALL_FUNCTIONS) {
                                     override fun shouldExcludeFunctionFromInlining(symbol: IrFunctionSymbol) =
                                             symbol.owner !in functionsToInline
 
@@ -157,7 +160,7 @@
                                     }
                                 },
                         )
-                        inliner.lower(irBody, irFunction)
+                        irFunction.transform(inliner, null)
 
                         // KT-72336: This is not entirely correct since coroutinesLivenessAnalysisPhase could be turned off.
                         LivenessAnalysis.run(irBody) { it is IrSuspensionPoint }