[Wasm] Multimodule compilation
diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2WasmCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2WasmCompilerArguments.kt
index b794ac1..6a25ed7 100644
--- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2WasmCompilerArguments.kt
+++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2WasmCompilerArguments.kt
@@ -38,6 +38,13 @@
             field = value
         }
 
+    @Argument(value = "-Xwasm-multimodule-mode", description = "set multimodule compilation mode.")
+    var wasmMultimoduleCompilationMode: String? = null
+        set(value) {
+            checkFrozen()
+            field = value
+        }
+
     @Argument(value = "-Xwasm-generate-wat", description = "Generate a .wat file.")
     var wasmGenerateWat = false
         set(value) {
diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/pipeline/web/wasm/WasmBackendPipelinePhase.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/pipeline/web/wasm/WasmBackendPipelinePhase.kt
index 8ebe60a..7f82f62 100644
--- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/pipeline/web/wasm/WasmBackendPipelinePhase.kt
+++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/pipeline/web/wasm/WasmBackendPipelinePhase.kt
@@ -11,8 +11,10 @@
 import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations
 import org.jetbrains.kotlin.backend.wasm.ic.IrFactoryImplForWasmIC
 import org.jetbrains.kotlin.backend.wasm.ic.WasmModuleArtifact
+import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmCompiledFileFragment
 import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
 import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleMetadataCache
+import org.jetbrains.kotlin.backend.wasm.ir2wasm.getAllReferencedDeclarations
 import org.jetbrains.kotlin.backend.wasm.writeCompilationResult
 import org.jetbrains.kotlin.cli.common.perfManager
 import org.jetbrains.kotlin.cli.js.IcCachesArtifacts
@@ -21,13 +23,19 @@
 import org.jetbrains.kotlin.cli.pipeline.web.WebBackendPipelinePhase
 import org.jetbrains.kotlin.config.CompilerConfiguration
 import org.jetbrains.kotlin.config.moduleName
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
 import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
 import org.jetbrains.kotlin.ir.backend.js.WholeWorldStageController
 import org.jetbrains.kotlin.ir.backend.js.dce.DceDumpNameCache
 import org.jetbrains.kotlin.ir.backend.js.dce.dumpDeclarationIrSizesIfNeed
 import org.jetbrains.kotlin.ir.backend.js.loadIr
+import org.jetbrains.kotlin.ir.backend.js.loadIrForMultimoduleMode
+import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
+import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
 import org.jetbrains.kotlin.js.config.*
+import org.jetbrains.kotlin.library.KotlinLibrary
 import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.platform.wasm.WasmMultimoduleMode
 import org.jetbrains.kotlin.wasm.config.WasmConfigurationKeys
 import java.io.File
 
@@ -125,6 +133,41 @@
         wasmDebug: Boolean,
         generateDwarf: Boolean
     ): WasmCompilerResult {
+        val wasmMultimoduleCompilationMode = configuration.get(WasmConfigurationKeys.WASM_MULTIMODULE_MODE) ?: WasmMultimoduleMode.NONE
+        return when (wasmMultimoduleCompilationMode) {
+            WasmMultimoduleMode.NONE -> compileNormalMode(
+                configuration = configuration,
+                module = module,
+                outputName = outputName,
+                outputDir = outputDir,
+                propertyLazyInitialization = propertyLazyInitialization,
+                dce = dce,
+                dceDumpDeclarationIrSizesToFile = dceDumpDeclarationIrSizesToFile,
+                wasmDebug = wasmDebug,
+                generateDwarf = generateDwarf,
+            )
+            WasmMultimoduleMode.SLAVE, WasmMultimoduleMode.MASTER -> compileMultimoduleMode(
+                configuration = configuration,
+                module = module,
+                outputName = outputName,
+                outputDir = outputDir,
+                propertyLazyInitialization = propertyLazyInitialization,
+                isMaster = wasmMultimoduleCompilationMode == WasmMultimoduleMode.MASTER
+            )
+        }
+    }
+
+    internal fun compileNormalMode(
+        configuration: CompilerConfiguration,
+        module: ModulesStructure,
+        outputName: String,
+        outputDir: File,
+        propertyLazyInitialization: Boolean,
+        dce: Boolean,
+        dceDumpDeclarationIrSizesToFile: String?,
+        wasmDebug: Boolean,
+        generateDwarf: Boolean
+    ): WasmCompilerResult {
         val performanceManager = configuration.perfManager
         val generateDts = configuration.getBoolean(JSConfigurationKeys.GENERATE_DTS)
         val generateSourceMaps = configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)
@@ -165,7 +208,9 @@
             irFactory,
             allowIncompleteImplementations = dce,
             skipCommentInstructions = !generateWat,
+            useStringPool = true
         )
+
         val wasmCompiledFileFragments = allModules.map { codeGenerator.generateModuleAsSingleFileFragment(it) }
 
         val res = compileWasm(
@@ -195,4 +240,123 @@
 
         return res
     }
+
+    private fun compileMultimoduleMode(
+        configuration: CompilerConfiguration,
+        module: ModulesStructure,
+        outputName: String,
+        outputDir: File,
+        propertyLazyInitialization: Boolean,
+        isMaster: Boolean,
+    ): WasmCompilerResult {
+        val performanceManager = configuration.perfManager
+
+        val irFactory = IrFactoryImplForWasmIC(WholeWorldStageController())
+
+        val masterDeserializer: (JsIrLinker, ModuleDescriptor, KotlinLibrary) -> IrModuleFragment
+        val slaveDeserializer: (JsIrLinker, ModuleDescriptor, KotlinLibrary) -> IrModuleFragment
+        if (isMaster) {
+            masterDeserializer = JsIrLinker::deserializeFullModule
+            slaveDeserializer = JsIrLinker::deserializeOnlyHeaderModule
+        } else {
+            masterDeserializer = JsIrLinker::deserializeHeadersWithInlineBodies
+            slaveDeserializer = JsIrLinker::deserializeFullModule
+        }
+
+        val irModuleInfo = loadIrForMultimoduleMode(
+            depsDescriptors = module,
+            irFactory = irFactory,
+            masterDeserializer = masterDeserializer,
+            slaveDeserializer = slaveDeserializer,
+        )
+
+        //Hack - pre-load functional interfaces in case if IrLoader cut its count (KT-71039)
+        if (isMaster) {
+            repeat(25) {
+                irModuleInfo.bultins.functionN(it)
+                irModuleInfo.bultins.suspendFunctionN(it)
+                irModuleInfo.bultins.kFunctionN(it)
+                irModuleInfo.bultins.kSuspendFunctionN(it)
+            }
+        }
+
+        val (allModules, backendContext, typeScriptFragment) = compileToLoweredIr(
+            irModuleInfo,
+            module.mainModule,
+            configuration,
+            performanceManager,
+            exportedDeclarations = setOf(FqName("main")),
+            generateTypeScriptFragment = false,
+            propertyLazyInitialization = propertyLazyInitialization,
+            isIncremental = true,
+        )
+
+        performanceManager?.notifyIRGenerationStarted()
+
+        val generateWat = configuration.get(WasmConfigurationKeys.WASM_GENERATE_WAT, false)
+
+        val wasmModuleMetadataCache = WasmModuleMetadataCache(backendContext)
+        val codeGenerator = WasmModuleFragmentGenerator(
+            backendContext,
+            wasmModuleMetadataCache,
+            irFactory,
+            allowIncompleteImplementations = false,
+            skipCommentInstructions = !generateWat,
+            useStringPool = isMaster
+        )
+
+        val slaveModule = allModules.last()
+        val masterModules = allModules.dropLast(1)
+        val wasmCompiledFileFragments = mutableListOf<WasmCompiledFileFragment>()
+        val masterModuleName: String?
+        if (isMaster) {
+            masterModules.mapTo(wasmCompiledFileFragments) {
+                codeGenerator.generateModuleAsSingleFileFragmentWithIECExport(it)
+            }
+            masterModuleName = null
+        } else {
+            masterModuleName = "$outputName.master"
+            val slaveModuleFileFragment = codeGenerator.generateModuleAsSingleFileFragment(slaveModule)
+
+            val importedDeclarations = getAllReferencedDeclarations(slaveModuleFileFragment)
+            masterModules.mapTo(wasmCompiledFileFragments) {
+                codeGenerator.generateModuleAsSingleFileFragmentWithIECImport(it, masterModuleName, importedDeclarations)
+            }
+            wasmCompiledFileFragments.add(slaveModuleFileFragment)
+        }
+
+        val moduleName = if (isMaster) "${slaveModule.name.asString()}.master" else slaveModule.name.asString()
+        val outputFileName = if (isMaster) "${outputName}_master" else outputName
+
+        val res = compileWasm(
+            wasmCompiledFileFragments = wasmCompiledFileFragments,
+            moduleName = moduleName,
+            configuration = configuration,
+            typeScriptFragment = typeScriptFragment,
+            baseFileName = outputFileName,
+            emitNameSection = false,
+            generateWat = generateWat,
+            generateDwarf = false,
+            generateSourceMaps = false,
+            useDebuggerCustomFormatters = false,
+            moduleImportName = masterModuleName,
+            initializeUnit = isMaster,
+            exportThrowableTag = isMaster,
+            initializeStringPool = isMaster,
+        )
+
+        performanceManager?.notifyIRGenerationFinished()
+        performanceManager?.notifyGenerationFinished()
+
+        writeCompilationResult(
+            result = res,
+            dir = outputDir,
+            fileNameBase = outputFileName,
+            useDebuggerCustomFormatters = false
+        )
+
+        performanceManager?.notifyIRTranslationFinished()
+
+        return res
+    }
 }
diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/pipeline/web/wasm/WasmConfigurationUpdater.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/pipeline/web/wasm/WasmConfigurationUpdater.kt
index b306209..39511ac 100644
--- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/pipeline/web/wasm/WasmConfigurationUpdater.kt
+++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/pipeline/web/wasm/WasmConfigurationUpdater.kt
@@ -16,6 +16,7 @@
 import org.jetbrains.kotlin.config.phaseConfig
 import org.jetbrains.kotlin.js.config.propertyLazyInitialization
 import org.jetbrains.kotlin.js.config.wasmCompilation
+import org.jetbrains.kotlin.platform.wasm.WasmMultimoduleMode
 import org.jetbrains.kotlin.platform.wasm.WasmTarget
 import org.jetbrains.kotlin.wasm.config.WasmConfigurationKeys
 
@@ -50,6 +51,10 @@
         configuration.put(WasmConfigurationKeys.WASM_USE_JS_TAG, arguments.wasmUseJsTag ?: arguments.wasmUseNewExceptionProposal)
         configuration.put(WasmConfigurationKeys.WASM_GENERATE_DWARF, arguments.generateDwarf)
         configuration.put(WasmConfigurationKeys.WASM_FORCE_DEBUG_FRIENDLY_COMPILATION, arguments.forceDebugFriendlyCompilation)
+        configuration.put(
+            WasmConfigurationKeys.WASM_MULTIMODULE_MODE,
+            arguments.wasmMultimoduleCompilationMode?.let { WasmMultimoduleMode.fromName(it) } ?: WasmMultimoduleMode.NONE
+        )
         configuration.putIfNotNull(WasmConfigurationKeys.WASM_TARGET, arguments.wasmTarget?.let(WasmTarget::fromName))
         configuration.putIfNotNull(WasmConfigurationKeys.DCE_DUMP_DECLARATION_IR_SIZES_TO_FILE, arguments.irDceDumpDeclarationIrSizesToFile)
         configuration.propertyLazyInitialization = arguments.irPropertyLazyInitialization
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt
index b32de78..8399e4b 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt
@@ -231,6 +231,7 @@
     val unboxIntrinsic: IrSimpleFunctionSymbol = getInternalFunction("unboxIntrinsic")
 
     val stringGetLiteral = getFunction("stringLiteral", StandardNames.BUILT_INS_PACKAGE_FQ_NAME)
+    val createString = getFunction("createString", StandardNames.BUILT_INS_PACKAGE_FQ_NAME)
     val stringGetPoolSize = getInternalFunction("stringGetPoolSize")
 
     val testFun = maybeGetFunction("test", kotlinTestPackageFqName)
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compilerWithIC.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compilerWithIC.kt
index 03a9a57..2d6e1e5 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compilerWithIC.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compilerWithIC.kt
@@ -67,6 +67,7 @@
                 allowIncompleteImplementations,
                 if (safeFragmentTags) "${irFile.module.name.asString()}${irFile.path}" else null,
                 skipCommentInstructions = skipCommentInstructions,
+                useStringPool = true
             )
         )
     }
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt
index dfdd250..5fdee3b 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt
@@ -38,6 +38,8 @@
     private val functionContext: WasmFunctionCodegenContext,
     private val wasmModuleMetadataCache: WasmModuleMetadataCache,
     private val wasmModuleTypeTransformer: WasmModuleTypeTransformer,
+    private val useStringPool: Boolean,
+    private val inlineUnitGetter: Boolean,
 ) : IrVisitorVoid() {
     val body: WasmExpressionBuilder = functionContext.bodyGen
 
@@ -49,10 +51,17 @@
     private val unitInstanceField by lazy { backendContext.findUnitInstanceField() }
 
     fun WasmExpressionBuilder.buildGetUnit() {
-        buildGetGlobal(
-            wasmFileCodegenContext.referenceGlobalField(unitInstanceField.symbol),
-            SourceLocation.NoLocation("GET_UNIT")
-        )
+        if (inlineUnitGetter) {
+            buildGetGlobal(
+                wasmFileCodegenContext.referenceGlobalField(unitInstanceField.symbol),
+                SourceLocation.NoLocation("GET_UNIT")
+            )
+        } else {
+            buildCall(
+                wasmFileCodegenContext.referenceFunction(unitGetInstance.symbol),
+                SourceLocation.NoLocation("GET_UNIT")
+            )
+        }
     }
 
     fun getStructFieldRef(field: IrField): WasmSymbol<Int> {
@@ -443,7 +452,7 @@
     }
 
     override fun visitConst(expression: IrConst): Unit =
-        generateConstExpression(expression, body, wasmFileCodegenContext, backendContext, expression.getSourceLocation())
+        generateConstExpression(expression, body, wasmFileCodegenContext, backendContext, expression.getSourceLocation(), useStringPool)
 
     override fun visitGetField(expression: IrGetField) {
         val field: IrField = expression.symbol.owner
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt
index 1e0ac3f..9c23bc2 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt
@@ -36,6 +36,8 @@
     private val wasmModuleMetadataCache: WasmModuleMetadataCache,
     private val allowIncompleteImplementations: Boolean,
     private val skipCommentInstructions: Boolean,
+    private val useStringPool: Boolean = true,
+    private val inlineUnitGetter: Boolean = true,
 ) : IrVisitorVoid() {
 
     // Shortcuts
@@ -96,8 +98,9 @@
             "Sanity check that $declaration is a real function that can be used in calls"
         }
 
-        val functionTypeSymbol = wasmFileCodegenContext.referenceFunctionType(declaration.symbol)
+        if (wasmFileCodegenContext.handleFunctionWithImport(declaration.symbol)) return
 
+        val functionTypeSymbol = wasmFileCodegenContext.referenceFunctionType(declaration.symbol)
         val wasmImportModule = declaration.getWasmImportDescriptor()
         val jsCode = declaration.getJsFunAnnotation()
 
@@ -159,6 +162,8 @@
             functionCodegenContext,
             wasmModuleMetadataCache,
             wasmModuleTypeTransformer,
+            useStringPool,
+            inlineUnitGetter,
         )
 
         val declarationBody = declaration.body
@@ -311,6 +316,8 @@
 
         if (klass.isAbstractOrSealed) return
 
+        if (wasmFileCodegenContext.handleVTableWithImport(symbol)) return
+
         val vTableTypeReference = wasmFileCodegenContext.referenceVTableGcType(symbol)
         val vTableRefGcType = WasmRefType(WasmHeapType.Type(vTableTypeReference))
 
@@ -365,16 +372,7 @@
         val symbol = klass.symbol
         val superType = klass.getSuperClass(irBuiltIns)?.symbol
 
-        val fqnShouldBeEmitted = backendContext.configuration.languageVersionSettings.getFlag(allowFullyQualifiedNameInKClass)
-        val qualifier =
-            if (fqnShouldBeEmitted) {
-                (klass.originalFqName ?: klass.kotlinFqName).parentOrNull()?.asString() ?: ""
-            } else {
-                ""
-            }
-        val simpleName = klass.name.asString()
-        val (packageNameAddress, packageNamePoolId) = wasmFileCodegenContext.referenceStringLiteralAddressAndId(qualifier)
-        val (simpleNameAddress, simpleNamePoolId) = wasmFileCodegenContext.referenceStringLiteralAddressAndId(simpleName)
+        if (wasmFileCodegenContext.handleRTTIWithImport(symbol, superType)) return
 
         val location = SourceLocation.NoLocation("Create instance of rtti struct")
         val initRttiGlobal = buildWasmExpression {
@@ -385,13 +383,33 @@
                 buildRefNull(WasmHeapType.Simple.None, location)
             }
 
