From 1b412073b9c5be8d0fec2b13c70190118e61ff80 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 6 May 2024 21:35:38 +0200 Subject: [PATCH] wasm: support exception handling via native exception handling spec --- .../org/teavm/backend/wasm/WasmTarget.java | 36 ++-- .../wasm/generate/WasmGenerationContext.java | 9 +- .../wasm/generate/WasmGenerationVisitor.java | 167 ++++++++++++++---- .../backend/wasm/generate/WasmGenerator.java | 2 + .../ExceptionHandlingIntrinsic.java | 5 + .../wasm/intrinsics/WasmIntrinsicManager.java | 3 +- .../teavm/backend/wasm/model/WasmModule.java | 15 ++ .../org/teavm/backend/wasm/model/WasmTag.java | 33 ++++ .../wasm/model/expression/WasmCatch.java | 43 +++++ .../WasmDefaultExpressionVisitor.java | 19 ++ .../expression/WasmExpressionVisitor.java | 4 + .../WasmReplacingExpressionVisitor.java | 13 ++ .../wasm/model/expression/WasmThrow.java | 46 +++++ .../wasm/model/expression/WasmTry.java | 52 ++++++ .../wasm/render/WasmBinaryRenderer.java | 20 +++ .../render/WasmBinaryRenderingVisitor.java | 35 ++++ .../wasm/render/WasmCRenderingVisitor.java | 12 ++ .../wasm/render/WasmRenderingVisitor.java | 33 ++++ .../backend/wasm/render/WasmSignature.java | 11 +- .../wasm/render/WasmTypeInference.java | 12 ++ .../WasmExceptionHandlingTransform.java | 41 +++++ .../org/teavm/runtime/ExceptionHandling.java | 14 ++ .../main/java/org/teavm/cli/TeaVMRunner.java | 7 + .../java/org/teavm/tooling/TeaVMTool.java | 6 + .../teavm/tooling/builder/BuildStrategy.java | 2 + .../builder/InProcessBuildStrategy.java | 7 + .../tooling/builder/RemoteBuildStrategy.java | 5 + .../org/teavm/tooling/daemon/BuildDaemon.java | 1 + .../tooling/daemon/RemoteBuildRequest.java | 1 + .../org/teavm/gradle/TeaVMExtensionImpl.java | 2 + .../java/org/teavm/gradle/TeaVMPlugin.java | 2 + .../api/TeaVMWasmBaseConfiguration.java | 2 + .../teavm/gradle/tasks/GenerateWasiTask.java | 4 + .../teavm/gradle/tasks/GenerateWasmTask.java | 5 + .../junit/BaseWebAssemblyPlatformSupport.java | 5 + .../junit/WebAssemblyPlatformSupport.java | 5 + .../org/teavm/maven/TeaVMCompileMojo.java | 4 + 37 files changed, 632 insertions(+), 51 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/model/WasmTag.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/model/expression/WasmCatch.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/model/expression/WasmThrow.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/model/expression/WasmTry.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/transformation/WasmExceptionHandlingTransform.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index b808ab950..e194ecba4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -87,6 +87,7 @@ import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmMemorySegment; import org.teavm.backend.wasm.model.WasmModule; +import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.expression.WasmBlock; import org.teavm.backend.wasm.model.expression.WasmBranch; @@ -118,6 +119,7 @@ import org.teavm.backend.wasm.transformation.IndirectCallTraceTransformation; import org.teavm.backend.wasm.transformation.MemoryAccessTraceTransformation; import org.teavm.backend.wasm.transformation.WasiFileSystemProviderTransformer; import org.teavm.backend.wasm.transformation.WasiSupportClassTransformer; +import org.teavm.backend.wasm.transformation.WasmExceptionHandlingTransform; import org.teavm.common.ServiceRepository; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; @@ -164,7 +166,6 @@ import org.teavm.model.transformation.BoundCheckInsertion; import org.teavm.model.transformation.ClassPatch; import org.teavm.model.transformation.NullCheckInsertion; import org.teavm.model.util.AsyncMethodFinder; -import org.teavm.model.util.TransitionExtractor; import org.teavm.runtime.Allocator; import org.teavm.runtime.EventQueue; import org.teavm.runtime.ExceptionHandling; @@ -209,6 +210,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { private WasmRuntimeType runtimeType = WasmRuntimeType.TEAVM; private ReportingWasmBinaryStatsCollector statsCollector; private SourceFileResolver sourceFileResolver; + private boolean exceptionsUsed; @Override public void setController(TeaVMTargetController controller) { @@ -247,6 +249,9 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { transformers.add(new WasiSupportClassTransformer()); transformers.add(new WasiFileSystemProviderTransformer()); } + if (exceptionsUsed) { + transformers.add(new WasmExceptionHandlingTransform()); + } return transformers; } @@ -313,6 +318,10 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { this.sourceFileResolver = sourceFileResolver; } + public void setExceptionsUsed(boolean exceptionsUsed) { + this.exceptionsUsed = exceptionsUsed; + } + @Override public WasmRuntimeType getRuntimeType() { return runtimeType; @@ -388,6 +397,8 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "catchException", Throwable.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "peekException", + Throwable.class)).use(); dependencyAnalyzer.linkField(new FieldReference("java.lang.Object", "monitor")); @@ -454,25 +465,9 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { new CoroutineTransformation(controller.getUnprocessedClassSource(), asyncMethods, hasThreads) .apply(program, method.getReference()); shadowStackTransformer.apply(program, method); - //checkPhis(program, method); writeBarrierInsertion.apply(program); } - private void checkPhis(Program program, MethodReader method) { - var transitionExtractor = new TransitionExtractor(); - for (var block : program.getBasicBlocks()) { - for (var phi : block.getPhis()) { - for (var incoming : phi.getIncomings()) { - incoming.getSource().getLastInstruction().acceptVisitor(transitionExtractor); - if (!Arrays.asList(transitionExtractor.getTargets()).contains(block)) { - throw new RuntimeException("Method " + method.getReference() + ", block " - + block.getIndex() + ", from " + incoming.getSource().getIndex()); - } - } - } - } - } - @Override public void emit(ListableClassHolderSource classes, BuildTarget buildTarget, String outputName) throws IOException { @@ -502,8 +497,13 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { Decompiler decompiler = new Decompiler(classes, new HashSet<>(), false); var stringPool = classGenerator.getStringPool(); + WasmTag exceptionTag = null; + if (exceptionsUsed) { + exceptionTag = new WasmTag(); + module.addTag(exceptionTag); + } var context = new WasmGenerationContext(classes, module, controller.getDiagnostics(), - vtableProvider, tagRegistry, stringPool, names, characteristics); + vtableProvider, tagRegistry, stringPool, names, characteristics, exceptionTag); context.addIntrinsic(new AddressIntrinsic(classGenerator)); context.addIntrinsic(new StructureIntrinsic(classes, classGenerator)); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationContext.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationContext.java index d59382136..9804119f4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationContext.java @@ -24,6 +24,7 @@ import org.teavm.backend.wasm.generators.WasmMethodGenerator; import org.teavm.backend.wasm.intrinsics.WasmIntrinsic; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmModule; +import org.teavm.backend.wasm.model.WasmTag; import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.Import; import org.teavm.model.AnnotationReader; @@ -54,11 +55,12 @@ public class WasmGenerationContext { private List generators = new ArrayList<>(); private Map intrinsicCache = new HashMap<>(); private Map generatorCache = new HashMap<>(); + private WasmTag exceptionTag; public final List callSites = new ArrayList<>(); public WasmGenerationContext(ClassReaderSource classSource, WasmModule module, Diagnostics diagnostics, VirtualTableProvider vtableProvider, TagRegistry tagRegistry, WasmStringPool stringPool, - NameProvider names, Characteristics characteristics) { + NameProvider names, Characteristics characteristics, WasmTag exceptionTag) { this.classSource = classSource; this.module = module; this.diagnostics = diagnostics; @@ -67,6 +69,7 @@ public class WasmGenerationContext { this.stringPool = stringPool; this.names = names; this.characteristics = characteristics; + this.exceptionTag = exceptionTag; } public void addIntrinsic(WasmIntrinsic intrinsic) { @@ -162,6 +165,10 @@ public class WasmGenerationContext { return diagnostics; } + public WasmTag getExceptionTag() { + return exceptionTag; + } + public static class ImportedMethod { public final String name; public final String module; diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java index 6812394c2..417c73bbc 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java @@ -74,11 +74,13 @@ import org.teavm.backend.wasm.binary.DataPrimitives; import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; +import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.expression.WasmBlock; import org.teavm.backend.wasm.model.expression.WasmBranch; import org.teavm.backend.wasm.model.expression.WasmBreak; import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmCatch; import org.teavm.backend.wasm.model.expression.WasmConditional; import org.teavm.backend.wasm.model.expression.WasmConversion; import org.teavm.backend.wasm.model.expression.WasmDrop; @@ -111,6 +113,8 @@ import org.teavm.backend.wasm.model.expression.WasmStoreFloat64; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; import org.teavm.backend.wasm.model.expression.WasmStoreInt64; import org.teavm.backend.wasm.model.expression.WasmSwitch; +import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.backend.wasm.model.expression.WasmTry; import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.backend.wasm.render.WasmTypeInference; import org.teavm.diagnostics.Diagnostics; @@ -143,6 +147,8 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { "monitorExit", Object.class, void.class); private static final MethodReference CATCH_METHOD = new MethodReference(ExceptionHandling.class, "catchException", Throwable.class); + private static final MethodReference PEEK_EXCEPTION_METHOD = new MethodReference(ExceptionHandling.class, + "peekException", Throwable.class); private static final MethodReference THROW_METHOD = new MethodReference(ExceptionHandling.class, "throwException", Throwable.class, void.class); private static final MethodReference THROW_CCE_METHOD = new MethodReference(ExceptionHandling.class, @@ -195,6 +201,7 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { this.managed = context.characteristics.isManaged(currentMethod); } + void generate(Statement statement, List target) { var lastTargetSize = target.size(); resultConsumer = target; @@ -512,7 +519,6 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { block.setType(WasmType.INT32); block.setLocation(location); - accept(value); var cachedValue = exprCache.create(result, WasmType.INT32, location, block.getBody()); @@ -526,13 +532,17 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { var call = new WasmCall(context.names.forMethod(THROW_NPE_METHOD)); block.getBody().add(call); - var target = throwJumpTarget(); - var breakExpr = new WasmBreak(target); - if (target != rethrowBlock) { - breakExpr.setResult(generateGetHandlerId(callSiteId, location)); - block.getBody().add(new WasmDrop(breakExpr)); + if (context.getExceptionTag() == null) { + var target = throwJumpTarget(); + var breakExpr = new WasmBreak(target); + if (target != rethrowBlock) { + breakExpr.setResult(generateGetHandlerId(callSiteId, location)); + block.getBody().add(new WasmDrop(breakExpr)); + } else { + block.getBody().add(breakExpr); + } } else { - block.getBody().add(breakExpr); + block.getBody().add(new WasmUnreachable()); } cachedValue.release(); @@ -1072,7 +1082,9 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { targetList.add(drop); } - checkHandlerId(targetList, callSiteId, expr.getLocation()); + if (context.getExceptionTag() == null) { + checkHandlerId(targetList, callSiteId, expr.getLocation()); + } if (resultVar != null) { var getLocal = new WasmGetLocal(resultVar); getLocal.setLocation(expr.getLocation()); @@ -1653,17 +1665,22 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { ifNull.setResult(new WasmInt32Constant(0)); block.getBody().add(new WasmDrop(ifNull)); - WasmCall supertypeCall = new WasmCall(context.names.forSupertypeFunction(expr.getType())); - WasmExpression classRef = new WasmLoadInt32(4, cachedObject.expr(), WasmInt32Subtype.INT32); - classRef = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.SHL, classRef, new WasmInt32Constant(3)); - supertypeCall.getArguments().add(classRef); - block.getBody().add(supertypeCall); + block.getBody().add(instanceOfImpl(cachedObject.expr(), type)); cachedObject.release(); result = block; } + private WasmExpression instanceOfImpl(WasmExpression expression, ValueType type) { + WasmCall supertypeCall = new WasmCall(context.names.forSupertypeFunction(type)); + WasmExpression classRef = new WasmLoadInt32(4, expression, WasmInt32Subtype.INT32); + classRef = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.SHL, classRef, + new WasmInt32Constant(3)); + supertypeCall.getArguments().add(classRef); + return supertypeCall; + } + @Override public void visit(ThrowStatement statement) { var callSiteId = generateCallSiteId(statement.getLocation()); @@ -1674,13 +1691,17 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { call.setLocation(statement.getLocation()); resultConsumer.add(call); - var target = throwJumpTarget(); - var breakExpr = new WasmBreak(target); - breakExpr.setLocation(statement.getLocation()); - if (target != rethrowBlock) { - breakExpr.setResult(generateGetHandlerId(callSiteId, statement.getLocation())); + if (context.getExceptionTag() == null) { + var target = throwJumpTarget(); + var breakExpr = new WasmBreak(target); + breakExpr.setLocation(statement.getLocation()); + if (target != rethrowBlock) { + breakExpr.setResult(generateGetHandlerId(callSiteId, statement.getLocation())); + } + resultConsumer.add(breakExpr); + } else { + resultConsumer.add(new WasmUnreachable()); } - resultConsumer.add(breakExpr); } @Override @@ -1723,12 +1744,16 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { var call = new WasmCall(context.names.forMethod(THROW_CCE_METHOD)); block.getBody().add(call); - var target = throwJumpTarget(); - var breakExpr = new WasmBreak(target); - if (target != rethrowBlock) { - breakExpr.setResult(generateGetHandlerId(callSiteId, expr.getLocation())); + if (context.getExceptionTag() == null) { + var target = throwJumpTarget(); + var breakExpr = new WasmBreak(target); + if (target != rethrowBlock) { + breakExpr.setResult(generateGetHandlerId(callSiteId, expr.getLocation())); + } + block.getBody().add(breakExpr); + } else { + block.getBody().add(new WasmUnreachable()); } - block.getBody().add(breakExpr); valueToCast.release(); result = block; @@ -1743,7 +1768,9 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { var callSiteId = generateCallSiteId(statement.getLocation()); resultConsumer.add(generateRegisterCallSite(callSiteId, statement.getLocation())); resultConsumer.add(call); - checkHandlerId(resultConsumer, callSiteId, statement.getLocation()); + if (context.getExceptionTag() == null) { + checkHandlerId(resultConsumer, callSiteId, statement.getLocation()); + } } } @@ -1771,6 +1798,14 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { } tryCatchStatements.add(statement); + if (context.getExceptionTag() == null) { + emulatedTry(tryCatchStatements, statement.getProtectedBody()); + } else { + builtInTry(tryCatchStatements, statement.getProtectedBody()); + } + } + + private void emulatedTry(List tryCatchStatements, List protectedBody) { int firstId = handlers.size(); var innerCatchBlock = new WasmBlock(false); @@ -1794,7 +1829,7 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { var lastTryBlockBackup = lastTryBlock; lastTryBlock = bodyBlock; - visitMany(statement.getProtectedBody(), bodyBlock.getBody()); + visitMany(protectedBody, bodyBlock.getBody()); lastTryBlock = lastTryBlockBackup; handlers.subList(firstId, handlers.size()).clear(); @@ -1834,6 +1869,69 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { resultConsumer.add(outerCatchBlock); } + private void builtInTry(List tryCatchStatements, List protectedBody) { + var innerCatchBlock = new WasmBlock(false); + + var catchBlocks = new ArrayList(); + for (int i = 0; i < tryCatchStatements.size(); ++i) { + catchBlocks.add(new WasmBlock(false)); + } + var outerCatchBlock = catchBlocks.get(0); + + var tryBlock = new WasmTry(); + visitMany(protectedBody, tryBlock.getBody()); + if (!tryBlock.isTerminating()) { + tryBlock.getBody().add(new WasmBreak(outerCatchBlock)); + } + var catchClause = new WasmCatch(context.getExceptionTag()); + tryBlock.getCatches().add(catchClause); + innerCatchBlock.getBody().add(tryBlock); + + var obj = exprCache.create(new WasmCall(context.names.forMethod(PEEK_EXCEPTION_METHOD)), WasmType.INT32, + null, innerCatchBlock.getBody()); + var currentBlock = innerCatchBlock; + boolean catchesAll = false; + for (int i = tryCatchStatements.size() - 1; i >= 0; --i) { + var tryCatch = tryCatchStatements.get(i); + var catchBlock = catchBlocks.get(i); + if (tryCatch.getExceptionType() != null && !tryCatch.getExceptionType().equals(Throwable.class.getName())) { + var exceptionType = ValueType.object(tryCatch.getExceptionType()); + classGenerator.getClassPointer(exceptionType); + var isMatched = instanceOfImpl(obj.expr(), exceptionType); + innerCatchBlock.getBody().add(new WasmBranch(isMatched, currentBlock)); + } else { + innerCatchBlock.getBody().add(new WasmBreak(currentBlock)); + catchesAll = true; + } + currentBlock = catchBlock; + } + if (!catchesAll) { + innerCatchBlock.getBody().add(new WasmThrow(context.getExceptionTag())); + } + obj.release(); + + currentBlock = innerCatchBlock; + for (int i = tryCatchStatements.size() - 1; i >= 0; --i) { + var tryCatch = tryCatchStatements.get(i); + var catchBlock = catchBlocks.get(i); + catchBlock.getBody().add(currentBlock); + + var catchMethodName = context.names.forMethod(CATCH_METHOD); + var catchCall = new WasmCall(catchMethodName); + var catchWrapper = tryCatch.getExceptionVariable() != null + ? new WasmSetLocal(localVar(tryCatch.getExceptionVariable()), catchCall) + : new WasmDrop(catchCall); + catchBlock.getBody().add(catchWrapper); + visitMany(tryCatch.getHandler(), catchBlock.getBody()); + if (!catchBlock.isTerminating() && catchBlock != outerCatchBlock) { + catchBlock.getBody().add(new WasmBreak(outerCatchBlock)); + } + currentBlock = catchBlock; + } + + resultConsumer.add(outerCatchBlock); + } + private WasmBlock rethrowBlock() { if (rethrowBlock == null) { rethrowBlock = new WasmBlock(false); @@ -1924,11 +2022,15 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { var callSiteId = generateCallSiteId(expr.getLocation()); block.getBody().add(generateRegisterCallSite(callSiteId, expr.getLocation())); block.getBody().add(new WasmCall(context.names.forMethod(THROW_AIOOBE_METHOD))); - var br = new WasmBreak(throwJumpTarget()); - if (br.getTarget() != rethrowBlock) { - br.setResult(generateGetHandlerId(callSiteId, expr.getLocation())); + if (context.getExceptionTag() == null) { + var br = new WasmBreak(throwJumpTarget()); + if (br.getTarget() != rethrowBlock) { + br.setResult(generateGetHandlerId(callSiteId, expr.getLocation())); + } + block.getBody().add(br); + } else { + block.getBody().add(new WasmUnreachable()); } - block.getBody().add(br); result = block; } @@ -2154,6 +2256,11 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { public WasmExpression generateRegisterCallSite(int callSite, TextLocation location) { return WasmGenerationVisitor.this.generateRegisterCallSite(callSite, location); } + + @Override + public WasmTag getExceptionTag() { + return context.getExceptionTag(); + } }; private WasmExpression getReferenceToClass(WasmExpression instance) { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java index 55d849b54..b9622c3ee 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java @@ -24,6 +24,7 @@ import org.teavm.backend.wasm.binary.BinaryWriter; import org.teavm.backend.wasm.debug.info.VariableType; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; +import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmType; import org.teavm.interop.Export; import org.teavm.model.AnnotationReader; @@ -42,6 +43,7 @@ public class WasmGenerator { private BinaryWriter binaryWriter; private NameProvider names; private Predicate asyncMethods; + private WasmTag exceptionTag; public WasmGenerator(Decompiler decompiler, ClassHolderSource classSource, WasmGenerationContext context, WasmClassGenerator classGenerator, BinaryWriter binaryWriter, diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java index b4b81486d..d9f47a788 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/ExceptionHandlingIntrinsic.java @@ -30,6 +30,7 @@ import org.teavm.backend.wasm.model.expression.WasmIntBinary; import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; import org.teavm.backend.wasm.model.expression.WasmIntType; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; +import org.teavm.backend.wasm.model.expression.WasmThrow; import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.model.FieldReference; import org.teavm.model.MethodReference; @@ -59,6 +60,7 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic { case "jumpToFrame": case "abort": case "isObfuscated": + case "throwExceptionPlatformNative": return true; } return false; @@ -101,6 +103,9 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic { case "abort": return new WasmUnreachable(); + case "throwExceptionPlatformNative": + return new WasmThrow(manager.getExceptionTag()); + default: throw new IllegalArgumentException("Unknown method: " + invocation.getMethod()); } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/WasmIntrinsicManager.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/WasmIntrinsicManager.java index bb9d4b1fe..f5ef60e88 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/WasmIntrinsicManager.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/WasmIntrinsicManager.java @@ -20,6 +20,7 @@ import org.teavm.backend.lowlevel.generate.NameProvider; import org.teavm.backend.wasm.binary.BinaryWriter; import org.teavm.backend.wasm.generate.WasmStringPool; import org.teavm.backend.wasm.model.WasmLocal; +import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.diagnostics.Diagnostics; @@ -55,5 +56,5 @@ public interface WasmIntrinsicManager { WasmExpression generateRegisterCallSite(int callSite, TextLocation location); - + WasmTag getExceptionTag(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java index 1a80b814d..0807dc24c 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java @@ -31,6 +31,8 @@ public class WasmModule { private WasmFunction startFunction; private Map customSections = new LinkedHashMap<>(); private Map readonlyCustomSections = Collections.unmodifiableMap(customSections); + private List tags = new ArrayList<>(); + private List readonlyTags = Collections.unmodifiableList(tags); public void add(WasmFunction function) { if (functions.containsKey(function.getName())) { @@ -110,4 +112,17 @@ public class WasmModule { public void setStartFunction(WasmFunction startFunction) { this.startFunction = startFunction; } + + public void addTag(WasmTag tag) { + if (tag.module != null) { + throw new IllegalArgumentException("Given tag already belongs to some module"); + } + tags.add(tag); + tag.module = this; + tag.index = tags.size() - 1; + } + + public List getTags() { + return tags; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmTag.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmTag.java new file mode 100644 index 000000000..6781f719a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmTag.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.model; + +import java.util.ArrayList; +import java.util.List; + +public class WasmTag { + private List values = new ArrayList<>(); + WasmModule module; + int index; + + public List getValues() { + return values; + } + + public int getIndex() { + return index; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmCatch.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmCatch.java new file mode 100644 index 000000000..3553a1e57 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmCatch.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.model.expression; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.teavm.backend.wasm.model.WasmTag; + +public class WasmCatch { + private WasmTag tag; + private List body = new ArrayList<>(); + + public WasmCatch(WasmTag tag) { + Objects.requireNonNull(tag); + this.tag = tag; + } + + public WasmTag getTag() { + return tag; + } + + public void setTag(WasmTag tag) { + this.tag = tag; + } + + public List getBody() { + return body; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmDefaultExpressionVisitor.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmDefaultExpressionVisitor.java index 5638c0d7c..94c94e595 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmDefaultExpressionVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmDefaultExpressionVisitor.java @@ -199,4 +199,23 @@ public class WasmDefaultExpressionVisitor implements WasmExpressionVisitor { expression.getSourceIndex().acceptVisitor(this); expression.getCount().acceptVisitor(this); } + + @Override + public void visit(WasmTry expression) { + for (var part : expression.getBody()) { + part.acceptVisitor(this); + } + for (var catchClause : expression.getCatches()) { + for (var part : catchClause.getBody()) { + part.acceptVisitor(this); + } + } + } + + @Override + public void visit(WasmThrow expression) { + for (var arg : expression.getArguments()) { + arg.acceptVisitor(this); + } + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExpressionVisitor.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExpressionVisitor.java index 2a8308c7e..3441af816 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExpressionVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExpressionVisitor.java @@ -79,4 +79,8 @@ public interface WasmExpressionVisitor { void visit(WasmFill expression); void visit(WasmCopy expression); + + void visit(WasmTry expression); + + void visit(WasmThrow expression); } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmReplacingExpressionVisitor.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmReplacingExpressionVisitor.java index 0d7b375f1..65be02128 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmReplacingExpressionVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmReplacingExpressionVisitor.java @@ -251,4 +251,17 @@ public class WasmReplacingExpressionVisitor implements WasmExpressionVisitor { expression.getCount().acceptVisitor(this); expression.setCount(mapper.apply(expression.getCount())); } + + @Override + public void visit(WasmTry expression) { + replaceExpressions(expression.getBody()); + for (var catchClause : expression.getCatches()) { + replaceExpressions(catchClause.getBody()); + } + } + + @Override + public void visit(WasmThrow expression) { + replaceExpressions(expression.getArguments()); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmThrow.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmThrow.java new file mode 100644 index 000000000..3959aa4b2 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmThrow.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.model.expression; + +import java.util.ArrayList; +import java.util.List; +import org.teavm.backend.wasm.model.WasmTag; + +public class WasmThrow extends WasmExpression { + private WasmTag tag; + private List arguments = new ArrayList<>(); + + public WasmThrow(WasmTag tag) { + this.tag = tag; + } + + public WasmTag getTag() { + return tag; + } + + public void setTag(WasmTag tag) { + this.tag = tag; + } + + public List getArguments() { + return arguments; + } + + @Override + public void acceptVisitor(WasmExpressionVisitor visitor) { + visitor.visit(this); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmTry.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmTry.java new file mode 100644 index 000000000..a1a8b703e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmTry.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.model.expression; + +import java.util.ArrayList; +import java.util.List; +import org.teavm.backend.wasm.model.WasmType; + +public class WasmTry extends WasmExpression { + private List body = new ArrayList<>(); + private List catches = new ArrayList<>(); + private WasmType type; + + public List getBody() { + return body; + } + + public List getCatches() { + return catches; + } + + public WasmType getType() { + return type; + } + + public void setType(WasmType type) { + this.type = type; + } + + @Override + public void acceptVisitor(WasmExpressionVisitor visitor) { + visitor.visit(this); + } + + @Override + public boolean isTerminating() { + return !body.isEmpty() && body.get(body.size() - 1).isTerminating(); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java index 64496a2c1..72010c93a 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java @@ -46,6 +46,7 @@ public class WasmBinaryRenderer { private static final int SECTION_ELEMENT = 9; private static final int SECTION_CODE = 10; private static final int SECTION_DATA = 11; + private static final int SECTION_TAGS = 13; private static final int EXTERNAL_KIND_FUNCTION = 0; private static final int EXTERNAL_KIND_MEMORY = 2; @@ -92,6 +93,7 @@ public class WasmBinaryRenderer { renderFunctions(module); renderTable(module); renderMemory(module); + renderTags(module); renderExport(module); renderStart(module); renderElement(module); @@ -113,6 +115,9 @@ public class WasmBinaryRenderer { part.acceptVisitor(signatureCollector); } } + for (var tag : module.getTags()) { + registerSignature(WasmSignature.fromTag(tag)); + } section.writeLEB(signatures.size()); for (WasmSignature signature : signatures) { @@ -394,6 +399,21 @@ public class WasmBinaryRenderer { writeSection(SECTION_DATA, "data", section.getData()); } + private void renderTags(WasmModule module) { + if (module.getTags().isEmpty()) { + return; + } + + var section = new WasmBinaryWriter(); + section.writeLEB(module.getTags().size()); + for (var tag : module.getTags()) { + section.writeByte(0); + section.writeLEB(signatureIndexes.get(WasmSignature.fromTag(tag))); + } + + writeSection(SECTION_TAGS, "tags", section.getData()); + } + private void renderNames(WasmModule module) { WasmBinaryWriter section = new WasmBinaryWriter(); diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java index bb61ad342..488909af8 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java @@ -57,6 +57,8 @@ import org.teavm.backend.wasm.model.expression.WasmStoreFloat64; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; import org.teavm.backend.wasm.model.expression.WasmStoreInt64; import org.teavm.backend.wasm.model.expression.WasmSwitch; +import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.backend.wasm.model.expression.WasmTry; import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.model.MethodReference; import org.teavm.model.TextLocation; @@ -931,6 +933,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor { writer.writeByte(0xFC); writer.writeLEB(11); writer.writeByte(0); + popLocation(); } @Override @@ -943,6 +946,38 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor { writer.writeLEB(10); writer.writeByte(0); writer.writeByte(0); + popLocation(); + } + + @Override + public void visit(WasmTry expression) { + pushLocation(expression); + writer.writeByte(0x06); + writeBlockType(expression.getType()); + ++depth; + for (var part : expression.getBody()) { + part.acceptVisitor(this); + } + --depth; + for (var catchClause : expression.getCatches()) { + writer.writeByte(0x07); + writer.writeLEB(catchClause.getTag().getIndex()); + for (var part : catchClause.getBody()) { + part.acceptVisitor(this); + } + } + writer.writeByte(0xB); + popLocation(); + } + + @Override + public void visit(WasmThrow expression) { + for (var arg : expression.getArguments()) { + arg.acceptVisitor(this); + } + pushLocation(expression); + writer.writeByte(0x8); + writer.writeLEB(expression.getTag().getIndex()); } private int alignment(int value) { diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java index 19381165a..afac141c6 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java @@ -61,6 +61,8 @@ import org.teavm.backend.wasm.model.expression.WasmStoreFloat64; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; import org.teavm.backend.wasm.model.expression.WasmStoreInt64; import org.teavm.backend.wasm.model.expression.WasmSwitch; +import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.backend.wasm.model.expression.WasmTry; import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.model.TextLocation; @@ -1110,6 +1112,16 @@ class WasmCRenderingVisitor implements WasmExpressionVisitor { value = result; } + @Override + public void visit(WasmTry expression) { + value = new CExpression("/* TRY */"); + } + + @Override + public void visit(WasmThrow expression) { + value = new CExpression("/* THROW */"); + } + private CExpression checkAddress(CExpression index) { if (!memoryAccessChecked) { return index; diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderingVisitor.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderingVisitor.java index b9bd1eb17..f6df54660 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderingVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderingVisitor.java @@ -61,6 +61,8 @@ import org.teavm.backend.wasm.model.expression.WasmStoreFloat64; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; import org.teavm.backend.wasm.model.expression.WasmStoreInt64; import org.teavm.backend.wasm.model.expression.WasmSwitch; +import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.backend.wasm.model.expression.WasmTry; import org.teavm.backend.wasm.model.expression.WasmUnreachable; class WasmRenderingVisitor implements WasmExpressionVisitor { @@ -620,6 +622,37 @@ class WasmRenderingVisitor implements WasmExpressionVisitor { close(); } + @Override + public void visit(WasmTry expression) { + open().append("try"); + if (expression.getType() != null) { + append(" " + type(expression.getType())); + } + for (var part : expression.getBody()) { + line(part); + } + for (var catchClause : expression.getCatches()) { + lf().append("(catch ").append(String.valueOf(catchClause.getTag().getIndex())) + .append(" ").indent(); + for (var part : catchClause.getBody()) { + line(part); + } + lf().outdent().append(")"); + } + + close(); + } + + @Override + public void visit(WasmThrow expression) { + open().append("throw"); + append(" ").append(String.valueOf(expression.getTag().getIndex())); + for (var arg : expression.getArguments()) { + line(arg); + } + close(); + } + private String type(WasmType type) { switch (type) { case INT32: diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmSignature.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmSignature.java index 66cc96863..9c99b2a93 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmSignature.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmSignature.java @@ -17,6 +17,7 @@ package org.teavm.backend.wasm.render; import java.util.Arrays; import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmType; final class WasmSignature { @@ -43,7 +44,7 @@ final class WasmSignature { return Arrays.hashCode(types); } - public static WasmSignature fromFunction(WasmFunction function) { + static WasmSignature fromFunction(WasmFunction function) { WasmType[] types = new WasmType[function.getParameters().size() + 1]; types[0] = function.getResult(); for (int i = 0; i < function.getParameters().size(); ++i) { @@ -51,4 +52,12 @@ final class WasmSignature { } return new WasmSignature(types); } + + static WasmSignature fromTag(WasmTag tag) { + var types = new WasmType[tag.getValues().size() + 1]; + for (var i = 0; i < tag.getValues().size(); i++) { + types[i + 1] = tag.getValues().get(i); + } + return new WasmSignature(types); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmTypeInference.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmTypeInference.java index fc129d028..99227626b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmTypeInference.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmTypeInference.java @@ -52,6 +52,8 @@ import org.teavm.backend.wasm.model.expression.WasmStoreFloat64; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; import org.teavm.backend.wasm.model.expression.WasmStoreInt64; import org.teavm.backend.wasm.model.expression.WasmSwitch; +import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.backend.wasm.model.expression.WasmTry; import org.teavm.backend.wasm.model.expression.WasmUnreachable; public class WasmTypeInference implements WasmExpressionVisitor { @@ -227,6 +229,16 @@ public class WasmTypeInference implements WasmExpressionVisitor { result = null; } + @Override + public void visit(WasmTry expression) { + result = expression.getType(); + } + + @Override + public void visit(WasmThrow expression) { + result = null; + } + private static WasmType map(WasmIntType type) { switch (type) { case INT32: diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/WasmExceptionHandlingTransform.java b/core/src/main/java/org/teavm/backend/wasm/transformation/WasmExceptionHandlingTransform.java new file mode 100644 index 000000000..effc3b4b8 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/WasmExceptionHandlingTransform.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.transformation; + +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; +import org.teavm.model.emit.ProgramEmitter; +import org.teavm.runtime.ExceptionHandling; + +public class WasmExceptionHandlingTransform implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + if (cls.getName().equals(ExceptionHandling.class.getName())) { + processClass(cls, context); + } + } + + private void processClass(ClassHolder cls, ClassHolderTransformerContext context) { + var method = cls.getMethod(new MethodDescriptor("throwException", Throwable.class, void.class)); + var pm = ProgramEmitter.create(method, context.getHierarchy()); + pm.invoke(new MethodReference(ExceptionHandling.class, "throwExceptionPlatform", Throwable.class, void.class), + pm.var(1, Throwable.class)); + pm.exit(); + } +} diff --git a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java index 20453ba1e..28dc6c0ab 100644 --- a/core/src/main/java/org/teavm/runtime/ExceptionHandling.java +++ b/core/src/main/java/org/teavm/runtime/ExceptionHandling.java @@ -90,6 +90,20 @@ public final class ExceptionHandling { return exception; } + @Unmanaged + public static Throwable peekException() { + return thrownException; + } + + @Unmanaged + public static void throwExceptionPlatform(Throwable exception) { + thrownException = exception; + throwExceptionPlatformNative(); + } + + @Unmanaged + public static native void throwExceptionPlatformNative(); + @Unmanaged public static void throwException(Throwable exception) { thrownException = exception; diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java index 4303296f2..f5ec1b1e6 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -127,6 +127,10 @@ public final class TeaVMRunner { .hasArg() .desc("WebAssembly binary version (currently, only 1 is supported)") .build()); + options.addOption(Option.builder() + .longOpt("wasm-use-exceptions") + .desc("Specifies that WebAssembly exception handling instructions can be used") + .build()); options.addOption(Option.builder("e") .longOpt("entry-point") .argName("name") @@ -352,6 +356,9 @@ public final class TeaVMRunner { printUsage(); } } + if (commandLine.hasOption("wasm-use-exceptions")) { + tool.setWasmExceptionsUsed(true); + } } private void parseCOptions() { diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 033a3477d..ced297b60 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -107,6 +107,7 @@ public class TeaVMTool { private JavaScriptTarget javaScriptTarget; private WasmTarget webAssemblyTarget; private WasmBinaryVersion wasmVersion = WasmBinaryVersion.V_0x1; + private boolean wasmExceptionsUsed; private CTarget cTarget; private Set generatedFiles = new HashSet<>(); private int minHeapSize = 4 * (1 << 20); @@ -275,6 +276,10 @@ public class TeaVMTool { this.wasmVersion = wasmVersion; } + public void setWasmExceptionsUsed(boolean wasmExceptionsUsed) { + this.wasmExceptionsUsed = wasmExceptionsUsed; + } + public void setHeapDump(boolean heapDump) { this.heapDump = heapDump; } @@ -360,6 +365,7 @@ public class TeaVMTool { webAssemblyTarget.setMinHeapSize(minHeapSize); webAssemblyTarget.setMaxHeapSize(maxHeapSize); webAssemblyTarget.setObfuscated(obfuscated); + webAssemblyTarget.setExceptionsUsed(wasmExceptionsUsed); return webAssemblyTarget; } diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java index 59bd0a4c0..bdd204f61 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java @@ -81,6 +81,8 @@ public interface BuildStrategy { void setWasmVersion(WasmBinaryVersion wasmVersion); + void setWasmExceptionsUsed(boolean wasmExceptionsUsed); + void setMinHeapSize(int minHeapSize); void setMaxHeapSize(int maxHeapSize); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java index 9d76a2961..dad70e395 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java @@ -61,6 +61,7 @@ public class InProcessBuildStrategy implements BuildStrategy { private String[] transformers = new String[0]; private String[] classesToPreserve = new String[0]; private WasmBinaryVersion wasmVersion = WasmBinaryVersion.V_0x1; + private boolean wasmExceptionsUsed; private int minHeapSize = 4 * 1024 * 1024; private int maxHeapSize = 128 * 1024 * 1024; private final List sourceFileProviders = new ArrayList<>(); @@ -213,6 +214,11 @@ public class InProcessBuildStrategy implements BuildStrategy { this.wasmVersion = wasmVersion; } + @Override + public void setWasmExceptionsUsed(boolean wasmExceptionsUsed) { + this.wasmExceptionsUsed = wasmExceptionsUsed; + } + @Override public void setMinHeapSize(int minHeapSize) { this.minHeapSize = minHeapSize; @@ -266,6 +272,7 @@ public class InProcessBuildStrategy implements BuildStrategy { tool.getClassesToPreserve().addAll(Arrays.asList(classesToPreserve)); tool.setCacheDirectory(cacheDirectory != null ? new File(cacheDirectory) : null); tool.setWasmVersion(wasmVersion); + tool.setWasmExceptionsUsed(wasmExceptionsUsed); tool.setMinHeapSize(minHeapSize); tool.setMaxHeapSize(maxHeapSize); tool.setHeapDump(heapDump); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java index 07c662022..b9c32c305 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java @@ -187,6 +187,11 @@ public class RemoteBuildStrategy implements BuildStrategy { request.wasmVersion = wasmVersion; } + @Override + public void setWasmExceptionsUsed(boolean wasmExceptionsUsed) { + request.wasmExceptionsUsed = wasmExceptionsUsed; + } + @Override public void setMinHeapSize(int minHeapSize) { request.minHeapSize = minHeapSize; diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java index 4ce05395f..b6a46403a 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java @@ -161,6 +161,7 @@ public class BuildDaemon extends UnicastRemoteObject implements RemoteBuildServi tool.setJsModuleType(request.jsModuleType); tool.setStrict(request.strict); tool.setWasmVersion(request.wasmVersion); + tool.setWasmExceptionsUsed(request.wasmExceptionsUsed); tool.setMinHeapSize(request.minHeapSize); tool.setMaxHeapSize(request.maxHeapSize); tool.setHeapDump(request.heapDump); diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java index 36c870fa6..7e2316443 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java @@ -49,6 +49,7 @@ public class RemoteBuildRequest implements Serializable { public TeaVMOptimizationLevel optimizationLevel; public boolean fastDependencyAnalysis; public WasmBinaryVersion wasmVersion; + public boolean wasmExceptionsUsed; public int minHeapSize; public int maxHeapSize; public boolean heapDump; diff --git a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java index 9bc4d5b6d..4dd9ea54c 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java @@ -92,6 +92,7 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio .orElse(OptimizationLevel.AGGRESSIVE)); wasm.getTargetFileName().convention(project.provider(() -> project.getName() + ".wasm")); wasm.getAddedToWebApp().convention(property("wasm.addedToWebApp").map(Boolean::parseBoolean).orElse(false)); + wasm.getExceptionsUsed().convention(property("wasm.exceptionsUsed").map(Boolean::parseBoolean).orElse(true)); } private void setupWasiDefaults() { @@ -101,6 +102,7 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio wasi.getOptimization().convention(property("wasi.optimization").map(OptimizationLevel::valueOf) .orElse(OptimizationLevel.AGGRESSIVE)); wasi.getTargetFileName().convention(project.provider(() -> project.getName() + ".wasm")); + wasi.getExceptionsUsed().convention(property("wasi.exceptionsUsed").map(Boolean::parseBoolean).orElse(false)); } private void setupCDefaults() { diff --git a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java index fcb8afa06..e2539783a 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java @@ -191,6 +191,7 @@ public class TeaVMPlugin implements Plugin { project.getTasks().create(WASM_TASK_NAME, GenerateWasmTask.class, task -> { var wasm = extension.getWasm(); applyToTask(wasm, task, configuration); + task.getExceptionsUsed().convention(wasm.getExceptionsUsed()); task.getTargetFileName().convention(wasm.getTargetFileName()); task.getMinHeapSize().convention(wasm.getMinHeapSize()); task.getMaxHeapSize().convention(wasm.getMaxHeapSize()); @@ -202,6 +203,7 @@ public class TeaVMPlugin implements Plugin { project.getTasks().create(WASI_TASK_NAME, GenerateWasiTask.class, task -> { var wasi = extension.getWasi(); applyToTask(wasi, task, configuration); + task.getExceptionsUsed().convention(wasi.getExceptionsUsed()); task.getTargetFileName().convention(wasi.getTargetFileName()); task.getMinHeapSize().convention(wasi.getMinHeapSize()); task.getMaxHeapSize().convention(wasi.getMaxHeapSize()); diff --git a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmBaseConfiguration.java b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmBaseConfiguration.java index f7bfdb559..d03f40f0d 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmBaseConfiguration.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmBaseConfiguration.java @@ -18,5 +18,7 @@ package org.teavm.gradle.api; import org.gradle.api.provider.Property; public interface TeaVMWasmBaseConfiguration extends TeaVMCommonConfiguration, TeaVMNativeBaseConfiguration { + Property getExceptionsUsed(); + Property getTargetFileName(); } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasiTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasiTask.java index c877d844a..e175e4ed3 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasiTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasiTask.java @@ -28,6 +28,9 @@ public abstract class GenerateWasiTask extends TeaVMTask { getMaxHeapSize().convention(16); } + @Input + public abstract Property getExceptionsUsed(); + @Input public abstract Property getMinHeapSize(); @@ -36,6 +39,7 @@ public abstract class GenerateWasiTask extends TeaVMTask { @Override protected void setupBuilder(BuildStrategy builder) { + builder.setWasmExceptionsUsed(getExceptionsUsed().get()); builder.setTargetType(TeaVMTargetType.WEBASSEMBLY_WASI); builder.setMinHeapSize(getMinHeapSize().get() * MB); builder.setMaxHeapSize(getMaxHeapSize().get() * MB); diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmTask.java index e783bd314..a40ec650c 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmTask.java @@ -24,10 +24,14 @@ public abstract class GenerateWasmTask extends TeaVMTask { private static final int MB = 1024 * 1024; public GenerateWasmTask() { + getExceptionsUsed().convention(false); getMinHeapSize().convention(1); getMaxHeapSize().convention(16); } + @Input + public abstract Property getExceptionsUsed(); + @Input public abstract Property getMinHeapSize(); @@ -37,6 +41,7 @@ public abstract class GenerateWasmTask extends TeaVMTask { @Override protected void setupBuilder(BuildStrategy builder) { builder.setTargetType(TeaVMTargetType.WEBASSEMBLY); + builder.setWasmExceptionsUsed(getExceptionsUsed().get()); builder.setMinHeapSize(getMinHeapSize().get() * MB); builder.setMaxHeapSize(getMaxHeapSize().get() * MB); } diff --git a/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java index 25881ccbb..f2845feef 100644 --- a/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java @@ -41,12 +41,17 @@ abstract class BaseWebAssemblyPlatformSupport extends TestPlatformSupport additionalProcessing, String baseName, TeaVMTestConfiguration configuration, File path, AnnotatedElement element) { Supplier targetSupplier = () -> { WasmTarget target = new WasmTarget(); target.setRuntimeType(getRuntimeType()); + target.setExceptionsUsed(exceptionsUsed()); var sourceDirs = System.getProperty(SOURCE_DIRS); if (sourceDirs != null) { var dirs = new ArrayList(); diff --git a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyPlatformSupport.java index f64571953..c150a687e 100644 --- a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyPlatformSupport.java @@ -41,6 +41,11 @@ class WebAssemblyPlatformSupport extends BaseWebAssemblyPlatformSupport { : null; } + @Override + protected boolean exceptionsUsed() { + return true; + } + @Override TestPlatform getPlatform() { return TestPlatform.WEBASSEMBLY; diff --git a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java index 21adf8fcd..66fb23754 100644 --- a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java +++ b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java @@ -144,6 +144,9 @@ public class TeaVMCompileMojo extends AbstractMojo { @Parameter(property = "teavm.wasmVersion", defaultValue = "V_0x1") private WasmBinaryVersion wasmVersion = WasmBinaryVersion.V_0x1; + @Parameter(property = "teavm.wasmExceptionsUsed", defaultValue = "false") + private boolean wasmExceptionsUsed; + @Parameter(property = "teavm.minHeapSize", defaultValue = "4") private int minHeapSize; @@ -303,6 +306,7 @@ public class TeaVMCompileMojo extends AbstractMojo { builder.setCacheDirectory(cacheDirectory.getAbsolutePath()); builder.setTargetType(targetType); builder.setWasmVersion(wasmVersion); + builder.setWasmExceptionsUsed(wasmExceptionsUsed); builder.setHeapDump(heapDump); BuildResult result; result = builder.build();