[Wasm] Support JsModule and JsQualifier
This commit is contained in:
committed by
Space Team
parent
75d3ae4466
commit
d14d4c8510
+4
-1
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.wasm
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.ir.Ir
|
||||
import org.jetbrains.kotlin.backend.common.ir.Symbols
|
||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.JsModuleAndQualifierReference
|
||||
import org.jetbrains.kotlin.backend.wasm.lower.WasmSharedVariablesManager
|
||||
import org.jetbrains.kotlin.backend.wasm.utils.WasmInlineClassesUtils
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
@@ -23,7 +24,6 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmentSymbol
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.IrSimpleType
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeSystemContext
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
|
||||
@@ -63,6 +63,9 @@ class WasmBackendContext(
|
||||
val jsClosureCallers = mutableMapOf<IrSimpleType, IrSimpleFunction>()
|
||||
val jsToKotlinClosures = mutableMapOf<IrSimpleType, IrSimpleFunction>()
|
||||
|
||||
val jsModuleAndQualifierReferences =
|
||||
mutableSetOf<JsModuleAndQualifierReference>()
|
||||
|
||||
override val coroutineSymbols =
|
||||
JsCommonCoroutineSymbols(symbolTable, module,this)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.wasm
|
||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
||||
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
|
||||
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.checkNoUnboundSymbols
|
||||
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
|
||||
@@ -107,7 +108,10 @@ fun compileWasm(
|
||||
null
|
||||
}
|
||||
|
||||
val jsUninstantiatedWrapper = compiledWasmModule.generateAsyncJsWrapper("./$baseFileName.wasm")
|
||||
val jsUninstantiatedWrapper = compiledWasmModule.generateAsyncJsWrapper(
|
||||
"./$baseFileName.wasm",
|
||||
backendContext.jsModuleAndQualifierReferences
|
||||
)
|
||||
val jsWrapper = generateEsmExportsWrapper("./$baseFileName.uninstantiated.mjs")
|
||||
|
||||
val os = ByteArrayOutputStream()
|
||||
@@ -172,43 +176,74 @@ private fun generateSourceMap(
|
||||
return sourceMapBuilder.build()
|
||||
}
|
||||
|
||||
fun WasmCompiledModuleFragment.generateAsyncJsWrapper(wasmFilePath: String): String {
|
||||
fun WasmCompiledModuleFragment.generateAsyncJsWrapper(
|
||||
wasmFilePath: String,
|
||||
jsModuleAndQualifierReferences: Set<JsModuleAndQualifierReference>
|
||||
): String {
|
||||
|
||||
val jsCodeBody = jsFuns.joinToString(",\n") {
|
||||
"${it.importName.toJsStringLiteral()} : ${it.jsCode}"
|
||||
}
|
||||
|
||||
val jsCodeBodyIndented = jsCodeBody.prependIndent(" ")
|
||||
val jsCodeBodyIndented = jsCodeBody.prependIndent(" ")
|
||||
|
||||
val imports = jsModuleImports
|
||||
.toList()
|
||||
.sorted()
|
||||
.joinToString("") {
|
||||
val moduleSpecifier = it.toJsStringLiteral()
|
||||
" $moduleSpecifier: imports[$moduleSpecifier] ?? await import($moduleSpecifier),\n"
|
||||
" $moduleSpecifier: await _importModule($moduleSpecifier),\n"
|
||||
}
|
||||
|
||||
val referencesToQualifiedAndImportedDeclarations = jsModuleAndQualifierReferences
|
||||
.map {
|
||||
val module = it.module
|
||||
val qualifier = it.qualifier
|
||||
buildString {
|
||||
append(" const ")
|
||||
append(it.jsVariableName)
|
||||
append(" = ")
|
||||
if (module != null) {
|
||||
append("(await _importModule(${module.toJsStringLiteral()}))")
|
||||
if (qualifier != null)
|
||||
append(".")
|
||||
}
|
||||
if (qualifier != null) {
|
||||
append(qualifier)
|
||||
}
|
||||
append(";")
|
||||
}
|
||||
}.sorted()
|
||||
.joinToString("\n")
|
||||
|
||||
//language=js
|
||||
return """
|
||||
const externrefBoxes = new WeakMap();
|
||||
// ref must be non-null
|
||||
function tryGetOrSetExternrefBox(ref, ifNotCached) {
|
||||
if (typeof ref !== 'object') return ifNotCached;
|
||||
const cachedBox = externrefBoxes.get(ref);
|
||||
if (cachedBox !== void 0) return cachedBox;
|
||||
externrefBoxes.set(ref, ifNotCached);
|
||||
return ifNotCached;
|
||||
}
|
||||
|
||||
const js_code = {
|
||||
$jsCodeBodyIndented
|
||||
}
|
||||
|
||||
// Placed here to give access to it from externals (js_code)
|
||||
let wasmInstance;
|
||||
let require;
|
||||
let wasmExports;
|
||||
|
||||
export async function instantiate(imports={}, runInitializer=true) {
|
||||
const externrefBoxes = new WeakMap();
|
||||
// ref must be non-null
|
||||
function tryGetOrSetExternrefBox(ref, ifNotCached) {
|
||||
if (typeof ref !== 'object') return ifNotCached;
|
||||
const cachedBox = externrefBoxes.get(ref);
|
||||
if (cachedBox !== void 0) return cachedBox;
|
||||
externrefBoxes.set(ref, ifNotCached);
|
||||
return ifNotCached;
|
||||
}
|
||||
|
||||
async function _importModule(x) {
|
||||
return imports[x] ?? await import(x);
|
||||
}
|
||||
|
||||
$referencesToQualifiedAndImportedDeclarations
|
||||
|
||||
const js_code = {
|
||||
$jsCodeBodyIndented
|
||||
}
|
||||
|
||||
// Placed here to give access to it from externals (js_code)
|
||||
let wasmInstance;
|
||||
let require;
|
||||
let wasmExports;
|
||||
|
||||
const isNodeJs = (typeof process !== 'undefined') && (process.release.name === 'node');
|
||||
const isD8 = !isNodeJs && (typeof d8 !== 'undefined');
|
||||
const isBrowser = !isNodeJs && !isD8 && (typeof window !== 'undefined');
|
||||
|
||||
+15
-1
@@ -6,6 +6,20 @@
|
||||
package org.jetbrains.kotlin.backend.wasm.ir2wasm
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
|
||||
import java.util.Base64
|
||||
|
||||
fun String.toJsStringLiteral(): CharSequence =
|
||||
JsToStringGenerationVisitor.javaScriptString(this)
|
||||
JsToStringGenerationVisitor.javaScriptString(this)
|
||||
|
||||
data class JsModuleAndQualifierReference(
|
||||
val module: String?,
|
||||
val qualifier: String?,
|
||||
) {
|
||||
val jsVariableName = run {
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
+46
-5
@@ -8,12 +8,15 @@ package org.jetbrains.kotlin.backend.wasm.lower
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.JsModuleAndQualifierReference
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.backend.wasm.utils.getJsFunAnnotation
|
||||
import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportDescriptor
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsModule
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsQualifier
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
@@ -108,7 +111,7 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
|
||||
val dispatchReceiver = getter.dispatchReceiverParameter
|
||||
val jsCode =
|
||||
if (dispatchReceiver == null)
|
||||
"() => $propName"
|
||||
"() => ${referenceTopLevelExternalDeclaration(property)}"
|
||||
else
|
||||
"(_this) => _this.$propName"
|
||||
|
||||
@@ -130,7 +133,7 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
|
||||
val dispatchReceiver = setter.dispatchReceiverParameter
|
||||
val jsCode =
|
||||
if (dispatchReceiver == null)
|
||||
"(v) => $propName = v"
|
||||
"(v) => ${referenceTopLevelExternalDeclaration(property)} = v"
|
||||
else
|
||||
"(_this, v) => _this.$propName = v"
|
||||
|
||||
@@ -159,12 +162,19 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
|
||||
return
|
||||
}
|
||||
append('.')
|
||||
append(klass.getJsNameOrKotlinName())
|
||||
} else {
|
||||
append(referenceTopLevelExternalDeclaration(klass))
|
||||
}
|
||||
append(klass.getJsNameOrKotlinName())
|
||||
}
|
||||
|
||||
fun processExternalConstructor(constructor: IrConstructor) {
|
||||
val klass = constructor.constructedClass
|
||||
|
||||
// External interfaces can have synthetic primary constructors in K/JS
|
||||
if (klass.isInterface)
|
||||
return
|
||||
|
||||
processFunctionOrConstructor(
|
||||
function = constructor,
|
||||
name = klass.name,
|
||||
@@ -183,14 +193,26 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
|
||||
val jsFun = function.getJsFunAnnotation()
|
||||
// Wrap external functions without @JsFun to lambdas `foo` -> `(a, b) => foo(a, b)`.
|
||||
// This way we wouldn't fail if we don't call them.
|
||||
if (jsFun != null && function.valueParameters.all { it.defaultValue == null && it.varargElementType == null })
|
||||
if (jsFun != null &&
|
||||
function.valueParameters.all { it.defaultValue == null && it.varargElementType == null } &&
|
||||
currentFile.getJsQualifier() == null &&
|
||||
currentFile.getJsModule() == null
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
val jsFunctionReference = when {
|
||||
jsFun != null -> "($jsFun)"
|
||||
function.isTopLevelDeclaration -> referenceTopLevelExternalDeclaration(function)
|
||||
else -> function.getJsNameOrKotlinName().identifier
|
||||
}
|
||||
|
||||
processFunctionOrConstructor(
|
||||
function = function,
|
||||
name = function.name,
|
||||
returnType = function.returnType,
|
||||
isConstructor = false,
|
||||
jsFunctionReference = jsFun?.let { "($it)" } ?: function.getJsNameOrKotlinName().identifier
|
||||
jsFunctionReference = jsFunctionReference
|
||||
)
|
||||
}
|
||||
|
||||
@@ -341,6 +363,25 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
|
||||
addedDeclarations += res
|
||||
return res
|
||||
}
|
||||
|
||||
private fun referenceTopLevelExternalDeclaration(declaration: IrDeclarationWithName): String {
|
||||
var name = declaration.getJsNameOrKotlinName().identifier
|
||||
|
||||
val qualifier = currentFile.getJsQualifier()
|
||||
|
||||
val module = currentFile.getJsModule()
|
||||
?: declaration.getJsModule()?.also {
|
||||
// JsModule on top level declarations imports "default"
|
||||
name = "default"
|
||||
}
|
||||
|
||||
if (qualifier == null && module == null)
|
||||
return name
|
||||
|
||||
val qualifieReference = JsModuleAndQualifierReference(module, qualifier)
|
||||
context.jsModuleAndQualifierReferences += qualifieReference
|
||||
return qualifieReference.jsVariableName + "." + name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user