chore: initial commit.
diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/CompilerPhase.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/CompilerPhase.kt
index 35676cd..32d1a63 100644
--- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/CompilerPhase.kt
+++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/CompilerPhase.kt
@@ -36,6 +36,10 @@
     val stickyPostconditions: Set<Checker<Output>> get() = emptySet()
 }
 
+interface RepeatableCompilerPhase<in Context : CommonBackendContext, Input, Output> : CompilerPhase<Context, Input, Output> {
+    val changesAppeared: Boolean
+}
+
 fun <Context : CommonBackendContext, Input, Output> CompilerPhase<Context, Input, Output>.invokeToplevel(
     phaseConfig: PhaseConfig,
     context: Context,
@@ -66,7 +70,7 @@
         other(phaseState, data, context)
     }
 
-class NamedCompilerPhase<in Context : CommonBackendContext, Data>(
+open class NamedCompilerPhase<in Context : CommonBackendContext, Data>(
     val name: String,
     val description: String,
     val prerequisite: Set<NamedCompilerPhase<Context, *>> = emptySet(),
@@ -143,3 +147,24 @@
 
     override fun toString() = "Compiler Phase @$name"
 }
+
+open class RepeatableNamedCompilerPhase<in Context : CommonBackendContext, Data>(
+    name: String,
+    description: String,
+    prerequisite: Set<NamedCompilerPhase<Context, *>> = emptySet(),
+    private val lower: RepeatableCompilerPhase<Context, Data, Data>,
+    preconditions: Set<Checker<Data>> = emptySet(),
+    postconditions: Set<Checker<Data>> = emptySet(),
+) : RepeatableCompilerPhase<Context, Data, Data>, NamedCompilerPhase<Context, Data>(name, description, prerequisite, lower, preconditions, postconditions) {
+    final override var changesAppeared: Boolean = false
+
+    override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Data>, context: Context, input: Data): Data {
+        return super.invoke(phaseConfig, phaserState, context, input).also {
+            if (lower.changesAppeared) {
+                changesAppeared = true
+            }
+        }
+    }
+
+    fun reset() { changesAppeared = false }
+}
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt
index 8b0e649..ca77879 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt
@@ -29,7 +29,7 @@
 
 private fun ClassLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragment) = moduleFragment.files.forEach { runOnFilePostfix(it) }
 
