[Wasm] Use static import for wasm imports

^KT-65777 fixed
This commit is contained in:
Ilya Goncharov
2024-02-19 10:01:10 +00:00
committed by Space Team
parent 68e5fc7344
commit abb5f55087
22 changed files with 370 additions and 168 deletions
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.wasm.ir2wasm.JsModuleAndQualifierReference
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmCompiledModuleFragment
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
import org.jetbrains.kotlin.backend.wasm.ir2wasm.toJsStringLiteral
import org.jetbrains.kotlin.backend.wasm.lower.JsInteropFunctionsLowering
import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations
import org.jetbrains.kotlin.backend.wasm.utils.SourceMapGenerator
import org.jetbrains.kotlin.backend.wasm.export.ExportModelGenerator
@@ -34,6 +35,7 @@ import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToBinary
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToText
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
@@ -176,7 +178,10 @@ fun compileWasm(
"./$baseFileName.wasm",
backendContext.jsModuleAndQualifierReferences
)
jsWrapper = generateEsmExportsWrapper("./$baseFileName.uninstantiated.mjs")
jsWrapper = compiledWasmModule.generateEsmExportsWrapper(
"./$baseFileName.uninstantiated.mjs",
backendContext.jsModuleAndQualifierReferences
)
} else {
jsUninstantiatedWrapper = null
jsWrapper = compiledWasmModule.generateAsyncWasiWrapper("./$baseFileName.wasm")
@@ -215,7 +220,8 @@ const wasmInstance = new WebAssembly.Instance(wasmModule, wasi.getImportObject()
wasi.initialize(wasmInstance);
export default wasmInstance.exports;
const exports = wasmInstance.exports
${generateExports()}
"""
fun WasmCompiledModuleFragment.generateAsyncJsWrapper(
@@ -234,7 +240,7 @@ fun WasmCompiledModuleFragment.generateAsyncJsWrapper(
.sorted()
.joinToString("") {
val moduleSpecifier = it.toJsStringLiteral()
" $moduleSpecifier: imports[$moduleSpecifier] ?? await import($moduleSpecifier),\n"
" $moduleSpecifier: imports[$moduleSpecifier],\n"
}
val referencesToQualifiedAndImportedDeclarations = jsModuleAndQualifierReferences
@@ -246,7 +252,7 @@ fun WasmCompiledModuleFragment.generateAsyncJsWrapper(
append(it.jsVariableName)
append(" = ")
if (module != null) {
append("(imports[${module.toJsStringLiteral()}] ?? await import(${module.toJsStringLiteral()}))")
append("imports[${module.toJsStringLiteral()}]")
if (qualifier != null)
append(".")
}
@@ -352,10 +358,59 @@ For more information, see https://kotl.in/wasm-help
"""
}
fun generateEsmExportsWrapper(asyncWrapperFileName: String): String = /*language=js */ """
fun WasmCompiledModuleFragment.generateEsmExportsWrapper(
asyncWrapperFileName: String,
jsModuleAndQualifierReferences: MutableSet<JsModuleAndQualifierReference>
): String {
val importedModules = jsModuleImports
.map {
val moduleSpecifier = it.toJsStringLiteral().toString()
val importVariableString = JsModuleAndQualifierReference.encode(it)
moduleSpecifier to importVariableString
}
val referencesToImportedDeclarations = jsModuleAndQualifierReferences
.filter { it.module != null }
.map {
val module = it.module!!
val stringLiteral = module.toJsStringLiteral().toString()
stringLiteral to if (it.qualifier != null) {
it.importVariableName
} else {
it.jsVariableName
}
}
val allModules = (importedModules + referencesToImportedDeclarations)
.distinctBy {
it.first
}.sortedBy { it.first }
val importsImportedSection = allModules.joinToString("\n") {
buildString {
append("import * as ")
append(it.second)
append(" from ")
append(it.first)
append(";")
}
}
val imports = allModules.joinToString(",\n") {
" ${it.first}: ${it.second}"
}
/*language=js */
return """
$importsImportedSection
import { instantiate } from ${asyncWrapperFileName.toJsStringLiteral()};
export default (await instantiate()).exports;
const exports = (await instantiate({
$imports
})).exports;
${generateExports()}
"""
}
fun writeCompilationResult(
result: WasmCompilerResult,
@@ -384,3 +439,37 @@ fun writeCompilationResult(
File(dir, "$fileNameBase.d.ts").writeText(result.dts)
}
}
fun WasmCompiledModuleFragment.generateExports(): String {
// TODO: necessary to move export check onto common place
val exportNames = exports
.filterNot { it.name.startsWith(JsInteropFunctionsLowering.CALL_FUNCTION) }
.ifNotEmpty {
joinToString(",\n") {
" ${it.name}"
}
}?.let {
"""
|const {
|$it
|}
""".trimMargin()
}
/*language=js */
return """
export default new Proxy(exports, {
_shownError: false,
get(target, prop) {
if (!this._shownError) {
this._shownError = true;
if (typeof console !== "undefined") {
console.error("Do not use default import. Use corresponding named import instead.")
}
}
return target[prop];
}
});
${exportNames?.let { "export $it = exports;" }}
"""
}
@@ -15,11 +15,20 @@ data class JsModuleAndQualifierReference(
val module: String?,
val qualifier: String?,
) {
val jsVariableName = run {
private val moduleBase64 = module?.let { encode(it) }.orEmpty()
private val qualifierBase64 = qualifier?.let { encode(it) }.orEmpty()
val jsVariableName = "_ref_${moduleBase64}_$qualifierBase64"
val importVariableName = "_import_${moduleBase64}_$qualifierBase64"
companion object {
// Encode variable name as base64 to have a valid unique JS identifier
val encoder = Base64.getEncoder().withoutPadding()
val moduleBase64 = module?.let { encoder.encodeToString(module.encodeToByteArray()) }.orEmpty()
val qualifierBase64 = qualifier?.let { encoder.encodeToString(qualifier.encodeToByteArray()) }.orEmpty()
"_ref_${moduleBase64}_$qualifierBase64"
private val encoder = Base64.getEncoder().withoutPadding()
fun encode(value: String): String {
return encoder.encodeToString(value.encodeToByteArray())
}
}
}
@@ -466,7 +466,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
private fun createKotlinClosureCaller(info: FunctionTypeInfo): IrSimpleFunction {
val result = context.irFactory.buildFun {
name = Name.identifier("__callFunction_${info.signatureString}")
name = Name.identifier("$CALL_FUNCTION${info.signatureString}")
returnType = info.adaptedResultType
origin = KOTLIN_TO_JS_CLOSURE_ORIGIN
}
@@ -522,7 +522,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
append("(f) => (")
appendParameterList(arity)
append(") => wasmExports[")
append("__callFunction_${info.signatureString}".toJsStringLiteral())
append("$CALL_FUNCTION${info.signatureString}".toJsStringLiteral())
append("](f, ")
appendParameterList(arity)
append(")")
@@ -911,6 +911,10 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
}
}
}
companion object {
const val CALL_FUNCTION = "__callFunction_"
}
}
internal fun StringBuilder.appendParameterList(size: Int, name: String = "p", isEnd: Boolean = true) =