-            buildConstI32Symbol(packageNameAddress, location)
-            buildConstI32(qualifier.length, location)
-            buildConstI32Symbol(packageNamePoolId, location)
+            if (useStringPool) {
+                val fqnShouldBeEmitted = backendContext.configuration.languageVersionSettings.getFlag(allowFullyQualifiedNameInKClass)
+                val qualifier =
+                    if (fqnShouldBeEmitted) {
+                        (klass.originalFqName ?: klass.kotlinFqName).parentOrNull()?.asString() ?: ""
+                    } else {
+                        ""
+                    }
+                val simpleName = klass.name.asString()
+                val (packageNameAddress, packageNamePoolId) = wasmFileCodegenContext.referenceStringLiteralAddressAndId(qualifier)
+                val (simpleNameAddress, simpleNamePoolId) = wasmFileCodegenContext.referenceStringLiteralAddressAndId(simpleName)
 
-            buildConstI32Symbol(simpleNameAddress, location)
-            buildConstI32(simpleName.length, location)
-            buildConstI32Symbol(simpleNamePoolId, location)
+                buildConstI32Symbol(packageNameAddress, location)
+                buildConstI32(qualifier.length, location)
+                buildConstI32Symbol(packageNamePoolId, location)
+
+                buildConstI32Symbol(simpleNameAddress, location)
+                buildConstI32(simpleName.length, location)
+                buildConstI32Symbol(simpleNamePoolId, location)
+            } else {
+                buildConstI32(-1, location)
+                buildConstI32(-1, location)
+                buildConstI32(-1, location)
+                buildConstI32(-1, location)
+                buildConstI32(-1, location)
+                buildConstI32(-1, location)
+            }
 
             buildConstI64(wasmFileCodegenContext.referenceTypeId(symbol), location)
 
@@ -413,6 +431,8 @@
         if (klass.isAbstractOrSealed) return
         if (!klass.hasInterfaceSuperClass()) return
 