-private fun makeJsModulePhase(
+fun makeJsModulePhase(
     lowering: (JsIrBackendContext) -> FileLoweringPass,
     name: String,
     description: String,
@@ -41,7 +41,7 @@
     prerequisite = prerequisite
 )
 
-private fun makeCustomJsModulePhase(
+fun makeCustomJsModulePhase(
     op: (JsIrBackendContext, IrModuleFragment) -> Unit,
     description: String,
     name: String,
@@ -108,7 +108,7 @@
     prerequisite: Set<Lowering> = emptySet()
 ) = DeclarationLowering(name, description, prerequisite.map { it.modulePhase }.toSet(), lowering)
 
-private fun makeBodyLoweringPhase(
+fun makeBodyLoweringPhase(
     lowering: (JsIrBackendContext) -> BodyLoweringPass,
     name: String,
     description: String,
@@ -432,19 +432,6 @@
     description = "Lowering which wrap boolean in external declarations with Boolean() call and add diagnostic for such cases"
 )
 
-private val foldConstantLoweringPhase = makeBodyLoweringPhase(
-    { FoldConstantLowering(it, true) },
-    name = "FoldConstantLowering",
-    description = "[Optimization] Constant Folding",
-    prerequisite = setOf(propertyAccessorInlinerLoweringPhase)
-)
-
-private val computeStringTrimPhase = makeJsModulePhase(
-    ::StringTrimLowering,
-    name = "StringTrimLowering",
-    description = "Compute trimIndent and trimMargin operations on constant strings"
-).toModuleLowering()
-
 private val localDelegatedPropertiesLoweringPhase = makeBodyLoweringPhase(
     { LocalDelegatedPropertiesLowering() },
     name = "LocalDelegatedPropertiesLowering",
@@ -885,8 +872,6 @@
     propertyAccessorInlinerLoweringPhase,
     copyPropertyAccessorBodiesLoweringPass,
     booleanPropertyInExternalLowering,
-    foldConstantLoweringPhase,
-    computeStringTrimPhase,
     privateMembersLoweringPhase,
     privateMemberUsagesLoweringPhase,
     defaultArgumentStubGeneratorPhase,
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsOptimizationPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsOptimizationPhases.kt
new file mode 100644
index 0000000..f9b9ab1
--- /dev/null
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsOptimizationPhases.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.ir.backend.js
+
+import org.jetbrains.kotlin.backend.common.lower.StringTrimLowering
+import org.jetbrains.kotlin.backend.common.lower.optimizations.FoldConstantLowering
+import org.jetbrains.kotlin.backend.common.phaser.*
+import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
+
+private val computeStringTrimPhase = makeJsModulePhase(
+    ::StringTrimLowering,
+    name = "StringTrimLowering",
+    description = "Compute trimIndent and trimMargin operations on constant strings"
+).toModuleLowering()
+
+private val jsPrefixOptimizations = NamedCompilerPhase(
+    name = "IrOptimizationPrefix",
+    description = "IR lowerings with one-time optimizations before main optimization loop",
+    lower = computeStringTrimPhase.modulePhase
+)
+
+private val foldConstantLoweringPhase = makeBodyLoweringPhase(
+    { FoldConstantLowering(it, true) },
+    name = "FoldConstantLowering",
+    description = "[Optimization] Constant Folding",
+)
+
+private val jsMainOptimizations = NamedCompilerPhase(
+    name = "IrOptimizations",
+    description = "IR lowerings with one-time optimizations before main optimization loop",
+    lower = foldConstantLoweringPhase.modulePhase
+)
+
+private val validateIrAfterLowering = makeCustomJsModulePhase(
+    { context, module -> validationCallback(context, module) },
+    name = "ValidateIrAfterLowering",
+    description = "Validate IR after lowering"
+).toModuleLowering()
+
+private val jsPostfixOptimizations = NamedCompilerPhase(
+    name = "IrOptimizationPostifx",
+    description = "IR lowerings with one-time optimizations after main optimization loop",
+    lower = validateIrAfterLowering.modulePhase
+)
+
+const val MAX_NUMBER_OF_OPTIMIZATION_ITERATIONS = 10
+
+fun runOptimizationsLoop(
+    modules: Iterable<IrModuleFragment>,
+    context: JsIrBackendContext
+) {
+    // Run prefix optimizations
+    jsPrefixOptimizations.invokeToplevel(PhaseConfig(jsPrefixOptimizations), context, modules)
+
+    for (i in 0 until MAX_NUMBER_OF_OPTIMIZATION_ITERATIONS) {
+        jsMainOptimizations.invokeToplevel(PhaseConfig(jsMainOptimizations), context, modules)
+    }
+
+    // Run postfix optimizations with validations
+    jsPostfixOptimizations.invokeToplevel(PhaseConfig(jsPostfixOptimizations), context, modules)
+}
\ No newline at end of file
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt
index efbfbf9..94e7f24 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt
@@ -144,6 +144,7 @@
 
         if (modes.any { it.dce }) {
             eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects)
+            runOptimizationsLoop(modules, backendContext)
         }
 
         modes.filter { it.dce }.forEach {
diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/transform.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/transform.kt
index e1e8d4a..8a49603 100644
--- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/transform.kt
+++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/transform.kt
@@ -36,6 +36,10 @@
     }
 }
 
+interface Notifier {
+    fun notifyAboutChanges(): Unit
+}
+
 /**
  * Transforms a mutable list in place.
  * Each element `it` is replaced with a result of `transformation(it)`,
@@ -50,6 +54,22 @@
     }
 }
 
+
+/**
+ * The same method as `transformFlat` but also, notify about changes
+ */
+inline fun <T> MutableList<T>.transformFlatWithNotification(
+    notifier: Notifier? = null,
+    transformation: (T) -> List<T>?
+) {
+    var i = 0
+    while (i < size) {
+        val item = get(i)
+
+        i = replaceInPlace(transformation(item), i, notifier)
+    }
+}
+
 /**
  * Transforms a subset of a mutable list in place.
  * Each element `it` that has a type S is replaced with a result of `transformation(it)`,
@@ -69,7 +89,11 @@
     }
 }
 
-@PublishedApi internal fun <T> MutableList<T>.replaceInPlace(transformed: List<T>?, atIndex: Int): Int {
+@PublishedApi internal fun <T> MutableList<T>.replaceInPlace(
+    transformed: List<T>?,
+    atIndex: Int,
+    notifier: Notifier? = null
+): Int {
     var i = atIndex
     when (transformed?.size) {
         null -> i++
@@ -81,7 +105,11 @@
             removeAt(i)
         }
     }
-    return i
+    return i.also {
+        if (notifier != null && transformed != null) {
+           notifier.notifyAboutChanges()
+        }
+    }
 }
 
 /**