[Wasm] Add compiler flag to disable exception handling proposal

Fail with unreachable instead of throwing an exception

Merge-request: KT-MR-11481
Merged-by: Svyatoslav Kuzmich <svyatoslav.kuzmich@jetbrains.com>
This commit is contained in:
Svyatoslav Kuzmich
2023-08-07 16:19:29 +00:00
committed by Space Team
parent ec73815f80
commit 47ade4530c
17 changed files with 118 additions and 13 deletions
@@ -73,6 +73,7 @@ fun copyK2JSCompilerArguments(from: K2JSCompilerArguments, to: K2JSCompilerArgum
to.wasmGenerateWat = from.wasmGenerateWat
to.wasmKClassFqn = from.wasmKClassFqn
to.wasmTarget = from.wasmTarget
to.wasmUseTrapsInsteadOfExceptions = from.wasmUseTrapsInsteadOfExceptions
return to
}
@@ -630,6 +630,16 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
field = if (value.isNullOrEmpty()) null else value
}
@Argument(
value = "-Xwasm-use-traps-instead-of-exceptions",
description = "Trap instead of throwing exceptions"
)
var wasmUseTrapsInsteadOfExceptions = false
set(value) {
checkFrozen()
field = value
}
@Argument(
value = "-Xforce-deprecated-legacy-compiler-usage",
description = "The flag is used only for our inner infrastructure. It will be removed soon, so it's unsafe to use it nowadays."
@@ -170,6 +170,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
configuration.put(JSConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, arguments.wasmEnableArrayRangeChecks)
configuration.put(JSConfigurationKeys.WASM_ENABLE_ASSERTS, arguments.wasmEnableAsserts)
configuration.put(JSConfigurationKeys.WASM_GENERATE_WAT, arguments.wasmGenerateWat)
configuration.put(JSConfigurationKeys.WASM_USE_TRAPS_INSTEAD_OF_EXCEPTIONS, arguments.wasmUseTrapsInsteadOfExceptions)
configuration.putIfNotNull(JSConfigurationKeys.WASM_TARGET, arguments.wasmTarget?.let(WasmTarget::fromName))
configuration.put(JSConfigurationKeys.OPTIMIZE_GENERATED_JS, arguments.optimizeGeneratedJs)
@@ -24,11 +24,11 @@ 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.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrTypeSystemContext
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.addChild
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -97,7 +97,10 @@ fun compileWasm(
generateWat: Boolean = false,
generateSourceMaps: Boolean = false,
): WasmCompilerResult {
val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns)
val compiledWasmModule = WasmCompiledModuleFragment(
backendContext.irBuiltIns,
backendContext.configuration.getBoolean(JSConfigurationKeys.WASM_USE_TRAPS_INSTEAD_OF_EXCEPTIONS)
)
val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = allowIncompleteImplementations)
allModules.forEach { codeGenerator.collectInterfaceTables(it) }
allModules.forEach { codeGenerator.generateModule(it) }
@@ -137,12 +137,22 @@ class BodyGenerator(
override fun visitThrow(expression: IrThrow) {
generateExpression(expression.value)
if (context.backendContext.configuration.getBoolean(JSConfigurationKeys.WASM_USE_TRAPS_INSTEAD_OF_EXCEPTIONS)) {
body.buildUnreachable(SourceLocation.NoLocation("Unreachable is inserted instead of a `throw` instruction"))
return
}
body.buildThrow(functionContext.tagIdx, expression.getSourceLocation())
}
override fun visitTry(aTry: IrTry) {
assert(aTry.isCanonical(irBuiltIns)) { "expected canonical try/catch" }
if (context.backendContext.configuration.getBoolean(JSConfigurationKeys.WASM_USE_TRAPS_INSTEAD_OF_EXCEPTIONS)) {
generateExpression(aTry.tryResult)
return
}
val resultType = context.transformBlockResultType(aTry.type)
body.buildTry(null, resultType)
generateExpression(aTry.tryResult)
@@ -15,7 +15,10 @@ import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.wasm.ir.*
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
class WasmCompiledModuleFragment(
val irBuiltIns: IrBuiltIns,
generateTrapsInsteadOfExceptions: Boolean,
) {
val functions =
ReferencableAndDefinable<IrFunctionSymbol, WasmFunction>()
val globalFields =
@@ -49,7 +52,7 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
),
emptyList()
)
val tag = WasmTag(tagFuncType)
val tags = if (generateTrapsInsteadOfExceptions) emptyList() else listOf(WasmTag(tagFuncType))
val typeInfo = ReferencableAndDefinable<IrClassSymbol, ConstantDataElement>()
@@ -238,7 +241,7 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
elements = emptyList(),
data = data,
dataCount = true,
tags = listOf(tag)
tags = tags
)
module.calculateIds()
return module
+2
View File
@@ -60,6 +60,8 @@ where advanced options include:
-Xwasm-generate-wat Generate wat file
-Xwasm-kclass-fqn Enable support for FQ names in KClass
-Xwasm-target Set up Wasm target (wasm-js or wasm-wasi)
-Xwasm-use-traps-instead-of-exceptions
Trap instead of throwing exceptions
-Xallow-any-scripts-in-source-roots
Allow to compile any scripts along with regular Kotlin sources
-Xallow-kotlin-package Allow compiling code in package 'kotlin' and allow not requiring kotlin.stdlib in module-info
@@ -0,0 +1,46 @@
// TARGET_BACKEND: WASM
// DISABLE_WASM_EXCEPTION_HANDLING
// MODULE: main
// FILE: foo.kt
@JsExport
fun sumNumbers(x: Int): Int =
(0..x).toList().sum()
@JsExport
fun add(x: Int, y: Int): Int =
x + y
@JsExport
fun throws() {
try {
error("Test Error")
} catch (e: Throwable) {
// This code normally catches the error,
// but with exception handling disabled it should fail with trap
}
}
// FILE: entry.mjs
import wasmExports from "./index.mjs";
if (wasmExports.add(10, 20) !== 30) {
throw "Fail add";
}
if (wasmExports.sumNumbers(10) !== 55) {
throw "Fail sumNumbers";
}
var thrown = false;
try {
wasmExports.throws();
} catch(e) {
thrown = true;
}
if (!thrown) {
throw "Fail exception was not thrown";
}
@@ -13,6 +13,10 @@ object WasmEnvironmentConfigurationDirectives : SimpleDirectivesContainer() {
description = "Run kotlin.test unit tests (function marked with @Test)",
)
val DISABLE_WASM_EXCEPTION_HANDLING by directive(
description = "Generate wasm without EH proposal and test in runtime with EH turned off",
)
// Next directives are used only inside test system and must not be present in test file
val PATH_TO_TEST_DIR by stringDirective(
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives.PROPERTY_LAZY_INITIALIZATION
import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives.SOURCE_MAP_EMBED_SOURCES
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives.DISABLE_WASM_EXCEPTION_HANDLING
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.frontend.classic.moduleDescriptorProvider
@@ -155,5 +156,7 @@ class WasmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfi
configuration.put(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, sourceMapSourceEmbedding)
configuration.put(CommonConfigurationKeys.EXPECT_ACTUAL_LINKER, EXPECT_ACTUAL_LINKER in registeredDirectives)
configuration.put(JSConfigurationKeys.WASM_USE_TRAPS_INSTEAD_OF_EXCEPTIONS, DISABLE_WASM_EXCEPTION_HANDLING in registeredDirectives)
}
}
@@ -124,6 +124,9 @@ public class JSConfigurationKeys {
public static final CompilerConfigurationKey<WasmTarget> WASM_TARGET =
CompilerConfigurationKey.create("wasm target");
public static final CompilerConfigurationKey<Boolean> WASM_USE_TRAPS_INSTEAD_OF_EXCEPTIONS =
CompilerConfigurationKey.create("use wasm traps instead of throwing exceptions");
public static final CompilerConfigurationKey<ZipFileSystemAccessor> ZIP_FILE_SYSTEM_ACCESSOR =
CompilerConfigurationKey.create("zip file system accessor, used for klib reading");
@@ -123,9 +123,11 @@ class WasmIrToBinary(
}
// tag section
appendSection(WasmBinary.Section.TAG) {
appendVectorSize(tags.size)
tags.forEach { appendTag(it) }
if (tags.isNotEmpty()) {
appendSection(WasmBinary.Section.TAG) {
appendVectorSize(tags.size)
tags.forEach { appendTag(it) }
}
}
appendSection(WasmBinary.Section.GLOBAL) {
@@ -607,7 +609,7 @@ abstract class ByteWriter {
fun writeUByte(v: UByte) {
writeByte(v.toByte())
}
fun writeUInt32(v: UInt) {
writeByte(v.toByte())
writeByte((v shr 8).toByte())
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.test.DebugMode
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives.DISABLE_WASM_EXCEPTION_HANDLING
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives.RUN_UNIT_TESTS
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.services.TestServices
@@ -191,6 +192,8 @@ class WasmBoxRunner(
val testFileText = originalFile.readText()
val failsIn: List<String> = InTextDirectivesUtils.findListWithPrefixes(testFileText, "// WASM_FAILS_IN: ")
val disableExceptions = DISABLE_WASM_EXCEPTION_HANDLING in testServices.moduleStructure.allDirectives
val exceptions = listOf(WasmVM.V8, WasmVM.SpiderMonkey).mapNotNull map@{ vm ->
try {
if (debugMode >= DebugMode.DEBUG) {
@@ -199,7 +202,8 @@ class WasmBoxRunner(
vm.run(
"./${entryMjs}",
jsFilePaths,
workingDirectory = dir
workingDirectory = dir,
disableExceptionHandlingIfPossible = disableExceptions
)
if (vm.shortName in failsIn) {
return@map AssertionError("The test expected to fail in ${vm.name}. Please update the testdata.")
@@ -17,10 +17,10 @@ internal sealed class WasmVM(val shortName: String) {
val name: String = javaClass.simpleName
protected val tool = ExternalTool(System.getProperty("javascript.engine.path.$name"))
abstract fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?)
abstract fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?, disableExceptionHandlingIfPossible: Boolean = false)
object V8 : WasmVM("V8") {
override fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?) {
override fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?, disableExceptionHandlingIfPossible: Boolean) {
tool.run(
"--experimental-wasm-gc",
"--wasm-final-types",
@@ -34,10 +34,11 @@ internal sealed class WasmVM(val shortName: String) {
}
object SpiderMonkey : WasmVM("SM") {
override fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?) {
override fun run(entryMjs: String, jsFiles: List<String>, workingDirectory: File?, disableExceptionHandlingIfPossible: Boolean) {
tool.run(
"--wasm-verbose",
"--wasm-gc",
*if (disableExceptionHandlingIfPossible) arrayOf("--no-wasm-exceptions") else emptyArray(),
"--wasm-function-references",
*jsFiles.flatMap { listOf("-f", it) }.toTypedArray(),
"--module=$entryMjs",
@@ -139,6 +139,12 @@ public class FirWasmCodegenWasmJsInteropTestGenerated extends AbstractFirWasmCod
runTest("compiler/testData/codegen/boxWasmJsInterop/nameClash.kt");
}
@Test
@TestMetadata("noExceptions.kt")
public void testNoExceptions() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/noExceptions.kt");
}
@Test
@TestMetadata("nullableExternRefs.kt")
public void testNullableExternRefs() throws Exception {
@@ -139,6 +139,12 @@ public class K1WasmCodegenWasmJsInteropTestGenerated extends AbstractK1WasmCodeg
runTest("compiler/testData/codegen/boxWasmJsInterop/nameClash.kt");
}
@Test
@TestMetadata("noExceptions.kt")
public void testNoExceptions() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/noExceptions.kt");
}
@Test
@TestMetadata("nullableExternRefs.kt")
public void testNullableExternRefs() throws Exception {