[Wasm] Basic CLI

-Xwasm option that will produce wasm instead of JS when used with -Xir-produce-js
Does not affect klib production
This commit is contained in:
Svyatoslav Kuzmich
2020-10-19 17:34:31 +03:00
parent 80f316168e
commit e51a76bc4e
7 changed files with 125 additions and 112 deletions
@@ -178,6 +178,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xerror-tolerance-policy", description = "Set up error tolerance policy (NONE, SEMANTIC, SYNTAX, ALL)")
var errorTolerancePolicy: String? by NullableStringFreezableVar(null)
@Argument(value = "-Xwasm", description = "Use experimental WebAssembly compiler backend")
var wasm: Boolean by FreezableVar(false)
override fun checkIrSupport(languageVersionSettings: LanguageVersionSettings, collector: MessageCollector) {
if (!isIrBackendEnabled()) return
@@ -196,4 +199,4 @@ fun K2JSCompilerArguments.isPreIrBackendDisabled(): Boolean =
irOnly || irProduceJs || irProduceKlibFile
fun K2JSCompilerArguments.isIrBackendEnabled(): Boolean =
irProduceKlibDir || irProduceJs || irProduceKlibFile
irProduceKlibDir || irProduceJs || irProduceKlibFile || wasm
+1
View File
@@ -12,6 +12,7 @@ dependencies {
compile(project(":compiler:ir.backend.common"))
compile(project(":compiler:ir.serialization.js"))
compile(project(":compiler:backend.js"))
compile(project(":compiler:backend.wasm"))
compile(project(":js:js.translator"))
compile(project(":js:js.serializer"))
compile(project(":js:js.dce"))
@@ -8,7 +8,10 @@ package org.jetbrains.kotlin.cli.js
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.backend.wasm.compileWasm
import org.jetbrains.kotlin.backend.wasm.wasmPhases
import org.jetbrains.kotlin.cli.common.*
import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR
import org.jetbrains.kotlin.cli.common.ExitCode.OK
@@ -34,6 +37,7 @@ import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory
import org.jetbrains.kotlin.js.config.*
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.util.Logger
@@ -216,6 +220,33 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
MainModule.SourceFiles(sourcesFiles)
}
if (arguments.wasm) {
val res = compileWasm(
projectJs,
mainModule,
AnalyzerWithCompilerReport(config.configuration),
config.configuration,
PhaseConfig(wasmPhases),
allDependencies = resolvedLibraries,
friendDependencies = friendDependencies,
exportedDeclarations = setOf(FqName("main"))
)
val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!!
outputWasmFile.writeBytes(res.wasm)
val outputWatFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wat")!!
outputWatFile.writeText(res.wat)
val runner = """
const wasmBinary = read(String.raw`${outputWasmFile.absoluteFile}`, 'binary');
const wasmModule = new WebAssembly.Module(wasmBinary);
const wasmInstance = new WebAssembly.Instance(wasmModule, { runtime, js_code });
wasmInstance.exports.main();
""".trimIndent()
outputFile.writeText(res.js + "\n" + runner)
return OK
}
val compiledModule = try {
compile(
projectJs,
@@ -32,7 +32,7 @@ class WasmCompilerResult(val wat: String, val js: String, val wasm: ByteArray)
fun compileWasm(
project: Project,
files: List<KtFile>,
mainModule: MainModule,
analyzer: AbstractAnalyzerWithCompilerReport,
configuration: CompilerConfiguration,
phaseConfig: PhaseConfig,
@@ -42,21 +42,26 @@ fun compileWasm(
): WasmCompilerResult {
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
loadIr(
project, MainModule.SourceFiles(files), analyzer, configuration, allDependencies, friendDependencies,
project, mainModule, analyzer, configuration, allDependencies, friendDependencies,
PersistentIrFactory
)
val allModules = when (mainModule) {
is MainModule.SourceFiles -> dependencyModules + listOf(moduleFragment)
is MainModule.Klib -> dependencyModules
}
val moduleDescriptor = moduleFragment.descriptor
val context = WasmBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, exportedDeclarations, configuration)
// Load declarations referenced during `context` initialization
dependencyModules.forEach {
allModules.forEach {
val irProviders = generateTypicalIrProviderList(it.descriptor, irBuiltIns, symbolTable, deserializer)
ExternalDependenciesGenerator(symbolTable, irProviders, configuration.languageVersionSettings)
.generateUnboundSymbolsAsDependencies()
}
val irFiles = dependencyModules.flatMap { it.files } + moduleFragment.files
val irFiles = allModules.flatMap { it.files }
moduleFragment.files.clear()
moduleFragment.files += irFiles
@@ -77,13 +82,86 @@ fun compileWasm(
watGenerator.appendWasmModule(linkedModule)
val wat = watGenerator.toString()
val js = compiledWasmModule.generateJs()
val os = ByteArrayOutputStream()
WasmIrToBinary(os, linkedModule).appendWasmModule()
val byteArray = os.toByteArray()
return WasmCompilerResult(
wat,
generateStringLiteralsSupport(compiledWasmModule.stringLiterals),
wat = wat,
js = js,
wasm = byteArray
)
}
fun WasmCompiledModuleFragment.generateJs(): String {
val runtime = """
const runtime = {
String_plus(str1, str2) {
return str1 + String(str2);
},
String_getLength(str) {
return str.length;
},
String_getChar(str, index) {
return str.charCodeAt(index);
},
String_compareTo(str1, str2) {
if (str1 > str2) return 1;
if (str1 < str2) return -1;
return 0;
},
String_equals(str, other) {
return str === other;
},
String_subsequence(str, startIndex, endIndex) {
return str.substring(startIndex, endIndex);
},
String_getLiteral(index) {
return runtime.stringLiterals[index];
},
coerceToString(value) {
return String(value);
},
Char_toString(char) {
return String.fromCharCode(char)
},
JsArray_new(size) {
return new Array(size);
},
JsArray_get(array, index) {
return array[index];
},
JsArray_set(array, index, value) {
array[index] = value;
},
JsArray_getSize(array) {
return array.length;
},
identity(x) {
return x;
},
println(value) {
console.log(">>> " + value)
}
};
""".trimIndent()
return runtime + generateStringLiteralsSupport(stringLiterals)
}
+1
View File
@@ -21,6 +21,7 @@ where advanced options include:
-Xir-produce-klib-file Generate packed klib into file specified by -output. Disables pre-IR backend
-Xmetadata-only Generate *.meta.js and *.kjsm files only
-Xtyped-arrays Translate primitive arrays to JS typed arrays
-Xwasm Use experimental WebAssembly compiler backend
-Xallow-kotlin-package Allow compiling code in package 'kotlin' and allow not requiring kotlin.stdlib in module-info
-Xallow-result-return-type Allow compiling code when `kotlin.Result` is used as a return type
-Xcheck-phase-conditions Check pre- and postconditions on phases
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.ir.backend.js.MainModule
import org.jetbrains.kotlin.ir.backend.js.loadKlib
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.facade.TranslationUnit
@@ -114,7 +115,7 @@ abstract class BasicWasmBoxTest(
testFunction: String
) {
val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file }
val debugMode =getBoolean("kotlin.js.debugMode")
val debugMode = getBoolean("kotlin.js.debugMode")
val phaseConfig = if (debugMode) {
val allPhasesSet = wasmPhases.toPhaseMap().values.toSet()
@@ -137,7 +138,7 @@ abstract class BasicWasmBoxTest(
val compilerResult = compileWasm(
project = config.project,
files = filesToCompile,
mainModule = MainModule.SourceFiles(filesToCompile),
analyzer = AnalyzerWithCompilerReport(config.configuration),
configuration = config.configuration,
phaseConfig = phaseConfig,
@@ -150,8 +151,6 @@ abstract class BasicWasmBoxTest(
outputWatFile.write(compilerResult.wat)
outputWasmFile.writeBytes(compilerResult.wasm)
val runtime = File("libraries/stdlib/wasm/runtime/runtime.js").readText()
val testRunner = """
const wasmBinary = read(String.raw`${outputWasmFile.absoluteFile}`, 'binary');
const wasmModule = new WebAssembly.Module(wasmBinary);
@@ -162,7 +161,7 @@ abstract class BasicWasmBoxTest(
throw `Wrong box result '${'$'}{actualResult}'; Expected "OK"`;
""".trimIndent()
outputJsFile.write(runtime + "\n" + compilerResult.js + "\n" + testRunner)
outputJsFile.write(compilerResult.js + "\n" + testRunner)
}
-100
View File
@@ -1,100 +0,0 @@
/*
* Copyright 2010-2019 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.
*/
const runtime = {
/**
* kotlin.String#plus(other: Any?): String
* @return {string}
*/
String_plus(str1, str2) {
if (typeof str1 != "string") throw `Illegal argument str1: ${str1}`;
return str1 + String(str2);
},
/**
* @return {number}
*/
String_getLength(str) {
if (typeof str != "string") throw `Illegal argument x: ${str}`;
return str.length;
},
/**
* @return {number}
*/
String_getChar(str, index) {
if (typeof str != "string") throw `Illegal argument str: ${str}`;
if (typeof index != "number") throw `Illegal argument index: ${index}`;
return str.charCodeAt(index);
},
/**
* @return {number}
*/
String_compareTo(str1, str2) {
if (str1 > str2) return 1;
if (str1 < str2) return -1;
return 0;
},
/**
* @return {boolean}
*/
String_equals(str, other) {
// if (typeof str != "string") throw `Illegal argument str: ${str}`;
return str === other;
},
/**
* @return {string}
*/
String_subsequence(str, startIndex, endIndex) {
return str.substring(startIndex, endIndex);
},
String_getLiteral(index) {
return runtime.stringLiterals[index];
},
coerceToString(value) {
return String(value);
},
/**
* @return {string}
*/
Char_toString(char) {
return String.fromCharCode(char)
},
JsArray_new(size) {
if (typeof size != "number") throw `Illegal argument size: ${size}`;
return new Array(size);
},
JsArray_get(array, index) {
if (typeof index != "number") throw `Illegal argument index: ${index}`;
if (array.length <= index) throw `Index out of bounds: index=${index} length=${array.length}`;
return array[index];
},
JsArray_set(array, index, value) {
if (typeof index != "number") throw `Illegal argument index: ${index}`;
if (array.length <= index) throw `Index out of bounds: index=${index} length=${array.length}`;
array[index] = value;
},
JsArray_getSize(array) {
return array.length;
},
identity(x) {
return x;
},
println(value) {
console.log(">>> " + value)
}
};