+        if (wasmFileCodegenContext.handleClassITableWithImport(klass.symbol)) return
+
         val location = SourceLocation.NoLocation("Create instance of itable struct")
 
         val initITableGlobal = buildWasmExpression {
@@ -535,7 +555,6 @@
     override fun visitField(declaration: IrField) {
         // Member fields are generated as part of struct type
         if (!declaration.isStatic) return
-
         val wasmType = wasmModuleTypeTransformer.transformType(declaration.type)
 
         val initBody = mutableListOf<WasmInstr>()
@@ -549,7 +568,8 @@
                     wasmExpressionGenerator,
                     wasmFileCodegenContext,
                     backendContext,
-                    initValue.getSourceLocation(declaration.symbol, declaration.fileOrNull)
+                    initValue.getSourceLocation(declaration.symbol, declaration.fileOrNull),
+                    useStringPool,
                 )
             } else {
                 val stubFunction = WasmFunction.Defined("static_fun_stub", WasmSymbol())
@@ -567,6 +587,8 @@
                     functionCodegenContext,
                     wasmModuleMetadataCache,
                     wasmModuleTypeTransformer,
+                    useStringPool,
+                    inlineUnitGetter,
                 )
                 bodyGenerator.generateExpression(initValue)
                 wasmFileCodegenContext.addFieldInitializer(
@@ -625,7 +647,8 @@
     body: WasmExpressionBuilder,
     context: WasmFileCodegenContext,
     backendContext: WasmBackendContext,
-    location: SourceLocation
+    location: SourceLocation,
+    useStringPool: Boolean,
 ) =
     when (val kind = expression.kind) {
         is IrConstKind.Null -> {
@@ -643,12 +666,28 @@
         is IrConstKind.Double -> body.buildConstF64(expression.value as Double, location)
         is IrConstKind.String -> {
             val stringValue = expression.value as String
-            val (literalAddress, literalPoolId) = context.referenceStringLiteralAddressAndId(stringValue)
+
             body.commentGroupStart { "const string: \"$stringValue\"" }
-            body.buildConstI32Symbol(literalPoolId, location)
-            body.buildConstI32Symbol(literalAddress, location)
-            body.buildConstI32(stringValue.length, location)
-            body.buildCall(context.referenceFunction(backendContext.wasmSymbols.stringGetLiteral), location)
+
+            val (literalAddress, literalPoolId) = context.referenceStringLiteralAddressAndId(stringValue)
+            if (useStringPool) {
+                body.buildConstI32Symbol(literalPoolId, location)
+                body.buildConstI32Symbol(literalAddress, location)
+                body.buildConstI32(stringValue.length, location)
+                body.buildCall(context.referenceFunction(backendContext.wasmSymbols.stringGetLiteral), location)
+            } else {
+                body.buildConstI32Symbol(literalAddress, location)
+                body.buildConstI32(stringValue.length, location)
+
+                val createString = backendContext.wasmSymbols.createString
+                val wasmCharArrayType = createString.owner.parameters[0].type
+                val arrayGcType = WasmImmediate.GcType(
+                    context.referenceGcType(wasmCharArrayType.getRuntimeClass(backendContext.irBuiltIns).symbol)
+                )
+                body.buildInstr(WasmOp.ARRAY_NEW_DATA, location, arrayGcType, WasmImmediate.DataIdx(0))
+                body.buildCall(context.referenceFunction(createString), location)
+            }
+
             body.commentGroupEnd()
         }
     }
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/RecursiveTypesUtils.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/RecursiveTypesUtils.kt
index c573b9e..0cc7f2e 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/RecursiveTypesUtils.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/RecursiveTypesUtils.kt
@@ -10,7 +10,7 @@
 import org.jetbrains.kotlin.utils.yieldIfNotNull
 import org.jetbrains.kotlin.wasm.ir.*
 
-typealias RecursiveTypeGroup = List<WasmTypeDeclaration>
+typealias RecursiveTypeGroup = MutableList<WasmTypeDeclaration>
 
 private fun WasmType.toTypeDeclaration(): WasmTypeDeclaration? {
     val heapType = when (val type = this) {
@@ -54,17 +54,10 @@
 }
 
 
-fun createRecursiveTypeGroups(types: Sequence<WasmTypeDeclaration>): List<RecursiveTypeGroup> {
+fun createRecursiveTypeGroups(types: Sequence<WasmTypeDeclaration>): MutableList<RecursiveTypeGroup> {
     val componentFinder = StronglyConnectedComponents(::dependencyTypes)
     types.forEach(componentFinder::visit)
-
-    val components = componentFinder.findComponents()
-
-    components.forEach { component ->
-        component.sortBy(::wasmTypeDeclarationOrderKey)
-    }
-
-    return components
+    return componentFinder.findComponents()
 }
 
 private fun typeFingerprint(type: WasmType, currentHash: Hash128Bits, visited: MutableSet<WasmTypeDeclaration>): Hash128Bits {
@@ -129,31 +122,62 @@
     WasmStructRef,
 )
 
-internal fun encodeIndex(index: Int): List<WasmStructFieldDeclaration> {
+internal fun encodeIndex(index: UInt): List<WasmStructFieldDeclaration> {
     var current = index
     val result = mutableListOf<WasmStructFieldDeclaration>()
     //i31 type is not used by kotlin/wasm, so mixin index would never clash with regular signature
     result.add(WasmStructFieldDeclaration("", WasmI31Ref, false))
-    while (current != 0) {
-        result.add(WasmStructFieldDeclaration("", indexes[current % 10], false))
-        current /= 10
+    while (current != 0U) {
+        result.add(WasmStructFieldDeclaration("", indexes[(current % 10U).toInt()], false))
+        current /= 10U
     }
     return result
 }
 
-internal fun addMixInGroup(group: RecursiveTypeGroup, mixInIndexesForGroups: MutableMap<Hash128Bits, Int>): RecursiveTypeGroup {
-    val firstDeclaration = group.firstOrNull() ?: return group
+internal fun canonicalSort(group: RecursiveTypeGroup, stableSort: Boolean) {
+    if (group.size == 1) return
+    if (stableSort) {
+        group.sortWith(WasmTypeDeclaratorByFingerprint())
+    }
+    group.sortBy(::wasmTypeDeclarationOrderKey)
+}
 
-    val fingerprint = wasmDeclarationFingerprint(firstDeclaration, Hash128Bits(), visited = mutableSetOf())
+private class WasmTypeDeclaratorByFingerprint : Comparator<WasmTypeDeclaration> {
+    private val fingerprintCache = mutableMapOf<WasmTypeDeclaration, Hash128Bits>()
 
-    val groupIndex = mixInIndexesForGroups[fingerprint]
-    if (groupIndex != null) {
-        val nextIndex = groupIndex + 1
-        mixInIndexesForGroups[fingerprint] = nextIndex
-        val mixIn = WasmStructDeclaration("mixin_$nextIndex", encodeIndex(nextIndex), null, true)
-        return group + mixIn
-    } else {
-        mixInIndexesForGroups[fingerprint] = 0
-        return group
+    private fun getFingerprint(type: WasmTypeDeclaration) = fingerprintCache.getOrPut(type) {
+        wasmDeclarationFingerprint(type, Hash128Bits(), visited = mutableSetOf())
+    }
+
+    private fun diff(x: ULong, y: ULong): Int {
+        if (x == y) return 0
+        return if (x > y) {
+            val diff = x - y
+            if (diff > Int.MAX_VALUE.toUInt()) {
+                Int.MAX_VALUE
+            } else {
+                (x - y).toInt()
+            }
+        } else {
+            val diff = y - x
+            if (diff > Int.MAX_VALUE.toUInt()) {
+                Int.MIN_VALUE
+            } else {
+                -diff.toInt()
+            }
+        }
+    }
+
+    override fun compare(
+        o1: WasmTypeDeclaration,
+        o2: WasmTypeDeclaration,
+    ): Int {
+        val o1Hash = getFingerprint(o1)
+        val o2Hash = getFingerprint(o2)
+        return if (o1Hash.highBytes == o2Hash.highBytes) {
+            diff(o1Hash.lowBytes, o2Hash.lowBytes)
+        } else {
+            diff(o1Hash.highBytes, o2Hash.highBytes)
+        }
     }
 }
\ No newline at end of file
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt
index 927edb9..9740ca4 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt
@@ -21,6 +21,7 @@
 import org.jetbrains.kotlin.utils.addToStdlib.runIf
 import org.jetbrains.kotlin.wasm.ir.*
 import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
+import kotlin.math.exp
 
 class BuiltinIdSignatures(
     val throwable: IdSignature?,
@@ -153,11 +154,16 @@
         return definedFunctions to importedFunctions
     }
 
-    private fun createAndExportServiceFunctions(definedFunctions: MutableList<WasmFunction.Defined>, exports: MutableList<WasmExport<*>>) {
-        val fieldInitializerFunction = createFieldInitializerFunction()
+    private fun createAndExportServiceFunctions(
+        definedFunctions: MutableList<WasmFunction.Defined>,
+        exports: MutableList<WasmExport<*>>,
+        initializeUnit: Boolean,
+        initializeStringPool: Boolean,
+    ) {
+        val fieldInitializerFunction = createFieldInitializerFunction(initializeStringPool)
         definedFunctions.add(fieldInitializerFunction)
 
-        val masterInitFunction = createAndExportMasterInitFunction(fieldInitializerFunction)
+        val masterInitFunction = createAndExportMasterInitFunction(fieldInitializerFunction, initializeUnit)
         exports.add(WasmExport.Function("_initialize", masterInitFunction))
         definedFunctions.add(masterInitFunction)
 
@@ -168,7 +174,7 @@
         }
     }
 
-    fun linkWasmCompiledFragments(): WasmModule {
+    fun linkWasmCompiledFragments(moduleImportName: String?, initializeUnit: Boolean, exportThrowableTag: Boolean, initializeStringPool: Boolean): WasmModule {
         // TODO: Implement optimal ir linkage KT-71040
         bindUnboundSymbols()
         val canonicalFunctionTypes = bindUnboundFunctionTypes()
@@ -184,16 +190,13 @@
         val exports = mutableListOf<WasmExport<*>>()
         wasmCompiledFileFragments.flatMapTo(exports) { it.exports }
 
-        val memory = createAndExportMemory(exports)
+        val memories = createAndExportMemory(exports, moduleImportName)
+        val (importedMemories, definedMemories) = memories.partition { it.importPair != null }
 
-        createAndExportServiceFunctions(definedFunctions, exports)
+        createAndExportServiceFunctions(definedFunctions, exports, initializeUnit, initializeStringPool)
 
-        val throwableDeclaration = tryFindBuiltInType { it.throwable }
-            ?: compilationException("kotlin.Throwable is not found in fragments", null)
-
-        val tags = getTags(throwableDeclaration)
+        val tags = getTags(exports, moduleImportName, exportThrowableTag)
         val (importedTags, definedTags) = tags.partition { it.importPair != null }
-        val importsInOrder = importedFunctions + importedTags
 
         val additionalTypes = mutableListOf<WasmTypeDeclaration>()
         additionalTypes.add(parameterlessNoReturnFunctionType)
@@ -202,6 +205,13 @@
         val syntheticTypes = mutableListOf<WasmTypeDeclaration>()
         createAndBindSpecialITableTypes(syntheticTypes)
         val globals = getGlobals(syntheticTypes)
+        val (importedGlobals, definedGlobals) = globals.partition { it.importPair != null }
+
+        val importsInOrder = mutableListOf<WasmNamedModuleField>()
+        importsInOrder.addAll(importedFunctions)
+        importsInOrder.addAll(importedTags)
+        importsInOrder.addAll(importedGlobals)
+        importsInOrder.addAll(importedMemories)
 
         val recursiveTypeGroups = getTypes(syntheticTypes, canonicalFunctionTypes, additionalTypes)
 
@@ -209,17 +219,19 @@
             recGroups = recursiveTypeGroups,
             importsInOrder = importsInOrder,
             importedFunctions = importedFunctions,
+            importedMemories = importedMemories,
             definedFunctions = definedFunctions,
+            importedTags = importedTags.filterNot { it.importPair?.moduleName == "intrinsics" },
             tables = emptyList(),
-            memories = listOf(memory),
-            globals = globals,
+            memories = definedMemories,
+            globals = definedGlobals,
+            importedGlobals = importedGlobals,
             exports = exports,
             startFunction = null,  // Module is initialized via export call
             elements = emptyList(),
             data = data,
             dataCount = true,
-            tags = definedTags,
-            importedTags = importedTags,
+            tags = definedTags
         ).apply { calculateIds() }
     }
 
@@ -329,34 +341,43 @@
         return syntheticTypes
     }
 
-    private fun getTags(throwableDeclaration: WasmTypeDeclaration): List<WasmTag> {
+    private fun getTags(exports: MutableList<WasmExport<*>>, moduleImportName: String?, exportThrowableTag: Boolean): List<WasmTag> {
+        if (generateTrapsInsteadOfExceptions) return emptyList()
+
+        val throwableDeclaration = tryFindBuiltInType { it.throwable }
+            ?: compilationException("kotlin.Throwable is not found in fragments", null)
         val tagFuncType = WasmRefNullType(WasmHeapType.Type(WasmSymbol(throwableDeclaration)))
 
         val throwableTagFuncType = WasmFunctionType(
             parameterTypes = listOf(tagFuncType),
             resultTypes = emptyList()
         )
+
+        val throwableImport = moduleImportName?.let {
+            WasmImportDescriptor(moduleImportName, WasmSymbol("tag_throwable"))
+        }
+        val throwableTag = WasmTag(throwableTagFuncType, throwableImport)
+        if (exportThrowableTag) {
+            exports.add(WasmExport.Tag("tag_throwable", throwableTag))
+        }
+
+        wasmCompiledFileFragments.forEach {
+            it.throwableTagIndex?.bind(0)
+        }
+
+        if (!itsPossibleToCatchJsErrorSeparately) return listOf(throwableTag)
+
         val jsExceptionTagFuncType = WasmFunctionType(
             parameterTypes = listOf(WasmExternRef),
             resultTypes = emptyList()
         )
 
-        val tags = listOfNotNull(
-            runIf(!generateTrapsInsteadOfExceptions && itsPossibleToCatchJsErrorSeparately) {
-                WasmTag(jsExceptionTagFuncType, WasmImportDescriptor("intrinsics", WasmSymbol("js_error_tag")))
-            },
-            runIf(!generateTrapsInsteadOfExceptions) { WasmTag(throwableTagFuncType) }
-        )
-        val throwableTagIndex = tags.indexOfFirst { it.type === throwableTagFuncType }
+        val jsErrorTagImport = WasmImportDescriptor("intrinsics", WasmSymbol("js_error_tag"))
+        val jsErrorTag = WasmTag(jsExceptionTagFuncType, jsErrorTagImport)
         wasmCompiledFileFragments.forEach {
-            it.throwableTagIndex?.bind(throwableTagIndex)
+            it.jsExceptionTagIndex?.bind(1)
         }
-        val jsExceptionTagIndex = tags.indexOfFirst { it.type === jsExceptionTagFuncType }
-        wasmCompiledFileFragments.forEach {
-            it.jsExceptionTagIndex?.bind(jsExceptionTagIndex)
-        }
-
-        return tags
+        return listOf(throwableTag, jsErrorTag)
     }
 
     private fun getTypes(
@@ -365,30 +386,42 @@
         additionalTypes: List<WasmTypeDeclaration>,
     ): List<RecursiveTypeGroup> {
         val gcTypes = wasmCompiledFileFragments.flatMap { it.gcTypes.elements }
+        val vTableGcTypes = wasmCompiledFileFragments.flatMap { it.vTableGcTypes.elements }
 
         val recGroupTypes = sequence {
             yieldAll(additionalRecGroupTypes)
             yieldAll(gcTypes)
-            yieldAll(wasmCompiledFileFragments.asSequence().flatMap { it.vTableGcTypes.elements })
+            yieldAll(vTableGcTypes)
             yieldAll(canonicalFunctionTypes.values)
         }
 
         val recursiveGroups = createRecursiveTypeGroups(recGroupTypes)
 
-        val mixInIndexesForGroups = mutableMapOf<Hash128Bits, Int>()
-        val groupsWithMixIns = mutableListOf<RecursiveTypeGroup>()
+        recursiveGroups.forEach { group ->
+            if (group.singleOrNull() is WasmArrayDeclaration) {
+                return@forEach
+            }
 
-        recursiveGroups.mapTo(groupsWithMixIns) { group ->
-            if (group.any { it in gcTypes } && group.singleOrNull() !is WasmArrayDeclaration) {
-                addMixInGroup(group, mixInIndexesForGroups)
-            } else {
-                group
+            val needMixIn = group.any { it in gcTypes }
+            val needStableSort = needMixIn || group.any { it in vTableGcTypes }
+
+            canonicalSort(group, needStableSort)
+
+            if (needMixIn) {
+                val firstIdSignature = group.firstOrNull { it is WasmStructDeclaration }?.let { firstStruct ->
+                    wasmCompiledFileFragments.firstNotNullOfOrNull {
+                        it.gcTypes.wasmToIr[firstStruct] ?: it.vTableGcTypes.wasmToIr[firstStruct]
+                    }
+                }
+                if (firstIdSignature != null) {
+                    val mixIn = WasmStructDeclaration("mixin_type", encodeIndex(firstIdSignature.toString().hashCode().toUInt()), null, true)
+                    group.add(mixIn)
+                }
             }
         }
 
-        additionalTypes.forEach { groupsWithMixIns.add(listOf(it)) }
-
-        return groupsWithMixIns
+        additionalTypes.forEach { recursiveGroups.add(mutableListOf(it)) }
+        return recursiveGroups
     }
 
     private fun getGlobals(additionalTypes: MutableList<WasmTypeDeclaration>) = mutableListOf<WasmGlobal>().apply {
@@ -400,24 +433,26 @@
         createRttiTypeAndProcessRttiGlobals(this, additionalTypes)
     }
 
-    private fun createAndExportMemory(exports: MutableList<WasmExport<*>>): WasmMemory {
+    private fun createAndExportMemory(exports: MutableList<WasmExport<*>>, moduleImportName: String?): List<WasmMemory> {
         val memorySizeInPages = 0
-        val memory = WasmMemory(WasmLimits(memorySizeInPages.toUInt(), null /* "unlimited" */))
+        val importPair = moduleImportName?.let { WasmImportDescriptor(it, WasmSymbol("memory")) }
+        val memory = WasmMemory(WasmLimits(memorySizeInPages.toUInt(), null/* "unlimited" */), importPair)
 
         // Need to export the memory in order to pass complex objects to the host language.
         // Export name "memory" is a WASI ABI convention.
         val exportMemory = WasmExport.Memory("memory", memory)
         exports.add(exportMemory)
-        return memory
+        return listOf(memory)
     }
 
-    private fun createAndExportMasterInitFunction(fieldInitializerFunction: WasmFunction): WasmFunction.Defined {
-        val unitGetInstance = tryFindBuiltInFunction { it.unitGetInstance }
-            ?: compilationException("kotlin.Unit_getInstance is not file in fragments", null)
-
+    private fun createAndExportMasterInitFunction(fieldInitializerFunction: WasmFunction, initializeUnit: Boolean,): WasmFunction.Defined {
         val masterInitFunction = WasmFunction.Defined("_initialize", WasmSymbol(parameterlessNoReturnFunctionType))
         with(WasmExpressionBuilder(masterInitFunction.instructions)) {
-            buildCall(WasmSymbol(unitGetInstance), serviceCodeLocation)
+            if (initializeUnit) {
+                val unitGetInstance = tryFindBuiltInFunction { it.unitGetInstance }
+                    ?: compilationException("kotlin.Unit_getInstance is not file in fragments", null)
+                buildCall(WasmSymbol(unitGetInstance), serviceCodeLocation)
+            }
             buildCall(WasmSymbol(fieldInitializerFunction), serviceCodeLocation)
             wasmCompiledFileFragments.forEach { fragment ->
                 fragment.mainFunctionWrappers.forEach { signature ->
@@ -491,7 +526,7 @@
         return startUnitTestsFunction
     }
 
-    private fun createFieldInitializerFunction(): WasmFunction.Defined {
+    private fun createFieldInitializerFunction(initializeStringPool: Boolean): WasmFunction.Defined {
         val fieldInitializerFunction = WasmFunction.Defined("_fieldInitialize", WasmSymbol(parameterlessNoReturnFunctionType))
         with(WasmExpressionBuilder(fieldInitializerFunction.instructions)) {
             var stringPoolInitializer: Pair<FieldInitializer, WasmSymbol<WasmGlobal>>? = null
@@ -511,10 +546,12 @@
                     }
                 }
             }
-            stringPoolInitializer?.let {
-                expression.add(0, WasmInstrWithoutLocation(WasmOp.GLOBAL_SET, listOf(WasmImmediate.GlobalIdx(it.second))))
-                expression.addAll(0, it.first.instructions)
-            } ?: compilationException("stringPool initializer not found!", type = null)
+            if (initializeStringPool) {
+                stringPoolInitializer?.let {
+                    expression.add(0, WasmInstrWithoutLocation(WasmOp.GLOBAL_SET, listOf(WasmImmediate.GlobalIdx(it.second))))
+                    expression.addAll(0, it.first.instructions)
+                } ?: compilationException("stringPool initializer not found!", type = null)
+            }
         }
         return fieldInitializerFunction
     }
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFileCodegenContextWithExport.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFileCodegenContextWithExport.kt
new file mode 100644
index 0000000..8cee765
--- /dev/null
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFileCodegenContextWithExport.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.backend.wasm.ir2wasm
+
+import org.jetbrains.kotlin.ir.declarations.IdSignatureRetriever
+import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithVisibility
+import org.jetbrains.kotlin.ir.overrides.isEffectivelyPrivate
+import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
+import org.jetbrains.kotlin.wasm.ir.WasmExport
+import org.jetbrains.kotlin.wasm.ir.WasmFunction
+import org.jetbrains.kotlin.wasm.ir.WasmGlobal
+
+class WasmFileCodegenContextWithExport(
+    wasmFileFragment: WasmCompiledFileFragment,
+    idSignatureRetriever: IdSignatureRetriever,
+) : WasmFileCodegenContext(wasmFileFragment, idSignatureRetriever) {
+    override fun defineFunction(irFunction: IrFunctionSymbol, wasmFunction: WasmFunction) {
+        super.defineFunction(irFunction, wasmFunction)
+        val owner = irFunction.owner
+        if (owner.isEffectivelyPrivate()) return
+        val signature = idSignatureRetriever.declarationSignature(owner)
+        addExport(
+            WasmExport.Function(
+                field = wasmFunction,
+                name = "$FunctionImportPrefix$signature"
+            )
+        )
+    }
+
+    override fun defineGlobalVTable(irClass: IrClassSymbol, wasmGlobal: WasmGlobal) {
+        super.defineGlobalVTable(irClass, wasmGlobal)
+        exportDeclarationGlobal(irClass.owner, TypeGlobalImportPrefix.VTABLE, wasmGlobal)
+    }
+
+    override fun defineGlobalClassITable(irClass: IrClassSymbol, wasmGlobal: WasmGlobal) {
+        super.defineGlobalClassITable(irClass, wasmGlobal)
+        exportDeclarationGlobal(irClass.owner, TypeGlobalImportPrefix.ITABLE, wasmGlobal)
+    }
+
+    override fun defineRttiGlobal(global: WasmGlobal, irClass: IrClassSymbol, irSuperClass: IrClassSymbol?) {
+        super.defineRttiGlobal(global, irClass, irSuperClass)
+        exportDeclarationGlobal(irClass.owner, TypeGlobalImportPrefix.RTTI, global)
+    }
+
+    private fun exportDeclarationGlobal(declaration: IrDeclarationWithVisibility, prefix: TypeGlobalImportPrefix, global: WasmGlobal) {
+        if (declaration.isEffectivelyPrivate()) return
+        val signature = idSignatureRetriever.declarationSignature(declaration)
+        addExport(
+            WasmExport.Global(
+                name = "${prefix.prefix}$signature",
+                field = global
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFileCodegenContextWithImport.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFileCodegenContextWithImport.kt
new file mode 100644
index 0000000..9dd06ad
--- /dev/null
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFileCodegenContextWithImport.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.backend.wasm.ir2wasm
+
+/*
+ * Copyright 2010-2020 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.
+ */
+
+import org.jetbrains.kotlin.ir.declarations.IdSignatureRetriever
+import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
+import org.jetbrains.kotlin.ir.util.IdSignature
+import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
+import org.jetbrains.kotlin.wasm.ir.*
+
+fun getAllReferencedDeclarations(wasmCompiledFileFragment: WasmCompiledFileFragment): Set<IdSignature> {
+    val signatures = mutableSetOf<IdSignature>()
+    signatures.addAll(wasmCompiledFileFragment.functions.unbound.keys)
+    signatures.addAll(wasmCompiledFileFragment.globalFields.unbound.keys)
+    signatures.addAll(wasmCompiledFileFragment.globalVTables.unbound.keys)
+    signatures.addAll(wasmCompiledFileFragment.globalClassITables.unbound.keys)
+    wasmCompiledFileFragment.rttiElements?.let {
+        signatures.addAll(it.globalReferences.unbound.keys)
+    }
+    return signatures
+}
+
+class WasmFileCodegenContextWithImport(
+    wasmFileFragment: WasmCompiledFileFragment,
+    idSignatureRetriever: IdSignatureRetriever,
+    private val moduleName: String,
+    private val importDeclarations: Set<IdSignature>,
+) : WasmFileCodegenContext(wasmFileFragment, idSignatureRetriever) {
+    override fun handleFunctionWithImport(declaration: IrFunctionSymbol): Boolean {
+        val signature = idSignatureRetriever.declarationSignature(declaration.owner)
+        if (signature !in importDeclarations) return true
+        val functionTypeSymbol = referenceFunctionType(declaration)
+        defineFunction(
+            declaration,
+            WasmFunction.Imported(
+                name = declaration.owner.fqNameWhenAvailable.toString(),
+                type = functionTypeSymbol,
+                importPair = WasmImportDescriptor(moduleName, WasmSymbol("$FunctionImportPrefix$signature"))
+            )
+        )
+        return true
+    }
+
+    override fun handleVTableWithImport(declaration: IrClassSymbol): Boolean {
+        val signature = idSignatureRetriever.declarationSignature(declaration.owner)
+        if (signature !in importDeclarations) return true
+        val global = WasmGlobal(
+            name = "<classVTable>",
+            type = WasmRefType(WasmHeapType.Type(referenceVTableGcType(declaration))),
+            isMutable = false,
+            init = emptyList(),
+            importPair = WasmImportDescriptor(moduleName, WasmSymbol("${TypeGlobalImportPrefix.VTABLE.prefix}$signature"))
+        )
+        defineGlobalVTable(irClass = declaration, wasmGlobal = global)
+        return true
+    }
+
+    override fun handleClassITableWithImport(declaration: IrClassSymbol): Boolean {
+        val signature = idSignatureRetriever.declarationSignature(declaration.owner)
+        if (signature !in importDeclarations) return true
+        val global = WasmGlobal(
+            name = "<classITable>",
+            type = WasmRefType(WasmHeapType.Type(interfaceTableTypes.wasmAnyArrayType)),
+            isMutable = false,
+            init = emptyList(),
+            importPair = WasmImportDescriptor(moduleName, WasmSymbol("${TypeGlobalImportPrefix.ITABLE.prefix}$signature"))
+        )
+        defineGlobalClassITable(irClass = declaration, wasmGlobal = global)
+        return true
+    }
+
+    override fun handleRTTIWithImport(declaration: IrClassSymbol, superType: IrClassSymbol?): Boolean {
+        val signature = idSignatureRetriever.declarationSignature(declaration.owner)
+        if (signature !in importDeclarations) return true
+        val rttiGlobal = WasmGlobal(
+            name = "${declaration.owner.fqNameWhenAvailable}_rtti",
+            type = WasmRefType(WasmHeapType.Type(rttiType)),
+            isMutable = false,
+            init = emptyList(),
+            importPair = WasmImportDescriptor(moduleName, WasmSymbol("${TypeGlobalImportPrefix.RTTI.prefix}$signature"))
+        )
+        defineRttiGlobal(global = rttiGlobal, irClass = declaration, irSuperClass = superType)
+        return true
+    }
+}
\ No newline at end of file
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt
index 5eb3335..7591cca 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt
@@ -10,15 +10,32 @@
 import org.jetbrains.kotlin.ir.declarations.IdSignatureRetriever
 import org.jetbrains.kotlin.ir.declarations.IrDeclaration
 import org.jetbrains.kotlin.ir.declarations.IrValueParameter
-import org.jetbrains.kotlin.ir.symbols.*
+import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
+import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
+import org.jetbrains.kotlin.ir.symbols.IrSymbol
 import org.jetbrains.kotlin.ir.types.IrType
 import org.jetbrains.kotlin.ir.util.IdSignature
 import org.jetbrains.kotlin.wasm.ir.*
 
-class WasmFileCodegenContext(
+
+enum class TypeGlobalImportPrefix(val prefix: String) {
+    VTABLE("vtable_"),
+    ITABLE("itable_"),
+    RTTI("rtti_"),
+}
+
+const val FunctionImportPrefix = "func_"
+
+open class WasmFileCodegenContext(
     private val wasmFileFragment: WasmCompiledFileFragment,
-    private val idSignatureRetriever: IdSignatureRetriever,
+    protected val idSignatureRetriever: IdSignatureRetriever,
 ) {
+    open fun handleFunctionWithImport(declaration: IrFunctionSymbol): Boolean = false
+    open fun handleVTableWithImport(declaration: IrClassSymbol): Boolean = false
+    open fun handleClassITableWithImport(declaration: IrClassSymbol): Boolean = false
+    open fun handleRTTIWithImport(declaration: IrClassSymbol, superType: IrClassSymbol?): Boolean = false
+
     private fun IrSymbol.getReferenceKey(): IdSignature =
         idSignatureRetriever.declarationSignature(this.owner as IrDeclaration)!!
 
@@ -38,7 +55,7 @@
     private fun IrClassSymbol.getSignature(): IdSignature =
         idSignatureRetriever.declarationSignature(this.owner)!!
 
-    fun defineFunction(irFunction: IrFunctionSymbol, wasmFunction: WasmFunction) {
+    open fun defineFunction(irFunction: IrFunctionSymbol, wasmFunction: WasmFunction) {
         wasmFileFragment.functions.define(irFunction.getReferenceKey(), wasmFunction)
     }
 
@@ -46,11 +63,11 @@
         wasmFileFragment.globalFields.define(irField.getReferenceKey(), wasmGlobal)
     }
 
-    fun defineGlobalVTable(irClass: IrClassSymbol, wasmGlobal: WasmGlobal) {
+    open fun defineGlobalVTable(irClass: IrClassSymbol, wasmGlobal: WasmGlobal) {
         wasmFileFragment.globalVTables.define(irClass.getReferenceKey(), wasmGlobal)
     }
 
-    fun defineGlobalClassITable(irClass: IrClassSymbol, wasmGlobal: WasmGlobal) {
+    open fun defineGlobalClassITable(irClass: IrClassSymbol, wasmGlobal: WasmGlobal) {
         wasmFileFragment.globalClassITables.define(irClass.getReferenceKey(), wasmGlobal)
     }
 
@@ -179,7 +196,7 @@
 
     val rttiType: WasmSymbol<WasmStructDeclaration> get() = rttiElements.rttiType
 
-    fun defineRttiGlobal(global: WasmGlobal, irClass: IrClassSymbol, irSuperClass: IrClassSymbol?) {
+    open fun defineRttiGlobal(global: WasmGlobal, irClass: IrClassSymbol, irSuperClass: IrClassSymbol?) {
         rttiElements.globals.add(
             RttiGlobal(
                 global = global,
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt
index a022fb6..0d0b459 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt
@@ -10,6 +10,7 @@
 import org.jetbrains.kotlin.ir.backend.js.utils.findUnitGetInstanceFunction
 import org.jetbrains.kotlin.ir.declarations.*
 import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
+import org.jetbrains.kotlin.ir.util.IdSignature
 import org.jetbrains.kotlin.ir.util.fileOrNull
 import org.jetbrains.kotlin.ir.visitors.acceptVoid
 
@@ -19,12 +20,39 @@
     private val idSignatureRetriever: IdSignatureRetriever,
     private val allowIncompleteImplementations: Boolean,
     private val skipCommentInstructions: Boolean,
+    private val useStringPool: Boolean,
 ) {
-    fun generateModuleAsSingleFileFragment(irModuleFragment: IrModuleFragment): WasmCompiledFileFragment {
+    fun generateModuleAsSingleFileFragment(
+        irModuleFragment: IrModuleFragment,
+    ): WasmCompiledFileFragment {
         val wasmFileFragment = WasmCompiledFileFragment(fragmentTag = null)
         val wasmFileCodegenContext = WasmFileCodegenContext(wasmFileFragment, idSignatureRetriever)
-        val wasmModuleTypeTransformer = WasmModuleTypeTransformer(backendContext, wasmFileCodegenContext)
+        generate(irModuleFragment, wasmFileCodegenContext)
+        return wasmFileFragment
+    }
 
+    fun generateModuleAsSingleFileFragmentWithIECImport(
+        irModuleFragment: IrModuleFragment,
+        moduleName: String,
+        importDeclarations: Set<IdSignature>,
+    ): WasmCompiledFileFragment {
+        val wasmFileFragment = WasmCompiledFileFragment(fragmentTag = null)
+        val wasmFileCodegenContext = WasmFileCodegenContextWithImport(wasmFileFragment, idSignatureRetriever, moduleName, importDeclarations)
+        generate(irModuleFragment, wasmFileCodegenContext)
+        return wasmFileFragment
+    }
+
+    fun generateModuleAsSingleFileFragmentWithIECExport(
+        irModuleFragment: IrModuleFragment,
+    ): WasmCompiledFileFragment {
+        val wasmFileFragment = WasmCompiledFileFragment(fragmentTag = null)
+        val wasmFileCodegenContext = WasmFileCodegenContextWithExport(wasmFileFragment, idSignatureRetriever)
+        generate(irModuleFragment, wasmFileCodegenContext)
+        return wasmFileFragment
+    }
+
+    private fun generate(irModuleFragment: IrModuleFragment, wasmFileCodegenContext: WasmFileCodegenContext) {
+        val wasmModuleTypeTransformer = WasmModuleTypeTransformer(backendContext, wasmFileCodegenContext)
         for (irFile in irModuleFragment.files) {
             compileIrFile(
                 irFile,
@@ -34,9 +62,9 @@
                 wasmFileCodegenContext,
                 wasmModuleTypeTransformer,
                 skipCommentInstructions,
+                useStringPool,
             )
         }
-        return wasmFileFragment
     }
 }
 
@@ -48,6 +76,7 @@
     allowIncompleteImplementations: Boolean,
     fragmentTag: String?,
     skipCommentInstructions: Boolean,
+    useStringPool: Boolean,
 ): WasmCompiledFileFragment {
     val wasmFileFragment = WasmCompiledFileFragment(fragmentTag)
     val wasmFileCodegenContext = WasmFileCodegenContext(wasmFileFragment, idSignatureRetriever)
@@ -60,6 +89,7 @@
         wasmFileCodegenContext,
         wasmModuleTypeTransformer,
         skipCommentInstructions,
+        useStringPool,
     )
     return wasmFileFragment
 }
@@ -72,6 +102,7 @@
     wasmFileCodegenContext: WasmFileCodegenContext,
     wasmModuleTypeTransformer: WasmModuleTypeTransformer,
     skipCommentInstructions: Boolean,
+    useStringPool: Boolean,
 ) {
     val generator = DeclarationGenerator(
         backendContext,
@@ -80,6 +111,7 @@
         wasmModuleMetadataCache,
         allowIncompleteImplementations,
         skipCommentInstructions,
+        useStringPool,
     )
     for (irDeclaration in irFile.declarations) {
         irDeclaration.acceptVoid(generator)
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/StronglyConnectedComponents.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/StronglyConnectedComponents.kt
index 4126693..790fb3c 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/StronglyConnectedComponents.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/StronglyConnectedComponents.kt
@@ -23,7 +23,7 @@
         }
     }
 
-    fun findComponents(): List<MutableList<T>> {
+    fun findComponents(): MutableList<MutableList<T>> {
         visited.clear()
         val result = mutableListOf<MutableList<T>>()
         while (stack.isNotEmpty()) {
diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/wasmCompiler.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/wasmCompiler.kt
index 9187b35..0bb077c 100644
--- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/wasmCompiler.kt
+++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/wasmCompiler.kt
@@ -69,6 +69,7 @@
     exportedDeclarations: Set<FqName> = emptySet(),
     generateTypeScriptFragment: Boolean,
     propertyLazyInitialization: Boolean,
+    isIncremental: Boolean = false,
 ): LoweredIrWithExtraArtifacts {
     val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, irLinker) = irModuleInfo
 
@@ -78,7 +79,14 @@
     }
 
     val moduleDescriptor = moduleFragment.descriptor
-    val context = WasmBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, propertyLazyInitialization, configuration)
+    val context = WasmBackendContext(
+        module = moduleDescriptor,
+        irBuiltIns = irBuiltIns,
+        symbolTable = symbolTable,
+        irModuleFragment = moduleFragment,
+        propertyLazyInitialization = propertyLazyInitialization,
+        configuration = configuration,
+    )
 
     // Create stubs
     ExternalDependenciesGenerator(symbolTable, listOf(irLinker)).generateUnboundSymbolsAsDependencies()
@@ -107,7 +115,7 @@
         allModules,
         context,
         context.irFactory.stageController as WholeWorldStageController,
-        isIncremental = false,
+        isIncremental = isIncremental,
     )
 
     performanceManager?.notifyIRLoweringFinished()
@@ -147,7 +155,11 @@
     generateWat: Boolean = false,
     generateSourceMaps: Boolean = false,
     useDebuggerCustomFormatters: Boolean = false,
-    generateDwarf: Boolean = false
+    generateDwarf: Boolean = false,
+    moduleImportName: String? = null,
+    initializeUnit: Boolean = true,
+    exportThrowableTag: Boolean = false,
+    initializeStringPool: Boolean = true,
 ): WasmCompilerResult {
     val useJsTag = configuration.getBoolean(WasmConfigurationKeys.WASM_USE_JS_TAG)
     val isWasmJsTarget = configuration.get(WasmConfigurationKeys.WASM_TARGET) != WasmTarget.WASI
@@ -158,7 +170,7 @@
         isWasmJsTarget && useJsTag,
     )
 
-    val linkedModule = wasmCompiledModuleFragment.linkWasmCompiledFragments()
+    val linkedModule = wasmCompiledModuleFragment.linkWasmCompiledFragments(moduleImportName, initializeUnit, exportThrowableTag, initializeStringPool)
 
     val dwarfGeneratorForBinary = runIf(generateDwarf) {
         DwarfGenerator()
@@ -206,6 +218,9 @@
             jsFuns.addAll(fragment.jsFuns.values)
             jsModuleAndQualifierReferences.addAll(fragment.jsModuleAndQualifierReferences)
         }
+        if (moduleImportName != null) {
+            jsModuleImports.add(moduleImportName)
+        }
 
         jsUninstantiatedWrapper = generateAsyncJsWrapper(
             jsModuleImports,
diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt
index bb94ab5..f45e502 100644
--- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt
+++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt
@@ -17,6 +17,7 @@
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContextImpl
 import org.jetbrains.kotlin.backend.common.linkage.issues.checkNoUnboundSymbols
+import org.jetbrains.kotlin.backend.common.linkage.partial.PartialLinkageSupportForLinker
 import org.jetbrains.kotlin.backend.common.linkage.partial.createPartialLinkageSupportForLinker
 import org.jetbrains.kotlin.backend.common.overrides.FakeOverrideChecker
 import org.jetbrains.kotlin.backend.common.serialization.*
@@ -41,6 +42,7 @@
 import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
 import org.jetbrains.kotlin.ir.descriptors.IrDescriptorBasedFunctionFactory
 import org.jetbrains.kotlin.ir.linkage.IrDeserializer
+import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageConfig
 import org.jetbrains.kotlin.ir.linkage.partial.partialLinkageConfig
 import org.jetbrains.kotlin.ir.util.DeclarationStubGenerator
 import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
@@ -214,6 +216,92 @@
 }
 
 @OptIn(ObsoleteDescriptorBasedAPI::class)
+fun loadIrForMultimoduleMode(
+    depsDescriptors: ModulesStructure,
+    irFactory: IrFactory,
+    masterDeserializer: (JsIrLinker, ModuleDescriptor, KotlinLibrary) -> IrModuleFragment,
+    slaveDeserializer: (JsIrLinker, ModuleDescriptor, KotlinLibrary) -> IrModuleFragment,
+): IrModuleInfo {
+    val mainModule = depsDescriptors.mainModule
+    val configuration = depsDescriptors.compilerConfiguration
+    val allDependencies = depsDescriptors.allDependencies
+    val messageCollector = configuration.messageCollector
+
+    val signaturer = IdSignatureDescriptor(JsManglerDesc)
+    val symbolTable = SymbolTable(signaturer, irFactory)
+
+    check(mainModule is MainModule.Klib)
+
+    val mainPath = File(mainModule.libPath).canonicalPath
+    val mainModuleLibA = allDependencies.find { it.libraryFile.canonicalPath == mainPath }
+        ?: error("No module with ${mainModule.libPath} found")
+    val moduleDescriptor = depsDescriptors.getModuleDescriptor(mainModuleLibA)
+    val sortedDependencies = sortDependencies(depsDescriptors.moduleDependencies)
+    val friendModules = mapOf(mainModuleLibA.uniqueName to depsDescriptors.friendDependencies.map { it.uniqueName })
+
+    val typeTranslator = TypeTranslatorImpl(symbolTable, configuration.languageVersionSettings, moduleDescriptor)
+    val irBuiltIns = IrBuiltInsOverDescriptors(moduleDescriptor.builtIns, typeTranslator, symbolTable)
+
+    val irLinker = JsIrLinker(
+        currentModule = null,
+        messageCollector = messageCollector,
+        builtIns = irBuiltIns,
+        symbolTable = symbolTable,
+        partialLinkageSupport = createPartialLinkageSupportForLinker(
+            partialLinkageConfig = PartialLinkageConfig.DEFAULT,
+            builtIns = irBuiltIns,
+            messageCollector = messageCollector
+        ),
+        icData = null,
+        friendModules = friendModules
+    )
+
+    val stdlibDependency = sortedDependencies.first()
+    val mainDependency = sortedDependencies.last()
+
+    val deserializedFragments = mutableListOf<IrModuleFragment>()
+
+    val deserializedStdlib = masterDeserializer(
+        irLinker,
+        depsDescriptors.getModuleDescriptor(stdlibDependency),
+        stdlibDependency
+    )
+    deserializedFragments.add(deserializedStdlib)
+
+    sortedDependencies.forEach { klib ->
+        if (klib != stdlibDependency && klib != mainDependency) {
+            deserializedFragments.add(
+                masterDeserializer(irLinker, depsDescriptors.getModuleDescriptor(klib), klib)
+            )
+        }
+    }
+
+    val deserializedMain = slaveDeserializer(irLinker, depsDescriptors.getModuleDescriptor(mainDependency), mainDependency)
+    deserializedFragments.add(deserializedMain)
+
+    irBuiltIns.functionFactory = IrDescriptorBasedFunctionFactory(
+        irBuiltIns = irBuiltIns,
+        symbolTable = symbolTable,
+        typeTranslator = typeTranslator,
+        getPackageFragment = FunctionTypeInterfacePackages().makePackageAccessor(deserializedStdlib),
+        referenceFunctionsWhenKFunctionAreReferenced = true
+    )
+
+    irLinker.init(null)
+    ExternalDependenciesGenerator(symbolTable, listOf(irLinker)).generateUnboundSymbolsAsDependencies()
+    irLinker.postProcess(inOrAfterLinkageStep = true)
+
+    return IrModuleInfo(
+        deserializedMain,
+        deserializedFragments,
+        irBuiltIns,
+        symbolTable,
+        irLinker,
+        emptyMap()
+    )
+}
+
+@OptIn(ObsoleteDescriptorBasedAPI::class)
 fun getIrModuleInfoForKlib(
     moduleDescriptor: ModuleDescriptor,
     sortedDependencies: Collection<KotlinLibrary>,
diff --git a/libraries/stdlib/wasm/builtins/kotlin/String.kt b/libraries/stdlib/wasm/builtins/kotlin/String.kt
index ab94495..4b0d147 100644
--- a/libraries/stdlib/wasm/builtins/kotlin/String.kt
+++ b/libraries/stdlib/wasm/builtins/kotlin/String.kt
@@ -133,11 +133,14 @@
     }
 }
 
-@Suppress("NOTHING_TO_INLINE")
-internal inline fun WasmCharArray.createString(): String =
+internal fun WasmCharArray.createString(): String =
     String(null, this.len(), this)
 
 internal fun stringLiteral(poolId: Int, startAddress: Int, length: Int): String {
+    if (poolId == -1) {
+        return ""
+    }
+
     val cached = stringPool[poolId]
     if (cached !== null) {
         return cached
diff --git a/wasm/wasm.config/src/org/jetbrains/kotlin/platform/wasm/MultimoduleMode.kt b/wasm/wasm.config/src/org/jetbrains/kotlin/platform/wasm/MultimoduleMode.kt
new file mode 100644
index 0000000..a853f81
--- /dev/null
+++ b/wasm/wasm.config/src/org/jetbrains/kotlin/platform/wasm/MultimoduleMode.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.platform.wasm
+
+enum class WasmMultimoduleMode(val alias: String) {
+    NONE("none"),
+    MASTER("master"),
+    SLAVE("slave");
+
+    companion object {
+        fun fromName(name: String): WasmMultimoduleMode? = WasmMultimoduleMode.entries.firstOrNull { it.alias == name }
+    }
+}
\ No newline at end of file
diff --git a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/config/WasmConfigurationKeys.java b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/config/WasmConfigurationKeys.java
index 3fa6777..0e4844e 100644
--- a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/config/WasmConfigurationKeys.java
+++ b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/config/WasmConfigurationKeys.java
@@ -6,6 +6,7 @@
 package org.jetbrains.kotlin.wasm.config;
 
 import org.jetbrains.kotlin.config.CompilerConfigurationKey;
+import org.jetbrains.kotlin.platform.wasm.WasmMultimoduleMode;
 import org.jetbrains.kotlin.platform.wasm.WasmTarget;
 
 public class WasmConfigurationKeys {
@@ -41,4 +42,7 @@
 
     public static final CompilerConfigurationKey<Boolean> WASM_FORCE_DEBUG_FRIENDLY_COMPILATION =
             CompilerConfigurationKey.create("avoid optimizations that can break debugging.");
+
+    public static final CompilerConfigurationKey<WasmMultimoduleMode> WASM_MULTIMODULE_MODE =
+            CompilerConfigurationKey.create("set multimodule compilation mode.");
 }
diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmLoweringFacade.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmLoweringFacade.kt
index 27bbeea..14ebba1 100644
--- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmLoweringFacade.kt
+++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmLoweringFacade.kt
@@ -89,6 +89,7 @@
             moduleInfo.symbolTable.irFactory as IrFactoryImplForWasmIC,
             allowIncompleteImplementations = false,
             skipCommentInstructions = !generateWat,
+            useStringPool = true,
         )
         val wasmCompiledFileFragments = allModules.map { codeGenerator.generateModuleAsSingleFileFragment(it) }
 
@@ -116,6 +117,7 @@
             moduleInfo.symbolTable.irFactory as IrFactoryImplForWasmIC,
             allowIncompleteImplementations = true,
             skipCommentInstructions = !generateWat,
+            useStringPool = true,
         )
         val wasmCompiledFileFragmentsDce = allModules.map { codeGeneratorDce.generateModuleAsSingleFileFragment(it) }