[Wasm] Add uninstantiated MJS wrapper
It allows * Custom imports * Ability to skip initializer
This commit is contained in:
@@ -368,22 +368,22 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
eliminateDeadDeclarations(allModules, backendContext)
|
||||
}
|
||||
|
||||
val sourceMapFileName = if (configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) "$outputName.map" else null
|
||||
val generateSourceMaps = configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)
|
||||
|
||||
val res = compileWasm(
|
||||
allModules = allModules,
|
||||
backendContext = backendContext,
|
||||
baseFileName = outputName,
|
||||
emitNameSection = arguments.wasmDebug,
|
||||
allowIncompleteImplementations = arguments.irDce,
|
||||
generateWat = true,
|
||||
sourceMapFileName = sourceMapFileName
|
||||
generateSourceMaps = generateSourceMaps
|
||||
)
|
||||
|
||||
writeCompilationResult(
|
||||
result = res,
|
||||
dir = outputDir,
|
||||
fileNameBase = outputName,
|
||||
sourceMapFileName = sourceMapFileName
|
||||
)
|
||||
|
||||
return OK
|
||||
|
||||
@@ -33,7 +33,8 @@ import java.io.File
|
||||
|
||||
class WasmCompilerResult(
|
||||
val wat: String?,
|
||||
val js: String,
|
||||
val jsUninstantiatedWrapper: String,
|
||||
val jsWrapper: String,
|
||||
val wasm: ByteArray,
|
||||
val sourceMap: String?
|
||||
)
|
||||
@@ -86,10 +87,11 @@ fun compileToLoweredIr(
|
||||
fun compileWasm(
|
||||
allModules: List<IrModuleFragment>,
|
||||
backendContext: WasmBackendContext,
|
||||
baseFileName: String,
|
||||
emitNameSection: Boolean = false,
|
||||
allowIncompleteImplementations: Boolean = false,
|
||||
generateWat: Boolean = false,
|
||||
sourceMapFileName: String? = null,
|
||||
generateSourceMaps: Boolean = false,
|
||||
): WasmCompilerResult {
|
||||
val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns)
|
||||
val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = allowIncompleteImplementations)
|
||||
@@ -105,12 +107,14 @@ fun compileWasm(
|
||||
null
|
||||
}
|
||||
|
||||
val js = compiledWasmModule.generateJs()
|
||||
val jsUninstantiatedWrapper = compiledWasmModule.generateAsyncJsWrapper("./$baseFileName.wasm")
|
||||
val jsWrapper = generateEsmExportsWrapper("./$baseFileName.uninstantiated.mjs")
|
||||
|
||||
val os = ByteArrayOutputStream()
|
||||
|
||||
val sourceMapFileName = "$baseFileName.map".takeIf { generateSourceMaps }
|
||||
val sourceLocationMappings =
|
||||
if (sourceMapFileName != null) mutableListOf<SourceLocationMapping>() else null
|
||||
if (generateSourceMaps) mutableListOf<SourceLocationMapping>() else null
|
||||
|
||||
val wasmIrToBinary =
|
||||
WasmIrToBinary(
|
||||
@@ -128,7 +132,8 @@ fun compileWasm(
|
||||
|
||||
return WasmCompilerResult(
|
||||
wat = wat,
|
||||
js = js,
|
||||
jsUninstantiatedWrapper = jsUninstantiatedWrapper,
|
||||
jsWrapper = jsWrapper,
|
||||
wasm = byteArray,
|
||||
sourceMap = generateSourceMap(backendContext.configuration, sourceLocationMappings)
|
||||
)
|
||||
@@ -167,46 +172,43 @@ private fun generateSourceMap(
|
||||
return sourceMapBuilder.build()
|
||||
}
|
||||
|
||||
fun WasmCompiledModuleFragment.generateJs(): String {
|
||||
//language=js
|
||||
val runtime = """
|
||||
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;
|
||||
} """.trimIndent()
|
||||
fun WasmCompiledModuleFragment.generateAsyncJsWrapper(wasmFilePath: String): String {
|
||||
val jsCodeBody = jsFuns.joinToString(",\n") {
|
||||
"${it.importName.toJsStringLiteral()} : ${it.jsCode}"
|
||||
}
|
||||
|
||||
val jsCodeBodyIndented = jsCodeBody.prependIndent(" ")
|
||||
|
||||
val imports = jsModuleImports
|
||||
.toList()
|
||||
.sorted()
|
||||
.joinToString("\n") {
|
||||
.joinToString("") {
|
||||
val moduleSpecifier = it.toJsStringLiteral()
|
||||
" $moduleSpecifier: await import($moduleSpecifier),"
|
||||
" $moduleSpecifier: imports[$moduleSpecifier] ?? await import($moduleSpecifier),\n"
|
||||
}
|
||||
|
||||
val jsCodeBody = jsFuns.joinToString(",\n") {
|
||||
"${it.importName.toJsStringLiteral()} : ${it.jsCode}"
|
||||
}
|
||||
val jsCodeBodyIndented = jsCodeBody.prependIndent(" ")
|
||||
val importObject = """
|
||||
const _import_object = {
|
||||
${imports}
|
||||
js_code: {
|
||||
${jsCodeBodyIndented}
|
||||
}
|
||||
};
|
||||
"""
|
||||
|
||||
return runtime + importObject
|
||||
//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;
|
||||
}
|
||||
|
||||
fun generateJsWasmLoader(wasmFilePath: String, externalJs: String): String =
|
||||
externalJs + """
|
||||
|
||||
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 isNodeJs = (typeof process !== 'undefined') && (process.release.name === 'node');
|
||||
const isD8 = !isNodeJs && (typeof d8 !== 'undefined');
|
||||
const isBrowser = !isNodeJs && !isD8 && (typeof window !== 'undefined');
|
||||
@@ -215,8 +217,12 @@ fun generateJsWasmLoader(wasmFilePath: String, externalJs: String): String =
|
||||
throw "Supported JS engine not detected";
|
||||
}
|
||||
|
||||
let wasmInstance;
|
||||
let require; // Placed here to give access to it from externals (js_code)
|
||||
const wasmFilePath = ${wasmFilePath.toJsStringLiteral()};
|
||||
const importObject = {
|
||||
js_code,
|
||||
$imports
|
||||
};
|
||||
|
||||
if (isNodeJs) {
|
||||
const module = await import(/* webpackIgnore: true */'node:module');
|
||||
require = module.default.createRequire(import.meta.url);
|
||||
@@ -225,31 +231,40 @@ fun generateJsWasmLoader(wasmFilePath: String, externalJs: String): String =
|
||||
const url = require('url');
|
||||
const filepath = url.fileURLToPath(import.meta.url);
|
||||
const dirpath = path.dirname(filepath);
|
||||
const wasmBuffer = fs.readFileSync(path.resolve(dirpath, '$wasmFilePath'));
|
||||
const wasmBuffer = fs.readFileSync(path.resolve(dirpath, wasmFilePath));
|
||||
const wasmModule = new WebAssembly.Module(wasmBuffer);
|
||||
wasmInstance = new WebAssembly.Instance(wasmModule, _import_object);
|
||||
wasmInstance = new WebAssembly.Instance(wasmModule, importObject);
|
||||
}
|
||||
|
||||
if (isD8) {
|
||||
const wasmBuffer = read('$wasmFilePath', 'binary');
|
||||
const wasmBuffer = read(wasmFilePath, 'binary');
|
||||
const wasmModule = new WebAssembly.Module(wasmBuffer);
|
||||
wasmInstance = new WebAssembly.Instance(wasmModule, _import_object);
|
||||
wasmInstance = new WebAssembly.Instance(wasmModule, importObject);
|
||||
}
|
||||
|
||||
if (isBrowser) {
|
||||
wasmInstance = (await WebAssembly.instantiateStreaming(fetch('$wasmFilePath'), _import_object)).instance;
|
||||
wasmInstance = (await WebAssembly.instantiateStreaming(fetch(wasmFilePath), importObject)).instance;
|
||||
}
|
||||
|
||||
const wasmExports = wasmInstance.exports;
|
||||
wasmExports.__init();
|
||||
export default wasmExports;
|
||||
""".trimIndent()
|
||||
wasmExports = wasmInstance.exports;
|
||||
if (runInitializer) {
|
||||
wasmExports.__init();
|
||||
}
|
||||
|
||||
return { instance: wasmInstance, exports: wasmExports };
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
fun generateEsmExportsWrapper(asyncWrapperFileName: String): String = /*language=js */ """
|
||||
import { instantiate } from ${asyncWrapperFileName.toJsStringLiteral()};
|
||||
export default (await instantiate()).exports;
|
||||
"""
|
||||
|
||||
fun writeCompilationResult(
|
||||
result: WasmCompilerResult,
|
||||
dir: File,
|
||||
fileNameBase: String,
|
||||
sourceMapFileName: String?
|
||||
fileNameBase: String
|
||||
) {
|
||||
dir.mkdirs()
|
||||
if (result.wat != null) {
|
||||
@@ -257,10 +272,10 @@ fun writeCompilationResult(
|
||||
}
|
||||
File(dir, "$fileNameBase.wasm").writeBytes(result.wasm)
|
||||
|
||||
val jsWithLoader = generateJsWasmLoader("./$fileNameBase.wasm", result.js)
|
||||
File(dir, "$fileNameBase.mjs").writeText(jsWithLoader)
|
||||
File(dir, "$fileNameBase.uninstantiated.mjs").writeText(result.jsUninstantiatedWrapper)
|
||||
File(dir, "$fileNameBase.mjs").writeText(result.jsWrapper)
|
||||
|
||||
if (sourceMapFileName != null) {
|
||||
File(dir, sourceMapFileName).writeText(result.sourceMap!!)
|
||||
if (result.sourceMap != null) {
|
||||
File(dir, "$fileNameBase.map").writeText(result.sourceMap)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
|
||||
// FILE: wasmImport.kt
|
||||
import kotlin.wasm.WasmImport
|
||||
|
||||
@WasmImport("foo")
|
||||
external fun inc1(x: Int): Int
|
||||
|
||||
@WasmImport("~!@#\$%^&*()_+`-={}|[]\\\\:\\\";'<>?,./", "inc2")
|
||||
external fun inc2(x: Int): Int
|
||||
|
||||
@WasmImport("./bar.mjs", "inc3")
|
||||
external fun inc3(x: Int): Int
|
||||
|
||||
@JsExport
|
||||
fun myBox(): String {
|
||||
if (inc1(5) != 6) return "KFail1"
|
||||
if (inc2(5) != 6) return "KFail2"
|
||||
if (inc3(5) != 6) return "KFail3"
|
||||
return "OK"
|
||||
}
|
||||
|
||||
var initialized: Int = 0
|
||||
|
||||
@JsExport
|
||||
fun getInitialized(): Int = initialized
|
||||
|
||||
fun main() {
|
||||
initialized = 100
|
||||
}
|
||||
|
||||
// FILE: entry.mjs
|
||||
import { instantiate } from "./index.uninstantiated.mjs";
|
||||
|
||||
let inc = x => x + 1;
|
||||
|
||||
let imports = {
|
||||
"foo": { inc1 : inc },
|
||||
"~!@#\$%^&*()_+\`-={}|[]\\\\:\\\";'<>?,./" : { inc2 : inc },
|
||||
"./bar.mjs" : { inc3 : inc },
|
||||
}
|
||||
|
||||
let { exports } = await instantiate(imports);
|
||||
|
||||
if (exports.getInitialized() !== 100) {
|
||||
throw "Fail1"
|
||||
}
|
||||
|
||||
if (exports.myBox() != "OK") {
|
||||
throw "Fail2"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// TARGET_BACKEND: WASM
|
||||
|
||||
// FILE: wasmImport.kt
|
||||
import kotlin.wasm.WasmImport
|
||||
|
||||
@WasmImport("foo")
|
||||
external fun inc(x: Int): Int
|
||||
|
||||
@JsExport
|
||||
fun myBox(): String {
|
||||
if (inc(5) != 6) return "KFail1"
|
||||
return "OK"
|
||||
}
|
||||
|
||||
var initialized: Int = 0
|
||||
|
||||
@JsExport
|
||||
fun getInitialized(): Int = initialized
|
||||
|
||||
fun main() {
|
||||
initialized = 100
|
||||
}
|
||||
|
||||
// FILE: entry.mjs
|
||||
import { instantiate } from "./index.uninstantiated.mjs";
|
||||
|
||||
let inc = x => x + 1;
|
||||
|
||||
let imports = {
|
||||
"foo": { inc },
|
||||
}
|
||||
|
||||
let { exports } = await instantiate(imports, /*runInitializer=*/false);
|
||||
if (exports.getInitialized() !== 0) {
|
||||
throw "Fail1"
|
||||
}
|
||||
exports.__init();
|
||||
if (exports.getInitialized() !== 100) {
|
||||
throw "Fail2"
|
||||
}
|
||||
|
||||
if (exports.myBox() != "OK") {
|
||||
throw "Fail3"
|
||||
}
|
||||
@@ -69,6 +69,8 @@ abstract class BasicWasmBoxTest(
|
||||
val jsFilesAfter = mutableListOf<String>()
|
||||
val mjsFiles = mutableListOf<String>()
|
||||
|
||||
var entryMjs: String? = "test.mjs"
|
||||
|
||||
inputFiles.forEach {
|
||||
val name = it.fileName
|
||||
when {
|
||||
@@ -81,8 +83,13 @@ abstract class BasicWasmBoxTest(
|
||||
name.endsWith(".js") ->
|
||||
jsFilesBefore += name
|
||||
|
||||
name.endsWith(".mjs") ->
|
||||
name.endsWith(".mjs") -> {
|
||||
mjsFiles += name
|
||||
val fileName = File(name).name
|
||||
if (fileName == "entry.mjs") {
|
||||
entryMjs = fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +143,12 @@ abstract class BasicWasmBoxTest(
|
||||
)
|
||||
|
||||
val generateWat = debugMode >= DebugMode.DEBUG
|
||||
val baseFileName = "index"
|
||||
|
||||
val compilerResult = compileWasm(
|
||||
allModules = allModules,
|
||||
backendContext = backendContext,
|
||||
baseFileName = baseFileName,
|
||||
emitNameSection = true,
|
||||
allowIncompleteImplementations = false,
|
||||
generateWat = generateWat,
|
||||
@@ -150,6 +159,7 @@ abstract class BasicWasmBoxTest(
|
||||
val compilerResultWithDCE = compileWasm(
|
||||
allModules = allModules,
|
||||
backendContext = backendContext,
|
||||
baseFileName = baseFileName,
|
||||
emitNameSection = true,
|
||||
allowIncompleteImplementations = true,
|
||||
generateWat = generateWat,
|
||||
@@ -189,6 +199,7 @@ abstract class BasicWasmBoxTest(
|
||||
val path = dir.absolutePath
|
||||
println(" ------ $name Wat file://$path/index.wat")
|
||||
println(" ------ $name Wasm file://$path/index.wasm")
|
||||
println(" ------ $name JS file://$path/index.uninstantiated.mjs")
|
||||
println(" ------ $name JS file://$path/index.mjs")
|
||||
println(" ------ $name Test file://$path/test.mjs")
|
||||
val projectName = "kotlin"
|
||||
@@ -221,7 +232,7 @@ abstract class BasicWasmBoxTest(
|
||||
)
|
||||
}
|
||||
|
||||
writeCompilationResult(res, dir, "index", sourceMapFileName = null)
|
||||
writeCompilationResult(res, dir, baseFileName)
|
||||
File(dir, "test.mjs").writeText(testJs)
|
||||
|
||||
for (mjsPath: String in mjsFiles) {
|
||||
@@ -234,7 +245,7 @@ abstract class BasicWasmBoxTest(
|
||||
"--experimental-wasm-gc",
|
||||
*jsFilesBefore.map { File(it).absolutePath }.toTypedArray(),
|
||||
"--module",
|
||||
"./test.mjs",
|
||||
"./${entryMjs}",
|
||||
*jsFilesAfter.map { File(it).absolutePath }.toTypedArray(),
|
||||
workingDirectory = dir
|
||||
)
|
||||
|
||||
+10
@@ -55,6 +55,16 @@ public class IrCodegenWasmJsInteropWasmTestGenerated extends AbstractIrCodegenWa
|
||||
runTest("compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("imperativeWrapperInitialised.kt")
|
||||
public void testImperativeWrapperInitialised() throws Exception {
|
||||
runTest("compiler/testData/codegen/boxWasmJsInterop/imperativeWrapperInitialised.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("imperativeWrapperUninitialised.kt")
|
||||
public void testImperativeWrapperUninitialised() throws Exception {
|
||||
runTest("compiler/testData/codegen/boxWasmJsInterop/imperativeWrapperUninitialised.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("jsExport.kt")
|
||||
public void testJsExport() throws Exception {
|
||||
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
|
||||
|
||||
Reference in New Issue
Block a user