From 90acaae4193596f57538a526958bb0ffe98f6ded Mon Sep 17 00:00:00 2001 From: Valentin Windischbauer Date: Wed, 17 Sep 2025 16:50:59 +0200 Subject: [PATCH 01/12] Implement WebAssembly exception handling proposal. Co-authored-by: Florian Huemer --- wasm/docs/contributor/TestsAndBenchmarks.md | 2 +- .../wasm/test/AbstractBinarySuite.java | 34 +- .../org/graalvm/wasm/test/WasmFileSuite.java | 8 +- .../org/graalvm/wasm/test/WasmJsApiSuite.java | 4 +- .../org/graalvm/wasm/test/WasmTestSuite.java | 6 +- .../test/suites/bytecode/BytecodeSuite.java | 45 +- .../bytecode/MultiInstantiationSuite.java | 27 +- .../test/suites/control/ExceptionSuite.java | 61 + .../ReferenceTypesValidationSuite.java | 22 +- .../exceptions/exceptions_global_unwind.opts | 1 + .../exceptions_global_unwind.result | 1 + .../exceptions/exceptions_global_unwind.wat | 70 + .../exceptions/exceptions_indirect_call.opts | 1 + .../exceptions_indirect_call.result | 1 + .../exceptions/exceptions_indirect_call.wat | 71 + .../exceptions/exceptions_local_unwind.opts | 1 + .../exceptions/exceptions_local_unwind.result | 1 + .../exceptions/exceptions_local_unwind.wat | 75 + .../exceptions/exceptions_single_func.opts | 1 + .../exceptions/exceptions_single_func.result | 1 + .../exceptions/exceptions_single_func.wat | 63 + .../exceptions/exceptions_with_params.opts | 1 + .../exceptions/exceptions_with_params.result | 1 + .../exceptions/exceptions_with_params.wat | 63 + .../exceptions/multi_exception_types.opts | 1 + .../exceptions/multi_exception_types.result | 1 + .../test/exceptions/multi_exception_types.wat | 165 + .../src/test/exceptions/throw_ref.opts | 1 + .../src/test/exceptions/throw_ref.result | 1 + .../src/test/exceptions/throw_ref.wat | 74 + .../src/test/exceptions/wasm_test_index | 7 + .../test/linker/import_export_exceptions.opts | 1 + .../linker/import_export_exceptions.result | 1 + .../import_export_exceptions/friend.wat | 54 + .../linker/import_export_exceptions/main.wat | 60 + .../man-in-the-middle.wat | 50 + .../src/test/linker/wasm_test_index | 1 + .../graalvm/wasm/utils/WasmBinaryTools.java | 4 +- .../src/org/graalvm/wasm/Assert.java | 6 + .../src/org/graalvm/wasm/BinaryParser.java | 251 +- .../org/graalvm/wasm/BinaryStreamParser.java | 17 +- .../src/org/graalvm/wasm/GlobalRegistry.java | 2 +- .../src/org/graalvm/wasm/Linker.java | 107 +- .../src/org/graalvm/wasm/MemoryRegistry.java | 4 - .../src/org/graalvm/wasm/ModuleLimits.java | 15 +- .../src/org/graalvm/wasm/RuntimeState.java | 22 + .../src/org/graalvm/wasm/SymbolTable.java | 129 +- .../src/org/graalvm/wasm/TableRegistry.java | 6 +- .../src/org/graalvm/wasm/TagRegistry.java | 80 + .../src/org/graalvm/wasm/WasmCodeEntry.java | 15 +- .../org/graalvm/wasm/WasmContextOptions.java | 10 + .../org/graalvm/wasm/WasmInstantiator.java | 30 +- .../src/org/graalvm/wasm/WasmModule.java | 12 +- .../src/org/graalvm/wasm/WasmOptions.java | 3 + .../src/org/graalvm/wasm/WasmStore.java | 6 + .../src/org/graalvm/wasm/WasmTable.java | 6 +- .../src/org/graalvm/wasm/WasmTag.java | 59 + .../src/org/graalvm/wasm/WasmType.java | 8 +- .../wasm/api/ExecuteHostFunctionNode.java | 6 +- .../src/org/graalvm/wasm/api/FuncType.java | 161 + .../graalvm/wasm/api/ImportExportKind.java | 5 +- .../wasm/api/InteropCallAdapterNode.java | 8 +- .../src/org/graalvm/wasm/api/JsConstants.java | 4 +- .../src/org/graalvm/wasm/api/TableKind.java | 19 +- .../src/org/graalvm/wasm/api/ValueType.java | 35 +- .../src/org/graalvm/wasm/api/WebAssembly.java | 130 +- .../org/graalvm/wasm/constants/Bytecode.java | 4 + .../wasm/constants/BytecodeBitEncoding.java | 1 + .../wasm/constants/ExceptionHandlerType.java | 49 + .../wasm/constants/ExportIdentifier.java | 3 +- .../wasm/constants/ImportIdentifier.java | 3 +- .../graalvm/wasm/constants/Instructions.java | 6 + .../graalvm/wasm/constants/NameSection.java | 49 + .../org/graalvm/wasm/constants/Section.java | 20 +- .../org/graalvm/wasm/exception/Failure.java | 9 +- .../wasm/exception/WasmRuntimeException.java | 113 + .../org/graalvm/wasm/globals/WasmGlobal.java | 9 +- .../graalvm/wasm/nodes/WasmFunctionNode.java | 2896 +++++++++-------- .../wasm/nodes/WasmFunctionRootNode.java | 4 + .../wasm/parser/bytecode/BytecodeParser.java | 21 +- .../parser/bytecode/RuntimeBytecodeGen.java | 54 +- .../org/graalvm/wasm/parser/ir/CodeEntry.java | 11 +- .../wasm/parser/validation/BlockFrame.java | 16 +- .../wasm/parser/validation/ControlFrame.java | 14 +- .../parser/validation/ExceptionHandler.java | 72 + .../parser/validation/ExceptionTable.java | 76 + .../wasm/parser/validation/IfFrame.java | 15 +- .../wasm/parser/validation/LoopFrame.java | 7 +- .../wasm/parser/validation/ParserState.java | 99 +- .../wasm/parser/validation/TryTableFrame.java | 58 + .../parser/validation/ValidationErrors.java | 40 +- 91 files changed, 4185 insertions(+), 1602 deletions(-) create mode 100644 wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/control/ExceptionSuite.java create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/wasm_test_index create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/friend.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/main.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/man-in-the-middle.wat create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TagRegistry.java create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmTag.java create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/FuncType.java create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ExceptionHandlerType.java create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/NameSection.java create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/WasmRuntimeException.java create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ExceptionHandler.java create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ExceptionTable.java create mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/TryTableFrame.java diff --git a/wasm/docs/contributor/TestsAndBenchmarks.md b/wasm/docs/contributor/TestsAndBenchmarks.md index 9c8ac8cc7d43..88c00152b689 100644 --- a/wasm/docs/contributor/TestsAndBenchmarks.md +++ b/wasm/docs/contributor/TestsAndBenchmarks.md @@ -5,7 +5,7 @@ Building GraalWasm using the `mx build` command will also create the `wasm-tests.jar`, which contains the main test cases. To run these tests, the WebAssembly binary toolkit is needed. -1. Download the binary of the [WebAssembly binary toolkit(wabt)](https://github.com/WebAssembly/wabt) and extract it. +1. Download the binary of the [WebAssembly binary toolkit(wabt)](https://github.com/WebAssembly/wabt) (**1.0.37** or higher) and extract it. 2. Set `WABT_DIR`: ```bash diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java index b52ae37f9fab..bc065856b093 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java @@ -339,6 +339,29 @@ private byte[] generateGlobalSection() { } } + private static final class BinaryTags { + private final ByteArrayList attributes = new ByteArrayList(); + private final ByteArrayList typeIndices = new ByteArrayList(); + + private void add(byte attribute, byte typeIndex) { + attributes.add(attribute); + typeIndices.add(typeIndex); + } + + private byte[] generateTagSection() { + ByteArrayList b = new ByteArrayList(); + b.add(getByte("0d")); + b.add((byte) 0); // length is patched at the end + b.add((byte) attributes.size()); + for (int i = 0; i < attributes.size(); i++) { + b.add(attributes.get(i)); + b.add(typeIndices.get(i)); + } + b.set(1, (byte) (b.size() - 2)); + return b.toArray(); + } + } + private static final class BinaryCustomSections { private final List names = new ArrayList<>(); private final List sections = new ArrayList<>(); @@ -373,6 +396,7 @@ protected static class BinaryBuilder { private final BinaryElements binaryElements = new BinaryElements(); private final BinaryDatas binaryDatas = new BinaryDatas(); private final BinaryGlobals binaryGlobals = new BinaryGlobals(); + private final BinaryTags binaryTags = new BinaryTags(); private final BinaryCustomSections binaryCustomSections = new BinaryCustomSections(); @@ -416,6 +440,11 @@ public BinaryBuilder addGlobal(byte mutability, byte valueType, String hexCode) return this; } + public BinaryBuilder addTag(byte attribute, byte typeIndex) { + binaryTags.add(attribute, typeIndex); + return this; + } + public BinaryBuilder addCustomSection(String name, byte[] section) { binaryCustomSections.add(name, section); return this; @@ -443,8 +472,9 @@ public byte[] build() { final byte[] codeSection = binaryFunctions.generateCodeSection(); final byte[] dataSection = binaryDatas.generateDataSection(); final byte[] customSections = binaryCustomSections.generateCustomSections(); + final byte[] tagSection = binaryTags.generateTagSection(); final int totalLength = preamble.length + typeSection.length + functionSection.length + tableSection.length + memorySection.length + globalSection.length + exportSection.length + - elementSection.length + dataCountSection.length + codeSection.length + dataSection.length + customSections.length; + elementSection.length + dataCountSection.length + codeSection.length + dataSection.length + customSections.length + tagSection.length; final byte[] binary = new byte[totalLength]; int length = 0; System.arraycopy(preamble, 0, binary, length, preamble.length); @@ -457,6 +487,8 @@ public byte[] build() { length += tableSection.length; System.arraycopy(memorySection, 0, binary, length, memorySection.length); length += memorySection.length; + System.arraycopy(tagSection, 0, binary, length, tagSection.length); + length += tagSection.length; System.arraycopy(globalSection, 0, binary, length, globalSection.length); length += globalSection.length; System.arraycopy(exportSection, 0, binary, length, exportSection.length); diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java index 0ba2efbac6e8..0848b3942a85 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java @@ -399,13 +399,17 @@ private WasmTestStatus runTestCase(WasmCase testCase, Engine sharedEngine) { EnumSet options = EnumSet.noneOf(WasmBinaryTools.WabtOption.class); String threadsOption = testCase.options().getProperty("wasm.Threads"); - if (threadsOption != null && threadsOption.equals("true")) { + if ("true".equals(threadsOption)) { options.add(WasmBinaryTools.WabtOption.THREADS); } String multiMemoryOption = testCase.options().getProperty("wasm.MultiMemory"); - if (multiMemoryOption != null && multiMemoryOption.equals("true")) { + if ("true".equals(multiMemoryOption)) { options.add(WasmBinaryTools.WabtOption.MULTI_MEMORY); } + String exceptionsOption = testCase.options().getProperty("wasm.Exceptions"); + if ("true".equals(exceptionsOption)) { + options.add(WasmBinaryTools.WabtOption.EXCEPTIONS); + } ArrayList sources = testCase.getSources(options); runInContexts(testCase, contextBuilder, sources, sharedEngine, testOut); diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java index f14c9c62a712..3d8e933eccd7 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java @@ -854,11 +854,11 @@ public void testExportCountsLimit() throws IOException { context.readModule(binaryWithMixedExports, limits); final int noLimit = Integer.MAX_VALUE; - limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 6, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit); + limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 6, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit); context.readModule(binaryWithMixedExports, limits); try { - limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 5, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit); + limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 5, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit); context.readModule(binaryWithMixedExports, limits); Assert.fail("Should have failed - export count exceeds the limit"); } catch (WasmException ex) { diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestSuite.java index 8ed15d65393c..25df395381a0 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -50,6 +50,7 @@ import org.graalvm.wasm.test.suites.bytecode.MultiInstantiationSuite; import org.graalvm.wasm.test.suites.control.BlockWithLocalsSuite; import org.graalvm.wasm.test.suites.control.BranchBlockSuite; +import org.graalvm.wasm.test.suites.control.ExceptionSuite; import org.graalvm.wasm.test.suites.control.IfThenElseSuite; import org.graalvm.wasm.test.suites.control.LoopBlockSuite; import org.graalvm.wasm.test.suites.control.MultiValueSuite; @@ -62,9 +63,9 @@ import org.graalvm.wasm.test.suites.memory.MemorySuite; import org.graalvm.wasm.test.suites.memory.MultiMemorySuite; import org.graalvm.wasm.test.suites.memory.ThreadsSuite; -import org.graalvm.wasm.test.suites.validation.ReferenceTypesValidationSuite; import org.graalvm.wasm.test.suites.table.TableSuite; import org.graalvm.wasm.test.suites.validation.MultiValueValidationSuite; +import org.graalvm.wasm.test.suites.validation.ReferenceTypesValidationSuite; import org.graalvm.wasm.test.suites.validation.ValidationSuite; import org.graalvm.wasm.test.suites.wasi.WasiSuite; import org.graalvm.wasm.test.suites.webassembly.EmscriptenSuite; @@ -105,6 +106,7 @@ MultiInstantiationSuite.class, MultiMemorySuite.class, ThreadsSuite.class, + ExceptionSuite.class, DebugValidationSuite.class, DebugObjectFactorySuite.class }) diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/BytecodeSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/BytecodeSuite.java index 1046bc4e75f9..e19969a153fa 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/BytecodeSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/BytecodeSuite.java @@ -45,6 +45,7 @@ import org.graalvm.wasm.WasmType; import org.graalvm.wasm.constants.Bytecode; +import org.graalvm.wasm.constants.ExceptionHandlerType; import org.graalvm.wasm.constants.SegmentMode; import org.graalvm.wasm.parser.bytecode.RuntimeBytecodeGen; import org.junit.Assert; @@ -168,7 +169,7 @@ public void testBrU8Max() { expected[255] = (byte) 0xFF; test(b -> { for (int i = 0; i < 254; i++) { - b.add(0); + b.addOp(0); } b.addBranch(0); }, expected); @@ -194,7 +195,7 @@ public void testBrI32MinBackward() { expected[259] = (byte) 0xFF; test(b -> { for (int i = 0; i < 255; i++) { - b.add(0); + b.addOp(0); } b.addBranch(0); }, expected); @@ -212,7 +213,7 @@ public void testBrIfU8Max() { expected[255] = (byte) 0xFF; test(b -> { for (int i = 0; i < 254; i++) { - b.add(0); + b.addOp(0); } b.addBranchIf(0); }, expected); @@ -238,7 +239,7 @@ public void testBrIfI32MinBackward() { expected[259] = (byte) 0xFF; test(b -> { for (int i = 0; i < 255; i++) { - b.add(0); + b.addOp(0); } b.addBranchIf(0); }, expected); @@ -448,52 +449,52 @@ public void testMemoryInstructionInvalidOpcodeI32() { @Test public void testAddMin() { - test(b -> b.add(0x00), new byte[]{0x00}); + test(b -> b.addOp(0x00), new byte[]{0x00}); } @Test public void testAddMax() { - test(b -> b.add(0xFF), new byte[]{(byte) 0xFF}); + test(b -> b.addOp(0xFF), new byte[]{(byte) 0xFF}); } @Test public void testInvalidAdd() { - testAssertion(b -> b.add(256), "opcode does not fit into byte"); + testAssertion(b -> b.addOp(256), "opcode does not fit into byte"); } @Test public void testAddImmediateMin() { - test(b -> b.add(0x01, 0), new byte[]{0x01, 0x00, 0x00, 0x00, 0x00}); + test(b -> b.addOp(0x01, 0), new byte[]{0x01, 0x00, 0x00, 0x00, 0x00}); } @Test public void testAddImmediateMax() { - test(b -> b.add(0x01, 0xFFFFFFFF), new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); + test(b -> b.addOp(0x01, 0xFFFFFFFF), new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); } @Test public void testInvalidAddImmediate() { - testAssertion(b -> b.add(256, 0), "opcode does not fit into byte"); + testAssertion(b -> b.addOp(256, 0), "opcode does not fit into byte"); } @Test public void testAddImmediate64Max() { - test(b -> b.add(0x01, 0xFFFFFFFFFFFFFFFFL), new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); + test(b -> b.addOp(0x01, 0xFFFFFFFFFFFFFFFFL), new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); } @Test public void testInvalidAddImmediate64() { - testAssertion(b -> b.add(256, 0xFFL), "opcode does not fit into byte"); + testAssertion(b -> b.addOp(256, 0xFFL), "opcode does not fit into byte"); } @Test public void testAddImmediateMax2() { - test(b -> b.add(0x01, 0, 0xFFFFFFFF), new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); + test(b -> b.addOp(0x01, 0, 0xFFFFFFFF), new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); } @Test public void testInvalidAddImmediate2() { - testAssertion(b -> b.add(256, 0, 0), "opcode does not fit into byte"); + testAssertion(b -> b.addOp(256, 0, 0), "opcode does not fit into byte"); } @Test @@ -686,6 +687,11 @@ public void testElemHeaderExternref() { test(b -> b.addElemHeader(SegmentMode.ACTIVE, 8, WasmType.EXTERNREF_TYPE, 0, null, -1), new byte[]{0x40, 0x20, 0x08}); } + @Test + public void testElemHeaderExnref() { + test(b -> b.addElemHeader(SegmentMode.ACTIVE, 8, WasmType.EXNREF_TYPE, 0, null, -1), new byte[]{0x40, 0x30, 0x08}); + } + @Test public void testElemHeaderMinU8TableIndex() { test(b -> b.addElemHeader(SegmentMode.ACTIVE, 0, WasmType.FUNCREF_TYPE, 1, null, -1), new byte[]{0x50, 0x10, 0x00, 0x01}); @@ -915,4 +921,15 @@ public void testCodeEntryLocals() { public void testCodeEntryResults() { test(b -> b.addCodeEntry(0, 0, 0, 0, 1), new byte[]{0x05, 0x00}); } + + @Test + public void testCatchExceptionHandler() { + test(b -> b.addExceptionHandler(5, 10, ExceptionHandlerType.CATCH, 0, 10), new byte[]{0x05, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00}); + } + + @Test + public void testCatchRefExceptionHandler() { + test(b -> b.addExceptionHandler(0, 12, ExceptionHandlerType.CATCH_REF, 1, 256), + new byte[]{0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}); + } } diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java index 0b0293b26995..637e5e76fdbe 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java @@ -42,6 +42,7 @@ package org.graalvm.wasm.test.suites.bytecode; import java.io.IOException; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.function.BiConsumer; @@ -56,8 +57,10 @@ import org.graalvm.wasm.WasmLanguage; import org.graalvm.wasm.WasmModule; import org.graalvm.wasm.WasmTable; +import org.graalvm.wasm.WasmTag; import org.graalvm.wasm.api.Dictionary; import org.graalvm.wasm.api.Executable; +import org.graalvm.wasm.api.FuncType; import org.graalvm.wasm.api.Sequence; import org.graalvm.wasm.api.TableKind; import org.graalvm.wasm.api.ValueType; @@ -85,6 +88,7 @@ public class MultiInstantiationSuite { private static void test(byte[] testSource, Function importFun, BiConsumer check) throws IOException { final Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID); contextBuilder.option("wasm.Builtins", "testutil:testutil"); + contextBuilder.option("wasm.Exceptions", "true"); try (Context context = contextBuilder.build()) { Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(binaryWithExports), "main"); Source source = sourceBuilder.build(); @@ -146,10 +150,12 @@ public void testImportsAndExports() throws IOException, InterruptedException { final byte[] source = WasmBinaryTools.compileWat("main", """ (module (type (;0;) (func (result i32))) + (type (;1;) (func)) (import "a" "f" (func (type 0))) (import "a" "t" (table 2 2 funcref)) (import "a" "m" (memory 1 1)) (import "a" "g" (global i32)) + (import "a" "e" (tag (type 1))) (func (type 0) i32.const 13) (func (export "main") (type 0) i32.const 0) (func (export "test") (type 0) @@ -163,13 +169,22 @@ public void testImportsAndExports() throws IOException, InterruptedException { i32.load i32.add ) + (func (export "exception") (result exnref) + block (result exnref) + try_table (catch_ref 0 0) + throw 0 + end + unreachable + end + ) (export "f" (func 0)) (export "t" (table 0)) (export "m" (memory 0)) (export "g" (global 0)) + (export "e" (tag 0)) (elem (i32.const 1) func 1) ) - """); + """, EnumSet.of(WasmBinaryTools.WabtOption.EXCEPTIONS)); final Executable tableFun = new Executable(args -> 13); test(source, wasm -> { final Dictionary imports = new Dictionary(); @@ -188,6 +203,9 @@ public void testImportsAndExports() throws IOException, InterruptedException { final WasmGlobal g = wasm.globalAlloc(ValueType.i32, false, 4); a.addMember("g", g); + final WasmTag e = WebAssembly.tagAlloc(FuncType.fromString("()")); + a.addMember("e", e); + imports.addMember("a", a); return imports; }, (wasm, i) -> { @@ -211,6 +229,13 @@ public void testImportsAndExports() throws IOException, InterruptedException { final int gValue = lib.asInt(lib.execute(globalRead, g)); Assert.assertEquals("Global value does not match", 4, gValue); + final Object e = WebAssembly.instanceExport(i, "e"); + final Object exception = WebAssembly.instanceExport(i, "exception"); + final Object eInstance = lib.execute(exception); + final Object tagRead = wasm.readMember("exn_read"); + final Object eInstanceTag = lib.execute(tagRead, eInstance); + Assert.assertSame("Exception tag does not match", e, eInstanceTag); + final Object test = WebAssembly.instanceExport(i, "test"); final int result = lib.asInt(lib.execute(test)); Assert.assertEquals("Invalid test value", 64, result); diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/control/ExceptionSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/control/ExceptionSuite.java new file mode 100644 index 000000000000..85f1c5ea6ecf --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/control/ExceptionSuite.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.wasm.test.suites.control; + +import java.io.IOException; + +import org.graalvm.wasm.test.WasmFileSuite; +import org.junit.Test; + +public class ExceptionSuite extends WasmFileSuite { + @Override + protected String testResource() { + return "exceptions"; + } + + @Override + @Test + public void test() throws IOException { + // This is here just to make mx aware of the test suite class. + super.test(); + } +} diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ReferenceTypesValidationSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ReferenceTypesValidationSuite.java index b87d677ea2fb..e5b22eb4fd84 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ReferenceTypesValidationSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ReferenceTypesValidationSuite.java @@ -534,8 +534,10 @@ public void testElemDropElementIndexDoesNotExist() throws IOException { public void testMultipleTables() throws IOException { // (table 1 1 funcref) // (table 1 1 externref) - final byte[] binary = newBuilder().addTable((byte) 1, (byte) 1, WasmType.FUNCREF_TYPE).addTable((byte) 1, (byte) 1, WasmType.EXTERNREF_TYPE).build(); - runParserTest(binary, Context::eval); + // (table 1 1 exnref) + final byte[] binary = newBuilder().addTable((byte) 1, (byte) 1, WasmType.FUNCREF_TYPE).addTable((byte) 1, (byte) 1, WasmType.EXTERNREF_TYPE).addTable((byte) 1, (byte) 1, + WasmType.EXNREF_TYPE).build(); + runParserTest(binary, options -> options.option("wasm.Exceptions", "true"), Context::eval); } @Test @@ -882,6 +884,22 @@ public void testGlobalWithNull() throws IOException { }); } + @Test + public void testGlobalWithNullException() throws IOException { + // (global exnref (ref.null)) + // (type (func (result exnref))) + // (func (export "main") (type 0) + // global.get 0 + // ) + final byte[] binary = newBuilder().addGlobal(GlobalModifier.CONSTANT, WasmType.EXNREF_TYPE, "D0 69 0B").addType(EMPTY_BYTES, new byte[]{WasmType.EXNREF_TYPE}).addFunction((byte) 0, + EMPTY_BYTES, "23 00 0B").addFunctionExport((byte) 0, "main").build(); + runRuntimeTest(binary, options -> options.option("wasm.Exceptions", "true"), instance -> { + Value main = instance.getMember("main"); + Value result = main.execute(); + Assert.assertTrue("Unexpected result value", result.isNull()); + }); + } + @Test public void testGlobalWithFunction() throws IOException { // (global funcref (ref.func 0)) diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.opts new file mode 100644 index 000000000000..cdae3e9aeaf7 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.opts @@ -0,0 +1 @@ +wasm.Exceptions=true \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.result new file mode 100644 index 000000000000..f56a85687e59 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.result @@ -0,0 +1 @@ +int 32 \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.wat new file mode 100644 index 000000000000..ac4f7a1937ae --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_global_unwind.wat @@ -0,0 +1,70 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func)) + (tag $e0 (type $t0)) + (tag $e1 (type $t0)) + + (func (export "_main") (result i32) + block $h (result exnref) + try_table (result i32) (catch_ref $e0 $h) + call $call_throw + i32.const 1 + end + return + end + drop + i32.const 32 + ) + + (func $call_throw + block $h0 + try_table (catch $e1 $h0) + call $throw + end + return + end + ) + + (func $throw + throw $e0 + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.opts new file mode 100644 index 000000000000..cdae3e9aeaf7 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.opts @@ -0,0 +1 @@ +wasm.Exceptions=true \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.result new file mode 100644 index 000000000000..f56a85687e59 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.result @@ -0,0 +1 @@ +int 32 \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.wat new file mode 100644 index 000000000000..caf3504a43c9 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_indirect_call.wat @@ -0,0 +1,71 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t1 (func)) + (tag $e1 (type $t1)) + + (type $funcType (func (result i32))) + + (func $func1 (result i32) + throw $e1 + ) + (func $func2 (result i32) + i32.const 200 + ) + + (table 2 funcref) + + (elem (i32.const 0) $func1 $func2) + + (func (export "_main") (result i32) + block $h (result exnref) + try_table (result i32) (catch_ref $e1 $h) + i32.const 0 + call_indirect (type $funcType) + i32.const 31 + return + end + return + end + drop + i32.const 32 + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.opts new file mode 100644 index 000000000000..cdae3e9aeaf7 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.opts @@ -0,0 +1 @@ +wasm.Exceptions=true \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.result new file mode 100644 index 000000000000..250027ef01ca --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.result @@ -0,0 +1 @@ +int 23 \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.wat new file mode 100644 index 000000000000..61495f3a149a --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_local_unwind.wat @@ -0,0 +1,75 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func)) + (tag $e0 (type $t0)) + (tag $e1 (type $t0)) + + (func $throw_e0 (param i32) + local.get 0 + if + i32.const 55 + drop + else + throw $e0 + end + ) + + (func (export "_main") (result i32) + block $h (result exnref) + try_table (result i32) (catch_ref $e0 $h) + block $h1 + try_table (result i32) (catch $e1 $h1) + i32.const 0 + call $throw_e0 + i32.const 42 + end + return + end + i32.const 21 + return + end + return + end + drop + i32.const 23 + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.opts new file mode 100644 index 000000000000..cdae3e9aeaf7 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.opts @@ -0,0 +1 @@ +wasm.Exceptions=true \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.result new file mode 100644 index 000000000000..250027ef01ca --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.result @@ -0,0 +1 @@ +int 23 \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.wat new file mode 100644 index 000000000000..7d845e0628ab --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_single_func.wat @@ -0,0 +1,63 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func)) + (tag $e0 (type $t0)) + (tag $e1 (type $t0)) + + (func (export "_main") (result i32) + block $h + try_table (result i32) (catch $e0 $h) + block $h1 + try_table (result i32) (catch $e1 $h1) + throw $e0 + i32.const 42 + end + return + end + i32.const 21 + return + end + return + end + i32.const 23 + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.opts new file mode 100644 index 000000000000..cdae3e9aeaf7 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.opts @@ -0,0 +1 @@ +wasm.Exceptions=true \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.result new file mode 100644 index 000000000000..58b76943af05 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.result @@ -0,0 +1 @@ +int 4 \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.wat new file mode 100644 index 000000000000..80585fadfa85 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.wat @@ -0,0 +1,63 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func (param i32))) + (tag $e0 (type $t0)) + (tag $e1 (type $t0)) + + (func (export "_main") (result i32) + block $h (result i32) + try_table (result i32) (catch $e0 $h) + block $h1 (result i32) + try_table (result i32) (catch $e1 $h) + i32.const 4 + throw $e0 + i32.const 42 + end + return + end + i32.const 21 + return + end + return + end + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.opts new file mode 100644 index 000000000000..cdae3e9aeaf7 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.opts @@ -0,0 +1 @@ +wasm.Exceptions=true \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.result new file mode 100644 index 000000000000..912cbe96964c --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.result @@ -0,0 +1 @@ +int 1005 \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.wat new file mode 100644 index 000000000000..a174e198b1ab --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/multi_exception_types.wat @@ -0,0 +1,165 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $empty (func)) + (type $int (func (param i32 i64))) + (type $float (func (param f32 f64))) + (type $func (func (param funcref))) + (type $exc (func (param exnref))) + + (tag $emptyTag (type $empty)) + (tag $intTag (type $int)) + (tag $floatTag (type $float)) + (tag $funcTag (type $func)) + (tag $excTag (type $exc)) + + (func $f1 (result i32) + i32.const 1 + ) + + (func $f2 (result i32) + i32.const 2 + ) + + (table $table 2 2 funcref) + (elem (i32.const 0) $f1 $f2) + + (func $thrower (param i32) + block (result exnref) + try_table (catch_ref $emptyTag 0) + block + block + block + local.get 0 + i32.const 0 + i32.ne + br_if 0 + i32.const 13 + i64.const 42 + throw $intTag + end + local.get 0 + i32.const 1 + i32.ne + br_if 0 + f32.const 3.14 + f64.const 9.81 + throw $floatTag + end + local.get 0 + i32.const 2 + i32.ne + br_if 0 + i32.const 1 + table.get $table + throw $funcTag + end + throw $emptyTag + end + return + end + throw $excTag + ) + + (func (export "_main") (result i32) + block $invalid + block (result funcref) + try_table (catch $funcTag 0) + block (result i32 i64) + try_table (catch $intTag 0) + i32.const 0 + call $thrower + i32.const 1001 + return + end + unreachable + end + i64.const 42 + i64.ne + br_if $invalid + i32.const 13 + i32.ne + br_if $invalid + block (result f32 f64) + try_table (catch $floatTag 0) + i32.const 1 + call $thrower + i32.const 1002 + return + end + unreachable + end + f64.const 9.81 + f64.ne + br_if $invalid + f32.const 3.14 + f32.ne + br_if $invalid + + i32.const 2 + call $thrower + i32.const 1003 + return + end + unreachable + end + ref.is_null + br_if $invalid + block + try_table (catch $emptyTag 0) + block (result exnref) + try_table (catch $excTag 0) + i32.const 3 + call $thrower + i32.const 1004 + return + end + unreachable + end + throw_ref + end + end + i32.const 1005 + return + end + i32.const 1000 + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.opts new file mode 100644 index 000000000000..cdae3e9aeaf7 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.opts @@ -0,0 +1 @@ +wasm.Exceptions=true \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.result new file mode 100644 index 000000000000..250027ef01ca --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.result @@ -0,0 +1 @@ +int 23 \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.wat new file mode 100644 index 000000000000..1ce94f5e5819 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.wat @@ -0,0 +1,74 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func)) + + (tag $e0 (type $t0)) + (tag $e1 (type $t0)) + + (func $throw_e0 (param i32) + local.get 0 + if + i32.const 55 + drop + else + throw $e0 + end + ) + + (func (export "_main") (result i32) + block $h1 (result exnref) + try_table (result exnref) (catch_ref $e0 $h1) + block $h (result exnref) + try_table (result i32) (catch_ref $e0 $h) + i32.const 0 + call $throw_e0 + i32.const 42 + end + return + end + throw_ref + end + end + drop + i32.const 23 + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/wasm_test_index b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/wasm_test_index new file mode 100644 index 000000000000..fb58064bdfc5 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/wasm_test_index @@ -0,0 +1,7 @@ +exceptions_global_unwind +exceptions_indirect_call +exceptions_local_unwind +exceptions_single_func +exceptions_with_params +multi_exception_types +throw_ref \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions.opts b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions.opts new file mode 100644 index 000000000000..cdae3e9aeaf7 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions.opts @@ -0,0 +1 @@ +wasm.Exceptions=true \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions.result b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions.result new file mode 100644 index 000000000000..f56a85687e59 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions.result @@ -0,0 +1 @@ +int 32 \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/friend.wat b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/friend.wat new file mode 100644 index 000000000000..23d08a2cba92 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/friend.wat @@ -0,0 +1,54 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func)) + (tag $e0 (type $t0)) + (export "ex" (tag $e0)) + (func (export "throw_exception") (param i32) + local.get 0 + if + i32.const 55 + drop + else + throw $e0 + end + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/main.wat b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/main.wat new file mode 100644 index 000000000000..2109eac01487 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/main.wat @@ -0,0 +1,60 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func)) + (type $t1 (func (param i32))) + + (import "man-in-the-middle" "ex" (tag $e0 (type $t0))) + (import "man-in-the-middle" "throw_exception" (func (type $t1))) + + (func (export "_main") (result i32) + block $h (result exnref) + try_table (result i32) (catch_ref $e0 $h) + i32.const 0 + call 0 + i32.const 2 + end + return + end + drop + i32.const 32 + ) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/man-in-the-middle.wat b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/man-in-the-middle.wat new file mode 100644 index 000000000000..f0d9332aca94 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/linker/import_export_exceptions/man-in-the-middle.wat @@ -0,0 +1,50 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func)) + (type $t1 (func (param i32))) + + (import "friend" "ex" (tag $e0 (type $t0))) + (import "friend" "throw_exception" (func (type $t1))) + + (export "ex" (tag $e0)) + (export "throw_exception" (func 0)) +) \ No newline at end of file diff --git a/wasm/src/org.graalvm.wasm.test/src/test/linker/wasm_test_index b/wasm/src/org.graalvm.wasm.test/src/test/linker/wasm_test_index index d65be401b10c..9ed1ae0c444c 100644 --- a/wasm/src/org.graalvm.wasm.test/src/test/linker/wasm_test_index +++ b/wasm/src/org.graalvm.wasm.test/src/test/linker/wasm_test_index @@ -3,6 +3,7 @@ import_export_functions/main;friend;man-in-the-middle import_export_table/main;man-in-the-middle;table-registry import_export_globals/main;man-in-the-middle;runtime import_export_mutable_globals/main;man-in-the-middle;runtime +import_export_exceptions/main;man-in-the-middle;friend import_func_twice/main;lib global_initialization/main;mediator;runtime relative_data_section/main;constant-pools;runtime diff --git a/wasm/src/org.graalvm.wasm.utils/src/org/graalvm/wasm/utils/WasmBinaryTools.java b/wasm/src/org.graalvm.wasm.utils/src/org/graalvm/wasm/utils/WasmBinaryTools.java index a606bcce5575..8a51900528f5 100644 --- a/wasm/src/org.graalvm.wasm.utils/src/org/graalvm/wasm/utils/WasmBinaryTools.java +++ b/wasm/src/org.graalvm.wasm.utils/src/org/graalvm/wasm/utils/WasmBinaryTools.java @@ -68,7 +68,8 @@ public class WasmBinaryTools { public enum WabtOption { MULTI_MEMORY, - THREADS + THREADS, + EXCEPTIONS } private interface OutputSupplier { @@ -148,6 +149,7 @@ private static List wat2wasmCmdLine(EnumSet options) { switch (option) { case MULTI_MEMORY -> commandLine.add("--enable-multi-memory"); case THREADS -> commandLine.add("--enable-threads"); + case EXCEPTIONS -> commandLine.add("--enable-exceptions"); } } return commandLine; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Assert.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Assert.java index 9d8e5aaa8fe5..55f94fc23eed 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Assert.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Assert.java @@ -199,6 +199,12 @@ public static void assertTrue(boolean condition, String message, Failure failure } } + public static void assertFunctionTypeEquals(SymbolTable.FunctionType t1, SymbolTable.FunctionType t2, Failure failure) throws WasmException { + if (!t1.equals(t2)) { + fail(failure, "%s: %s should = %s", failure.name, t1, t2); + } + } + @TruffleBoundary public static RuntimeException fail(Failure failure, String format, Object... args) throws WasmException { throw WasmException.format(failure, format, args); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java index 5276b48e6519..40bced228b60 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java @@ -48,6 +48,7 @@ import static org.graalvm.wasm.Assert.assertUnsignedIntLessOrEqual; import static org.graalvm.wasm.Assert.assertUnsignedLongLessOrEqual; import static org.graalvm.wasm.Assert.fail; +import static org.graalvm.wasm.WasmType.EXNREF_TYPE; import static org.graalvm.wasm.WasmType.EXTERNREF_TYPE; import static org.graalvm.wasm.WasmType.F32_TYPE; import static org.graalvm.wasm.WasmType.F64_TYPE; @@ -77,11 +78,13 @@ import org.graalvm.wasm.api.Vector128Shape; import org.graalvm.wasm.collection.ByteArrayList; import org.graalvm.wasm.constants.Bytecode; +import org.graalvm.wasm.constants.ExceptionHandlerType; import org.graalvm.wasm.constants.ExportIdentifier; import org.graalvm.wasm.constants.GlobalModifier; import org.graalvm.wasm.constants.ImportIdentifier; import org.graalvm.wasm.constants.Instructions; import org.graalvm.wasm.constants.LimitsPrefix; +import org.graalvm.wasm.constants.NameSection; import org.graalvm.wasm.constants.Section; import org.graalvm.wasm.constants.SegmentMode; import org.graalvm.wasm.debugging.parser.DebugUtil; @@ -92,6 +95,7 @@ import org.graalvm.wasm.parser.bytecode.RuntimeBytecodeGen; import org.graalvm.wasm.parser.ir.CallNode; import org.graalvm.wasm.parser.ir.CodeEntry; +import org.graalvm.wasm.parser.validation.ExceptionHandler; import org.graalvm.wasm.parser.validation.ParserState; import com.oracle.truffle.api.CompilerDirectives; @@ -118,6 +122,7 @@ public class BinaryParser extends BinaryStreamParser { private final boolean multiMemory; private final boolean threads; private final boolean simd; + private final boolean exceptions; @TruffleBoundary public BinaryParser(WasmModule module, WasmContext context, byte[] data) { @@ -133,6 +138,7 @@ public BinaryParser(WasmModule module, WasmContext context, byte[] data) { this.multiMemory = context.getContextOptions().supportMultiMemory(); this.threads = context.getContextOptions().supportThreads(); this.simd = context.getContextOptions().supportSIMD(); + this.exceptions = context.getContextOptions().supportExceptions(); } @TruffleBoundary @@ -192,6 +198,9 @@ private void readSymbolSections() { case Section.MEMORY: readMemorySection(); break; + case Section.TAG: + readTagSection(); + break; case Section.GLOBAL: readGlobalSection(); break; @@ -314,14 +323,15 @@ private int allocateDebugOffsets(BytecodeGen customData) { * binary specification */ private void readNameSection() { - if (!isEOF() && peek1() == 0) { - readModuleName(); - } - if (!isEOF() && peek1() == 1) { - readFunctionNames(); + if (isEOF()) { + return; } - if (!isEOF() && peek1() == 2) { - readLocalNames(); + final int section = peek1(); + switch (section) { + case NameSection.MODULE_NAME -> readModuleName(); + case NameSection.FUNCTION_NAME -> readFunctionNames(); + case NameSection.LOCAL_NAME -> readLocalNames(); + case NameSection.TAG_NAME -> readTagNames(); } } @@ -332,7 +342,7 @@ private void readNameSection() { */ private void readModuleName() { final int subsectionId = read1(); - assert subsectionId == 0; + assert subsectionId == NameSection.MODULE_NAME; final int size = readLength(); // We don't currently use debug module name. offset += size; @@ -345,7 +355,7 @@ private void readModuleName() { */ private void readFunctionNames() { final int subsectionId = read1(); - assert subsectionId == 1; + assert subsectionId == NameSection.FUNCTION_NAME; final int size = readLength(); final int startOffset = offset; final int length = readLength(); @@ -367,12 +377,20 @@ private void readFunctionNames() { */ private void readLocalNames() { final int subsectionId = read1(); - assert subsectionId == 2; + assert subsectionId == NameSection.LOCAL_NAME; final int size = readLength(); // We don't currently use debug local names. offset += size; } + private void readTagNames() { + final int subsectionId = read1(); + assert subsectionId == NameSection.TAG_NAME; + final int size = readLength(); + // We don't currently use debug tag names. + offset += size; + } + private void readTypeSection() { final int typeCount = readLength(); module.limits().checkTypeCount(typeCount); @@ -407,7 +425,7 @@ private void readImportSection() { break; } case ImportIdentifier.TABLE: { - final byte elemType = readRefType(); + final byte elemType = readRefType(exceptions); if (!bulkMemoryAndRefTypes) { assertByteEqual(elemType, FUNCREF_TYPE, "Invalid element type for table import", Failure.UNSPECIFIED_MALFORMED); } @@ -425,10 +443,17 @@ private void readImportSection() { break; } case ImportIdentifier.GLOBAL: { - byte type = readValueType(bulkMemoryAndRefTypes, simd); + byte type = readValueType(bulkMemoryAndRefTypes, simd, exceptions); byte mutability = readMutability(); - int index = module.symbolTable().numGlobals(); - module.symbolTable().importGlobal(moduleName, memberName, index, type, mutability); + int globalIndex = module.symbolTable().numGlobals(); + module.symbolTable().importGlobal(moduleName, memberName, globalIndex, type, mutability); + break; + } + case ImportIdentifier.TAG: { + final byte attribute = read1(); + final int typeIndex = readTypeIndex(); + final int tagIndex = module.symbolTable().tagCount(); + module.symbolTable().importTag(moduleName, memberName, tagIndex, attribute, typeIndex); break; } default: { @@ -454,7 +479,7 @@ private void readTableSection() { module.limits().checkTableCount(startingTableIndex + tableCount); for (int tableIndex = startingTableIndex; tableIndex != startingTableIndex + tableCount; tableIndex++) { assertTrue(!isEOF(), Failure.LENGTH_OUT_OF_BOUNDS); - final byte elemType = readRefType(); + final byte elemType = readRefType(exceptions); readTableLimits(multiResult); module.symbolTable().allocateTable(tableIndex, multiResult[0], multiResult[1], elemType, bulkMemoryAndRefTypes); } @@ -526,7 +551,7 @@ private ByteArrayList readCodeEntryLocals() { final int groupLength = readUnsignedInt32(); localsLength += groupLength; module.limits().checkLocalCount(localsLength); - final byte t = readValueType(bulkMemoryAndRefTypes, simd); + final byte t = readValueType(bulkMemoryAndRefTypes, simd, exceptions); for (int i = 0; i != groupLength; ++i) { localTypes.add(t); } @@ -553,26 +578,18 @@ private byte[] extractBlockResultTypes(int typeIndex) { } private static byte[] encapsulateResultType(int type) { - switch (type) { - case VOID_TYPE: - return WasmType.VOID_TYPE_ARRAY; - case I32_TYPE: - return WasmType.I32_TYPE_ARRAY; - case I64_TYPE: - return WasmType.I64_TYPE_ARRAY; - case F32_TYPE: - return WasmType.F32_TYPE_ARRAY; - case F64_TYPE: - return WasmType.F64_TYPE_ARRAY; - case V128_TYPE: - return WasmType.V128_TYPE_ARRAY; - case FUNCREF_TYPE: - return WasmType.FUNCREF_TYPE_ARRAY; - case EXTERNREF_TYPE: - return WasmType.EXTERNREF_TYPE_ARRAY; - default: - throw WasmException.create(Failure.UNSPECIFIED_INTERNAL); - } + return switch (type) { + case VOID_TYPE -> WasmType.VOID_TYPE_ARRAY; + case I32_TYPE -> WasmType.I32_TYPE_ARRAY; + case I64_TYPE -> WasmType.I64_TYPE_ARRAY; + case F32_TYPE -> WasmType.F32_TYPE_ARRAY; + case F64_TYPE -> WasmType.F64_TYPE_ARRAY; + case V128_TYPE -> WasmType.V128_TYPE_ARRAY; + case FUNCREF_TYPE -> WasmType.FUNCREF_TYPE_ARRAY; + case EXTERNREF_TYPE -> WasmType.EXTERNREF_TYPE_ARRAY; + case EXNREF_TYPE -> WasmType.EXNREF_TYPE_ARRAY; + default -> throw WasmException.create(Failure.UNSPECIFIED_INTERNAL); + }; } private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTypes, int sourceCodeEndOffset, boolean hasNextFunction, RuntimeBytecodeGen bytecode, @@ -604,7 +621,7 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy case Instructions.BLOCK: { final byte[] blockParamTypes; final byte[] blockResultTypes; - readBlockType(multiResult, bulkMemoryAndRefTypes, simd); + readBlockType(multiResult, bulkMemoryAndRefTypes, simd, exceptions); // Extract value based on result arity. if (multiResult[1] == SINGLE_RESULT_VALUE) { blockParamTypes = WasmType.VOID_TYPE_ARRAY; @@ -625,7 +642,7 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy // Jumps are targeting the loop instruction for OSR. final byte[] loopParamTypes; final byte[] loopResultTypes; - readBlockType(multiResult, bulkMemoryAndRefTypes, simd); + readBlockType(multiResult, bulkMemoryAndRefTypes, simd, exceptions); // Extract value based on result arity. if (multiResult[1] == SINGLE_RESULT_VALUE) { loopParamTypes = WasmType.VOID_TYPE_ARRAY; @@ -646,7 +663,7 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy state.popChecked(I32_TYPE); // condition final byte[] ifParamTypes; final byte[] ifResultTypes; - readBlockType(multiResult, bulkMemoryAndRefTypes, simd); + readBlockType(multiResult, bulkMemoryAndRefTypes, simd, exceptions); // Extract value based on result arity. if (multiResult[1] == SINGLE_RESULT_VALUE) { ifParamTypes = WasmType.VOID_TYPE_ARRAY; @@ -664,7 +681,8 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy break; } case Instructions.END: { - state.exit(multiValue); + final byte[] endResultTypes = state.exit(multiValue); + state.pushAll(endResultTypes); if (state.controlStackSize() == 0) { /* * If control stack is empty, we should have reached the end of the function @@ -797,7 +815,7 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy checkBulkMemoryAndRefTypesSupport(opcode); final int length = readLength(); assertIntEqual(length, 1, Failure.INVALID_RESULT_ARITY); - final byte t = readValueType(bulkMemoryAndRefTypes, simd); + final byte t = readValueType(bulkMemoryAndRefTypes, simd, exceptions); state.popChecked(I32_TYPE); state.popChecked(t); state.popChecked(t); @@ -809,6 +827,49 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy } break; } + case Instructions.TRY_TABLE: { + checkExceptionHandlingSupport(opcode); + final byte[] tryTableParamTypes; + final byte[] tryTableResultTypes; + readBlockType(multiResult, bulkMemoryAndRefTypes, simd, exceptions); + + if (multiResult[1] == SINGLE_RESULT_VALUE) { + tryTableParamTypes = WasmType.VOID_TYPE_ARRAY; + tryTableResultTypes = encapsulateResultType(multiResult[0]); + } else if (multiValue) { + final int typeIndex = multiResult[0]; + state.checkFunctionTypeExists(typeIndex, module.typeCount()); + tryTableParamTypes = extractBlockParamTypes(typeIndex); + tryTableResultTypes = extractBlockResultTypes(typeIndex); + } else { + throw WasmException.create(Failure.DISABLED_MULTI_VALUE); + } + state.popAll(tryTableParamTypes); + final ExceptionHandler[] handlers = readExceptionHandlers(state); + state.enterTryTable(tryTableParamTypes, tryTableResultTypes, handlers); + break; + } + case Instructions.THROW: { + checkExceptionHandlingSupport(opcode); + final int tagIndex = readTagIndex(); + final int typeIndex = module.tagTypeIndex(tagIndex); + final byte[] paramTypes = module.typeAt(typeIndex).paramTypes(); + state.popAll(paramTypes); + state.addMiscFlag(); + state.addInstruction(Bytecode.THROW, tagIndex); + + state.setUnreachable(); + break; + } + case Instructions.THROW_REF: { + checkExceptionHandlingSupport(opcode); + state.popChecked(EXNREF_TYPE); + state.addMiscFlag(); + state.addInstruction(Bytecode.THROW_REF); + + state.setUnreachable(); + break; + } case Instructions.LOCAL_GET: { final int localIndex = readLocalIndex(); assertUnsignedIntLess(localIndex, locals.length, Failure.UNKNOWN_LOCAL); @@ -1030,6 +1091,17 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy } final int bytecodeEndOffset = bytecode.location(); + final int exceptionTableOffset; + if (state.needsExceptionTable()) { + exceptionTableOffset = bytecode.location(); + state.generateExceptionTable(); + } else { + exceptionTableOffset = 0; + } + bytecode.add(exceptionTableOffset); + + final int functionEndOffset = bytecode.location(); + if (offsetToLineIndexMap == null) { bytecode.addCodeEntry(functionIndex, state.maxStackSize(), bytecodeEndOffset - bytecodeStartOffset, locals.length, resultTypes.length); for (byte local : locals) { @@ -1046,12 +1118,12 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy } // Do not override the code entry offset when rereading the function. - module.setCodeEntryOffset(codeEntryIndex, bytecodeEndOffset); + module.setCodeEntryOffset(codeEntryIndex, functionEndOffset); } else { // Make sure we notify a statement exit before leaving the function bytecode.addNotify(-1, -1); } - return new CodeEntry(functionIndex, state.maxStackSize(), locals, resultTypes, callNodes, bytecodeStartOffset, bytecodeEndOffset, state.usesMemoryZero()); + return new CodeEntry(functionIndex, state.maxStackSize(), locals, resultTypes, callNodes, bytecodeStartOffset, bytecodeEndOffset, state.usesMemoryZero(), exceptionTableOffset); } private void readNumericInstructions(ParserState state, int opcode) { @@ -1530,7 +1602,7 @@ private void readNumericInstructions(ParserState state, int opcode) { break; case Instructions.REF_NULL: checkBulkMemoryAndRefTypesSupport(opcode); - final byte type = readRefType(); + final byte type = readRefType(exceptions); state.push(type); state.addInstruction(Bytecode.REF_NULL); break; @@ -2299,6 +2371,10 @@ private void checkRelaxedSIMDSupport(int vectorOpcode) { checkContextOption(wasmContext.getContextOptions().supportRelaxedSIMD(), "Relaxed vector instructions are not enabled (opcode: 0x%02x 0x%x)", Instructions.VECTOR, vectorOpcode); } + private void checkExceptionHandlingSupport(int opcode) { + checkContextOption(wasmContext.getContextOptions().supportExceptions(), "Exception handling is not enabled (opcode: 0x%02x)", opcode); + } + private void store(ParserState state, byte type, int n, long[] result) { int alignHint = readAlignHint(n); final int memoryIndex = readMemoryIndexFromAlignHint(alignHint); @@ -2417,6 +2493,48 @@ private void atomicWait(ParserState state, byte type, int n, long[] result) { result[1] = memoryOffset; } + private ExceptionHandler[] readExceptionHandlers(ParserState state) { + final int length = readLength(); + final ExceptionHandler[] handlers = new ExceptionHandler[length]; + + for (int i = 0; i < length; i += 2) { + final int opcode = read1() & 0xFF; + switch (opcode) { + case ExceptionHandlerType.CATCH -> { + final int tag = readTagIndex(); + final int label = readUnsignedInt32(); + assertUnsignedIntLess(label, state.controlStackSize(), Failure.INVALID_CATCH_CLAUSE_LABEL); + final int typeIndex = module.tagTypeIndex(tag); + final byte[] paramTypes = module.typeAt(typeIndex).paramTypes(); + handlers[i] = state.enterCatchClause(opcode, tag, label); + state.pushAll(paramTypes); + } + case ExceptionHandlerType.CATCH_REF -> { + final int tag = readTagIndex(); + final int label = readUnsignedInt32(); + assertUnsignedIntLess(label, state.controlStackSize(), Failure.INVALID_CATCH_CLAUSE_LABEL); + final int typeIndex = module.tagTypeIndex(tag); + final byte[] paramTypes = module.typeAt(typeIndex).paramTypes(); + handlers[i] = state.enterCatchClause(opcode, tag, label); + state.pushAll(paramTypes); + state.push(EXNREF_TYPE); + } + case ExceptionHandlerType.CATCH_ALL -> { + final int label = readUnsignedInt32(); + handlers[i] = state.enterCatchClause(opcode, -1, label); + } + case ExceptionHandlerType.CATCH_ALL_REF -> { + final int label = readUnsignedInt32(); + handlers[i] = state.enterCatchClause(opcode, -1, label); + state.push(EXNREF_TYPE); + } + default -> Assert.fail(Failure.MALFORMED_CATCH, String.format("Invalid catch clause type: 0x%02X", opcode)); + } + state.exit(multiValue); + } + return handlers; + } + private Pair readOffsetExpression() { // Table offset expression must be a constant expression with result type i32. // https://webassembly.github.io/spec/core/syntax/modules.html#element-segments @@ -2491,7 +2609,7 @@ private Pair readConstantExpression(byte resultType, boolean onl } case Instructions.REF_NULL: checkBulkMemoryAndRefTypesSupport(opcode); - final byte type = readRefType(); + final byte type = readRefType(exceptions); state.push(type); state.addInstruction(Bytecode.REF_NULL); if (calculable) { @@ -2638,7 +2756,7 @@ private long[] readElemExpressions(byte elemType) { throw WasmException.format(Failure.ILLEGAL_OPCODE, "Illegal opcode for constant expression: 0x%02X", opcode); } case Instructions.REF_NULL: - final byte type = readRefType(); + final byte type = readRefType(exceptions); if (bulkMemoryAndRefTypes && type != elemType) { fail(Failure.TYPE_MISMATCH, "Invalid ref.null type: 0x%02X", type); } @@ -2710,7 +2828,7 @@ private void readElementSection(RuntimeBytecodeGen bytecode) { } if (useExpressions) { if (useType) { - elemType = readRefType(); + elemType = readRefType(exceptions); } else { elemType = FUNCREF_TYPE; } @@ -2809,6 +2927,11 @@ private void readExportSection() { module.symbolTable().exportGlobal(exportName, index); break; } + case ExportIdentifier.TAG: { + final int index = readTagIndex(); + module.symbolTable().exportTag(index, exportName); + break; + } default: { fail(Failure.UNSPECIFIED_MALFORMED, "Invalid export type identifier: 0x%02X", exportType); } @@ -2816,13 +2939,28 @@ private void readExportSection() { } } + private void readTagSection() { + final int tagCount = readLength(); + module.limits().checkTagCount(tagCount); + final int startingTagIndex = module.symbolTable().tagCount(); + for (int tagIndex = startingTagIndex; tagIndex != startingTagIndex + tagCount; tagIndex++) { + assertTrue(!isEOF(), Failure.LENGTH_OUT_OF_BOUNDS); + // 0x00 means exception + final byte attribute = read1(); + assertByteEqual(attribute, (byte) WasmTag.Attribute.EXCEPTION, Failure.MALFORMED_TAG_ATTRIBUTE); + final int type = readTypeIndex(); + + module.symbolTable().allocateTag(tagIndex, attribute, type); + } + } + private void readGlobalSection() { final int globalCount = readLength(); module.limits().checkGlobalCount(globalCount); final int startingGlobalIndex = module.symbolTable().numGlobals(); for (int globalIndex = startingGlobalIndex; globalIndex != startingGlobalIndex + globalCount; globalIndex++) { assertTrue(!isEOF(), Failure.LENGTH_OUT_OF_BOUNDS); - final byte type = readValueType(bulkMemoryAndRefTypes, simd); + final byte type = readValueType(bulkMemoryAndRefTypes, simd, exceptions); // 0x00 means const, 0x01 means var final byte mutability = readMutability(); // Global initialization expressions must be constant expressions: @@ -2852,7 +2990,7 @@ private void readDataSection(RuntimeBytecodeGen bytecode) { } final int droppedDataInstanceOffset = bytecode.location(); module.setDroppedDataInstanceOffset(droppedDataInstanceOffset); - bytecode.add(Bytecode.UNREACHABLE); + bytecode.addOp(Bytecode.UNREACHABLE); for (int dataSegmentIndex = 0; dataSegmentIndex != dataSegmentCount; ++dataSegmentIndex) { assertTrue(!isEOF(), Failure.LENGTH_OUT_OF_BOUNDS); final int mode; @@ -2951,14 +3089,14 @@ private void readFunctionType() { private void readParameterList(int funcTypeIdx, int paramCount) { for (int paramIdx = 0; paramIdx != paramCount; ++paramIdx) { - byte type = readValueType(bulkMemoryAndRefTypes, simd); + byte type = readValueType(bulkMemoryAndRefTypes, simd, exceptions); module.symbolTable().registerFunctionTypeParameterType(funcTypeIdx, paramIdx, type); } } private void readResultList(int funcTypeIdx, int resultCount) { for (int resultIdx = 0; resultIdx != resultCount; resultIdx++) { - byte type = readValueType(bulkMemoryAndRefTypes, simd); + byte type = readValueType(bulkMemoryAndRefTypes, simd, exceptions); module.symbolTable().registerFunctionTypeResultType(funcTypeIdx, resultIdx, type); } } @@ -3006,6 +3144,12 @@ private int readFunctionIndex() { return readUnsignedInt32(); } + private int readTagIndex() { + final int index = readUnsignedInt32(); + module.symbolTable().checkTagIndex(index); + return index; + } + private int readTableIndex() { final int index = readUnsignedInt32(); assertTrue(module.symbolTable().checkTableIndex(index), Failure.UNKNOWN_TABLE); @@ -3043,12 +3187,15 @@ private byte readImportType() { return read1(); } - private byte readRefType() { + private byte readRefType(boolean allowExnType) { final byte refType = read1(); switch (refType) { case FUNCREF_TYPE: case EXTERNREF_TYPE: break; + case EXNREF_TYPE: + assertTrue(allowExnType, Failure.MALFORMED_REFERENCE_TYPE); + break; default: fail(Failure.MALFORMED_REFERENCE_TYPE, "Unexpected reference type"); break; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryStreamParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryStreamParser.java index 22b493b058bb..aefcc4f50679 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryStreamParser.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryStreamParser.java @@ -284,7 +284,7 @@ protected int offset() { * @param result The array used for returning the result. * */ - protected void readBlockType(int[] result, boolean allowRefTypes, boolean allowVecType) { + protected void readBlockType(int[] result, boolean allowRefTypes, boolean allowVecType, boolean allowExnType) { byte type = peek1(data, offset); switch (type) { case WasmType.VOID_TYPE: @@ -309,6 +309,12 @@ protected void readBlockType(int[] result, boolean allowRefTypes, boolean allowV result[0] = type; result[1] = SINGLE_RESULT_VALUE; break; + case WasmType.EXNREF_TYPE: + Assert.assertTrue(allowExnType, Failure.MALFORMED_VALUE_TYPE); + offset++; + result[0] = type; + result[1] = SINGLE_RESULT_VALUE; + break; default: long valueAndLength = peekSignedInt32AndLength(data, offset); result[0] = value(valueAndLength); @@ -318,7 +324,7 @@ protected void readBlockType(int[] result, boolean allowRefTypes, boolean allowV } } - protected static byte peekValueType(byte[] data, int offset, boolean allowRefTypes, boolean allowVecType) { + protected static byte peekValueType(byte[] data, int offset, boolean allowRefTypes, boolean allowVecType, boolean allowExnType) { byte b = peek1(data, offset); switch (b) { case WasmType.I32_TYPE: @@ -333,14 +339,17 @@ protected static byte peekValueType(byte[] data, int offset, boolean allowRefTyp case WasmType.EXTERNREF_TYPE: Assert.assertTrue(allowRefTypes, Failure.MALFORMED_VALUE_TYPE); break; + case WasmType.EXNREF_TYPE: + Assert.assertTrue(allowExnType, Failure.MALFORMED_VALUE_TYPE); + break; default: Assert.fail(Failure.MALFORMED_VALUE_TYPE, "Invalid value type: 0x%02X", b); } return b; } - protected byte readValueType(boolean allowRefTypes, boolean allowVecType) { - byte b = peekValueType(data, offset, allowRefTypes, allowVecType); + protected byte readValueType(boolean allowRefTypes, boolean allowVecType, boolean allowExnType) { + byte b = peekValueType(data, offset, allowRefTypes, allowVecType, allowExnType); offset++; return b; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/GlobalRegistry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/GlobalRegistry.java index 84488f666628..47d8a54b2cfd 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/GlobalRegistry.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/GlobalRegistry.java @@ -124,7 +124,7 @@ public void store(byte globalValueType, int address, Object value) { case WasmType.F32_TYPE -> storeFloat(address, (float) value); case WasmType.F64_TYPE -> storeDouble(address, (double) value); case WasmType.V128_TYPE -> storeVector128(address, (Vector128) value); - case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE -> storeReference(address, value); + case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> storeReference(address, value); default -> throw CompilerDirectives.shouldNotReachHere(); } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java index 1894dee3e9c2..c9df221f452d 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java @@ -41,6 +41,7 @@ package org.graalvm.wasm; import static org.graalvm.wasm.Assert.assertByteEqual; +import static org.graalvm.wasm.Assert.assertFunctionTypeEquals; import static org.graalvm.wasm.Assert.assertTrue; import static org.graalvm.wasm.Assert.assertUnsignedIntGreaterOrEqual; import static org.graalvm.wasm.Assert.assertUnsignedIntLess; @@ -79,10 +80,12 @@ import org.graalvm.wasm.Linker.ResolutionDag.ExportGlobalSym; import org.graalvm.wasm.Linker.ResolutionDag.ExportMemorySym; import org.graalvm.wasm.Linker.ResolutionDag.ExportTableSym; +import org.graalvm.wasm.Linker.ResolutionDag.ExportTagSym; import org.graalvm.wasm.Linker.ResolutionDag.ImportFunctionSym; import org.graalvm.wasm.Linker.ResolutionDag.ImportGlobalSym; import org.graalvm.wasm.Linker.ResolutionDag.ImportMemorySym; import org.graalvm.wasm.Linker.ResolutionDag.ImportTableSym; +import org.graalvm.wasm.Linker.ResolutionDag.ImportTagSym; import org.graalvm.wasm.Linker.ResolutionDag.InitializeGlobalSym; import org.graalvm.wasm.Linker.ResolutionDag.Resolver; import org.graalvm.wasm.Linker.ResolutionDag.Sym; @@ -490,7 +493,7 @@ void resolveMemoryImport(WasmStore store, WasmInstance instance, ImportDescripto final WasmMemory importedMemory; final WasmMemory externalMemory = lookupImportObject(instance, importDescriptor, imports, WasmMemory.class); if (externalMemory != null) { - final int contextMemoryIndex = store.memories().registerExternal(externalMemory); + final int contextMemoryIndex = store.memories().register(externalMemory); importedMemory = store.memories().memory(contextMemoryIndex); assert memoryIndex == importDescriptor.targetIndex(); } else { @@ -539,6 +542,49 @@ void resolveMemoryExport(WasmInstance instance, int memoryIndex, String exported }); } + void resolveTagImport(WasmStore store, WasmInstance instance, ImportDescriptor importDescriptor, int tagIndex, SymbolTable.FunctionType type, ImportValueSupplier imports) { + final String importedModuleName = importDescriptor.moduleName(); + final String importedTagName = importDescriptor.memberName(); + final Runnable resolveAction = () -> { + final WasmTag importedTag; + final WasmTag externalTag = lookupImportObject(instance, importDescriptor, imports, WasmTag.class); + if (externalTag != null) { + final int contextTagIndex = store.tags().register(externalTag); + importedTag = store.tags().tag(contextTagIndex); + assert tagIndex == importDescriptor.targetIndex(); + } else { + final WasmInstance importedInstance = store.lookupModuleInstance(importedModuleName); + if (importedInstance == null) { + throw WasmException.create(Failure.UNKNOWN_IMPORT, String.format("The module '%s', referenced in the import of tag '%s' in module '%s', does not exist", + importedModuleName, importedTagName, instance.name())); + } + final WasmModule importedModule = importedInstance.module(); + if (importedModule.exportedTags().isEmpty()) { + throw WasmException.create(Failure.UNKNOWN_IMPORT, + String.format("The imported module '%s' does not export any tags, so cannot resolve tag '%s' imported in module '%s'.", + importedModuleName, importedTagName, instance.name())); + } + final Integer exportedTagIndex = importedModule.exportedTags().get(importedTagName); + if (exportedTagIndex == null) { + throw WasmException.create(Failure.UNKNOWN_IMPORT, + "Tag '" + importedTagName + "', imported into module '" + instance.name() + "', was not exported in the module '" + importedModuleName + "'."); + } + importedTag = importedInstance.tag(exportedTagIndex); + } + assertFunctionTypeEquals(type, importedTag.type(), Failure.INCOMPATIBLE_IMPORT_TYPE); + instance.setTag(tagIndex, importedTag); + }; + resolutionDag.resolveLater(new ImportTagSym(instance.name(), importDescriptor, tagIndex), new Sym[]{new ExportTagSym(importedModuleName, importedTagName)}, resolveAction); + } + + void resolveTagExport(WasmInstance instance, int tagIndex, String exportedTagName) { + final WasmModule module = instance.module(); + final ImportDescriptor importDescriptor = module.symbolTable().importedTag(tagIndex); + final Sym[] dependencies = importDescriptor != null ? new Sym[]{new ImportTagSym(module.name(), importDescriptor, tagIndex)} : ResolutionDag.NO_DEPENDENCIES; + resolutionDag.resolveLater(new ExportTagSym(module.name(), exportedTagName), dependencies, () -> { + }); + } + private static Object lookupGlobal(WasmInstance instance, int index) { final SymbolTable symbolTable = instance.symbolTable(); final byte type = symbolTable.globalValueType(index); @@ -782,7 +828,7 @@ void resolveTableImport(WasmStore store, WasmInstance instance, ImportDescriptor final int tableAddress; if (externalTable != null) { assert tableIndex == importDescriptor.targetIndex(); - tableAddress = store.tables().registerExternal(externalTable); + tableAddress = store.tables().register(externalTable); } else { final WasmInstance importedInstance = store.lookupModuleInstance(importDescriptor.moduleName()); final String importedModuleName = importDescriptor.moduleName(); @@ -1201,6 +1247,63 @@ public boolean equals(Object object) { } } + static class ImportTagSym extends Sym { + final ImportDescriptor importDescriptor; + final int tagIndex; + + ImportTagSym(String moduleName, ImportDescriptor importDescriptor, int tagIndex) { + super(moduleName); + this.importDescriptor = importDescriptor; + this.tagIndex = tagIndex; + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "(import tag %s from %s into %s with index %d)", + importDescriptor.memberName(), importDescriptor.moduleName(), moduleName, tagIndex); + } + + @Override + public int hashCode() { + return moduleName.hashCode() ^ importDescriptor.hashCode() ^ tagIndex; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ImportTagSym that)) { + return false; + } + return this.moduleName.equals(that.moduleName) && this.importDescriptor.equals(that.importDescriptor) && this.tagIndex == that.tagIndex; + } + } + + static class ExportTagSym extends Sym { + final String tagName; + + ExportTagSym(String moduleName, String tagName) { + super(moduleName); + this.tagName = tagName; + } + + @Override + public String toString() { + return String.format("(export tag %s from %s)", tagName, moduleName); + } + + @Override + public int hashCode() { + return moduleName.hashCode() ^ tagName.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ExportTagSym that)) { + return false; + } + return this.moduleName.equals(that.moduleName) && this.tagName.equals(that.tagName); + } + } + static class DataSym extends Sym { final int dataSegmentId; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/MemoryRegistry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/MemoryRegistry.java index 082cbf51a01c..6626700053fe 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/MemoryRegistry.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/MemoryRegistry.java @@ -76,10 +76,6 @@ public int register(WasmMemory memory) { return index; } - public int registerExternal(WasmMemory externalMemory) { - return register(externalMemory); - } - public WasmMemory memory(int index) { assert index < numMemories; return memories[index]; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java index 1c4aab15b47c..d998cd662db3 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java @@ -40,14 +40,14 @@ */ package org.graalvm.wasm; -import org.graalvm.wasm.exception.Failure; - import static org.graalvm.wasm.Assert.assertUnsignedIntLessOrEqual; import static org.graalvm.wasm.Assert.assertUnsignedLongLessOrEqual; import static org.graalvm.wasm.constants.Sizes.MAX_MEMORY_64_INSTANCE_SIZE; import static org.graalvm.wasm.constants.Sizes.MAX_MEMORY_INSTANCE_SIZE; import static org.graalvm.wasm.constants.Sizes.MAX_TABLE_INSTANCE_SIZE; +import org.graalvm.wasm.exception.Failure; + /** * Limits on various aspects of a module. */ @@ -73,11 +73,12 @@ public final class ModuleLimits { private final int tableInstanceSizeLimit; private final int memoryInstanceSizeLimit; private final long memory64InstanceSizeLimit; + private final int tagCountLimit; public ModuleLimits(int moduleSizeLimit, int typeCountLimit, int functionCountLimit, int tableCountLimit, int memoryCountLimit, int importCountLimit, int exportCountLimit, int globalCountLimit, int dataSegmentCountLimit, int elementSegmentCountLimit, int functionSizeLimit, int paramCountLimit, int resultCountLimit, int localCountLimit, - int tableInstanceSizeLimit, int memoryInstanceSizeLimit, long memory64InstanceSizeLimit) { + int tableInstanceSizeLimit, int memoryInstanceSizeLimit, long memory64InstanceSizeLimit, int tagCountLimit) { this.moduleSizeLimit = minUnsigned(moduleSizeLimit, Integer.MAX_VALUE); this.typeCountLimit = minUnsigned(typeCountLimit, Integer.MAX_VALUE); this.functionCountLimit = minUnsigned(functionCountLimit, Integer.MAX_VALUE); @@ -95,6 +96,7 @@ public ModuleLimits(int moduleSizeLimit, int typeCountLimit, int functionCountLi this.tableInstanceSizeLimit = minUnsigned(tableInstanceSizeLimit, MAX_TABLE_INSTANCE_SIZE); this.memoryInstanceSizeLimit = minUnsigned(memoryInstanceSizeLimit, MAX_MEMORY_INSTANCE_SIZE); this.memory64InstanceSizeLimit = minUnsigned(memory64InstanceSizeLimit, MAX_MEMORY_64_INSTANCE_SIZE); + this.tagCountLimit = minUnsigned(tagCountLimit, Integer.MAX_VALUE); } private static int minUnsigned(int a, int b) { @@ -122,7 +124,8 @@ private static long minUnsigned(long a, long b) { Integer.MAX_VALUE, MAX_TABLE_INSTANCE_SIZE, MAX_MEMORY_INSTANCE_SIZE, - MAX_MEMORY_64_INSTANCE_SIZE); + MAX_MEMORY_64_INSTANCE_SIZE, + Integer.MAX_VALUE); public void checkModuleSize(int size) { assertUnsignedIntLessOrEqual(size, moduleSizeLimit, Failure.MODULE_SIZE_LIMIT_EXCEEDED); @@ -198,6 +201,10 @@ public void checkMemoryInstanceSize(long size, boolean indexType64) { } } + public void checkTagCount(int size) { + assertUnsignedIntLessOrEqual(size, tagCountLimit, Failure.TAG_COUNT_LIMIT_EXCEEDED); + } + public int tableInstanceSizeLimit() { return tableInstanceSizeLimit; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/RuntimeState.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/RuntimeState.java index 9244d8a82593..ecbc7c544f5f 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/RuntimeState.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/RuntimeState.java @@ -60,6 +60,7 @@ public abstract class RuntimeState { private static final int INITIAL_TABLES_SIZE = 1; private static final int INITIAL_MEMORIES_SIZE = 1; + private static final int INITIAL_TAG_SIZE = 1; private final WasmStore store; private final WasmModule module; @@ -85,6 +86,8 @@ public abstract class RuntimeState { @CompilationFinal(dimensions = 1) private WasmMemory[] memories; + @CompilationFinal(dimensions = 1) private WasmTag[] tags; + /** * The passive elem instances that can be used to lazily initialize tables. They can potentially * be dropped after using them. They can be set to null even in compiled code, therefore they @@ -133,12 +136,21 @@ private void ensureMemoriesCapacity(int index) { } } + private void ensureTagCapacity(int index) { + if (index >= tags.length) { + final WasmTag[] nTags = new WasmTag[Math.max(Integer.highestOneBit(index) << 1, 2 * tags.length)]; + System.arraycopy(tags, 0, nTags, 0, tags.length); + tags = nTags; + } + } + public RuntimeState(WasmStore store, WasmModule module, int numberOfFunctions, int droppedDataInstanceOffset) { this.store = store; this.module = module; this.globals = new GlobalRegistry(module.numInternalGlobals(), module.numExternalGlobals()); this.tableAddresses = new int[INITIAL_TABLES_SIZE]; this.memories = new WasmMemory[INITIAL_MEMORIES_SIZE]; + this.tags = new WasmTag[INITIAL_TAG_SIZE]; this.targets = new CallTarget[numberOfFunctions]; this.functionInstances = new WasmFunctionInstance[numberOfFunctions]; this.linkState = Linker.LinkState.nonLinked; @@ -275,6 +287,16 @@ public void setMemory(int index, WasmMemory memory) { memories[index] = memory; } + public WasmTag tag(int index) { + return tags[index]; + } + + public void setTag(int index, WasmTag tag) { + ensureTagCapacity(index); + checkNotLinked(); + tags[index] = tag; + } + public WasmFunctionInstance functionInstance(WasmFunction function) { int functionIndex = function.index(); WasmFunctionInstance functionInstance = functionInstances[functionIndex]; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java index 0ae03dd76b3d..619457de6b57 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java @@ -76,6 +76,7 @@ public abstract class SymbolTable { private static final int INITIAL_DATA_SIZE = 512; private static final int INITIAL_TYPE_SIZE = 128; private static final int INITIAL_FUNCTION_TYPES_SIZE = 128; + private static final int INITIAL_TAG_TYPE_SIZE = 1; private static final byte GLOBAL_MUTABLE_BIT = 0x01; private static final byte GLOBAL_EXPORTED_BIT = 0x02; private static final byte GLOBAL_INITIALIZED_BIT = 0x04; @@ -97,6 +98,10 @@ public static final class FunctionType { this.hashCode = Arrays.hashCode(paramTypes) ^ Arrays.hashCode(resultTypes); } + public static FunctionType create(byte[] paramTypes, byte[] resultTypes) { + return new FunctionType(paramTypes, resultTypes); + } + public byte[] paramTypes() { return paramTypes; } @@ -172,6 +177,13 @@ public record TableInfo(int initialSize, int maximumSize, byte elemType) { public record MemoryInfo(long initialSize, long maximumSize, boolean indexType64, boolean shared) { } + /** + * @param attribute Attribute of the tag. + * @param typeIndex The type index of the tag. + */ + public record TagInfo(byte attribute, int typeIndex) { + } + /** * Encodes the parameter and result types of each function type. *

@@ -320,7 +332,7 @@ public record MemoryInfo(long initialSize, long maximumSize, boolean indexType64 @CompilationFinal private int numGlobalInitializersBytecode; /** - * The descriptor of the table of this module. + * The descriptor of the tables of this module. *

* In the current WebAssembly specification, a module can use at most one table. The value * {@code null} denotes that this module uses no table. @@ -340,22 +352,39 @@ public record MemoryInfo(long initialSize, long maximumSize, boolean indexType64 @CompilationFinal private final EconomicMap exportedTables; /** - * The descriptors of the memory of this module. + * The descriptors of the memories of this module. */ @CompilationFinal(dimensions = 1) private MemoryInfo[] memories; @CompilationFinal private int memoryCount; /** - * The memory used in this module. + * The memories used in this module. */ @CompilationFinal private final EconomicMap importedMemories; /** - * The name(s) of the exported memory of this module, if any. + * The name(s) of the exported memories of this module, if any. */ @CompilationFinal private final EconomicMap exportedMemories; + /** + * The descriptors of the tags of this module. + */ + @CompilationFinal(dimensions = 1) private TagInfo[] tags; + + @CompilationFinal private int tagCount; + + /** + * The tags used in this module. + */ + @CompilationFinal private final EconomicMap importedTags; + + /** + * The name(s) of the exported tags of this module, if any. + */ + @CompilationFinal private final EconomicMap exportedTags; + /** * List of all custom sections. */ @@ -423,6 +452,9 @@ public record MemoryInfo(long initialSize, long maximumSize, boolean indexType64 this.memoryCount = 0; this.importedMemories = EconomicMap.create(); this.exportedMemories = EconomicMap.create(); + this.tags = new TagInfo[INITIAL_TAG_TYPE_SIZE]; + this.importedTags = EconomicMap.create(); + this.exportedTags = EconomicMap.create(); this.customSections = new ArrayList<>(); this.elemSegmentCount = 0; this.dataCountExists = false; @@ -1120,6 +1152,95 @@ public boolean memoryIsShared(int index) { return memory.shared; } + private void ensureTagCapacity(int index) { + if (index >= tags.length) { + final TagInfo[] nTags = new TagInfo[Math.max(Integer.highestOneBit(index) << 1, 2 * tags.length)]; + System.arraycopy(tags, 0, nTags, 0, tags.length); + tags = nTags; + } + } + + public void allocateTag(int index, byte attribute, int typeIndex) { + checkNotParsed(); + addTag(index, attribute, typeIndex); + module().addLinkAction((context, store, instance, imports) -> { + final WasmTag tag = new WasmTag(typeAt(typeIndex)); + final int tagAddress = store.tags().register(tag); + final WasmTag allocatedTag = store.tags().tag(tagAddress); + instance.setTag(index, allocatedTag); + }); + } + + public void importTag(String moduleName, String tagName, int index, byte attribute, int typeIndex) { + checkNotParsed(); + addTag(index, attribute, typeIndex); + final ImportDescriptor importedTag = new ImportDescriptor(moduleName, tagName, ImportIdentifier.TAG, index, numImportedSymbols()); + final FunctionType type = typeAt(typeIndex); + importedTags.put(index, importedTag); + importSymbol(importedTag); + module().addLinkAction((context, store, instance, imports) -> { + store.linker().resolveTagImport(store, instance, importedTag, index, type, imports); + }); + } + + void addTag(int index, byte attribute, int typeIndex) { + ensureTagCapacity(index); + final TagInfo tag = new TagInfo(attribute, typeIndex); + tags[index] = tag; + tagCount++; + } + + public void checkTagIndex(int tagIndex) { + assertUnsignedIntLess(tagIndex, tagCount, Failure.UNKNOWN_TAG); + } + + public void exportTag(int tagIndex, String name) { + checkNotParsed(); + exportSymbol(name); + if (!checkExistingTagIndex(tagIndex)) { + throw WasmException.create(Failure.UNSPECIFIED_INVALID, "No tag with the specified index has been declared or imported, so it cannot be exported."); + } + exportedTags.put(name, tagIndex); + module().addLinkAction((context, store, instance, imports) -> { + store.linker().resolveTagExport(instance, tagIndex, name); + }); + } + + public int tagCount() { + return tagCount; + } + + public ImportDescriptor importedTag(int index) { + return importedTags.get(index); + } + + public EconomicMap importTagDescriptors() { + final EconomicMap reverseMap = EconomicMap.create(); + final MapCursor cursor = importedTags.getEntries(); + while (cursor.advance()) { + reverseMap.put(cursor.getValue(), cursor.getKey()); + } + return reverseMap; + } + + public EconomicMap exportedTags() { + return exportedTags; + } + + private boolean checkExistingTagIndex(int index) { + return Integer.compareUnsigned(index, tagCount) < 0; + } + + public int tagTypeIndex(int index) { + assert index < tags.length; + return tags[index].typeIndex(); + } + + public byte tagAttribute(int index) { + assert index < tags.length; + return tags[index].attribute(); + } + void allocateCustomSection(String name, int offset, int length) { customSections.add(new WasmCustomSection(name, offset, length)); } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TableRegistry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TableRegistry.java index bd637eb7cd17..6bd47231718a 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TableRegistry.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TableRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -73,10 +73,6 @@ public int register(WasmTable table) { return index; } - public int registerExternal(WasmTable table) { - return register(table); - } - public WasmTable table(int index) { assert index < numTables; return tables[index]; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TagRegistry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TagRegistry.java new file mode 100644 index 000000000000..a7b0c645337d --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TagRegistry.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.wasm; + +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; + +public class TagRegistry { + private static final int INITIAL_TAG_SIZE = 2; + + @CompilationFinal(dimensions = 1) private WasmTag[] tags; + private int numTags; + + public TagRegistry() { + this.tags = new WasmTag[INITIAL_TAG_SIZE]; + this.numTags = 0; + } + + private void ensureCapacity() { + if (numTags == tags.length) { + final WasmTag[] updatedTags = new WasmTag[tags.length * 2]; + System.arraycopy(tags, 0, updatedTags, 0, numTags); + tags = updatedTags; + } + } + + public int count() { + return numTags; + } + + public int register(WasmTag tag) { + ensureCapacity(); + final int index = numTags; + tags[index] = tag; + numTags++; + return index; + } + + public WasmTag tag(int index) { + assert index < numTags; + return tags[index]; + } +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmCodeEntry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmCodeEntry.java index 3c9dcf85854c..e3161b57bd6d 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmCodeEntry.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmCodeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -50,11 +50,13 @@ public final class WasmCodeEntry { @CompilationFinal(dimensions = 1) private final byte[] localTypes; @CompilationFinal(dimensions = 1) private final byte[] resultTypes; private final BranchProfile errorBranch = BranchProfile.create(); + private final BranchProfile exceptionBranch = BranchProfile.create(); private final int numLocals; private final int resultCount; private final boolean usesMemoryZero; + private final int exceptionTableOffset; - public WasmCodeEntry(WasmFunction function, byte[] bytecode, byte[] localTypes, byte[] resultTypes, boolean usesMemoryZero) { + public WasmCodeEntry(WasmFunction function, byte[] bytecode, byte[] localTypes, byte[] resultTypes, boolean usesMemoryZero, int exceptionTableOffset) { this.function = function; this.bytecode = bytecode; this.localTypes = localTypes; @@ -62,6 +64,7 @@ public WasmCodeEntry(WasmFunction function, byte[] bytecode, byte[] localTypes, this.resultTypes = resultTypes; this.resultCount = resultTypes.length; this.usesMemoryZero = usesMemoryZero; + this.exceptionTableOffset = exceptionTableOffset; } public WasmFunction function() { @@ -96,10 +99,18 @@ public void errorBranch() { errorBranch.enter(); } + public void exceptionBranch() { + exceptionBranch.enter(); + } + public boolean usesMemoryZero() { return usesMemoryZero; } + public int exceptionTableOffset() { + return exceptionTableOffset; + } + @Override public String toString() { return "wasm-code-entry:" + functionIndex(); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContextOptions.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContextOptions.java index 3547f5dc99f4..70667ba2e074 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContextOptions.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContextOptions.java @@ -60,6 +60,7 @@ public final class WasmContextOptions { @CompilationFinal private boolean threads; @CompilationFinal private boolean simd; @CompilationFinal private boolean relaxedSimd; + @CompilationFinal private boolean exceptions; @CompilationFinal private boolean memoryOverheadMode; @CompilationFinal private boolean constantRandomGet; @@ -91,6 +92,7 @@ private void setOptionValues() { this.unsafeMemory = readBooleanOption(WasmOptions.UseUnsafeMemory); this.simd = readBooleanOption(WasmOptions.SIMD); this.relaxedSimd = readBooleanOption(WasmOptions.RelaxedSIMD); + this.exceptions = readBooleanOption(WasmOptions.Exceptions); this.memoryOverheadMode = readBooleanOption(WasmOptions.MemoryOverheadMode); this.constantRandomGet = readBooleanOption(WasmOptions.WasiConstantRandomGet); this.directByteBufferMemoryAccess = readBooleanOption(WasmOptions.DirectByteBufferMemoryAccess); @@ -159,6 +161,10 @@ public boolean supportRelaxedSIMD() { return relaxedSimd; } + public boolean supportExceptions() { + return exceptions; + } + public boolean memoryOverheadMode() { return memoryOverheadMode; } @@ -192,6 +198,7 @@ public int hashCode() { hash = 53 * hash + (this.unsafeMemory ? 1 : 0); hash = 53 * hash + (this.simd ? 1 : 0); hash = 53 * hash + (this.relaxedSimd ? 1 : 0); + hash = 53 * hash + (this.exceptions ? 1 : 0); hash = 53 * hash + (this.memoryOverheadMode ? 1 : 0); hash = 53 * hash + (this.constantRandomGet ? 1 : 0); hash = 53 * hash + (this.directByteBufferMemoryAccess ? 1 : 0); @@ -241,6 +248,9 @@ public boolean equals(Object obj) { if (this.relaxedSimd != other.relaxedSimd) { return false; } + if (this.exceptions != other.exceptions) { + return false; + } if (this.memoryOverheadMode != other.memoryOverheadMode) { return false; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java index 505fc5158976..8231729cb088 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java @@ -198,6 +198,33 @@ static List recreateLinkActions(WasmModule module) { }); } + for (int i = 0; i < module.tagCount(); i++) { + final int tagIndex = i; + final int typeIndex = module.tagTypeIndex(tagIndex); + final SymbolTable.FunctionType type = module.typeAt(typeIndex); + final ImportDescriptor tagDescriptor = module.importedTag(tagIndex); + if (tagDescriptor != null) { + linkActions.add((context, store, instance, imports) -> { + store.linker().resolveTagImport(store, instance, tagDescriptor, tagIndex, type, imports); + }); + } else { + linkActions.add((context, store, instance, imports) -> { + final WasmTag tag = new WasmTag(type); + final int address = store.tags().register(tag); + final WasmTag allocatedTag = store.tags().tag(address); + instance.setTag(tagIndex, allocatedTag); + }); + } + } + final MapCursor exportedTags = module.exportedTags().getEntries(); + while (exportedTags.advance()) { + final String tagName = exportedTags.getKey(); + final int tagIndex = exportedTags.getValue(); + linkActions.add((context, store, instance, imports) -> { + store.linker().resolveTagExport(instance, tagIndex, tagName); + }); + } + final byte[] bytecode = module.bytecode(); for (int i = 0; i < module.dataInstanceCount(); i++) { @@ -500,7 +527,8 @@ private CallTarget instantiateCodeEntry(WasmStore store, WasmModule module, Wasm } private WasmFunctionRootNode instantiateCodeEntryRootNode(WasmStore store, WasmModule module, CodeEntry codeEntry, WasmFunction function) { - final WasmCodeEntry wasmCodeEntry = new WasmCodeEntry(function, module.bytecode(), codeEntry.localTypes(), codeEntry.resultTypes(), codeEntry.usesMemoryZero()); + final WasmCodeEntry wasmCodeEntry = new WasmCodeEntry(function, module.bytecode(), codeEntry.localTypes(), codeEntry.resultTypes(), codeEntry.usesMemoryZero(), + codeEntry.exceptionTableOffset()); final FrameDescriptor frameDescriptor = createFrameDescriptor(codeEntry.localTypes(), codeEntry.maxStackSize()); final Node[] callNodes = setupCallNodes(module, codeEntry); final WasmFixedMemoryImplFunctionNode functionNode = WasmFixedMemoryImplFunctionNode.create(module, wasmCodeEntry, codeEntry.bytecodeStartOffset(), codeEntry.bytecodeEndOffset(), callNodes); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmModule.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmModule.java index ff61dd18f33a..30cdd6b0d177 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmModule.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmModule.java @@ -196,10 +196,6 @@ public void setCodeEntries(CodeEntry[] codeEntries) { this.codeEntries = codeEntries; } - public boolean hasCodeEntries() { - return codeEntries != null; - } - public void setParsed() { isParsed = true; } @@ -346,6 +342,7 @@ private ImportValueSupplier resolveModuleImports(Object importObject, ExceptionP case ImportIdentifier.TABLE -> requireWasmTable(member, descriptor, exceptionProvider); case ImportIdentifier.MEMORY -> requireWasmMemory(member, descriptor, exceptionProvider); case ImportIdentifier.GLOBAL -> requireWasmGlobal(member, descriptor, exceptionProvider); + case ImportIdentifier.TAG -> requireWasmTag(member, descriptor, exceptionProvider); default -> throw WasmException.create(Failure.UNSPECIFIED_INTERNAL, "Unknown import descriptor type: " + descriptor.identifier()); }); } @@ -414,4 +411,11 @@ private static WasmGlobal requireWasmGlobal(Object member, ImportDescriptor impo } return global; } + + private static WasmTag requireWasmTag(Object member, ImportDescriptor importDescriptor, ExceptionProvider exceptionProvider) { + if (!(member instanceof WasmTag tag)) { + throw exceptionProvider.createLinkError(Failure.INCOMPATIBLE_IMPORT_TYPE, "Member " + member + " " + importDescriptor + " is not a valid tag."); + } + return tag; + } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmOptions.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmOptions.java index 0477b343700c..3b8100ebea56 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmOptions.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmOptions.java @@ -136,6 +136,9 @@ public enum ConstantsStorePolicy { category = OptionCategory.EXPERT, stability = OptionStability.EXPERIMENTAL, usageSyntax = "true|false") // public static final OptionKey RelaxedSIMD = new OptionKey<>(false); + @Option(help = "Enable support for exception handling", category = OptionCategory.EXPERT, stability = OptionStability.EXPERIMENTAL, usageSyntax = "false|true") // + public static final OptionKey Exceptions = new OptionKey<>(false); + @Option(help = "In this mode memories and tables are not initialized.", category = OptionCategory.INTERNAL, stability = OptionStability.EXPERIMENTAL, usageSyntax = "false|true") // public static final OptionKey MemoryOverheadMode = new OptionKey<>(false); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmStore.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmStore.java index 9fad7f7c479f..43f684d6700b 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmStore.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmStore.java @@ -70,6 +70,7 @@ public final class WasmStore implements TruffleObject { private final WasmLanguage language; private final MemoryRegistry memoryRegistry; private final TableRegistry tableRegistry; + private final TagRegistry tagRegistry; private final Linker linker; private final Map moduleInstances; private final FdManager filesManager; @@ -81,6 +82,7 @@ public WasmStore(WasmContext context, WasmLanguage language) { this.contextOptions = context.getContextOptions(); this.tableRegistry = new TableRegistry(); this.memoryRegistry = new MemoryRegistry(); + this.tagRegistry = new TagRegistry(); this.moduleInstances = new LinkedHashMap<>(); this.linker = new Linker(); this.filesManager = context.fdManager(); @@ -106,6 +108,10 @@ public TableRegistry tables() { return tableRegistry; } + public TagRegistry tags() { + return tagRegistry; + } + public Linker linker() { return linker; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmTable.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmTable.java index 39b9dbeac11b..dc5d0813bd05 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmTable.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmTable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -46,9 +46,9 @@ import java.util.Arrays; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import org.graalvm.wasm.constants.Sizes; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.interop.TruffleObject; public final class WasmTable extends EmbedderDataHolder implements TruffleObject { @@ -92,7 +92,7 @@ private WasmTable(int declaredMinSize, int declaredMaxSize, int initialSize, int assert compareUnsigned(maxAllowedSize, declaredMaxSize) <= 0; assert compareUnsigned(maxAllowedSize, MAX_TABLE_INSTANCE_SIZE) <= 0; assert compareUnsigned(declaredMaxSize, MAX_TABLE_DECLARATION_SIZE) <= 0; - assert elemType == WasmType.FUNCREF_TYPE || elemType == WasmType.EXTERNREF_TYPE; + assert WasmType.isReferenceType(elemType); this.declaredMinSize = declaredMinSize; this.declaredMaxSize = declaredMaxSize; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmTag.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmTag.java new file mode 100644 index 000000000000..1e0ecf0afd03 --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmTag.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.wasm; + +import com.oracle.truffle.api.interop.TruffleObject; + +public final class WasmTag extends EmbedderDataHolder implements TruffleObject { + public static final class Attribute { + public static final int EXCEPTION = 0; + } + + private final SymbolTable.FunctionType type; + + public WasmTag(SymbolTable.FunctionType type) { + this.type = type; + } + + public SymbolTable.FunctionType type() { + return type; + } +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmType.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmType.java index b90c85f4988f..9edc05402c12 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmType.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -86,6 +86,8 @@ public class WasmType implements TruffleObject { @CompilationFinal(dimensions = 1) public static final byte[] FUNCREF_TYPE_ARRAY = {FUNCREF_TYPE}; public static final byte EXTERNREF_TYPE = 0x6F; @CompilationFinal(dimensions = 1) public static final byte[] EXTERNREF_TYPE_ARRAY = {EXTERNREF_TYPE}; + public static final byte EXNREF_TYPE = 0x69; + @CompilationFinal(dimensions = 1) public static final byte[] EXNREF_TYPE_ARRAY = {EXNREF_TYPE}; public static final WasmType VOID = new WasmType("void"); public static final WasmType NULL = new WasmType("null"); @@ -118,6 +120,8 @@ public static String toString(int valueType) { return "funcref"; case EXTERNREF_TYPE: return "externref"; + case EXNREF_TYPE: + return "exnref"; default: throw WasmException.create(Failure.UNSPECIFIED_INTERNAL, null, "Unknown value type: 0x" + Integer.toHexString(valueType)); } @@ -132,7 +136,7 @@ public static boolean isVectorType(byte type) { } public static boolean isReferenceType(byte type) { - return type == FUNCREF_TYPE || type == EXTERNREF_TYPE || type == UNKNOWN_TYPE; + return type == FUNCREF_TYPE || type == EXTERNREF_TYPE || type == EXNREF_TYPE || type == UNKNOWN_TYPE; } public static int getCommonValueType(byte[] types) { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ExecuteHostFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ExecuteHostFunctionNode.java index 9eb4bcafcd79..3e9440eac6af 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ExecuteHostFunctionNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ExecuteHostFunctionNode.java @@ -40,7 +40,6 @@ */ package org.graalvm.wasm.api; -import com.oracle.truffle.api.nodes.RootNode; import org.graalvm.wasm.WasmArguments; import org.graalvm.wasm.WasmConstant; import org.graalvm.wasm.WasmContext; @@ -64,6 +63,7 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.profiles.BranchProfile; /** @@ -132,7 +132,7 @@ private Object convertResult(Object result, byte resultType) throws UnsupportedM case WasmType.I64_TYPE -> asLong(result); case WasmType.F32_TYPE -> asFloat(result); case WasmType.F64_TYPE -> asDouble(result); - case WasmType.V128_TYPE, WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE -> result; + case WasmType.V128_TYPE, WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> result; default -> { throw WasmException.format(Failure.UNSPECIFIED_TRAP, this, "Unknown result type: %d", resultType); } @@ -172,7 +172,7 @@ private void pushMultiValueResult(Object result, int resultCount) { } objectMultiValueStack[i] = value; } - case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE -> objectMultiValueStack[i] = value; + case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> objectMultiValueStack[i] = value; default -> { errorBranch.enter(); throw WasmException.format(Failure.UNSPECIFIED_TRAP, this, "Unknown result type: %d", resultType); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/FuncType.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/FuncType.java new file mode 100644 index 000000000000..b4cbb603842d --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/FuncType.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.wasm.api; + +import org.graalvm.wasm.SymbolTable; +import org.graalvm.wasm.exception.WasmJsApiException; + +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; + +/** + * Represents the type of functions and exceptions in the JS API. + */ +public final class FuncType { + private static final ValueType[] EMTPY = {}; + + @CompilationFinal(dimensions = 1) private final ValueType[] params; + @CompilationFinal(dimensions = 1) private final ValueType[] results; + + private FuncType(ValueType[] params, ValueType[] results) { + this.params = params; + this.results = results; + } + + public static FuncType fromString(String s) { + final int leftPar = s.indexOf('('); + final int rightPar = s.indexOf(')'); + if (leftPar == -1 || rightPar == -1) { + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "Invalid function type format"); + } + final String paramTypesString = s.substring(leftPar + 1, rightPar); + final ValueType[] params; + if (paramTypesString.isEmpty()) { + params = EMTPY; + } else { + final String[] paramTypes = s.substring(leftPar + 1, rightPar).split("\\s"); + params = new ValueType[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + params[i] = ValueType.valueOf(paramTypes[i]); + } + } + final String resultTypesString = s.substring(rightPar + 1); + final ValueType[] results; + if (resultTypesString.isEmpty()) { + results = EMTPY; + } else { + final String[] resultTypes = s.substring(rightPar + 1).split("\\s"); + results = new ValueType[resultTypes.length]; + for (int i = 0; i < resultTypes.length; i++) { + results[i] = ValueType.valueOf(resultTypes[i]); + } + } + return new FuncType(params, results); + } + + public static FuncType fromFunctionType(SymbolTable.FunctionType functionType) { + final byte[] paramTypes = functionType.paramTypes(); + final byte[] resultTypes = functionType.resultTypes(); + + final ValueType[] params = new ValueType[paramTypes.length]; + final ValueType[] results = new ValueType[resultTypes.length]; + + for (int i = 0; i < paramTypes.length; i++) { + params[i] = ValueType.fromByteValue(paramTypes[i]); + } + for (int i = 0; i < resultTypes.length; i++) { + results[i] = ValueType.fromByteValue(resultTypes[i]); + } + return new FuncType(params, results); + } + + public ValueType paramTypeAt(int index) { + return params[index]; + } + + public int paramCount() { + return params.length; + } + + public ValueType resultTypeAt(int index) { + return results[index]; + } + + public int resultCount() { + return results.length; + } + + public SymbolTable.FunctionType toFunctionType() { + final byte[] paramTypes = new byte[params.length]; + final byte[] resultTypes = new byte[results.length]; + + for (int i = 0; i < paramTypes.length; i++) { + paramTypes[i] = params[i].byteValue(); + } + for (int i = 0; i < resultTypes.length; i++) { + resultTypes[i] = results[i].byteValue(); + } + return SymbolTable.FunctionType.create(paramTypes, resultTypes); + } + + public String toString(StringBuilder b) { + b.append("("); + for (int i = 0; i < params.length; i++) { + if (i != 0) { + b.append(' '); + } + b.append(params[i]); + } + b.append(')'); + for (int i = 0; i < results.length; i++) { + if (i != 0) { + b.append(' '); + } + b.append(results[i]); + } + return b.toString(); + } + + @Override + public String toString() { + return toString(new StringBuilder()); + } +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ImportExportKind.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ImportExportKind.java index 48d144342d61..3fc7c8fe697c 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ImportExportKind.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ImportExportKind.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -46,7 +46,8 @@ public enum ImportExportKind { function, table, memory, - global; + global, + tag; @CompilerDirectives.TruffleBoundary public static ImportExportKind parse(String name) { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/InteropCallAdapterNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/InteropCallAdapterNode.java index 4325c30629e0..c74567a05517 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/InteropCallAdapterNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/InteropCallAdapterNode.java @@ -49,6 +49,7 @@ import org.graalvm.wasm.WasmType; import org.graalvm.wasm.exception.Failure; import org.graalvm.wasm.exception.WasmException; +import org.graalvm.wasm.exception.WasmRuntimeException; import org.graalvm.wasm.nodes.WasmIndirectCallNode; import com.oracle.truffle.api.CallTarget; @@ -170,6 +171,11 @@ private static void validateArgument(Object[] arguments, int offset, byte[] para case WasmType.EXTERNREF_TYPE -> { return; } + case WasmType.EXNREF_TYPE -> { + if (value instanceof WasmRuntimeException || value == WasmConstant.NULL) { + return; + } + } default -> throw WasmException.create(Failure.UNKNOWN_TYPE); } throw UnsupportedTypeException.create(arguments); @@ -209,7 +215,7 @@ private static Object popMultiValueResult(long[] primitiveMultiValueStack, Objec case WasmType.I64_TYPE -> primitiveMultiValueStack[i]; case WasmType.F32_TYPE -> Float.intBitsToFloat((int) primitiveMultiValueStack[i]); case WasmType.F64_TYPE -> Double.longBitsToDouble(primitiveMultiValueStack[i]); - case WasmType.V128_TYPE, WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE -> { + case WasmType.V128_TYPE, WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> { Object obj = objectMultiValueStack[i]; objectMultiValueStack[i] = null; yield obj; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java index 74f8522b41c0..ba602c5772d9 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java @@ -64,6 +64,7 @@ private JsConstants() { private static final int LOCAL_COUNT_LIMIT = 50000; private static final int TABLE_SIZE_LIMIT = 10000000; private static final int MEMORY_SIZE_LIMIT = 65536; + private static final int TAG_COUNT_LIMIT = 10000000; public static final ModuleLimits JS_LIMITS = new ModuleLimits( MODULE_SIZE_LIMIT, @@ -82,5 +83,6 @@ private JsConstants() { LOCAL_COUNT_LIMIT, TABLE_SIZE_LIMIT, MEMORY_SIZE_LIMIT, - MEMORY_SIZE_LIMIT); + MEMORY_SIZE_LIMIT, + TAG_COUNT_LIMIT); } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/TableKind.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/TableKind.java index 0c64d53d2a68..3e565932388b 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/TableKind.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/TableKind.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -44,7 +44,8 @@ public enum TableKind { externref(WasmType.EXTERNREF_TYPE), - anyfunc(WasmType.FUNCREF_TYPE); + anyfunc(WasmType.FUNCREF_TYPE), + exnref(WasmType.EXNREF_TYPE); private final byte byteValue; @@ -57,13 +58,11 @@ public byte byteValue() { } public static String toString(byte byteValue) { - switch (byteValue) { - case WasmType.EXTERNREF_TYPE: - return "externref"; - case WasmType.FUNCREF_TYPE: - return "anyfunc"; - default: - return ""; - } + return switch (byteValue) { + case WasmType.EXTERNREF_TYPE -> "externref"; + case WasmType.FUNCREF_TYPE -> "anyfunc"; + case WasmType.EXNREF_TYPE -> "exnref"; + default -> ""; + }; } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ValueType.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ValueType.java index a498188d96bb..61c707315525 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ValueType.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/ValueType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -53,7 +53,8 @@ public enum ValueType { f64(WasmType.F64_TYPE), v128(WasmType.V128_TYPE), anyfunc(WasmType.FUNCREF_TYPE), - externref(WasmType.EXTERNREF_TYPE); + externref(WasmType.EXTERNREF_TYPE), + exnref(WasmType.EXNREF_TYPE); private final byte byteValue; @@ -63,24 +64,18 @@ public enum ValueType { public static ValueType fromByteValue(byte value) { CompilerAsserts.neverPartOfCompilation(); - switch (value) { - case WasmType.I32_TYPE: - return i32; - case WasmType.I64_TYPE: - return i64; - case WasmType.F32_TYPE: - return f32; - case WasmType.F64_TYPE: - return f64; - case WasmType.V128_TYPE: - return v128; - case WasmType.FUNCREF_TYPE: - return anyfunc; - case WasmType.EXTERNREF_TYPE: - return externref; - default: + return switch (value) { + case WasmType.I32_TYPE -> i32; + case WasmType.I64_TYPE -> i64; + case WasmType.F32_TYPE -> f32; + case WasmType.F64_TYPE -> f64; + case WasmType.V128_TYPE -> v128; + case WasmType.FUNCREF_TYPE -> anyfunc; + case WasmType.EXTERNREF_TYPE -> externref; + case WasmType.EXNREF_TYPE -> exnref; + default -> throw WasmException.create(Failure.UNSPECIFIED_INTERNAL, null, "Unknown value type: 0x" + Integer.toHexString(value)); - } + }; } public byte byteValue() { @@ -96,6 +91,6 @@ public static boolean isVectorType(ValueType valueType) { } public static boolean isReferenceType(ValueType valueType) { - return valueType == anyfunc || valueType == externref; + return valueType == anyfunc || valueType == externref || valueType == exnref; } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java index bf0e70b0f312..b2e899fbf8a7 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java @@ -63,11 +63,13 @@ import org.graalvm.wasm.WasmModule; import org.graalvm.wasm.WasmStore; import org.graalvm.wasm.WasmTable; +import org.graalvm.wasm.WasmTag; import org.graalvm.wasm.WasmType; import org.graalvm.wasm.constants.ImportIdentifier; import org.graalvm.wasm.exception.Failure; import org.graalvm.wasm.exception.WasmException; import org.graalvm.wasm.exception.WasmJsApiException; +import org.graalvm.wasm.exception.WasmRuntimeException; import org.graalvm.wasm.globals.WasmGlobal; import org.graalvm.wasm.memory.WasmMemory; import org.graalvm.wasm.memory.WasmMemoryFactory; @@ -113,6 +115,12 @@ public WebAssembly(WasmContext currentContext) { addMember("global_read", new Executable(WebAssembly::globalRead)); addMember("global_write", new Executable(this::globalWrite)); + addMember("tag_alloc", new Executable(WebAssembly::tagAlloc)); + addMember("tag_type", new Executable(WebAssembly::tagType)); + + addMember("exn_alloc", new Executable(this::exnAlloc)); + addMember("exn_read", new Executable(WebAssembly::exnRead)); + addMember("module_imports", new Executable(WebAssembly::moduleImports)); addMember("module_exports", new Executable(WebAssembly::moduleExports)); @@ -248,6 +256,7 @@ public static Sequence moduleExports(WasmModule module) final Integer globalIndex = module.exportedGlobals().get(name); final Integer tableIndex = module.exportedTables().get(name); final Integer memoryIndex = module.exportedMemories().get(name); + final Integer tagIndex = module.exportedTags().get(name); if (memoryIndex != null) { String shared = module.memoryIsShared(memoryIndex) ? "shared" : "single"; @@ -260,6 +269,8 @@ public static Sequence moduleExports(WasmModule module) String valueType = ValueType.fromByteValue(module.globalValueType(globalIndex)).toString(); String mutability = module.isGlobalMutable(globalIndex) ? "mut" : "con"; list.add(new ModuleExportDescriptor(name, ImportExportKind.global.name(), valueType + " " + mutability)); + } else if (tagIndex != null) { + list.add(new ModuleExportDescriptor(name, ImportExportKind.tag.name(), WebAssembly.tagTypeToString(module, tagIndex))); } else { throw WasmException.create(Failure.UNSPECIFIED_INTERNAL, "Exported symbol list does not match the actual exports."); } @@ -281,6 +292,7 @@ public static List moduleImportsAsList(WasmModule module final EconomicMap importedGlobalDescriptors = module.importedGlobalDescriptors(); final EconomicMap importedTableDescriptors = module.importedTableDescriptors(); final EconomicMap importedMemoryDescriptors = module.importedMemoryDescriptors(); + final EconomicMap importedTagDescriptors = module.importTagDescriptors(); final ArrayList list = new ArrayList<>(); for (ImportDescriptor descriptor : module.importedSymbols()) { switch (descriptor.identifier()) { @@ -305,10 +317,19 @@ public static List moduleImportsAsList(WasmModule module } break; case ImportIdentifier.GLOBAL: - final Integer index = importedGlobalDescriptors.get(descriptor); - String valueType = ValueType.fromByteValue(module.globalValueType(index)).toString(); + final Integer globalIndex = importedGlobalDescriptors.get(descriptor); + String valueType = ValueType.fromByteValue(module.globalValueType(globalIndex)).toString(); list.add(new ModuleImportDescriptor(descriptor.moduleName(), descriptor.memberName(), ImportExportKind.global.name(), valueType)); break; + case ImportIdentifier.TAG: + final Integer tagIndex = importedTagDescriptors.get(descriptor); + if (tagIndex != null) { + list.add(new ModuleImportDescriptor(descriptor.moduleName(), descriptor.memberName(), ImportExportKind.tag.name(), + WebAssembly.tagTypeToString(module, tagIndex))); + } else { + throw WasmException.create(Failure.UNSPECIFIED_INTERNAL, "Tag import inconsistent."); + } + break; default: throw WasmException.create(Failure.UNSPECIFIED_INTERNAL, "Unknown import descriptor type: " + descriptor.identifier()); } @@ -420,10 +441,10 @@ public WasmTable tableAlloc(int initial, int maximum, TableKind elemKind, Object if (Integer.compareUnsigned(initial, JS_LIMITS.tableInstanceSizeLimit()) > 0) { throw new WasmJsApiException(WasmJsApiException.Kind.RangeError, "Min table size exceeds implementation limit"); } - if (elemKind != TableKind.externref && elemKind != TableKind.anyfunc) { + if (elemKind != TableKind.externref && elemKind != TableKind.anyfunc && elemKind != TableKind.exnref) { throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "Element type must be a reftype"); } - if (!refTypes && elemKind == TableKind.externref) { + if (!refTypes && (elemKind == TableKind.externref || elemKind == TableKind.exnref)) { throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "Element type must be anyfunc. Enable reference types to support externref"); } final int maxAllowedSize = minUnsigned(maximum, JS_LIMITS.tableInstanceSizeLimit()); @@ -563,6 +584,15 @@ public static String functionTypeToString(WasmFunction f) { return typeInfo.toString(); } + private static String tagTypeToString(WasmModule module, int tagIndex) { + CompilerAsserts.neverPartOfCompilation(); + final int attribute = module.tagAttribute(tagIndex); + assert attribute == WasmTag.Attribute.EXCEPTION; + final int typeIndex = module.tagTypeIndex(tagIndex); + + return FuncType.fromFunctionType(module.typeAt(typeIndex)).toString(); + } + private static Object memAlloc(Object[] args) { checkArgumentCount(args, 1); InteropLibrary lib = InteropLibrary.getUncached(); @@ -895,6 +925,93 @@ public Object globalWrite(WasmGlobal global, Object value) { return WasmConstant.VOID; } + public static Object tagAlloc(Object[] args) { + checkArgumentCount(args, 1); + final InteropLibrary lib = InteropLibrary.getUncached(); + final FuncType type; + try { + final String typeString = lib.asString(args[0]); + type = FuncType.fromString(typeString); + } catch (UnsupportedMessageException e) { + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument (func type) must be convertible to String"); + } catch (IllegalArgumentException e) { + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "Invalid func type"); + } + return tagAlloc(type); + } + + public static WasmTag tagAlloc(FuncType type) { + return new WasmTag(type.toFunctionType()); + } + + public static Object tagType(Object[] args) { + checkArgumentCount(args, 1); + if (!(args[0] instanceof WasmTag tag)) { + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be a wasm tag"); + } + return tag.type().toString(); + } + + public Object exnAlloc(Object[] args) { + checkArgumentCount(args, 1); + if (!(args[0] instanceof WasmTag tag)) { + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be a wasm tag"); + } + final FuncType type = FuncType.fromFunctionType(tag.type()); + final int paramCount = type.paramCount(); + checkArgumentCount(args, paramCount + 1); + final Object[] fields = new Object[paramCount]; + for (int i = 0; i < paramCount; i++) { + final ValueType paramType = type.paramTypeAt(i); + final Object value = args[i + 1]; + switch (paramType) { + case i32: + if (!(value instanceof Integer)) { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "Param type %s, value: %s", paramType, value); + } + break; + case i64: + if (!(value instanceof Long)) { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "Param type %s, value: %s", paramType, value); + } + break; + case f32: + if (!(value instanceof Float)) { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "Param type %s, value: %s", paramType, value); + } + break; + case f64: + if (!(value instanceof Double)) { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "Param type %s, value: %s", paramType, value); + } + break; + case anyfunc: + if (!refTypes) { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "Invalid value type. Reference types are not enabled"); + } + if (!(value == WasmConstant.NULL || value instanceof WasmFunctionInstance)) { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "Param type %s, value: %s", paramType, value); + } + break; + case externref: + if (!refTypes) { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "Invalid value type. Reference types are not enabled"); + } + break; + } + fields[i] = value; + } + return new WasmRuntimeException(null, tag, fields); + } + + public static Object exnRead(Object[] args) { + checkArgumentCount(args, 1); + if (!(args[0] instanceof WasmRuntimeException exn)) { + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be a wasm tag"); + } + return exn.tag(); + } + private static Object instanceExport(Object[] args) { checkArgumentCount(args, 2); if (!(args[0] instanceof WasmInstance instance)) { @@ -912,6 +1029,7 @@ public static Object instanceExport(WasmInstance instance, String name) { Integer globalIndex = instance.module().exportedGlobals().get(name); Integer tableIndex = instance.module().exportedTables().get(name); Integer memoryIndex = instance.module().exportedMemories().get(name); + Integer tagIndex = instance.module().exportedTags().get(name); if (function != null) { return instance.functionInstance(function); @@ -922,8 +1040,10 @@ public static Object instanceExport(WasmInstance instance, String name) { } else if (tableIndex != null) { final int address = instance.tableAddress(tableIndex); return instance.store().tables().table(address); + } else if (tagIndex != null) { + return instance.tag(tagIndex); } else { - throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, name + " is not a exported name of the given instance"); + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, name + " is not an exported name of the given instance"); } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Bytecode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Bytecode.java index 4f8b0dd0ec71..50d177c05564 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Bytecode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Bytecode.java @@ -372,6 +372,10 @@ public class Bytecode { public static final int TABLE_SIZE = 0x1A; public static final int TABLE_FILL = 0x1B; + // Exception opcodes + public static final int THROW = 0x1C; + public static final int THROW_REF = 0x1D; + // Atomic opcodes public static final int ATOMIC_I32_LOAD = 0x00; public static final int ATOMIC_I64_LOAD = 0x01; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/BytecodeBitEncoding.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/BytecodeBitEncoding.java index 30b5841207f8..18d81b85860d 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/BytecodeBitEncoding.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/BytecodeBitEncoding.java @@ -139,6 +139,7 @@ public class BytecodeBitEncoding { public static final int ELEM_SEG_TYPE_FUNREF = 0b0001_0000; public static final int ELEM_SEG_TYPE_EXTERNREF = 0b0010_0000; + public static final int ELEM_SEG_TYPE_EXNREF = 0b0011_0000; public static final int ELEM_SEG_MODE_VALUE = 0b0000_1111; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ExceptionHandlerType.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ExceptionHandlerType.java new file mode 100644 index 000000000000..0a6bafdec74b --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ExceptionHandlerType.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.wasm.constants; + +public final class ExceptionHandlerType { + public static final int CATCH = 0x00; + public static final int CATCH_REF = 0x01; + public static final int CATCH_ALL = 0x02; + public static final int CATCH_ALL_REF = 0x03; +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ExportIdentifier.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ExportIdentifier.java index 48c7b5f2098d..73cc154a8f39 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ExportIdentifier.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ExportIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -45,6 +45,7 @@ public final class ExportIdentifier { public static final int TABLE = 0x01; public static final int MEMORY = 0x02; public static final int GLOBAL = 0x03; + public static final int TAG = 0x04; private ExportIdentifier() { } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ImportIdentifier.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ImportIdentifier.java index 7c70b351d7ff..b04e2268010b 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ImportIdentifier.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/ImportIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -45,6 +45,7 @@ public final class ImportIdentifier { public static final int TABLE = 0x01; public static final int MEMORY = 0x02; public static final int GLOBAL = 0x03; + public static final int TAG = 0x04; private ImportIdentifier() { } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Instructions.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Instructions.java index f73d8907dc06..4e9d4a1a9bea 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Instructions.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Instructions.java @@ -53,6 +53,10 @@ public final class Instructions { public static final int LOOP = 0x03; public static final int IF = 0x04; public static final int ELSE = 0x05; + + public static final int THROW = 0x08; + public static final int THROW_REF = 0xA; + public static final int END = 0x0B; public static final int BR = 0x0C; @@ -67,6 +71,8 @@ public final class Instructions { public static final int SELECT = 0x1B; public static final int SELECT_T = 0x1C; + public static final int TRY_TABLE = 0x1F; + public static final int LOCAL_GET = 0x20; public static final int LOCAL_SET = 0x21; public static final int LOCAL_TEE = 0x22; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/NameSection.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/NameSection.java new file mode 100644 index 000000000000..fd2ad3d95ffe --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/NameSection.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.wasm.constants; + +public final class NameSection { + public static final int MODULE_NAME = 0; + public static final int FUNCTION_NAME = 1; + public static final int LOCAL_NAME = 2; + public static final int TAG_NAME = 11; +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Section.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Section.java index 07b91f5d9884..e6f07b790008 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Section.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Section.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -54,8 +54,9 @@ public final class Section { public static final int CODE = 10; public static final int DATA = 11; public static final int DATA_COUNT = 12; + public static final int TAG = 13; - private static final int[] SECTION_ORDER = new int[13]; + private static final int[] SECTION_ORDER = new int[14]; public static final int LAST_SECTION_ID = SECTION_ORDER.length - 1; static { @@ -65,13 +66,14 @@ public final class Section { SECTION_ORDER[FUNCTION] = 3; SECTION_ORDER[TABLE] = 4; SECTION_ORDER[MEMORY] = 5; - SECTION_ORDER[GLOBAL] = 6; - SECTION_ORDER[EXPORT] = 7; - SECTION_ORDER[START] = 8; - SECTION_ORDER[ELEMENT] = 9; - SECTION_ORDER[DATA_COUNT] = 10; - SECTION_ORDER[CODE] = 11; - SECTION_ORDER[DATA] = 12; + SECTION_ORDER[TAG] = 6; + SECTION_ORDER[GLOBAL] = 7; + SECTION_ORDER[EXPORT] = 8; + SECTION_ORDER[START] = 9; + SECTION_ORDER[ELEMENT] = 10; + SECTION_ORDER[DATA_COUNT] = 11; + SECTION_ORDER[CODE] = 12; + SECTION_ORDER[DATA] = 13; } private Section() { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/Failure.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/Failure.java index 154162c96e95..d09b0a1d1b3f 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/Failure.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/Failure.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -66,6 +66,8 @@ public enum Failure { END_OPCODE_EXPECTED(Type.MALFORMED, "END opcode expected"), UNEXPECTED_CONTENT_AFTER_LAST_SECTION(Type.MALFORMED, "unexpected content after last section"), MALFORMED_MEMOP_FLAGS(Type.MALFORMED, "malformed memop flags"), + MALFORMED_CATCH(Type.MALFORMED, "malformed catch clause"), + MALFORMED_TAG_ATTRIBUTE(Type.MALFORMED, "malformed tag attribute"), // GraalWasm-specific: INVALID_SECTION_ORDER(Type.MALFORMED, "invalid section order"), DISABLED_MULTI_VALUE(Type.MALFORMED, "multi-value is not enabled"), @@ -101,6 +103,7 @@ public enum Failure { UNKNOWN_DATA_SEGMENT(Type.INVALID, "unknown data segment"), UNKNOWN_REFERENCE(Type.INVALID, "unknown reference"), UNDECLARED_FUNCTION_REFERENCE(Type.INVALID, "undeclared function reference"), + UNKNOWN_TAG(Type.INVALID, "unknown tag"), // GraalWasm-specific: MODULE_SIZE_LIMIT_EXCEEDED(Type.INVALID, "module size exceeds limit"), @@ -116,6 +119,7 @@ public enum Failure { FUNCTION_SIZE_LIMIT_EXCEEDED(Type.INVALID, "function size exceeds limit"), PARAMETERS_COUNT_LIMIT_EXCEEDED(Type.INVALID, "parameters count exceeds limit"), RESULT_COUNT_LIMIT_EXCEEDED(Type.INVALID, "result values count exceeds limit"), + TAG_COUNT_LIMIT_EXCEEDED(Type.INVALID, "tag count exceeds limit"), // TODO(mbovel): replace UNSPECIFIED_UNLINKABLE usages with appropriate errors. UNSPECIFIED_UNLINKABLE(Type.UNLINKABLE, "unspecified"), @@ -159,7 +163,8 @@ public enum Failure { NON_REPRESENTABLE_EXTRA_DATA_VALUE(Type.MALFORMED, "value cannot be represented in extra data"), - INVALID_LANE_INDEX(Type.INVALID, "invalid lane index"); + INVALID_LANE_INDEX(Type.INVALID, "invalid lane index"), + INVALID_CATCH_CLAUSE_LABEL(Type.INVALID, "invalid catch clause label"); public enum Type { TRAP("trap"), diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/WasmRuntimeException.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/WasmRuntimeException.java new file mode 100644 index 000000000000..ce475d5c8cc8 --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/WasmRuntimeException.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.wasm.exception; + +import org.graalvm.wasm.WasmTag; + +import com.oracle.truffle.api.exception.AbstractTruffleException; +import com.oracle.truffle.api.interop.ExceptionType; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.Node; + +/** + * Represents exceptions thrown and handled by WebAssembly functions. + */ +@SuppressWarnings({"serial", "static-method"}) +@ExportLibrary(InteropLibrary.class) +public final class WasmRuntimeException extends AbstractTruffleException { + private final WasmTag tag; + private final Object[] fields; + + public WasmRuntimeException(Node location, WasmTag tag, Object[] fields) { + super(location); + this.tag = tag; + this.fields = fields; + } + + public WasmTag tag() { + return tag; + } + + public Object[] fields() { + return fields; + } + + @ExportMessage + public boolean isException() { + return true; + } + + @ExportMessage + public RuntimeException throwException() { + throw this; + } + + @ExportMessage + public ExceptionType getExceptionType() { + return ExceptionType.RUNTIME_ERROR; + } + + @ExportMessage + public boolean hasArrayElements() { + return true; + } + + @ExportMessage + public long getArraySize() { + return fields.length; + } + + @ExportMessage + public boolean isArrayElementReadable(long index) { + return 0 <= index && index < getArraySize(); + } + + @ExportMessage + public Object readArrayElement(long index) throws InvalidArrayIndexException { + if (!isArrayElementReadable(index)) { + throw InvalidArrayIndexException.create(index); + } + return fields[(int) index]; + } +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/globals/WasmGlobal.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/globals/WasmGlobal.java index 8f5b1a1a3edc..c6afa14d4d95 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/globals/WasmGlobal.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/globals/WasmGlobal.java @@ -48,6 +48,7 @@ import org.graalvm.wasm.api.ValueType; import org.graalvm.wasm.api.Vector128; import org.graalvm.wasm.constants.GlobalModifier; +import org.graalvm.wasm.exception.WasmRuntimeException; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.interop.InteropLibrary; @@ -178,7 +179,7 @@ Object readMember(String member) throws UnknownIdentifierException { case f32 -> Float.intBitsToFloat(loadAsInt()); case f64 -> Double.longBitsToDouble(loadAsLong()); case v128 -> loadAsVector128(); - case anyfunc, externref -> loadAsReference(); + case anyfunc, externref, exnref -> loadAsReference(); }; } @@ -228,6 +229,12 @@ void writeMember(String member, Object value, } throw UnsupportedMessageException.create(); } + case exnref -> { + if (value == WasmConstant.NULL || value instanceof WasmRuntimeException) { + storeReference(value); + } + throw UnsupportedMessageException.create(); + } } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java index ca355f717a31..3cb0ae135f0c 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java @@ -77,14 +77,17 @@ import org.graalvm.wasm.WasmMath; import org.graalvm.wasm.WasmModule; import org.graalvm.wasm.WasmTable; +import org.graalvm.wasm.WasmTag; import org.graalvm.wasm.WasmType; import org.graalvm.wasm.api.Vector128; import org.graalvm.wasm.api.Vector128Ops; import org.graalvm.wasm.constants.Bytecode; import org.graalvm.wasm.constants.BytecodeBitEncoding; +import org.graalvm.wasm.constants.ExceptionHandlerType; import org.graalvm.wasm.constants.Vector128OpStackEffects; import org.graalvm.wasm.exception.Failure; import org.graalvm.wasm.exception.WasmException; +import org.graalvm.wasm.exception.WasmRuntimeException; import org.graalvm.wasm.memory.WasmMemory; import org.graalvm.wasm.memory.WasmMemoryLibrary; @@ -266,1431 +269,1549 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i int opcode = Bytecode.UNREACHABLE; loop: while (offset < bytecodeEndOffset) { - opcode = rawPeekU8(bytecode, offset); - offset++; - CompilerAsserts.partialEvaluationConstant(offset); - switch (opcode) { - case Bytecode.UNREACHABLE: - enterErrorBranch(); - throw WasmException.create(Failure.UNREACHABLE, this); - case Bytecode.NOP: - break; - case Bytecode.SKIP_LABEL_U8: - case Bytecode.SKIP_LABEL_U16: - case Bytecode.SKIP_LABEL_I32: - offset += opcode; - break; - case Bytecode.RETURN: { - // A return statement causes the termination of the current function, i.e. - // causes the execution to resume after the instruction that invoked - // the current frame. - if (backEdgeCounter.count > 0) { - LoopNode.reportLoopCount(this, backEdgeCounter.count); + try { + opcode = rawPeekU8(bytecode, offset); + offset++; + CompilerAsserts.partialEvaluationConstant(offset); + switch (opcode) { + case Bytecode.UNREACHABLE: + enterErrorBranch(); + throw WasmException.create(Failure.UNREACHABLE, this); + case Bytecode.NOP: + break; + case Bytecode.SKIP_LABEL_U8: + case Bytecode.SKIP_LABEL_U16: + case Bytecode.SKIP_LABEL_I32: + offset += opcode; + break; + case Bytecode.RETURN: { + // A return statement causes the termination of the current function, i.e. + // causes the execution to resume after the instruction that invoked + // the current frame. + if (backEdgeCounter.count > 0) { + LoopNode.reportLoopCount(this, backEdgeCounter.count); + } + final int resultCount = codeEntry.resultCount(); + unwindStack(frame, stackPointer, localCount, resultCount); + dropStack(frame, stackPointer, localCount + resultCount); + return WasmConstant.RETURN_VALUE; } - final int resultCount = codeEntry.resultCount(); - unwindStack(frame, stackPointer, localCount, resultCount); - dropStack(frame, stackPointer, localCount + resultCount); - return WasmConstant.RETURN_VALUE; - } - case Bytecode.LABEL_U8: { - final int value = rawPeekU8(bytecode, offset); - offset++; - final int stackSize = (value & BytecodeBitEncoding.LABEL_U8_STACK_VALUE); - final int targetStackPointer = stackSize + localCount; - switch ((value & BytecodeBitEncoding.LABEL_U8_RESULT_MASK)) { - case BytecodeBitEncoding.LABEL_U8_RESULT_NUM: - WasmFrame.copyPrimitive(frame, stackPointer - 1, targetStackPointer); - dropStack(frame, stackPointer, targetStackPointer + 1); - stackPointer = targetStackPointer + 1; - break; - case BytecodeBitEncoding.LABEL_U8_RESULT_OBJ: - WasmFrame.copyObject(frame, stackPointer - 1, targetStackPointer); - dropStack(frame, stackPointer, targetStackPointer + 1); - stackPointer = targetStackPointer + 1; - break; - default: - dropStack(frame, stackPointer, targetStackPointer); - stackPointer = targetStackPointer; - break; + case Bytecode.LABEL_U8: { + final int value = rawPeekU8(bytecode, offset); + offset++; + final int stackSize = (value & BytecodeBitEncoding.LABEL_U8_STACK_VALUE); + final int targetStackPointer = stackSize + localCount; + switch ((value & BytecodeBitEncoding.LABEL_U8_RESULT_MASK)) { + case BytecodeBitEncoding.LABEL_U8_RESULT_NUM: + WasmFrame.copyPrimitive(frame, stackPointer - 1, targetStackPointer); + dropStack(frame, stackPointer, targetStackPointer + 1); + stackPointer = targetStackPointer + 1; + break; + case BytecodeBitEncoding.LABEL_U8_RESULT_OBJ: + WasmFrame.copyObject(frame, stackPointer - 1, targetStackPointer); + dropStack(frame, stackPointer, targetStackPointer + 1); + stackPointer = targetStackPointer + 1; + break; + default: + dropStack(frame, stackPointer, targetStackPointer); + stackPointer = targetStackPointer; + break; + } + break; } - break; - } - case Bytecode.LABEL_U16: { - final int value = rawPeekU16(bytecode, offset); - final int stackSize = rawPeekU8(bytecode, offset + 1); - offset += 2; - final int resultCount = (value & BytecodeBitEncoding.LABEL_U16_RESULT_VALUE); - final int resultType = (value & BytecodeBitEncoding.LABEL_U16_RESULT_TYPE_MASK); - final int targetStackPointer = stackSize + localCount; - switch (resultType) { - case BytecodeBitEncoding.LABEL_U16_RESULT_TYPE_NUM: - unwindPrimitiveStack(frame, stackPointer, targetStackPointer, resultCount); - break; - case BytecodeBitEncoding.LABEL_U16_RESULT_TYPE_OBJ: - unwindObjectStack(frame, stackPointer, targetStackPointer, resultCount); - break; - case BytecodeBitEncoding.LABEL_U16_RESULT_TYPE_MIX: - unwindStack(frame, stackPointer, targetStackPointer, resultCount); - break; + case Bytecode.LABEL_U16: { + final int value = rawPeekU16(bytecode, offset); + final int stackSize = rawPeekU8(bytecode, offset + 1); + offset += 2; + final int resultCount = (value & BytecodeBitEncoding.LABEL_U16_RESULT_VALUE); + final int resultType = (value & BytecodeBitEncoding.LABEL_U16_RESULT_TYPE_MASK); + final int targetStackPointer = stackSize + localCount; + switch (resultType) { + case BytecodeBitEncoding.LABEL_U16_RESULT_TYPE_NUM: + unwindPrimitiveStack(frame, stackPointer, targetStackPointer, resultCount); + break; + case BytecodeBitEncoding.LABEL_U16_RESULT_TYPE_OBJ: + unwindObjectStack(frame, stackPointer, targetStackPointer, resultCount); + break; + case BytecodeBitEncoding.LABEL_U16_RESULT_TYPE_MIX: + unwindStack(frame, stackPointer, targetStackPointer, resultCount); + break; + } + dropStack(frame, stackPointer, targetStackPointer + resultCount); + stackPointer = targetStackPointer + resultCount; + break; } - dropStack(frame, stackPointer, targetStackPointer + resultCount); - stackPointer = targetStackPointer + resultCount; - break; - } - case Bytecode.LABEL_I32: { - final int resultType = rawPeekU8(bytecode, offset); - final int resultCount = rawPeekI32(bytecode, offset + 1); - final int stackSize = rawPeekI32(bytecode, offset + 5); - offset += 9; - final int targetStackPointer = stackSize + localCount; - switch (resultType) { - case BytecodeBitEncoding.LABEL_RESULT_TYPE_NUM: - unwindPrimitiveStack(frame, stackPointer, targetStackPointer, resultCount); - break; - case BytecodeBitEncoding.LABEL_RESULT_TYPE_OBJ: - unwindObjectStack(frame, stackPointer, targetStackPointer, resultCount); - break; - case BytecodeBitEncoding.LABEL_RESULT_TYPE_MIX: - unwindStack(frame, stackPointer, targetStackPointer, resultCount); - break; + case Bytecode.LABEL_I32: { + final int resultType = rawPeekU8(bytecode, offset); + final int resultCount = rawPeekI32(bytecode, offset + 1); + final int stackSize = rawPeekI32(bytecode, offset + 5); + offset += 9; + final int targetStackPointer = stackSize + localCount; + switch (resultType) { + case BytecodeBitEncoding.LABEL_RESULT_TYPE_NUM: + unwindPrimitiveStack(frame, stackPointer, targetStackPointer, resultCount); + break; + case BytecodeBitEncoding.LABEL_RESULT_TYPE_OBJ: + unwindObjectStack(frame, stackPointer, targetStackPointer, resultCount); + break; + case BytecodeBitEncoding.LABEL_RESULT_TYPE_MIX: + unwindStack(frame, stackPointer, targetStackPointer, resultCount); + break; + } + dropStack(frame, stackPointer, targetStackPointer + resultCount); + stackPointer = targetStackPointer + resultCount; + break; } - dropStack(frame, stackPointer, targetStackPointer + resultCount); - stackPointer = targetStackPointer + resultCount; - break; - } - case Bytecode.LOOP: { - TruffleSafepoint.poll(this); - if (CompilerDirectives.hasNextTier() && ++backEdgeCounter.count >= REPORT_LOOP_STRIDE) { - LoopNode.reportLoopCount(this, REPORT_LOOP_STRIDE); - if (CompilerDirectives.inInterpreter() && BytecodeOSRNode.pollOSRBackEdge(this, REPORT_LOOP_STRIDE)) { - Object result = BytecodeOSRNode.tryOSR(this, offset, new WasmOSRInterpreterState(stackPointer, lineIndex), null, frame); - if (result != null) { - return result; + case Bytecode.LOOP: { + TruffleSafepoint.poll(this); + if (CompilerDirectives.hasNextTier() && ++backEdgeCounter.count >= REPORT_LOOP_STRIDE) { + LoopNode.reportLoopCount(this, REPORT_LOOP_STRIDE); + if (CompilerDirectives.inInterpreter() && BytecodeOSRNode.pollOSRBackEdge(this, REPORT_LOOP_STRIDE)) { + Object result = BytecodeOSRNode.tryOSR(this, offset, new WasmOSRInterpreterState(stackPointer, lineIndex), null, frame); + if (result != null) { + return result; + } } + backEdgeCounter.count = 0; } - backEdgeCounter.count = 0; + break; } - break; - } - case Bytecode.IF: { - stackPointer--; - if (profileCondition(bytecode, offset + 4, popBoolean(frame, stackPointer))) { - offset += 6; - } else { - final int offsetDelta = rawPeekI32(bytecode, offset); - offset += offsetDelta; + case Bytecode.IF: { + stackPointer--; + if (profileCondition(bytecode, offset + 4, popBoolean(frame, stackPointer))) { + offset += 6; + } else { + final int offsetDelta = rawPeekI32(bytecode, offset); + offset += offsetDelta; + } + break; } - break; - } - case Bytecode.BR_U8: { - final int offsetDelta = rawPeekU8(bytecode, offset); - // BR_U8 encodes the back jump value as a positive byte value. BR_U8 can never - // perform a forward jump. - offset -= offsetDelta; - break; - } - case Bytecode.BR_I32: { - final int offsetDelta = rawPeekI32(bytecode, offset); - offset += offsetDelta; - break; - } - case Bytecode.BR_IF_U8: { - stackPointer--; - if (profileCondition(bytecode, offset + 1, popBoolean(frame, stackPointer))) { + case Bytecode.BR_U8: { final int offsetDelta = rawPeekU8(bytecode, offset); - // BR_IF_U8 encodes the back jump value as a positive byte value. BR_IF_U8 - // can never perform a forward jump. + // BR_U8 encodes the back jump value as a positive byte value. BR_U8 can + // never + // perform a forward jump. offset -= offsetDelta; - } else { - offset += 3; + break; } - break; - } - case Bytecode.BR_IF_I32: { - stackPointer--; - if (profileCondition(bytecode, offset + 4, popBoolean(frame, stackPointer))) { + case Bytecode.BR_I32: { final int offsetDelta = rawPeekI32(bytecode, offset); offset += offsetDelta; - } else { - offset += 6; + break; } - break; - } - case Bytecode.BR_TABLE_U8: { - stackPointer--; - int index = popInt(frame, stackPointer); - final int size = rawPeekU8(bytecode, offset); - final int counterOffset = offset + 1; - - if (CompilerDirectives.inInterpreter()) { - if (index < 0 || index >= size) { - // If unsigned index is larger or equal to the table size use the - // default (last) index. - index = size - 1; + case Bytecode.BR_IF_U8: { + stackPointer--; + if (profileCondition(bytecode, offset + 1, popBoolean(frame, stackPointer))) { + final int offsetDelta = rawPeekU8(bytecode, offset); + // BR_IF_U8 encodes the back jump value as a positive byte value. + // BR_IF_U8 + // can never perform a forward jump. + offset -= offsetDelta; + } else { + offset += 3; } - - final int indexOffset = offset + 3 + index * 6; - updateBranchTableProfile(bytecode, counterOffset, indexOffset + 4); - final int offsetDelta = rawPeekI32(bytecode, indexOffset); - offset = indexOffset + offsetDelta; break; - } else { - // This loop is implemented to create a separate path for every index. This - // guarantees that all values inside the if statement are treated as compile - // time constants, since the loop is unrolled. - // We keep track of the sum of the preceding profiles to adjust the - // independent probabilities to conditional ones. This gets explained in - // profileBranchTable(). - int precedingSum = 0; - for (int i = 0; i < size; i++) { - final int indexOffset = offset + 3 + i * 6; - int profile = rawPeekU16(bytecode, indexOffset + 4); - if (profileBranchTable(bytecode, counterOffset, profile, precedingSum, i == index || i == size - 1)) { - final int offsetDelta = rawPeekI32(bytecode, indexOffset); - offset = indexOffset + offsetDelta; - continue loop; - } - precedingSum += profile; - } - throw CompilerDirectives.shouldNotReachHere("br_table"); } - } - case Bytecode.BR_TABLE_I32: { - stackPointer--; - int index = popInt(frame, stackPointer); - final int size = rawPeekI32(bytecode, offset); - final int counterOffset = offset + 4; - - if (CompilerDirectives.inInterpreter()) { - if (index < 0 || index >= size) { - // If unsigned index is larger or equal to the table size use the - // default (last) index. - index = size - 1; + case Bytecode.BR_IF_I32: { + stackPointer--; + if (profileCondition(bytecode, offset + 4, popBoolean(frame, stackPointer))) { + final int offsetDelta = rawPeekI32(bytecode, offset); + offset += offsetDelta; + } else { + offset += 6; } - - final int indexOffset = offset + 6 + index * 6; - updateBranchTableProfile(bytecode, counterOffset, indexOffset + 4); - final int offsetDelta = rawPeekI32(bytecode, indexOffset); - offset = indexOffset + offsetDelta; break; - } else { - // This loop is implemented to create a separate path for every index. This - // guarantees that all values inside the if statement are treated as compile - // time constants, since the loop is unrolled. - int precedingSum = 0; - for (int i = 0; i < size; i++) { - final int indexOffset = offset + 6 + i * 6; - int profile = rawPeekU16(bytecode, indexOffset + 4); - if (profileBranchTable(bytecode, counterOffset, profile, precedingSum, i == index || i == size - 1)) { - final int offsetDelta = rawPeekI32(bytecode, indexOffset); - offset = indexOffset + offsetDelta; - continue loop; + } + case Bytecode.BR_TABLE_U8: { + stackPointer--; + int index = popInt(frame, stackPointer); + final int size = rawPeekU8(bytecode, offset); + final int counterOffset = offset + 1; + + if (CompilerDirectives.inInterpreter()) { + if (index < 0 || index >= size) { + // If unsigned index is larger or equal to the table size use the + // default (last) index. + index = size - 1; } - precedingSum += profile; + + final int indexOffset = offset + 3 + index * 6; + updateBranchTableProfile(bytecode, counterOffset, indexOffset + 4); + final int offsetDelta = rawPeekI32(bytecode, indexOffset); + offset = indexOffset + offsetDelta; + break; + } else { + // This loop is implemented to create a separate path for every index. + // This + // guarantees that all values inside the if statement are treated as + // compile + // time constants, since the loop is unrolled. + // We keep track of the sum of the preceding profiles to adjust the + // independent probabilities to conditional ones. This gets explained in + // profileBranchTable(). + int precedingSum = 0; + for (int i = 0; i < size; i++) { + final int indexOffset = offset + 3 + i * 6; + int profile = rawPeekU16(bytecode, indexOffset + 4); + if (profileBranchTable(bytecode, counterOffset, profile, precedingSum, i == index || i == size - 1)) { + final int offsetDelta = rawPeekI32(bytecode, indexOffset); + offset = indexOffset + offsetDelta; + continue loop; + } + precedingSum += profile; + } + throw CompilerDirectives.shouldNotReachHere("br_table"); } - throw CompilerDirectives.shouldNotReachHere("br_table"); } - } - case Bytecode.CALL_U8: - case Bytecode.CALL_I32: { - final int callNodeIndex; - final int functionIndex; - if (opcode == Bytecode.CALL_U8) { - callNodeIndex = rawPeekU8(bytecode, offset); - functionIndex = rawPeekU8(bytecode, offset + 1); - offset += 2; - } else { - callNodeIndex = rawPeekI32(bytecode, offset); - functionIndex = rawPeekI32(bytecode, offset + 4); - offset += 8; + case Bytecode.BR_TABLE_I32: { + stackPointer--; + int index = popInt(frame, stackPointer); + final int size = rawPeekI32(bytecode, offset); + final int counterOffset = offset + 4; + + if (CompilerDirectives.inInterpreter()) { + if (index < 0 || index >= size) { + // If unsigned index is larger or equal to the table size use the + // default (last) index. + index = size - 1; + } + + final int indexOffset = offset + 6 + index * 6; + updateBranchTableProfile(bytecode, counterOffset, indexOffset + 4); + final int offsetDelta = rawPeekI32(bytecode, indexOffset); + offset = indexOffset + offsetDelta; + break; + } else { + // This loop is implemented to create a separate path for every index. + // This guarantees that all values inside the if statement are treated + // as compile time constants, since the loop is unrolled. + int precedingSum = 0; + for (int i = 0; i < size; i++) { + final int indexOffset = offset + 6 + i * 6; + int profile = rawPeekU16(bytecode, indexOffset + 4); + if (profileBranchTable(bytecode, counterOffset, profile, precedingSum, i == index || i == size - 1)) { + final int offsetDelta = rawPeekI32(bytecode, indexOffset); + offset = indexOffset + offsetDelta; + continue loop; + } + precedingSum += profile; + } + throw CompilerDirectives.shouldNotReachHere("br_table"); + } } + case Bytecode.CALL_U8: + case Bytecode.CALL_I32: { + final int callNodeIndex; + final int functionIndex; + if (opcode == Bytecode.CALL_U8) { + callNodeIndex = rawPeekU8(bytecode, offset); + functionIndex = rawPeekU8(bytecode, offset + 1); + offset += 2; + } else { + callNodeIndex = rawPeekI32(bytecode, offset); + functionIndex = rawPeekI32(bytecode, offset + 4); + offset += 8; + } - WasmFunction function = module.symbolTable().function(functionIndex); - int paramCount = function.paramCount(); + WasmFunction function = module.symbolTable().function(functionIndex); + int paramCount = function.paramCount(); - Object[] args = createArgumentsForCall(frame, function.typeIndex(), paramCount, stackPointer); - stackPointer -= paramCount; + Object[] args = createArgumentsForCall(frame, function.typeIndex(), paramCount, stackPointer); + stackPointer -= paramCount; - stackPointer = executeDirectCall(frame, stackPointer, instance, callNodeIndex, function, args); - CompilerAsserts.partialEvaluationConstant(stackPointer); - break; - } - case Bytecode.CALL_INDIRECT_U8: - case Bytecode.CALL_INDIRECT_I32: { - // Extract the function object. - stackPointer--; - final SymbolTable symtab = module.symbolTable(); - - final int callNodeIndex; - final int expectedFunctionTypeIndex; - final int tableIndex; - if (opcode == Bytecode.CALL_INDIRECT_U8) { - callNodeIndex = rawPeekU8(bytecode, offset); - expectedFunctionTypeIndex = rawPeekU8(bytecode, offset + 1); - tableIndex = rawPeekU8(bytecode, offset + 2); - offset += 3; - } else { - callNodeIndex = rawPeekI32(bytecode, offset); - expectedFunctionTypeIndex = rawPeekI32(bytecode, offset + 4); - tableIndex = rawPeekI32(bytecode, offset + 8); - offset += 12; - } - final WasmTable table = instance.store().tables().table(instance.tableAddress(tableIndex)); - final Object[] elements = table.elements(); - final int elementIndex = popInt(frame, stackPointer); - if (elementIndex < 0 || elementIndex >= elements.length) { - enterErrorBranch(); - throw WasmException.format(Failure.UNDEFINED_ELEMENT, this, "Element index '%d' out of table bounds.", elementIndex); - } - // Currently, table elements may only be functions. - // We can add a check here when this changes in the future. - final Object element = elements[elementIndex]; - if (element == WasmConstant.NULL) { - enterErrorBranch(); - throw WasmException.format(Failure.UNINITIALIZED_ELEMENT, this, "Table element at index %d is uninitialized.", elementIndex); - } - final WasmFunctionInstance functionInstance; - final WasmFunction function; - final CallTarget target; - final WasmContext functionInstanceContext; - if (element instanceof WasmFunctionInstance) { - functionInstance = (WasmFunctionInstance) element; - function = functionInstance.function(); - target = functionInstance.target(); - functionInstanceContext = functionInstance.context(); - } else { - enterErrorBranch(); - throw WasmException.format(Failure.UNSPECIFIED_TRAP, this, "Unknown table element type: %s", element); + stackPointer = executeDirectCall(frame, stackPointer, instance, callNodeIndex, function, args); + CompilerAsserts.partialEvaluationConstant(stackPointer); + break; } + case Bytecode.CALL_INDIRECT_U8: + case Bytecode.CALL_INDIRECT_I32: { + // Extract the function object. + stackPointer--; + final SymbolTable symtab = module.symbolTable(); + + final int callNodeIndex; + final int expectedFunctionTypeIndex; + final int tableIndex; + if (opcode == Bytecode.CALL_INDIRECT_U8) { + callNodeIndex = rawPeekU8(bytecode, offset); + expectedFunctionTypeIndex = rawPeekU8(bytecode, offset + 1); + tableIndex = rawPeekU8(bytecode, offset + 2); + offset += 3; + } else { + callNodeIndex = rawPeekI32(bytecode, offset); + expectedFunctionTypeIndex = rawPeekI32(bytecode, offset + 4); + tableIndex = rawPeekI32(bytecode, offset + 8); + offset += 12; + } + final WasmTable table = instance.store().tables().table(instance.tableAddress(tableIndex)); + final Object[] elements = table.elements(); + final int elementIndex = popInt(frame, stackPointer); + if (elementIndex < 0 || elementIndex >= elements.length) { + enterErrorBranch(); + throw WasmException.format(Failure.UNDEFINED_ELEMENT, this, "Element index '%d' out of table bounds.", elementIndex); + } + // Currently, table elements may only be functions. + // We can add a check here when this changes in the future. + final Object element = elements[elementIndex]; + if (element == WasmConstant.NULL) { + enterErrorBranch(); + throw WasmException.format(Failure.UNINITIALIZED_ELEMENT, this, "Table element at index %d is uninitialized.", elementIndex); + } + final WasmFunctionInstance functionInstance; + final WasmFunction function; + final CallTarget target; + final WasmContext functionInstanceContext; + if (element instanceof WasmFunctionInstance) { + functionInstance = (WasmFunctionInstance) element; + function = functionInstance.function(); + target = functionInstance.target(); + functionInstanceContext = functionInstance.context(); + } else { + enterErrorBranch(); + throw WasmException.format(Failure.UNSPECIFIED_TRAP, this, "Unknown table element type: %s", element); + } - int expectedTypeEquivalenceClass = symtab.equivalenceClass(expectedFunctionTypeIndex); + int expectedTypeEquivalenceClass = symtab.equivalenceClass(expectedFunctionTypeIndex); - // Target function instance must be from the same context. - assert functionInstanceContext == WasmContext.get(this); + // Target function instance must be from the same context. + assert functionInstanceContext == WasmContext.get(this); - // Validate that the target function type matches the expected type of the - // indirect call by performing an equivalence-class check. - if (expectedTypeEquivalenceClass != function.typeEquivalenceClass()) { - enterErrorBranch(); - failFunctionTypeCheck(function, expectedFunctionTypeIndex); - } + // Validate that the target function type matches the expected type of the + // indirect call by performing an equivalence-class check. + if (expectedTypeEquivalenceClass != function.typeEquivalenceClass()) { + enterErrorBranch(); + failFunctionTypeCheck(function, expectedFunctionTypeIndex); + } - // Invoke the resolved function. - int paramCount = module.symbolTable().functionTypeParamCount(expectedFunctionTypeIndex); - Object[] args = createArgumentsForCall(frame, expectedFunctionTypeIndex, paramCount, stackPointer); - stackPointer -= paramCount; - WasmArguments.setModuleInstance(args, functionInstance.moduleInstance()); + // Invoke the resolved function. + int paramCount = module.symbolTable().functionTypeParamCount(expectedFunctionTypeIndex); + Object[] args = createArgumentsForCall(frame, expectedFunctionTypeIndex, paramCount, stackPointer); + stackPointer -= paramCount; + WasmArguments.setModuleInstance(args, functionInstance.moduleInstance()); - final Object result = executeIndirectCallNode(callNodeIndex, target, args); - stackPointer = pushIndirectCallResult(frame, stackPointer, expectedFunctionTypeIndex, result, WasmLanguage.get(this)); - CompilerAsserts.partialEvaluationConstant(stackPointer); - break; - } - case Bytecode.DROP: { - stackPointer--; - dropPrimitive(frame, stackPointer); - break; - } - case Bytecode.DROP_OBJ: { - stackPointer--; - dropObject(frame, stackPointer); - break; - } - case Bytecode.SELECT: { - if (profileCondition(bytecode, offset, popBoolean(frame, stackPointer - 1))) { - drop(frame, stackPointer - 2); - } else { - WasmFrame.copyPrimitive(frame, stackPointer - 2, stackPointer - 3); - dropPrimitive(frame, stackPointer - 2); + final Object result = executeIndirectCallNode(callNodeIndex, target, args); + stackPointer = pushIndirectCallResult(frame, stackPointer, expectedFunctionTypeIndex, result, WasmLanguage.get(this)); + CompilerAsserts.partialEvaluationConstant(stackPointer); + break; } - offset += 2; - stackPointer -= 2; - break; - } - case Bytecode.SELECT_OBJ: { - if (profileCondition(bytecode, offset, popBoolean(frame, stackPointer - 1))) { - dropObject(frame, stackPointer - 2); - } else { - WasmFrame.copyObject(frame, stackPointer - 2, stackPointer - 3); - dropObject(frame, stackPointer - 2); + case Bytecode.DROP: { + stackPointer--; + dropPrimitive(frame, stackPointer); + break; } - offset += 2; - stackPointer -= 2; - break; - } - case Bytecode.LOCAL_GET_U8: { - final int index = rawPeekU8(bytecode, offset); - offset++; - local_get(frame, stackPointer, index); - stackPointer++; - break; - } - case Bytecode.LOCAL_GET_I32: { - final int index = rawPeekI32(bytecode, offset); - offset += 4; - local_get(frame, stackPointer, index); - stackPointer++; - break; - } - case Bytecode.LOCAL_GET_OBJ_U8: { - final int index = rawPeekU8(bytecode, offset); - offset++; - local_get_obj(frame, stackPointer, index); - stackPointer++; - break; - } - case Bytecode.LOCAL_GET_OBJ_I32: { - final int index = rawPeekI32(bytecode, offset); - offset += 4; - local_get_obj(frame, stackPointer, index); - stackPointer++; - break; - } - case Bytecode.LOCAL_SET_U8: { - final int index = rawPeekU8(bytecode, offset); - offset++; - stackPointer--; - local_set(frame, stackPointer, index); - break; - } - case Bytecode.LOCAL_SET_I32: { - final int index = rawPeekI32(bytecode, offset); - offset += 4; - stackPointer--; - local_set(frame, stackPointer, index); - break; - } - case Bytecode.LOCAL_SET_OBJ_U8: { - final int index = rawPeekU8(bytecode, offset); - offset++; - stackPointer--; - local_set_obj(frame, stackPointer, index); - break; - } - case Bytecode.LOCAL_SET_OBJ_I32: { - final int index = rawPeekI32(bytecode, offset); - offset += 4; - stackPointer--; - local_set_obj(frame, stackPointer, index); - break; - } - case Bytecode.LOCAL_TEE_U8: { - final int index = rawPeekU8(bytecode, offset); - offset++; - local_tee(frame, stackPointer - 1, index); - break; - } - case Bytecode.LOCAL_TEE_I32: { - final int index = rawPeekI32(bytecode, offset); - offset += 4; - local_tee(frame, stackPointer - 1, index); - break; - } - case Bytecode.LOCAL_TEE_OBJ_U8: { - final int index = rawPeekU8(bytecode, offset); - offset++; - local_tee_obj(frame, stackPointer - 1, index); - break; - } - case Bytecode.LOCAL_TEE_OBJ_I32: { - final int index = rawPeekI32(bytecode, offset); - offset += 4; - local_tee_obj(frame, stackPointer - 1, index); - break; - } - case Bytecode.GLOBAL_GET_U8: { - final int index = rawPeekU8(bytecode, offset); - offset++; - global_get(instance, frame, stackPointer, index); - stackPointer++; - break; - } - case Bytecode.GLOBAL_GET_I32: { - final int index = rawPeekI32(bytecode, offset); - offset += 4; - global_get(instance, frame, stackPointer, index); - stackPointer++; - break; - } - case Bytecode.GLOBAL_SET_U8: { - final int index = rawPeekU8(bytecode, offset); - offset++; - stackPointer--; - global_set(instance, frame, stackPointer, index); - break; - } - case Bytecode.GLOBAL_SET_I32: { - final int index = rawPeekI32(bytecode, offset); - offset += 4; - stackPointer--; - global_set(instance, frame, stackPointer, index); - break; - } - case Bytecode.I32_LOAD: - case Bytecode.I64_LOAD: - case Bytecode.F32_LOAD: - case Bytecode.F64_LOAD: - case Bytecode.I32_LOAD8_S: - case Bytecode.I32_LOAD8_U: - case Bytecode.I32_LOAD16_S: - case Bytecode.I32_LOAD16_U: - case Bytecode.I64_LOAD8_S: - case Bytecode.I64_LOAD8_U: - case Bytecode.I64_LOAD16_S: - case Bytecode.I64_LOAD16_U: - case Bytecode.I64_LOAD32_S: - case Bytecode.I64_LOAD32_U: { - final int encoding = rawPeekU8(bytecode, offset); - offset++; - final int indexType64 = encoding & BytecodeBitEncoding.MEMORY_64_FLAG; - final int offsetLength = encoding & BytecodeBitEncoding.MEMORY_OFFSET_MASK; - final int memoryIndex = rawPeekI32(bytecode, offset); - offset += 4; - final long memOffset; - switch (offsetLength) { - case BytecodeBitEncoding.MEMORY_OFFSET_U8: - memOffset = rawPeekU8(bytecode, offset); - offset++; - break; - case BytecodeBitEncoding.MEMORY_OFFSET_U32: - memOffset = rawPeekU32(bytecode, offset); - offset += 4; - break; - case BytecodeBitEncoding.MEMORY_OFFSET_I64: - memOffset = rawPeekI64(bytecode, offset); - offset += 8; - break; - default: - throw CompilerDirectives.shouldNotReachHere(); + case Bytecode.DROP_OBJ: { + stackPointer--; + dropObject(frame, stackPointer); + break; } - final long baseAddress; - if (indexType64 == 0) { - baseAddress = Integer.toUnsignedLong(popInt(frame, stackPointer - 1)); - } else { - baseAddress = popLong(frame, stackPointer - 1); + case Bytecode.SELECT: { + if (profileCondition(bytecode, offset, popBoolean(frame, stackPointer - 1))) { + drop(frame, stackPointer - 2); + } else { + WasmFrame.copyPrimitive(frame, stackPointer - 2, stackPointer - 3); + dropPrimitive(frame, stackPointer - 2); + } + offset += 2; + stackPointer -= 2; + break; } - final long address = effectiveMemoryAddress64(memOffset, baseAddress); - final WasmMemory memory = memory(instance, memoryIndex); - load(memory, memoryLib(memoryIndex), frame, stackPointer - 1, opcode, address); - break; - } - case Bytecode.I32_LOAD_U8: { - final int memOffset = rawPeekU8(bytecode, offset); - offset++; + case Bytecode.SELECT_OBJ: { + if (profileCondition(bytecode, offset, popBoolean(frame, stackPointer - 1))) { + dropObject(frame, stackPointer - 2); + } else { + WasmFrame.copyObject(frame, stackPointer - 2, stackPointer - 3); + dropObject(frame, stackPointer - 2); + } + offset += 2; + stackPointer -= 2; + break; + } + case Bytecode.LOCAL_GET_U8: { + final int index = rawPeekU8(bytecode, offset); + offset++; + local_get(frame, stackPointer, index); + stackPointer++; + break; + } + case Bytecode.LOCAL_GET_I32: { + final int index = rawPeekI32(bytecode, offset); + offset += 4; + local_get(frame, stackPointer, index); + stackPointer++; + break; + } + case Bytecode.LOCAL_GET_OBJ_U8: { + final int index = rawPeekU8(bytecode, offset); + offset++; + local_get_obj(frame, stackPointer, index); + stackPointer++; + break; + } + case Bytecode.LOCAL_GET_OBJ_I32: { + final int index = rawPeekI32(bytecode, offset); + offset += 4; + local_get_obj(frame, stackPointer, index); + stackPointer++; + break; + } + case Bytecode.LOCAL_SET_U8: { + final int index = rawPeekU8(bytecode, offset); + offset++; + stackPointer--; + local_set(frame, stackPointer, index); + break; + } + case Bytecode.LOCAL_SET_I32: { + final int index = rawPeekI32(bytecode, offset); + offset += 4; + stackPointer--; + local_set(frame, stackPointer, index); + break; + } + case Bytecode.LOCAL_SET_OBJ_U8: { + final int index = rawPeekU8(bytecode, offset); + offset++; + stackPointer--; + local_set_obj(frame, stackPointer, index); + break; + } + case Bytecode.LOCAL_SET_OBJ_I32: { + final int index = rawPeekI32(bytecode, offset); + offset += 4; + stackPointer--; + local_set_obj(frame, stackPointer, index); + break; + } + case Bytecode.LOCAL_TEE_U8: { + final int index = rawPeekU8(bytecode, offset); + offset++; + local_tee(frame, stackPointer - 1, index); + break; + } + case Bytecode.LOCAL_TEE_I32: { + final int index = rawPeekI32(bytecode, offset); + offset += 4; + local_tee(frame, stackPointer - 1, index); + break; + } + case Bytecode.LOCAL_TEE_OBJ_U8: { + final int index = rawPeekU8(bytecode, offset); + offset++; + local_tee_obj(frame, stackPointer - 1, index); + break; + } + case Bytecode.LOCAL_TEE_OBJ_I32: { + final int index = rawPeekI32(bytecode, offset); + offset += 4; + local_tee_obj(frame, stackPointer - 1, index); + break; + } + case Bytecode.GLOBAL_GET_U8: { + final int index = rawPeekU8(bytecode, offset); + offset++; + global_get(instance, frame, stackPointer, index); + stackPointer++; + break; + } + case Bytecode.GLOBAL_GET_I32: { + final int index = rawPeekI32(bytecode, offset); + offset += 4; + global_get(instance, frame, stackPointer, index); + stackPointer++; + break; + } + case Bytecode.GLOBAL_SET_U8: { + final int index = rawPeekU8(bytecode, offset); + offset++; + stackPointer--; + global_set(instance, frame, stackPointer, index); + break; + } + case Bytecode.GLOBAL_SET_I32: { + final int index = rawPeekI32(bytecode, offset); + offset += 4; + stackPointer--; + global_set(instance, frame, stackPointer, index); + break; + } + case Bytecode.I32_LOAD: + case Bytecode.I64_LOAD: + case Bytecode.F32_LOAD: + case Bytecode.F64_LOAD: + case Bytecode.I32_LOAD8_S: + case Bytecode.I32_LOAD8_U: + case Bytecode.I32_LOAD16_S: + case Bytecode.I32_LOAD16_U: + case Bytecode.I64_LOAD8_S: + case Bytecode.I64_LOAD8_U: + case Bytecode.I64_LOAD16_S: + case Bytecode.I64_LOAD16_U: + case Bytecode.I64_LOAD32_S: + case Bytecode.I64_LOAD32_U: { + final int encoding = rawPeekU8(bytecode, offset); + offset++; + final int indexType64 = encoding & BytecodeBitEncoding.MEMORY_64_FLAG; + final int offsetLength = encoding & BytecodeBitEncoding.MEMORY_OFFSET_MASK; + final int memoryIndex = rawPeekI32(bytecode, offset); + offset += 4; + final long memOffset; + switch (offsetLength) { + case BytecodeBitEncoding.MEMORY_OFFSET_U8: + memOffset = rawPeekU8(bytecode, offset); + offset++; + break; + case BytecodeBitEncoding.MEMORY_OFFSET_U32: + memOffset = rawPeekU32(bytecode, offset); + offset += 4; + break; + case BytecodeBitEncoding.MEMORY_OFFSET_I64: + memOffset = rawPeekI64(bytecode, offset); + offset += 8; + break; + default: + throw CompilerDirectives.shouldNotReachHere(); + } + final long baseAddress; + if (indexType64 == 0) { + baseAddress = Integer.toUnsignedLong(popInt(frame, stackPointer - 1)); + } else { + baseAddress = popLong(frame, stackPointer - 1); + } + final long address = effectiveMemoryAddress64(memOffset, baseAddress); + final WasmMemory memory = memory(instance, memoryIndex); + load(memory, memoryLib(memoryIndex), frame, stackPointer - 1, opcode, address); + break; + } + case Bytecode.I32_LOAD_U8: { + final int memOffset = rawPeekU8(bytecode, offset); + offset++; - int baseAddress = popInt(frame, stackPointer - 1); - final long address = effectiveMemoryAddress(memOffset, baseAddress); + int baseAddress = popInt(frame, stackPointer - 1); + final long address = effectiveMemoryAddress(memOffset, baseAddress); - int value = zeroMemoryLib.load_i32(zeroMemory, this, address); - pushInt(frame, stackPointer - 1, value); - break; - } - case Bytecode.I32_LOAD_I32: { - final int memOffset = rawPeekI32(bytecode, offset); - offset += 4; + int value = zeroMemoryLib.load_i32(zeroMemory, this, address); + pushInt(frame, stackPointer - 1, value); + break; + } + case Bytecode.I32_LOAD_I32: { + final int memOffset = rawPeekI32(bytecode, offset); + offset += 4; - int baseAddress = popInt(frame, stackPointer - 1); - final long address = effectiveMemoryAddress(memOffset, baseAddress); + int baseAddress = popInt(frame, stackPointer - 1); + final long address = effectiveMemoryAddress(memOffset, baseAddress); - int value = zeroMemoryLib.load_i32(zeroMemory, this, address); - pushInt(frame, stackPointer - 1, value); - break; - } - case Bytecode.I64_LOAD_U8: - case Bytecode.F32_LOAD_U8: - case Bytecode.F64_LOAD_U8: - case Bytecode.I32_LOAD8_S_U8: - case Bytecode.I32_LOAD8_U_U8: - case Bytecode.I32_LOAD16_S_U8: - case Bytecode.I32_LOAD16_U_U8: - case Bytecode.I64_LOAD8_S_U8: - case Bytecode.I64_LOAD8_U_U8: - case Bytecode.I64_LOAD16_S_U8: - case Bytecode.I64_LOAD16_U_U8: - case Bytecode.I64_LOAD32_S_U8: - case Bytecode.I64_LOAD32_U_U8: { - final int memOffset = rawPeekU8(bytecode, offset); - offset++; - - final int baseAddress = popInt(frame, stackPointer - 1); - final long address = effectiveMemoryAddress(memOffset, baseAddress); - - load(zeroMemory, zeroMemoryLib, frame, stackPointer - 1, opcode, address); - break; - } - case Bytecode.I64_LOAD_I32: - case Bytecode.F32_LOAD_I32: - case Bytecode.F64_LOAD_I32: - case Bytecode.I32_LOAD8_S_I32: - case Bytecode.I32_LOAD8_U_I32: - case Bytecode.I32_LOAD16_S_I32: - case Bytecode.I32_LOAD16_U_I32: - case Bytecode.I64_LOAD8_S_I32: - case Bytecode.I64_LOAD8_U_I32: - case Bytecode.I64_LOAD16_S_I32: - case Bytecode.I64_LOAD16_U_I32: - case Bytecode.I64_LOAD32_S_I32: - case Bytecode.I64_LOAD32_U_I32: { - final int memOffset = rawPeekI32(bytecode, offset); - offset += 4; + int value = zeroMemoryLib.load_i32(zeroMemory, this, address); + pushInt(frame, stackPointer - 1, value); + break; + } + case Bytecode.I64_LOAD_U8: + case Bytecode.F32_LOAD_U8: + case Bytecode.F64_LOAD_U8: + case Bytecode.I32_LOAD8_S_U8: + case Bytecode.I32_LOAD8_U_U8: + case Bytecode.I32_LOAD16_S_U8: + case Bytecode.I32_LOAD16_U_U8: + case Bytecode.I64_LOAD8_S_U8: + case Bytecode.I64_LOAD8_U_U8: + case Bytecode.I64_LOAD16_S_U8: + case Bytecode.I64_LOAD16_U_U8: + case Bytecode.I64_LOAD32_S_U8: + case Bytecode.I64_LOAD32_U_U8: { + final int memOffset = rawPeekU8(bytecode, offset); + offset++; + + final int baseAddress = popInt(frame, stackPointer - 1); + final long address = effectiveMemoryAddress(memOffset, baseAddress); + + load(zeroMemory, zeroMemoryLib, frame, stackPointer - 1, opcode, address); + break; + } + case Bytecode.I64_LOAD_I32: + case Bytecode.F32_LOAD_I32: + case Bytecode.F64_LOAD_I32: + case Bytecode.I32_LOAD8_S_I32: + case Bytecode.I32_LOAD8_U_I32: + case Bytecode.I32_LOAD16_S_I32: + case Bytecode.I32_LOAD16_U_I32: + case Bytecode.I64_LOAD8_S_I32: + case Bytecode.I64_LOAD8_U_I32: + case Bytecode.I64_LOAD16_S_I32: + case Bytecode.I64_LOAD16_U_I32: + case Bytecode.I64_LOAD32_S_I32: + case Bytecode.I64_LOAD32_U_I32: { + final int memOffset = rawPeekI32(bytecode, offset); + offset += 4; - final int baseAddress = popInt(frame, stackPointer - 1); - final long address = effectiveMemoryAddress(memOffset, baseAddress); + final int baseAddress = popInt(frame, stackPointer - 1); + final long address = effectiveMemoryAddress(memOffset, baseAddress); - load(zeroMemory, zeroMemoryLib, frame, stackPointer - 1, opcode, address); - break; - } - case Bytecode.I32_STORE: - case Bytecode.I64_STORE: - case Bytecode.F32_STORE: - case Bytecode.F64_STORE: - case Bytecode.I32_STORE_8: - case Bytecode.I32_STORE_16: - case Bytecode.I64_STORE_8: - case Bytecode.I64_STORE_16: - case Bytecode.I64_STORE_32: { - final int flags = rawPeekU8(bytecode, offset); - offset++; - final int indexType64 = flags & BytecodeBitEncoding.MEMORY_64_FLAG; - final int offsetEncoding = flags & BytecodeBitEncoding.MEMORY_OFFSET_MASK; - final int memoryIndex = rawPeekI32(bytecode, offset); - offset += 4; - final long memOffset; - switch (offsetEncoding) { - case BytecodeBitEncoding.MEMORY_OFFSET_U8: - memOffset = rawPeekU8(bytecode, offset); - offset++; - break; - case BytecodeBitEncoding.MEMORY_OFFSET_U32: - memOffset = rawPeekU32(bytecode, offset); - offset += 4; - break; - case BytecodeBitEncoding.MEMORY_OFFSET_I64: - memOffset = rawPeekI64(bytecode, offset); - offset += 8; - break; - default: - throw CompilerDirectives.shouldNotReachHere(); + load(zeroMemory, zeroMemoryLib, frame, stackPointer - 1, opcode, address); + break; } - final long baseAddress; - if (indexType64 == 0) { - baseAddress = Integer.toUnsignedLong(popInt(frame, stackPointer - 2)); - } else { - baseAddress = popLong(frame, stackPointer - 2); + case Bytecode.I32_STORE: + case Bytecode.I64_STORE: + case Bytecode.F32_STORE: + case Bytecode.F64_STORE: + case Bytecode.I32_STORE_8: + case Bytecode.I32_STORE_16: + case Bytecode.I64_STORE_8: + case Bytecode.I64_STORE_16: + case Bytecode.I64_STORE_32: { + final int flags = rawPeekU8(bytecode, offset); + offset++; + final int indexType64 = flags & BytecodeBitEncoding.MEMORY_64_FLAG; + final int offsetEncoding = flags & BytecodeBitEncoding.MEMORY_OFFSET_MASK; + final int memoryIndex = rawPeekI32(bytecode, offset); + offset += 4; + final long memOffset; + switch (offsetEncoding) { + case BytecodeBitEncoding.MEMORY_OFFSET_U8: + memOffset = rawPeekU8(bytecode, offset); + offset++; + break; + case BytecodeBitEncoding.MEMORY_OFFSET_U32: + memOffset = rawPeekU32(bytecode, offset); + offset += 4; + break; + case BytecodeBitEncoding.MEMORY_OFFSET_I64: + memOffset = rawPeekI64(bytecode, offset); + offset += 8; + break; + default: + throw CompilerDirectives.shouldNotReachHere(); + } + final long baseAddress; + if (indexType64 == 0) { + baseAddress = Integer.toUnsignedLong(popInt(frame, stackPointer - 2)); + } else { + baseAddress = popLong(frame, stackPointer - 2); + } + final long address = effectiveMemoryAddress64(memOffset, baseAddress); + final WasmMemory memory = memory(instance, memoryIndex); + store(memory, memoryLib(memoryIndex), frame, stackPointer - 1, opcode, address); + stackPointer -= 2; + break; } - final long address = effectiveMemoryAddress64(memOffset, baseAddress); - final WasmMemory memory = memory(instance, memoryIndex); - store(memory, memoryLib(memoryIndex), frame, stackPointer - 1, opcode, address); - stackPointer -= 2; - break; - } - case Bytecode.I32_STORE_U8: { - final int memOffset = rawPeekU8(bytecode, offset); - offset++; + case Bytecode.I32_STORE_U8: { + final int memOffset = rawPeekU8(bytecode, offset); + offset++; - final int baseAddress = popInt(frame, stackPointer - 2); - final long address = effectiveMemoryAddress(memOffset, baseAddress); + final int baseAddress = popInt(frame, stackPointer - 2); + final long address = effectiveMemoryAddress(memOffset, baseAddress); - final int value = popInt(frame, stackPointer - 1); - zeroMemoryLib.store_i32(zeroMemory, this, address, value); - stackPointer -= 2; - break; - } - case Bytecode.I32_STORE_I32: { - final int memOffset = rawPeekI32(bytecode, offset); - offset += 4; + final int value = popInt(frame, stackPointer - 1); + zeroMemoryLib.store_i32(zeroMemory, this, address, value); + stackPointer -= 2; + break; + } + case Bytecode.I32_STORE_I32: { + final int memOffset = rawPeekI32(bytecode, offset); + offset += 4; - final int baseAddress = popInt(frame, stackPointer - 2); - final long address = effectiveMemoryAddress(memOffset, baseAddress); + final int baseAddress = popInt(frame, stackPointer - 2); + final long address = effectiveMemoryAddress(memOffset, baseAddress); - final int value = popInt(frame, stackPointer - 1); - zeroMemoryLib.store_i32(zeroMemory, this, address, value); - stackPointer -= 2; - break; - } - case Bytecode.I64_STORE_U8: - case Bytecode.F32_STORE_U8: - case Bytecode.F64_STORE_U8: - case Bytecode.I32_STORE_8_U8: - case Bytecode.I32_STORE_16_U8: - case Bytecode.I64_STORE_8_U8: - case Bytecode.I64_STORE_16_U8: - case Bytecode.I64_STORE_32_U8: { - final int memOffset = rawPeekU8(bytecode, offset); - offset++; - - final int baseAddress = popInt(frame, stackPointer - 2); - final long address = effectiveMemoryAddress(memOffset, baseAddress); - - store(zeroMemory, zeroMemoryLib, frame, stackPointer - 1, opcode, address); - stackPointer -= 2; - - break; - } - case Bytecode.I64_STORE_I32: - case Bytecode.F32_STORE_I32: - case Bytecode.F64_STORE_I32: - case Bytecode.I32_STORE_8_I32: - case Bytecode.I32_STORE_16_I32: - case Bytecode.I64_STORE_8_I32: - case Bytecode.I64_STORE_16_I32: - case Bytecode.I64_STORE_32_I32: { - final int memOffset = rawPeekI32(bytecode, offset); - offset += 4; + final int value = popInt(frame, stackPointer - 1); + zeroMemoryLib.store_i32(zeroMemory, this, address, value); + stackPointer -= 2; + break; + } + case Bytecode.I64_STORE_U8: + case Bytecode.F32_STORE_U8: + case Bytecode.F64_STORE_U8: + case Bytecode.I32_STORE_8_U8: + case Bytecode.I32_STORE_16_U8: + case Bytecode.I64_STORE_8_U8: + case Bytecode.I64_STORE_16_U8: + case Bytecode.I64_STORE_32_U8: { + final int memOffset = rawPeekU8(bytecode, offset); + offset++; + + final int baseAddress = popInt(frame, stackPointer - 2); + final long address = effectiveMemoryAddress(memOffset, baseAddress); + + store(zeroMemory, zeroMemoryLib, frame, stackPointer - 1, opcode, address); + stackPointer -= 2; - final int baseAddress = popInt(frame, stackPointer - 2); - final long address = effectiveMemoryAddress(memOffset, baseAddress); + break; + } + case Bytecode.I64_STORE_I32: + case Bytecode.F32_STORE_I32: + case Bytecode.F64_STORE_I32: + case Bytecode.I32_STORE_8_I32: + case Bytecode.I32_STORE_16_I32: + case Bytecode.I64_STORE_8_I32: + case Bytecode.I64_STORE_16_I32: + case Bytecode.I64_STORE_32_I32: { + final int memOffset = rawPeekI32(bytecode, offset); + offset += 4; - store(zeroMemory, zeroMemoryLib, frame, stackPointer - 1, opcode, address); - stackPointer -= 2; + final int baseAddress = popInt(frame, stackPointer - 2); + final long address = effectiveMemoryAddress(memOffset, baseAddress); - break; - } - case Bytecode.MEMORY_SIZE: { - final int memoryIndex = rawPeekI32(bytecode, offset); - offset += 4; - final WasmMemory memory = memory(instance, memoryIndex); - int pageSize = (int) memoryLib(memoryIndex).size(memory); - pushInt(frame, stackPointer, pageSize); - stackPointer++; - break; - } - case Bytecode.MEMORY_GROW: { - final int memoryIndex = rawPeekI32(bytecode, offset); - offset += 4; - final WasmMemory memory = memory(instance, memoryIndex); - int extraSize = popInt(frame, stackPointer - 1); - int previousSize = (int) memoryLib(memoryIndex).grow(memory, extraSize); - pushInt(frame, stackPointer - 1, previousSize); - break; - } - case Bytecode.I32_CONST_I8: { - final int value = rawPeekI8(bytecode, offset); - offset++; + store(zeroMemory, zeroMemoryLib, frame, stackPointer - 1, opcode, address); + stackPointer -= 2; - pushInt(frame, stackPointer, value); - stackPointer++; - break; - } - case Bytecode.I32_CONST_I32: { - final int value = rawPeekI32(bytecode, offset); - offset += 4; + break; + } + case Bytecode.MEMORY_SIZE: { + final int memoryIndex = rawPeekI32(bytecode, offset); + offset += 4; + final WasmMemory memory = memory(instance, memoryIndex); + int pageSize = (int) memoryLib(memoryIndex).size(memory); + pushInt(frame, stackPointer, pageSize); + stackPointer++; + break; + } + case Bytecode.MEMORY_GROW: { + final int memoryIndex = rawPeekI32(bytecode, offset); + offset += 4; + final WasmMemory memory = memory(instance, memoryIndex); + int extraSize = popInt(frame, stackPointer - 1); + int previousSize = (int) memoryLib(memoryIndex).grow(memory, extraSize); + pushInt(frame, stackPointer - 1, previousSize); + break; + } + case Bytecode.I32_CONST_I8: { + final int value = rawPeekI8(bytecode, offset); + offset++; - pushInt(frame, stackPointer, value); - stackPointer++; - break; - } - case Bytecode.I64_CONST_I8: { - final long value = rawPeekI8(bytecode, offset); - offset++; - // endregion - pushLong(frame, stackPointer, value); - stackPointer++; - break; - } - case Bytecode.I64_CONST_I64: { - final long value = rawPeekI64(bytecode, offset); - offset += 8; - // endregion - pushLong(frame, stackPointer, value); - stackPointer++; - break; - } - case Bytecode.I32_EQZ: - i32_eqz(frame, stackPointer); - break; - case Bytecode.I32_EQ: - i32_eq(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_NE: - i32_ne(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_LT_S: - i32_lt_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_LT_U: - i32_lt_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_GT_S: - i32_gt_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_GT_U: - i32_gt_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_LE_S: - i32_le_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_LE_U: - i32_le_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_GE_S: - i32_ge_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_GE_U: - i32_ge_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_EQZ: - i64_eqz(frame, stackPointer); - break; - case Bytecode.I64_EQ: - i64_eq(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_NE: - i64_ne(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_LT_S: - i64_lt_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_LT_U: - i64_lt_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_GT_S: - i64_gt_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_GT_U: - i64_gt_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_LE_S: - i64_le_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_LE_U: - i64_le_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_GE_S: - i64_ge_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_GE_U: - i64_ge_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_EQ: - f32_eq(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_NE: - f32_ne(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_LT: - f32_lt(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_GT: - f32_gt(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_LE: - f32_le(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_GE: - f32_ge(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_EQ: - f64_eq(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_NE: - f64_ne(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_LT: - f64_lt(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_GT: - f64_gt(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_LE: - f64_le(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_GE: - f64_ge(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_CLZ: - i32_clz(frame, stackPointer); - break; - case Bytecode.I32_CTZ: - i32_ctz(frame, stackPointer); - break; - case Bytecode.I32_POPCNT: - i32_popcnt(frame, stackPointer); - break; - case Bytecode.I32_ADD: - i32_add(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_SUB: - i32_sub(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_MUL: - i32_mul(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_DIV_S: - i32_div_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_DIV_U: - i32_div_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_REM_S: - i32_rem_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_REM_U: - i32_rem_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_AND: - i32_and(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_OR: - i32_or(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_XOR: - i32_xor(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_SHL: - i32_shl(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_SHR_S: - i32_shr_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_SHR_U: - i32_shr_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_ROTL: - i32_rotl(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_ROTR: - i32_rotr(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_CLZ: - i64_clz(frame, stackPointer); - break; - case Bytecode.I64_CTZ: - i64_ctz(frame, stackPointer); - break; - case Bytecode.I64_POPCNT: - i64_popcnt(frame, stackPointer); - break; - case Bytecode.I64_ADD: - i64_add(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_SUB: - i64_sub(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_MUL: - i64_mul(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_DIV_S: - i64_div_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_DIV_U: - i64_div_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_REM_S: - i64_rem_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_REM_U: - i64_rem_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_AND: - i64_and(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_OR: - i64_or(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_XOR: - i64_xor(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_SHL: - i64_shl(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_SHR_S: - i64_shr_s(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_SHR_U: - i64_shr_u(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_ROTL: - i64_rotl(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I64_ROTR: - i64_rotr(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_CONST: { - float value = Float.intBitsToFloat(rawPeekI32(bytecode, offset)); - offset += 4; - pushFloat(frame, stackPointer, value); - stackPointer++; - break; - } - case Bytecode.F32_ABS: - f32_abs(frame, stackPointer); - break; - case Bytecode.F32_NEG: - f32_neg(frame, stackPointer); - break; - case Bytecode.F32_CEIL: - f32_ceil(frame, stackPointer); - break; - case Bytecode.F32_FLOOR: - f32_floor(frame, stackPointer); - break; - case Bytecode.F32_TRUNC: - f32_trunc(frame, stackPointer); - break; - case Bytecode.F32_NEAREST: - f32_nearest(frame, stackPointer); - break; - case Bytecode.F32_SQRT: - f32_sqrt(frame, stackPointer); - break; - case Bytecode.F32_ADD: - f32_add(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_SUB: - f32_sub(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_MUL: - f32_mul(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_DIV: - f32_div(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_MIN: - f32_min(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_MAX: - f32_max(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F32_COPYSIGN: - f32_copysign(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_CONST: { - double value = Double.longBitsToDouble(BinaryStreamParser.rawPeekI64(bytecode, offset)); - offset += 8; - pushDouble(frame, stackPointer, value); - stackPointer++; - break; - } - case Bytecode.F64_ABS: - f64_abs(frame, stackPointer); - break; - case Bytecode.F64_NEG: - f64_neg(frame, stackPointer); - break; - case Bytecode.F64_CEIL: - f64_ceil(frame, stackPointer); - break; - case Bytecode.F64_FLOOR: - f64_floor(frame, stackPointer); - break; - case Bytecode.F64_TRUNC: - f64_trunc(frame, stackPointer); - break; - case Bytecode.F64_NEAREST: - f64_nearest(frame, stackPointer); - break; - case Bytecode.F64_SQRT: - f64_sqrt(frame, stackPointer); - break; - case Bytecode.F64_ADD: - f64_add(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_SUB: - f64_sub(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_MUL: - f64_mul(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_DIV: - f64_div(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_MIN: - f64_min(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_MAX: - f64_max(frame, stackPointer); - stackPointer--; - break; - case Bytecode.F64_COPYSIGN: - f64_copysign(frame, stackPointer); - stackPointer--; - break; - case Bytecode.I32_WRAP_I64: - i32_wrap_i64(frame, stackPointer); - break; - case Bytecode.I32_TRUNC_F32_S: - i32_trunc_f32_s(frame, stackPointer); - break; - case Bytecode.I32_TRUNC_F32_U: - i32_trunc_f32_u(frame, stackPointer); - break; - case Bytecode.I32_TRUNC_F64_S: - i32_trunc_f64_s(frame, stackPointer); - break; - case Bytecode.I32_TRUNC_F64_U: - i32_trunc_f64_u(frame, stackPointer); - break; - case Bytecode.I64_EXTEND_I32_S: - i64_extend_i32_s(frame, stackPointer); - break; - case Bytecode.I64_EXTEND_I32_U: - i64_extend_i32_u(frame, stackPointer); - break; - case Bytecode.I64_TRUNC_F32_S: - i64_trunc_f32_s(frame, stackPointer); - break; - case Bytecode.I64_TRUNC_F32_U: - i64_trunc_f32_u(frame, stackPointer); - break; - case Bytecode.I64_TRUNC_F64_S: - i64_trunc_f64_s(frame, stackPointer); - break; - case Bytecode.I64_TRUNC_F64_U: - i64_trunc_f64_u(frame, stackPointer); - break; - case Bytecode.F32_CONVERT_I32_S: - f32_convert_i32_s(frame, stackPointer); - break; - case Bytecode.F32_CONVERT_I32_U: - f32_convert_i32_u(frame, stackPointer); - break; - case Bytecode.F32_CONVERT_I64_S: - f32_convert_i64_s(frame, stackPointer); - break; - case Bytecode.F32_CONVERT_I64_U: - f32_convert_i64_u(frame, stackPointer); - break; - case Bytecode.F32_DEMOTE_F64: - f32_demote_f64(frame, stackPointer); - break; - case Bytecode.F64_CONVERT_I32_S: - f64_convert_i32_s(frame, stackPointer); - break; - case Bytecode.F64_CONVERT_I32_U: - f64_convert_i32_u(frame, stackPointer); - break; - case Bytecode.F64_CONVERT_I64_S: - f64_convert_i64_s(frame, stackPointer); - break; - case Bytecode.F64_CONVERT_I64_U: - f64_convert_i64_u(frame, stackPointer); - break; - case Bytecode.F64_PROMOTE_F32: - f64_promote_f32(frame, stackPointer); - break; - case Bytecode.I32_REINTERPRET_F32: - i32_reinterpret_f32(frame, stackPointer); - break; - case Bytecode.I64_REINTERPRET_F64: - i64_reinterpret_f64(frame, stackPointer); - break; - case Bytecode.F32_REINTERPRET_I32: - f32_reinterpret_i32(frame, stackPointer); - break; - case Bytecode.F64_REINTERPRET_I64: - f64_reinterpret_i64(frame, stackPointer); - break; - case Bytecode.I32_EXTEND8_S: - i32_extend8_s(frame, stackPointer); - break; - case Bytecode.I32_EXTEND16_S: - i32_extend16_s(frame, stackPointer); - break; - case Bytecode.I64_EXTEND8_S: - i64_extend8_s(frame, stackPointer); - break; - case Bytecode.I64_EXTEND16_S: - i64_extend16_s(frame, stackPointer); - break; - case Bytecode.I64_EXTEND32_S: - i64_extend32_s(frame, stackPointer); - break; - case Bytecode.REF_NULL: - pushReference(frame, stackPointer, WasmConstant.NULL); - stackPointer++; - break; - case Bytecode.REF_IS_NULL: - final Object refType = popReference(frame, stackPointer - 1); - pushInt(frame, stackPointer - 1, refType == WasmConstant.NULL ? 1 : 0); - break; - case Bytecode.REF_FUNC: - final int functionIndex = rawPeekI32(bytecode, offset); - final WasmFunction function = module.symbolTable().function(functionIndex); - final WasmFunctionInstance functionInstance = instance.functionInstance(function); - pushReference(frame, stackPointer, functionInstance); - stackPointer++; - offset += 4; - break; - case Bytecode.TABLE_GET: { - final int tableIndex = rawPeekI32(bytecode, offset); - table_get(instance, frame, stackPointer, tableIndex); - offset += 4; - break; - } - case Bytecode.TABLE_SET: { - final int tableIndex = rawPeekI32(bytecode, offset); - table_set(instance, frame, stackPointer, tableIndex); - stackPointer -= 2; - offset += 4; - break; - } - case Bytecode.MISC: { - final int miscOpcode = rawPeekU8(bytecode, offset); - offset++; - CompilerAsserts.partialEvaluationConstant(miscOpcode); - switch (miscOpcode) { - case Bytecode.I32_TRUNC_SAT_F32_S: - i32_trunc_sat_f32_s(frame, stackPointer); - break; - case Bytecode.I32_TRUNC_SAT_F32_U: - i32_trunc_sat_f32_u(frame, stackPointer); - break; - case Bytecode.I32_TRUNC_SAT_F64_S: - i32_trunc_sat_f64_s(frame, stackPointer); - break; - case Bytecode.I32_TRUNC_SAT_F64_U: - i32_trunc_sat_f64_u(frame, stackPointer); - break; - case Bytecode.I64_TRUNC_SAT_F32_S: - i64_trunc_sat_f32_s(frame, stackPointer); - break; - case Bytecode.I64_TRUNC_SAT_F32_U: - i64_trunc_sat_f32_u(frame, stackPointer); - break; - case Bytecode.I64_TRUNC_SAT_F64_S: - i64_trunc_sat_f64_s(frame, stackPointer); - break; - case Bytecode.I64_TRUNC_SAT_F64_U: - i64_trunc_sat_f64_u(frame, stackPointer); - break; - case Bytecode.MEMORY_INIT: - case Bytecode.MEMORY64_INIT: { - final int dataIndex = rawPeekI32(bytecode, offset); - final int memoryIndex = rawPeekI32(bytecode, offset + 4); - executeMemoryInit(instance, frame, stackPointer, miscOpcode, memoryIndex, dataIndex); - stackPointer -= 3; - offset += 8; - break; - } - case Bytecode.DATA_DROP: { - final int dataIndex = rawPeekI32(bytecode, offset); - data_drop(instance, dataIndex); - offset += 4; - break; - } - case Bytecode.MEMORY_COPY: - case Bytecode.MEMORY64_COPY_D64_S64: - case Bytecode.MEMORY64_COPY_D64_S32: - case Bytecode.MEMORY64_COPY_D32_S64: { - final int destMemoryIndex = rawPeekI32(bytecode, offset); - final int srcMemoryIndex = rawPeekI32(bytecode, offset + 4); - executeMemoryCopy(instance, frame, stackPointer, miscOpcode, destMemoryIndex, srcMemoryIndex); - stackPointer -= 3; - offset += 8; - break; - } - case Bytecode.MEMORY_FILL: - case Bytecode.MEMORY64_FILL: { - final int memoryIndex = rawPeekI32(bytecode, offset); - executeMemoryFill(instance, frame, stackPointer, miscOpcode, memoryIndex); - stackPointer -= 3; - offset += 4; - break; - } - case Bytecode.TABLE_INIT: { - final int elementIndex = rawPeekI32(bytecode, offset); - final int tableIndex = rawPeekI32(bytecode, offset + 4); - - final int n = popInt(frame, stackPointer - 1); - final int src = popInt(frame, stackPointer - 2); - final int dst = popInt(frame, stackPointer - 3); - table_init(instance, n, src, dst, tableIndex, elementIndex); - stackPointer -= 3; - offset += 8; - break; - } - case Bytecode.ELEM_DROP: { - final int elementIndex = rawPeekI32(bytecode, offset); - instance.dropElemInstance(elementIndex); - offset += 4; - break; - } - case Bytecode.TABLE_COPY: { - final int srcIndex = rawPeekI32(bytecode, offset); - final int dstIndex = rawPeekI32(bytecode, offset + 4); - - final int n = popInt(frame, stackPointer - 1); - final int src = popInt(frame, stackPointer - 2); - final int dst = popInt(frame, stackPointer - 3); - table_copy(instance, n, src, dst, srcIndex, dstIndex); - stackPointer -= 3; - offset += 8; - break; - } - case Bytecode.TABLE_GROW: { - final int tableIndex = rawPeekI32(bytecode, offset); + pushInt(frame, stackPointer, value); + stackPointer++; + break; + } + case Bytecode.I32_CONST_I32: { + final int value = rawPeekI32(bytecode, offset); + offset += 4; + + pushInt(frame, stackPointer, value); + stackPointer++; + break; + } + case Bytecode.I64_CONST_I8: { + final long value = rawPeekI8(bytecode, offset); + offset++; + // endregion + pushLong(frame, stackPointer, value); + stackPointer++; + break; + } + case Bytecode.I64_CONST_I64: { + final long value = rawPeekI64(bytecode, offset); + offset += 8; + // endregion + pushLong(frame, stackPointer, value); + stackPointer++; + break; + } + case Bytecode.I32_EQZ: + i32_eqz(frame, stackPointer); + break; + case Bytecode.I32_EQ: + i32_eq(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_NE: + i32_ne(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_LT_S: + i32_lt_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_LT_U: + i32_lt_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_GT_S: + i32_gt_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_GT_U: + i32_gt_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_LE_S: + i32_le_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_LE_U: + i32_le_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_GE_S: + i32_ge_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_GE_U: + i32_ge_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_EQZ: + i64_eqz(frame, stackPointer); + break; + case Bytecode.I64_EQ: + i64_eq(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_NE: + i64_ne(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_LT_S: + i64_lt_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_LT_U: + i64_lt_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_GT_S: + i64_gt_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_GT_U: + i64_gt_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_LE_S: + i64_le_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_LE_U: + i64_le_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_GE_S: + i64_ge_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_GE_U: + i64_ge_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_EQ: + f32_eq(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_NE: + f32_ne(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_LT: + f32_lt(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_GT: + f32_gt(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_LE: + f32_le(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_GE: + f32_ge(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_EQ: + f64_eq(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_NE: + f64_ne(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_LT: + f64_lt(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_GT: + f64_gt(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_LE: + f64_le(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_GE: + f64_ge(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_CLZ: + i32_clz(frame, stackPointer); + break; + case Bytecode.I32_CTZ: + i32_ctz(frame, stackPointer); + break; + case Bytecode.I32_POPCNT: + i32_popcnt(frame, stackPointer); + break; + case Bytecode.I32_ADD: + i32_add(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_SUB: + i32_sub(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_MUL: + i32_mul(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_DIV_S: + i32_div_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_DIV_U: + i32_div_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_REM_S: + i32_rem_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_REM_U: + i32_rem_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_AND: + i32_and(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_OR: + i32_or(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_XOR: + i32_xor(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_SHL: + i32_shl(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_SHR_S: + i32_shr_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_SHR_U: + i32_shr_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_ROTL: + i32_rotl(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_ROTR: + i32_rotr(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_CLZ: + i64_clz(frame, stackPointer); + break; + case Bytecode.I64_CTZ: + i64_ctz(frame, stackPointer); + break; + case Bytecode.I64_POPCNT: + i64_popcnt(frame, stackPointer); + break; + case Bytecode.I64_ADD: + i64_add(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_SUB: + i64_sub(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_MUL: + i64_mul(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_DIV_S: + i64_div_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_DIV_U: + i64_div_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_REM_S: + i64_rem_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_REM_U: + i64_rem_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_AND: + i64_and(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_OR: + i64_or(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_XOR: + i64_xor(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_SHL: + i64_shl(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_SHR_S: + i64_shr_s(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_SHR_U: + i64_shr_u(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_ROTL: + i64_rotl(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I64_ROTR: + i64_rotr(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_CONST: { + float value = Float.intBitsToFloat(rawPeekI32(bytecode, offset)); + offset += 4; + pushFloat(frame, stackPointer, value); + stackPointer++; + break; + } + case Bytecode.F32_ABS: + f32_abs(frame, stackPointer); + break; + case Bytecode.F32_NEG: + f32_neg(frame, stackPointer); + break; + case Bytecode.F32_CEIL: + f32_ceil(frame, stackPointer); + break; + case Bytecode.F32_FLOOR: + f32_floor(frame, stackPointer); + break; + case Bytecode.F32_TRUNC: + f32_trunc(frame, stackPointer); + break; + case Bytecode.F32_NEAREST: + f32_nearest(frame, stackPointer); + break; + case Bytecode.F32_SQRT: + f32_sqrt(frame, stackPointer); + break; + case Bytecode.F32_ADD: + f32_add(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_SUB: + f32_sub(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_MUL: + f32_mul(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_DIV: + f32_div(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_MIN: + f32_min(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_MAX: + f32_max(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F32_COPYSIGN: + f32_copysign(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_CONST: { + double value = Double.longBitsToDouble(BinaryStreamParser.rawPeekI64(bytecode, offset)); + offset += 8; + pushDouble(frame, stackPointer, value); + stackPointer++; + break; + } + case Bytecode.F64_ABS: + f64_abs(frame, stackPointer); + break; + case Bytecode.F64_NEG: + f64_neg(frame, stackPointer); + break; + case Bytecode.F64_CEIL: + f64_ceil(frame, stackPointer); + break; + case Bytecode.F64_FLOOR: + f64_floor(frame, stackPointer); + break; + case Bytecode.F64_TRUNC: + f64_trunc(frame, stackPointer); + break; + case Bytecode.F64_NEAREST: + f64_nearest(frame, stackPointer); + break; + case Bytecode.F64_SQRT: + f64_sqrt(frame, stackPointer); + break; + case Bytecode.F64_ADD: + f64_add(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_SUB: + f64_sub(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_MUL: + f64_mul(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_DIV: + f64_div(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_MIN: + f64_min(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_MAX: + f64_max(frame, stackPointer); + stackPointer--; + break; + case Bytecode.F64_COPYSIGN: + f64_copysign(frame, stackPointer); + stackPointer--; + break; + case Bytecode.I32_WRAP_I64: + i32_wrap_i64(frame, stackPointer); + break; + case Bytecode.I32_TRUNC_F32_S: + i32_trunc_f32_s(frame, stackPointer); + break; + case Bytecode.I32_TRUNC_F32_U: + i32_trunc_f32_u(frame, stackPointer); + break; + case Bytecode.I32_TRUNC_F64_S: + i32_trunc_f64_s(frame, stackPointer); + break; + case Bytecode.I32_TRUNC_F64_U: + i32_trunc_f64_u(frame, stackPointer); + break; + case Bytecode.I64_EXTEND_I32_S: + i64_extend_i32_s(frame, stackPointer); + break; + case Bytecode.I64_EXTEND_I32_U: + i64_extend_i32_u(frame, stackPointer); + break; + case Bytecode.I64_TRUNC_F32_S: + i64_trunc_f32_s(frame, stackPointer); + break; + case Bytecode.I64_TRUNC_F32_U: + i64_trunc_f32_u(frame, stackPointer); + break; + case Bytecode.I64_TRUNC_F64_S: + i64_trunc_f64_s(frame, stackPointer); + break; + case Bytecode.I64_TRUNC_F64_U: + i64_trunc_f64_u(frame, stackPointer); + break; + case Bytecode.F32_CONVERT_I32_S: + f32_convert_i32_s(frame, stackPointer); + break; + case Bytecode.F32_CONVERT_I32_U: + f32_convert_i32_u(frame, stackPointer); + break; + case Bytecode.F32_CONVERT_I64_S: + f32_convert_i64_s(frame, stackPointer); + break; + case Bytecode.F32_CONVERT_I64_U: + f32_convert_i64_u(frame, stackPointer); + break; + case Bytecode.F32_DEMOTE_F64: + f32_demote_f64(frame, stackPointer); + break; + case Bytecode.F64_CONVERT_I32_S: + f64_convert_i32_s(frame, stackPointer); + break; + case Bytecode.F64_CONVERT_I32_U: + f64_convert_i32_u(frame, stackPointer); + break; + case Bytecode.F64_CONVERT_I64_S: + f64_convert_i64_s(frame, stackPointer); + break; + case Bytecode.F64_CONVERT_I64_U: + f64_convert_i64_u(frame, stackPointer); + break; + case Bytecode.F64_PROMOTE_F32: + f64_promote_f32(frame, stackPointer); + break; + case Bytecode.I32_REINTERPRET_F32: + i32_reinterpret_f32(frame, stackPointer); + break; + case Bytecode.I64_REINTERPRET_F64: + i64_reinterpret_f64(frame, stackPointer); + break; + case Bytecode.F32_REINTERPRET_I32: + f32_reinterpret_i32(frame, stackPointer); + break; + case Bytecode.F64_REINTERPRET_I64: + f64_reinterpret_i64(frame, stackPointer); + break; + case Bytecode.I32_EXTEND8_S: + i32_extend8_s(frame, stackPointer); + break; + case Bytecode.I32_EXTEND16_S: + i32_extend16_s(frame, stackPointer); + break; + case Bytecode.I64_EXTEND8_S: + i64_extend8_s(frame, stackPointer); + break; + case Bytecode.I64_EXTEND16_S: + i64_extend16_s(frame, stackPointer); + break; + case Bytecode.I64_EXTEND32_S: + i64_extend32_s(frame, stackPointer); + break; + case Bytecode.REF_NULL: + pushReference(frame, stackPointer, WasmConstant.NULL); + stackPointer++; + break; + case Bytecode.REF_IS_NULL: + final Object refType = popReference(frame, stackPointer - 1); + pushInt(frame, stackPointer - 1, refType == WasmConstant.NULL ? 1 : 0); + break; + case Bytecode.REF_FUNC: + final int functionIndex = rawPeekI32(bytecode, offset); + final WasmFunction function = module.symbolTable().function(functionIndex); + final WasmFunctionInstance functionInstance = instance.functionInstance(function); + pushReference(frame, stackPointer, functionInstance); + stackPointer++; + offset += 4; + break; + case Bytecode.TABLE_GET: { + final int tableIndex = rawPeekI32(bytecode, offset); + table_get(instance, frame, stackPointer, tableIndex); + offset += 4; + break; + } + case Bytecode.TABLE_SET: { + final int tableIndex = rawPeekI32(bytecode, offset); + table_set(instance, frame, stackPointer, tableIndex); + stackPointer -= 2; + offset += 4; + break; + } + case Bytecode.MISC: { + final int miscOpcode = rawPeekU8(bytecode, offset); + offset++; + CompilerAsserts.partialEvaluationConstant(miscOpcode); + switch (miscOpcode) { + case Bytecode.I32_TRUNC_SAT_F32_S: + i32_trunc_sat_f32_s(frame, stackPointer); + break; + case Bytecode.I32_TRUNC_SAT_F32_U: + i32_trunc_sat_f32_u(frame, stackPointer); + break; + case Bytecode.I32_TRUNC_SAT_F64_S: + i32_trunc_sat_f64_s(frame, stackPointer); + break; + case Bytecode.I32_TRUNC_SAT_F64_U: + i32_trunc_sat_f64_u(frame, stackPointer); + break; + case Bytecode.I64_TRUNC_SAT_F32_S: + i64_trunc_sat_f32_s(frame, stackPointer); + break; + case Bytecode.I64_TRUNC_SAT_F32_U: + i64_trunc_sat_f32_u(frame, stackPointer); + break; + case Bytecode.I64_TRUNC_SAT_F64_S: + i64_trunc_sat_f64_s(frame, stackPointer); + break; + case Bytecode.I64_TRUNC_SAT_F64_U: + i64_trunc_sat_f64_u(frame, stackPointer); + break; + case Bytecode.MEMORY_INIT: + case Bytecode.MEMORY64_INIT: { + final int dataIndex = rawPeekI32(bytecode, offset); + final int memoryIndex = rawPeekI32(bytecode, offset + 4); + executeMemoryInit(instance, frame, stackPointer, miscOpcode, memoryIndex, dataIndex); + stackPointer -= 3; + offset += 8; + break; + } + case Bytecode.DATA_DROP: { + final int dataIndex = rawPeekI32(bytecode, offset); + data_drop(instance, dataIndex); + offset += 4; + break; + } + case Bytecode.MEMORY_COPY: + case Bytecode.MEMORY64_COPY_D64_S64: + case Bytecode.MEMORY64_COPY_D64_S32: + case Bytecode.MEMORY64_COPY_D32_S64: { + final int destMemoryIndex = rawPeekI32(bytecode, offset); + final int srcMemoryIndex = rawPeekI32(bytecode, offset + 4); + executeMemoryCopy(instance, frame, stackPointer, miscOpcode, destMemoryIndex, srcMemoryIndex); + stackPointer -= 3; + offset += 8; + break; + } + case Bytecode.MEMORY_FILL: + case Bytecode.MEMORY64_FILL: { + final int memoryIndex = rawPeekI32(bytecode, offset); + executeMemoryFill(instance, frame, stackPointer, miscOpcode, memoryIndex); + stackPointer -= 3; + offset += 4; + break; + } + case Bytecode.TABLE_INIT: { + final int elementIndex = rawPeekI32(bytecode, offset); + final int tableIndex = rawPeekI32(bytecode, offset + 4); + + final int n = popInt(frame, stackPointer - 1); + final int src = popInt(frame, stackPointer - 2); + final int dst = popInt(frame, stackPointer - 3); + table_init(instance, n, src, dst, tableIndex, elementIndex); + stackPointer -= 3; + offset += 8; + break; + } + case Bytecode.ELEM_DROP: { + final int elementIndex = rawPeekI32(bytecode, offset); + instance.dropElemInstance(elementIndex); + offset += 4; + break; + } + case Bytecode.TABLE_COPY: { + final int srcIndex = rawPeekI32(bytecode, offset); + final int dstIndex = rawPeekI32(bytecode, offset + 4); + + final int n = popInt(frame, stackPointer - 1); + final int src = popInt(frame, stackPointer - 2); + final int dst = popInt(frame, stackPointer - 3); + table_copy(instance, n, src, dst, srcIndex, dstIndex); + stackPointer -= 3; + offset += 8; + break; + } + case Bytecode.TABLE_GROW: { + final int tableIndex = rawPeekI32(bytecode, offset); - final int n = popInt(frame, stackPointer - 1); - final Object val = popReference(frame, stackPointer - 2); + final int n = popInt(frame, stackPointer - 1); + final Object val = popReference(frame, stackPointer - 2); - final int res = table_grow(instance, n, val, tableIndex); - pushInt(frame, stackPointer - 2, res); - stackPointer--; - offset += 4; - break; - } - case Bytecode.TABLE_SIZE: { - final int tableIndex = rawPeekI32(bytecode, offset); - table_size(instance, frame, stackPointer, tableIndex); - stackPointer++; - offset += 4; - break; - } - case Bytecode.TABLE_FILL: { - final int tableIndex = rawPeekI32(bytecode, offset); - - final int n = popInt(frame, stackPointer - 1); - final Object val = popReference(frame, stackPointer - 2); - final int i = popInt(frame, stackPointer - 3); - table_fill(instance, n, val, i, tableIndex); - stackPointer -= 3; - offset += 4; - break; + final int res = table_grow(instance, n, val, tableIndex); + pushInt(frame, stackPointer - 2, res); + stackPointer--; + offset += 4; + break; + } + case Bytecode.TABLE_SIZE: { + final int tableIndex = rawPeekI32(bytecode, offset); + table_size(instance, frame, stackPointer, tableIndex); + stackPointer++; + offset += 4; + break; + } + case Bytecode.TABLE_FILL: { + final int tableIndex = rawPeekI32(bytecode, offset); + + final int n = popInt(frame, stackPointer - 1); + final Object val = popReference(frame, stackPointer - 2); + final int i = popInt(frame, stackPointer - 3); + table_fill(instance, n, val, i, tableIndex); + stackPointer -= 3; + offset += 4; + break; + } + case Bytecode.MEMORY64_SIZE: { + final int memoryIndex = rawPeekI32(bytecode, offset); + offset += 4; + final WasmMemory memory = memory(instance, memoryIndex); + long pageSize = memoryLib(memoryIndex).size(memory); + pushLong(frame, stackPointer, pageSize); + stackPointer++; + break; + } + case Bytecode.MEMORY64_GROW: { + final int memoryIndex = rawPeekI32(bytecode, offset); + offset += 4; + final WasmMemory memory = memory(instance, memoryIndex); + long extraSize = popLong(frame, stackPointer - 1); + long previousSize = memoryLib(memoryIndex).grow(memory, extraSize); + pushLong(frame, stackPointer - 1, previousSize); + break; + } + case Bytecode.THROW: { + codeEntry.exceptionBranch(); + final int tagIndex = rawPeekI32(bytecode, offset); + final int functionTypeIndex = module.tagTypeIndex(tagIndex); + final int numFields = module.functionTypeParamCount(functionTypeIndex); + final Object[] fields = createFieldsForException(frame, functionTypeIndex, numFields, stackPointer); + stackPointer -= numFields; + throw createException(instance.tag(tagIndex), fields); + } + case Bytecode.THROW_REF: { + codeEntry.exceptionBranch(); + final Object exception = frame.getObjectStatic(stackPointer - 1); + stackPointer--; + assert exception != null : "Exception object has to be a valid exception or wasm null"; + if (exception == WasmConstant.NULL) { + throw WasmException.create(Failure.NULL_REFERENCE); + } + assert exception instanceof WasmRuntimeException : "Only wasm exceptions can be thrown by throw_ref"; + throw (WasmRuntimeException) exception; + } + default: + throw CompilerDirectives.shouldNotReachHere(); } - case Bytecode.MEMORY64_SIZE: { - final int memoryIndex = rawPeekI32(bytecode, offset); - offset += 4; - final WasmMemory memory = memory(instance, memoryIndex); - long pageSize = memoryLib(memoryIndex).size(memory); - pushLong(frame, stackPointer, pageSize); - stackPointer++; + break; + } + case Bytecode.ATOMIC: { + final int atomicOpcode = rawPeekU8(bytecode, offset); + offset++; + CompilerAsserts.partialEvaluationConstant(atomicOpcode); + if (atomicOpcode == Bytecode.ATOMIC_FENCE) { break; } - case Bytecode.MEMORY64_GROW: { - final int memoryIndex = rawPeekI32(bytecode, offset); + + final int encoding = rawPeekU8(bytecode, offset); + offset++; + final int indexType64 = encoding & BytecodeBitEncoding.MEMORY_64_FLAG; + final int memoryIndex = rawPeekI32(bytecode, offset); + offset += 4; + final long memOffset; + if (indexType64 == 0) { + memOffset = rawPeekU32(bytecode, offset); offset += 4; - final WasmMemory memory = memory(instance, memoryIndex); - long extraSize = popLong(frame, stackPointer - 1); - long previousSize = memoryLib(memoryIndex).grow(memory, extraSize); - pushLong(frame, stackPointer - 1, previousSize); - break; + } else { + memOffset = rawPeekI64(bytecode, offset); + offset += 8; } - default: - throw CompilerDirectives.shouldNotReachHere(); + + final WasmMemory memory = memory(instance, memoryIndex); + final int stackPointerDecrement = executeAtomic(frame, stackPointer, atomicOpcode, memory, memoryLib(memoryIndex), memOffset, indexType64); + stackPointer -= stackPointerDecrement; + break; } - break; - } - case Bytecode.ATOMIC: { - final int atomicOpcode = rawPeekU8(bytecode, offset); - offset++; - CompilerAsserts.partialEvaluationConstant(atomicOpcode); - if (atomicOpcode == Bytecode.ATOMIC_FENCE) { + case Bytecode.VECTOR: { + final int vectorOpcode = rawPeekU8(bytecode, offset); + offset++; + CompilerAsserts.partialEvaluationConstant(vectorOpcode); + offset = executeVector(instance, frame, offset, stackPointer, vectorOpcode); + stackPointer += Vector128OpStackEffects.getVector128OpStackEffect(vectorOpcode); break; } - - final int encoding = rawPeekU8(bytecode, offset); - offset++; - final int indexType64 = encoding & BytecodeBitEncoding.MEMORY_64_FLAG; - final int memoryIndex = rawPeekI32(bytecode, offset); - offset += 4; - final long memOffset; - if (indexType64 == 0) { - memOffset = rawPeekU32(bytecode, offset); - offset += 4; - } else { - memOffset = rawPeekI64(bytecode, offset); + case Bytecode.NOTIFY: { + final int nextLineIndex = rawPeekI32(bytecode, offset); + final int sourceCodeLocation = rawPeekI32(bytecode, offset + 4); offset += 8; + assert notifyFunction != null; + notifyFunction.notifyLine(frame, lineIndex, nextLineIndex, sourceCodeLocation); + lineIndex = nextLineIndex; + break; } - - final WasmMemory memory = memory(instance, memoryIndex); - final int stackPointerDecrement = executeAtomic(frame, stackPointer, atomicOpcode, memory, memoryLib(memoryIndex), memOffset, indexType64); - stackPointer -= stackPointerDecrement; - break; + default: + throw CompilerDirectives.shouldNotReachHere(); } - case Bytecode.VECTOR: { - final int vectorOpcode = rawPeekU8(bytecode, offset); - offset++; - CompilerAsserts.partialEvaluationConstant(vectorOpcode); - offset = executeVector(instance, frame, offset, stackPointer, vectorOpcode); - stackPointer += Vector128OpStackEffects.getVector128OpStackEffect(vectorOpcode); - break; + } catch (WasmRuntimeException e) { + codeEntry.exceptionBranch(); + CompilerAsserts.partialEvaluationConstant(stackPointer); + CompilerAsserts.partialEvaluationConstant(offset); + + /* + * The source of the exception, i.e., the throw/throw_ref raising the exception or + * the call that forwarded the exception. + */ + final int source = offset; + + offset = codeEntry.exceptionTableOffset(); + + if (offset == 0) { + // no exception table, directly throw to the next function on the call stack + throw e; } - case Bytecode.NOTIFY: { - final int nextLineIndex = rawPeekI32(bytecode, offset); - final int sourceCodeLocation = rawPeekI32(bytecode, offset + 4); - offset += 8; - assert notifyFunction != null; - notifyFunction.notifyLine(frame, lineIndex, nextLineIndex, sourceCodeLocation); - lineIndex = nextLineIndex; - break; + + final WasmTag actualTag = e.tag(); + /* + * The exception table is encoded in the following format: + * + * from | to | type | tag index (optional) | target + * + * The values from (exclusive) and to (inclusive) define a range. If source is + * inside this range, the entry defines a possible exception handler for the + * exception. If the type of the exception handler is catch or catch_ref, we check + * whether the expected tag defined by the tag index and the tag of the exception + * object match. For catch_all and catch_all_ref we don't have this check. The catch + * and catch_all types push the values of the exception onto the stack, while + * catch_ref and catch_all_ref also push a reference to the exception itself. + * + * If we find a matching entry, execution continues at the label defined by target. + * + * If the current entry doesn't match, we continue to the next, until we reach a + * match or the end of the table. If we reach the end of the table, we rethrow the + * exception to the next function on the call stack. + */ + while (true) { + final int from = rawPeekI32(bytecode, offset); + if (from == -1) { + // we reached the end of the table + break; + } + offset += 4; + final int to = rawPeekI32(bytecode, offset); + offset += 4; + if (from < source && source <= to) { + final int type = rawPeekU8(bytecode, offset); + offset++; + switch (type) { + case ExceptionHandlerType.CATCH, ExceptionHandlerType.CATCH_REF -> { + final int tagIndex = rawPeekI32(bytecode, offset); + offset += 4; + final WasmTag expectedTag = instance.tag(tagIndex); + if (expectedTag == actualTag) { + final int functionTypeIndex = module.tagTypeIndex(tagIndex); + final int numFields = module.functionTypeParamCount(functionTypeIndex); + if (numFields != 0) { + pushExceptionFields(frame, e, functionTypeIndex, numFields, stackPointer); + stackPointer += numFields; + } + if (type == ExceptionHandlerType.CATCH_REF) { + pushReference(frame, stackPointer, e); + stackPointer++; + } + // load target + offset = rawPeekI32(bytecode, offset); + continue loop; + } else { + // skip target + offset += 4; + } + } + case ExceptionHandlerType.CATCH_ALL, ExceptionHandlerType.CATCH_ALL_REF -> { + // skip 0 tag + offset += 4; + if (type == ExceptionHandlerType.CATCH_ALL_REF) { + pushReference(frame, stackPointer, e); + stackPointer++; + } + // load target + offset = rawPeekI32(bytecode, offset); + continue loop; + } + } + } else { + // skip the entry + offset += 9; + } } - default: - throw CompilerDirectives.shouldNotReachHere(); + throw e; } } return WasmConstant.RETURN_VALUE; @@ -3252,6 +3373,7 @@ private void global_set(WasmInstance instance, VirtualFrame frame, int stackPoin break; case WasmType.FUNCREF_TYPE: case WasmType.EXTERNREF_TYPE: + case WasmType.EXNREF_TYPE: globals.storeReference(globalAddress, popReference(frame, stackPointer)); break; default: @@ -3283,6 +3405,7 @@ private void global_get(WasmInstance instance, VirtualFrame frame, int stackPoin break; case WasmType.FUNCREF_TYPE: case WasmType.EXTERNREF_TYPE: + case WasmType.EXNREF_TYPE: pushReference(frame, stackPointer, globals.loadAsReference(globalAddress)); break; default: @@ -4419,7 +4542,7 @@ private Object[] createArgumentsForCall(VirtualFrame frame, int functionTypeInde case WasmType.F32_TYPE -> popFloat(frame, stackPointer); case WasmType.F64_TYPE -> popDouble(frame, stackPointer); case WasmType.V128_TYPE -> vector128Ops().toVector128(popVector128(frame, stackPointer)); - case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE -> popReference(frame, stackPointer); + case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> popReference(frame, stackPointer); default -> throw WasmException.format(Failure.UNSPECIFIED_TRAP, this, "Unknown type: %d", type); }; WasmArguments.setArgument(args, i, arg); @@ -4427,6 +4550,59 @@ private Object[] createArgumentsForCall(VirtualFrame frame, int functionTypeInde return args; } + @TruffleBoundary + private WasmRuntimeException createException(WasmTag tag, Object[] fields) { + return new WasmRuntimeException(this, tag, fields); + } + + @ExplodeLoop + private Object[] createFieldsForException(VirtualFrame frame, int functionTypeIndex, int numFields, int stackPointerOffset) { + CompilerAsserts.partialEvaluationConstant(numFields); + CompilerAsserts.partialEvaluationConstant(functionTypeIndex); + CompilerAsserts.partialEvaluationConstant(stackPointerOffset); + final Object[] fields = new Object[numFields]; + int stackPointer = stackPointerOffset; + for (int i = numFields - 1; i >= 0; --i) { + stackPointer--; + byte type = module.symbolTable().functionTypeParamTypeAt(functionTypeIndex, i); + CompilerAsserts.partialEvaluationConstant(type); + final Object arg = switch (type) { + case WasmType.I32_TYPE -> popInt(frame, stackPointer); + case WasmType.I64_TYPE -> popLong(frame, stackPointer); + case WasmType.F32_TYPE -> popFloat(frame, stackPointer); + case WasmType.F64_TYPE -> popDouble(frame, stackPointer); + case WasmType.V128_TYPE -> vector128Ops().toVector128(popVector128(frame, stackPointer)); + case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> popReference(frame, stackPointer); + default -> throw WasmException.format(Failure.UNSPECIFIED_TRAP, this, "Unknown type: %d", type); + }; + fields[i] = arg; + } + return fields; + } + + @ExplodeLoop + private void pushExceptionFields(VirtualFrame frame, WasmRuntimeException e, int functionTypeIndex, int numFields, int stackPointerOffset) { + CompilerAsserts.partialEvaluationConstant(numFields); + CompilerAsserts.partialEvaluationConstant(functionTypeIndex); + CompilerAsserts.partialEvaluationConstant(stackPointerOffset); + final Object[] fields = e.fields(); + int stackPointer = stackPointerOffset; + for (int i = 0; i < numFields; i++) { + byte type = module.symbolTable().functionTypeParamTypeAt(functionTypeIndex, i); + CompilerAsserts.partialEvaluationConstant(type); + switch (type) { + case WasmType.I32_TYPE -> pushInt(frame, stackPointer, (int) fields[i]); + case WasmType.I64_TYPE -> pushLong(frame, stackPointer, (long) fields[i]); + case WasmType.F32_TYPE -> pushFloat(frame, stackPointer, (float) fields[i]); + case WasmType.F64_TYPE -> pushDouble(frame, stackPointer, (double) fields[i]); + case WasmType.V128_TYPE -> pushVector128(frame, stackPointer, vector128Ops().fromVector128((Vector128) fields[i])); + case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> pushReference(frame, stackPointer, fields[i]); + default -> throw WasmException.format(Failure.UNSPECIFIED_TRAP, this, "Unknown type: %d", type); + } + stackPointer++; + } + } + /** * Populates the stack with the result values of the current block (the one we are escaping * from). Reset the stack pointer to the target block stack pointer. @@ -4631,7 +4807,7 @@ private void pushResult(VirtualFrame frame, int stackPointer, byte resultType, O case WasmType.F32_TYPE -> pushFloat(frame, stackPointer, (float) result); case WasmType.F64_TYPE -> pushDouble(frame, stackPointer, (double) result); case WasmType.V128_TYPE -> pushVector128(frame, stackPointer, vector128Ops().fromVector128((Vector128) result)); - case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE -> pushReference(frame, stackPointer, result); + case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> pushReference(frame, stackPointer, result); default -> { throw WasmException.format(Failure.UNSPECIFIED_TRAP, this, "Unknown result type: %d", resultType); } @@ -4667,7 +4843,7 @@ private void extractMultiValueResult(VirtualFrame frame, int stackPointer, Objec pushVector128(frame, stackPointer + i, vector128Ops().fromVector128((Vector128) objectMultiValueStack[i])); objectMultiValueStack[i] = null; } - case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE -> { + case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE, WasmType.EXNREF_TYPE -> { pushReference(frame, stackPointer + i, objectMultiValueStack[i]); objectMultiValueStack[i] = null; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionRootNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionRootNode.java index 3010f425f62a..d1a8bf480031 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionRootNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionRootNode.java @@ -171,6 +171,7 @@ public Object executeWithInstance(VirtualFrame frame, WasmInstance instance) { return Vector128Ops.SINGLETON_IMPLEMENTATION.toVector128(popVector128(frame, localCount)); case WasmType.FUNCREF_TYPE: case WasmType.EXTERNREF_TYPE: + case WasmType.EXNREF_TYPE: return popReference(frame, localCount); default: throw WasmException.format(Failure.UNSPECIFIED_INTERNAL, this, "Unknown result type: %d", resultType); @@ -208,6 +209,7 @@ private void moveResultValuesToMultiValueStack(VirtualFrame frame, int resultCou break; case WasmType.FUNCREF_TYPE: case WasmType.EXTERNREF_TYPE: + case WasmType.EXNREF_TYPE: objectMultiValueStack[i] = popReference(frame, localCount + i); break; default: @@ -242,6 +244,7 @@ private void moveArgumentsToLocals(VirtualFrame frame) { break; case WasmType.FUNCREF_TYPE: case WasmType.EXTERNREF_TYPE: + case WasmType.EXNREF_TYPE: pushReference(frame, i, arg); break; } @@ -271,6 +274,7 @@ private void initializeLocals(VirtualFrame frame) { break; case WasmType.FUNCREF_TYPE: case WasmType.EXTERNREF_TYPE: + case WasmType.EXNREF_TYPE: pushReference(frame, i, WasmConstant.NULL); break; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/BytecodeParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/BytecodeParser.java index ed095a147d80..381b8ee59e80 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/BytecodeParser.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/BytecodeParser.java @@ -346,6 +346,9 @@ public static CodeEntry readCodeEntry(WasmModule module, byte[] bytecode, int co final int flags = bytecode[codeEntryOffset]; int effectiveOffset = codeEntryOffset + 1; + // the exception table offset is encoded in the 4 bytes before the code entry + final int exceptionTableOffset = BinaryStreamParser.rawPeekI32(bytecode, codeEntryOffset - 4); + final int functionIndex; switch (flags & BytecodeBitEncoding.CODE_ENTRY_FUNCTION_INDEX_MASK) { case BytecodeBitEncoding.CODE_ENTRY_FUNCTION_INDEX_ZERO: @@ -424,9 +427,17 @@ public static CodeEntry readCodeEntry(WasmModule module, byte[] bytecode, int co } else { results = Bytecode.EMPTY_BYTES; } - List callNodes = readCallNodes(bytecode, codeEntryOffset - length, codeEntryOffset); + final int endOffset; + if (exceptionTableOffset == 0) { + // no exception table + endOffset = (codeEntryOffset - 4); + } else { + endOffset = exceptionTableOffset; + } + final int startOffset = endOffset - length; + List callNodes = readCallNodes(bytecode, startOffset, endOffset); boolean usesMemoryZero = module.memoryCount() != 0; - return new CodeEntry(functionIndex, maxStackSize, locals, results, callNodes, codeEntryOffset - length, codeEntryOffset, usesMemoryZero); + return new CodeEntry(functionIndex, maxStackSize, locals, results, callNodes, startOffset, endOffset, usesMemoryZero, exceptionTableOffset); } /** @@ -774,7 +785,8 @@ private static List readCallNodes(byte[] bytecode, int startOffset, in case Bytecode.I64_TRUNC_SAT_F32_S: case Bytecode.I64_TRUNC_SAT_F32_U: case Bytecode.I64_TRUNC_SAT_F64_S: - case Bytecode.I64_TRUNC_SAT_F64_U: { + case Bytecode.I64_TRUNC_SAT_F64_U: + case Bytecode.THROW_REF: { break; } case Bytecode.MEMORY_FILL: @@ -797,7 +809,8 @@ private static List readCallNodes(byte[] bytecode, int startOffset, in case Bytecode.MEMORY64_COPY_D64_S32: case Bytecode.MEMORY64_COPY_D64_S64: case Bytecode.TABLE_INIT: - case Bytecode.TABLE_COPY: { + case Bytecode.TABLE_COPY: + case Bytecode.THROW: { offset += 8; break; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/RuntimeBytecodeGen.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/RuntimeBytecodeGen.java index 802a4916a44d..3e67faa0b8b7 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/RuntimeBytecodeGen.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/RuntimeBytecodeGen.java @@ -95,8 +95,7 @@ private void addProfile() { * * @param opcode The opcode */ - @Override - public void add(int opcode) { + public void addOp(int opcode) { assert fitsIntoUnsignedByte(opcode) : "opcode does not fit into byte"; add1(opcode); } @@ -108,7 +107,7 @@ public void add(int opcode) { * @param opcode The opcode * @param value The immediate value */ - public void add(int opcode, int value) { + public void addOp(int opcode, int value) { assert fitsIntoUnsignedByte(opcode) : "opcode does not fit into byte"; add1(opcode); add4(value); @@ -121,7 +120,7 @@ public void add(int opcode, int value) { * @param opcode The opcode * @param value The immediate value */ - public void add(int opcode, long value) { + public void addOp(int opcode, long value) { assert fitsIntoUnsignedByte(opcode) : "opcode does not fit into byte"; add1(opcode); add8(value); @@ -134,7 +133,7 @@ public void add(int opcode, long value) { * @param opcode The opcode * @param value The immediate value */ - public void add(int opcode, Vector128 value) { + public void addOp(int opcode, Vector128 value) { assert fitsIntoUnsignedByte(opcode) : "opcode does not fit into byte"; add1(opcode); add16(value); @@ -148,7 +147,7 @@ public void add(int opcode, Vector128 value) { * @param value1 The first immediate value * @param value2 The second immediate value */ - public void add(int opcode, int value1, int value2) { + public void addOp(int opcode, int value1, int value2) { assert fitsIntoUnsignedByte(opcode) : "opcode does not fit into byte"; add1(opcode); add4(value1); @@ -348,7 +347,7 @@ public int addLabel(int resultCount, int stackSize, int commonResultType) { */ public int addLoopLabel(int resultCount, int stackSize, int commonResultType) { int loopLabel = addLabel(resultCount, stackSize, commonResultType); - add(Bytecode.LOOP); + addOp(Bytecode.LOOP); return loopLabel; } @@ -479,6 +478,26 @@ public int addBranchTableItemLocation() { return location; } + /** + * Adds an exception handler that catches a specific exception type (tag), formatted as below. + * + *

+     * type (1 byte) | tag (4 byte) | target (4 byte)
+     * 
+ * + * @param type The opcode of the exception handler (see + * {@link org.graalvm.wasm.constants.ExceptionHandlerType}). + * @param tag The tag of the exception handler. + * @param target The target (jump location) of the exception handler. + */ + public void addExceptionHandler(int from, int to, int type, int tag, int target) { + add4(from); + add4(to); + add1(type); + add4(tag); + add4(target); + } + /** * Patches a jump offset location based on a given target location. * @@ -674,20 +693,15 @@ public void addDataRuntimeHeader(int length) { public int addElemHeader(int mode, int count, byte elemType, int tableIndex, byte[] offsetBytecode, int offsetAddress) { assert offsetBytecode == null || offsetAddress == -1 : "elem header does not allow offset bytecode and offset address"; assert mode == SegmentMode.ACTIVE || mode == SegmentMode.PASSIVE || mode == SegmentMode.DECLARATIVE : "invalid segment mode in elem header"; - assert elemType == WasmType.FUNCREF_TYPE || elemType == WasmType.EXTERNREF_TYPE : "invalid elem type in elem header"; + assert WasmType.isReferenceType(elemType) : "invalid elem type in elem header"; int location = location(); add1(0); - final int type; - switch (elemType) { - case WasmType.FUNCREF_TYPE: - type = BytecodeBitEncoding.ELEM_SEG_TYPE_FUNREF; - break; - case WasmType.EXTERNREF_TYPE: - type = BytecodeBitEncoding.ELEM_SEG_TYPE_EXTERNREF; - break; - default: - throw CompilerDirectives.shouldNotReachHere(); - } + final int type = switch (elemType) { + case WasmType.FUNCREF_TYPE -> BytecodeBitEncoding.ELEM_SEG_TYPE_FUNREF; + case WasmType.EXTERNREF_TYPE -> BytecodeBitEncoding.ELEM_SEG_TYPE_EXTERNREF; + case WasmType.EXNREF_TYPE -> BytecodeBitEncoding.ELEM_SEG_TYPE_EXNREF; + default -> throw CompilerDirectives.shouldNotReachHere(); + }; add1(type | mode); int flags = 0; @@ -861,7 +875,7 @@ public void addCodeEntry(int functionIndex, int maxStackSize, int length, int lo * @param sourceCodeLocation the location in the source */ public void addNotify(int lineNumber, int sourceCodeLocation) { - add(Bytecode.NOTIFY); + addOp(Bytecode.NOTIFY); add4(lineNumber); add4(sourceCodeLocation); } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/ir/CodeEntry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/ir/CodeEntry.java index a3248b3c2fc7..2e7624f7d01c 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/ir/CodeEntry.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/ir/CodeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -55,8 +55,10 @@ public final class CodeEntry { private final int bytecodeStartOffset; private final int bytecodeEndOffset; private final boolean usesMemoryZero; + private final int exceptionTableOffset; - public CodeEntry(int functionIndex, int maxStackSize, byte[] localTypes, byte[] resultTypes, List callNodes, int startOffset, int endOffset, boolean usesMemoryZero) { + public CodeEntry(int functionIndex, int maxStackSize, byte[] localTypes, byte[] resultTypes, List callNodes, int startOffset, int endOffset, boolean usesMemoryZero, + int exceptionTableOffset) { this.functionIndex = functionIndex; this.maxStackSize = maxStackSize; this.localTypes = localTypes; @@ -65,6 +67,7 @@ public CodeEntry(int functionIndex, int maxStackSize, byte[] localTypes, byte[] this.bytecodeStartOffset = startOffset; this.bytecodeEndOffset = endOffset; this.usesMemoryZero = usesMemoryZero; + this.exceptionTableOffset = exceptionTableOffset; } public int maxStackSize() { @@ -98,4 +101,8 @@ public int bytecodeEndOffset() { public boolean usesMemoryZero() { return usesMemoryZero; } + + public int exceptionTableOffset() { + return exceptionTableOffset; + } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/BlockFrame.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/BlockFrame.java index f5e39d7f5a0c..3f164c40c9d9 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/BlockFrame.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/BlockFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -41,6 +41,8 @@ package org.graalvm.wasm.parser.validation; +import java.util.ArrayList; + import org.graalvm.wasm.collection.IntArrayList; import org.graalvm.wasm.exception.Failure; import org.graalvm.wasm.exception.WasmException; @@ -51,10 +53,12 @@ */ class BlockFrame extends ControlFrame { private final IntArrayList branches; + private final ArrayList exceptionHandlers; BlockFrame(byte[] paramTypes, byte[] resultTypes, int initialStackSize, boolean unreachable) { super(paramTypes, resultTypes, initialStackSize, unreachable); branches = new IntArrayList(); + exceptionHandlers = new ArrayList<>(); } @Override @@ -69,13 +73,16 @@ void enterElse(ParserState state, RuntimeBytecodeGen bytecode) { @Override void exit(RuntimeBytecodeGen bytecode) { - if (branches.size() == 0) { + if (branches.size() == 0 && exceptionHandlers.isEmpty()) { return; } final int location = bytecode.addLabel(resultTypeLength(), initialStackSize(), commonResultType()); for (int branchLocation : branches.toArray()) { bytecode.patchLocation(branchLocation, location); } + for (ExceptionHandler catchEntry : exceptionHandlers) { + catchEntry.setTarget(location); + } } @Override @@ -92,4 +99,9 @@ void addBranchIf(RuntimeBytecodeGen bytecode) { void addBranchTableItem(RuntimeBytecodeGen bytecode) { branches.add(bytecode.addBranchTableItemLocation()); } + + @Override + void addExceptionHandler(ExceptionHandler handler) { + exceptionHandlers.add(handler); + } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ControlFrame.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ControlFrame.java index f14eafa8474a..4545dffbb890 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ControlFrame.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ControlFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -137,7 +137,7 @@ protected void resetUnreachable() { abstract void addBranch(RuntimeBytecodeGen bytecode); /** - * Adds a conditional branch targeting this control frame. Automatically patches the branch * + * Adds a conditional branch targeting this control frame. Automatically patches the branch * target as soon as it is available. * * @param bytecode The bytecode of the current control frame. @@ -146,11 +146,19 @@ protected void resetUnreachable() { abstract void addBranchIf(RuntimeBytecodeGen bytecode); /** - * Adds a branch table item targeting this control frame. Automatically patches the branch * + * Adds a branch table item targeting this control frame. Automatically patches the branch * target as soon as it is available. * * @param bytecode The bytecode of the current control frame. */ abstract void addBranchTableItem(RuntimeBytecodeGen bytecode); + + /** + * Adds an exception handler targeting this control frame. Automatically patches the exception + * handler target as soon as it is available. + * + * @param handler The exception handler that targets the frame. + */ + abstract void addExceptionHandler(ExceptionHandler handler); } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ExceptionHandler.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ExceptionHandler.java new file mode 100644 index 000000000000..59b95333bb4b --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ExceptionHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.wasm.parser.validation; + +/** + * Representation of an exception handler during parsing. + */ +public final class ExceptionHandler { + private final int type; + private final int tag; + private int target; + + public ExceptionHandler(int type, int tag) { + this.type = type; + this.tag = tag; + } + + public int type() { + return type; + } + + public int tag() { + return tag; + } + + public int target() { + return target; + } + + public void setTarget(int target) { + this.target = target; + } +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ExceptionTable.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ExceptionTable.java new file mode 100644 index 000000000000..03178f8bd3d5 --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ExceptionTable.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.wasm.parser.validation; + +import org.graalvm.wasm.parser.bytecode.RuntimeBytecodeGen; + +/** + * Represents exception handlers in the same range during parsing. + */ +public final class ExceptionTable { + private final int from; + private int to; + private final ExceptionHandler[] handlers; + + ExceptionTable(int from, ExceptionHandler[] handlers) { + this.from = from; + this.to = 0; + this.handlers = handlers; + } + + void setTo(int to) { + this.to = to; + } + + void generateExceptionTable(RuntimeBytecodeGen bytecode) { + assert to != 0 && to >= from : "Invalid exception table range"; + for (ExceptionHandler handler : handlers) { + if (handler.tag() == -1) { + // from (4 byte) | to (4 byte) | type (1 byte) | 0x0000_0000 | target (4 byte) + bytecode.addExceptionHandler(from, to, handler.type(), 0, handler.target()); + } else { + // from (4 byte) | to (4 byte) | type (1 byte) | tag (4 byte) | target (4 byte) + bytecode.addExceptionHandler(from, to, handler.type(), handler.tag(), handler.target()); + } + } + } +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/IfFrame.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/IfFrame.java index 36e791756852..fef91318d510 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/IfFrame.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/IfFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -41,6 +41,7 @@ package org.graalvm.wasm.parser.validation; +import java.util.ArrayList; import java.util.Arrays; import org.graalvm.wasm.collection.IntArrayList; @@ -54,12 +55,14 @@ class IfFrame extends ControlFrame { private final IntArrayList branchTargets; + private final ArrayList exceptionHandlers; private int falseJumpLocation; private boolean elseBranch; IfFrame(byte[] paramTypes, byte[] resultTypes, int initialStackSize, boolean unreachable, int falseJumpLocation) { super(paramTypes, resultTypes, initialStackSize, unreachable); branchTargets = new IntArrayList(); + exceptionHandlers = new ArrayList<>(); this.falseJumpLocation = falseJumpLocation; this.elseBranch = false; } @@ -85,7 +88,7 @@ void exit(RuntimeBytecodeGen bytecode) { if (!elseBranch && !Arrays.equals(paramTypes(), resultTypes())) { throw WasmException.create(Failure.TYPE_MISMATCH, "Expected else branch. If with incompatible param and result types requires else branch."); } - if (branchTargets.size() == 0) { + if (branchTargets.size() == 0 && exceptionHandlers.isEmpty()) { bytecode.patchLocation(falseJumpLocation, bytecode.location()); } else { final int location = bytecode.addLabel(resultTypeLength(), initialStackSize(), commonResultType()); @@ -93,6 +96,9 @@ void exit(RuntimeBytecodeGen bytecode) { for (int branchLocation : branchTargets.toArray()) { bytecode.patchLocation(branchLocation, location); } + for (ExceptionHandler catchEntry : exceptionHandlers) { + catchEntry.setTarget(location); + } } } @@ -110,4 +116,9 @@ void addBranchIf(RuntimeBytecodeGen bytecode) { void addBranchTableItem(RuntimeBytecodeGen bytecode) { branchTargets.add(bytecode.addBranchTableItemLocation()); } + + @Override + void addExceptionHandler(ExceptionHandler handler) { + exceptionHandlers.add(handler); + } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/LoopFrame.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/LoopFrame.java index a878f226ae4d..71b69f9281db 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/LoopFrame.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/LoopFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -84,4 +84,9 @@ void addBranchIf(RuntimeBytecodeGen bytecode) { void addBranchTableItem(RuntimeBytecodeGen bytecode) { bytecode.patchLocation(bytecode.addBranchTableItemLocation(), labelLocation); } + + @Override + void addExceptionHandler(ExceptionHandler handler) { + handler.setTarget(labelLocation); + } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ParserState.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ParserState.java index 551c4d74b4f1..05aed33ca6de 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ParserState.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ParserState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -43,6 +43,8 @@ import static java.lang.Integer.compareUnsigned; +import java.util.ArrayList; + import org.graalvm.wasm.Assert; import org.graalvm.wasm.WasmType; import org.graalvm.wasm.api.Vector128; @@ -63,6 +65,7 @@ public class ParserState { private final ByteArrayList valueStack; private final ControlStack controlStack; private final RuntimeBytecodeGen bytecode; + private final ArrayList exceptionTables; private int maxStackSize; private boolean usesMemoryZero; @@ -71,6 +74,7 @@ public ParserState(RuntimeBytecodeGen bytecode) { this.valueStack = new ByteArrayList(); this.controlStack = new ControlStack(); this.bytecode = bytecode; + this.exceptionTables = new ArrayList<>(); this.maxStackSize = 0; } @@ -314,8 +318,67 @@ public void enterElse() { pushAll(frame.paramTypes()); } + /** + * Creates a new try-table frame that holds information about the current try table and pushes + * it onto the control frame stack. + * + * @param paramTypes The param types of the try table that was entered. + * @param resultTypes The result types of the try table that was entered. + * @param handlers The exception handlers of the try table that was entered. + */ + public void enterTryTable(byte[] paramTypes, byte[] resultTypes, ExceptionHandler[] handlers) { + final TryTableFrame frame = new TryTableFrame(paramTypes, resultTypes, valueStack.size(), false, bytecode.location(), handlers); + controlStack.push(frame); + } + + /** + * Creates a new catch frame that holds information about the current catch clause and pushes it + * onto the control frame stack. + * + * @param opcode The opcode of the catch clause (exception handler type, see + * {@link org.graalvm.wasm.constants.ExceptionHandlerType}). + * @param tag The tag of the catch clause, if available. + * @param label The target label of the catch clause. + * @return A new exception handler for the catch clause. + */ + public ExceptionHandler enterCatchClause(int opcode, int tag, int label) { + checkLabelExists(label); + final ControlFrame labelFrame = getFrame(label); + // we reuse the block frame, instead of introducing a new catch frame. + final ControlFrame frame = new BlockFrame(WasmType.VOID_TYPE_ARRAY, labelFrame.labelTypes(), labelFrame.initialStackSize(), false); + controlStack.push(frame); + final ExceptionHandler e = new ExceptionHandler(opcode, tag); + labelFrame.addExceptionHandler(e); + return e; + } + + /** + * @return Whether the function contains any exception handlers. + */ + public boolean needsExceptionTable() { + return !exceptionTables.isEmpty(); + } + + /** + * Generates an exception table at the current location in the bytecode. The exception table has + * entries in the format: + * + *
+     *     from (4 byte) | to (4 byte) | opcode (1 byte) | tag (4 byte) (optional) | target (4 byte)
+     * 
+ * + * The exception table has a single 4 byte entry (0xffff_ffff) to indicate the end of the table. + */ + public void generateExceptionTable() { + for (ExceptionTable table : exceptionTables) { + table.generateExceptionTable(bytecode); + } + // add end indicator + bytecode.add(-1); + } + public void addInstruction(int instruction) { - bytecode.add(instruction); + bytecode.addOp(instruction); } public void addSelectInstruction(int instruction) { @@ -386,7 +449,7 @@ public void addReturn(boolean multiValue) { } checkResultTypes(frame); - bytecode.add(Bytecode.RETURN); + bytecode.addOp(Bytecode.RETURN); } /** @@ -411,21 +474,21 @@ public void addCall(int nodeIndex, int functionIndex) { * Adds the mics flag to the bytecode. */ public void addMiscFlag() { - bytecode.add(Bytecode.MISC); + bytecode.addOp(Bytecode.MISC); } /** * Adds the atomic flag to the bytecode. */ public void addAtomicFlag() { - bytecode.add(Bytecode.ATOMIC); + bytecode.addOp(Bytecode.ATOMIC); } /** * Adds the vector flag to the bytecode. */ public void addVectorFlag() { - bytecode.add(Bytecode.VECTOR); + bytecode.addOp(Bytecode.VECTOR); } /** @@ -435,7 +498,7 @@ public void addVectorFlag() { * @param value The immediate value */ public void addInstruction(int instruction, int value) { - bytecode.add(instruction, value); + bytecode.addOp(instruction, value); } /** @@ -445,7 +508,7 @@ public void addInstruction(int instruction, int value) { * @param value The immediate value */ public void addInstruction(int instruction, long value) { - bytecode.add(instruction, value); + bytecode.addOp(instruction, value); } /** @@ -455,7 +518,7 @@ public void addInstruction(int instruction, long value) { * @param value The immediate value */ public void addInstruction(int instruction, Vector128 value) { - bytecode.add(instruction, value); + bytecode.addOp(instruction, value); } /** @@ -466,7 +529,7 @@ public void addInstruction(int instruction, Vector128 value) { * @param value2 The second immediate value */ public void addInstruction(int instruction, int value1, int value2) { - bytecode.add(instruction, value1, value2); + bytecode.addOp(instruction, value1, value2); } /** @@ -554,7 +617,7 @@ public void addVectorMemoryLaneInstruction(int instruction, int memoryIndex, lon * @param laneIndex The lane index */ public void addVectorLaneInstruction(int instruction, byte laneIndex) { - bytecode.add(instruction); + bytecode.addOp(instruction); bytecode.add(laneIndex); } @@ -565,21 +628,27 @@ public void addVectorLaneInstruction(int instruction, byte laneIndex) { * * @throws WasmException If the number of return value types do not match with the remaining * stack or the number of return values is greater than 1. + * + * @return The types of the return values of the current frame. */ - public void exit(boolean multiValue) { + public byte[] exit(boolean multiValue) { Assert.assertTrue(!controlStack.isEmpty(), Failure.UNEXPECTED_END_OF_BLOCK); ControlFrame frame = controlStack.peek(); byte[] resultTypes = frame.resultTypes(); - frame.exit(bytecode); - checkStackAfterFrameExit(frame, resultTypes); + if (frame instanceof TryTableFrame e) { + final ExceptionTable t = e.table(); + t.setTo(bytecode.location()); + exceptionTables.add(t); + } + controlStack.pop(); if (!multiValue) { Assert.assertIntLessOrEqual(resultTypes.length, 1, "A block cannot return more than one value.", Failure.INVALID_RESULT_ARITY); } - pushAll(resultTypes); + return resultTypes; } /** diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/TryTableFrame.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/TryTableFrame.java new file mode 100644 index 000000000000..cc9b6f29a753 --- /dev/null +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/TryTableFrame.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.wasm.parser.validation; + +/** + * Representation of a wasm try table during module validation. + */ +public class TryTableFrame extends BlockFrame { + private final ExceptionTable table; + + TryTableFrame(byte[] paramTypes, byte[] resultTypes, int initialStackSize, boolean unreachable, int startOffset, ExceptionHandler[] handlers) { + super(paramTypes, resultTypes, initialStackSize, unreachable); + this.table = new ExceptionTable(startOffset, handlers); + } + + ExceptionTable table() { + return table; + } +} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ValidationErrors.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ValidationErrors.java index 7088d502788f..3e55f0dfec8c 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ValidationErrors.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ValidationErrors.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -41,34 +41,28 @@ package org.graalvm.wasm.parser.validation; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import java.util.StringJoiner; + +import org.graalvm.wasm.WasmType; import org.graalvm.wasm.exception.Failure; import org.graalvm.wasm.exception.WasmException; -import org.graalvm.wasm.WasmType; -import java.util.StringJoiner; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; public class ValidationErrors { private static String getValueTypeString(byte valueType) { - switch (valueType) { - case WasmType.VOID_TYPE: - return ""; - case WasmType.I32_TYPE: - return "i32"; - case WasmType.I64_TYPE: - return "i64"; - case WasmType.F32_TYPE: - return "f32"; - case WasmType.F64_TYPE: - return "f64"; - case WasmType.V128_TYPE: - return "v128"; - case WasmType.FUNCREF_TYPE: - return "funcref"; - case WasmType.EXTERNREF_TYPE: - return "externref"; - } - return "unknown"; + return switch (valueType) { + case WasmType.VOID_TYPE -> ""; + case WasmType.I32_TYPE -> "i32"; + case WasmType.I64_TYPE -> "i64"; + case WasmType.F32_TYPE -> "f32"; + case WasmType.F64_TYPE -> "f64"; + case WasmType.V128_TYPE -> "v128"; + case WasmType.FUNCREF_TYPE -> "funcref"; + case WasmType.EXTERNREF_TYPE -> "externref"; + case WasmType.EXNREF_TYPE -> "exnref"; + default -> "unknown"; + }; } private static String getValueTypesString(byte[] valueTypes) { From 41168dd87347580fa4c1bfe19a7a977bd13c1fe7 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Tue, 23 Sep 2025 19:21:36 +0200 Subject: [PATCH 02/12] Update WABT to version 1.0.37. --- ci/common.jsonnet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/common.jsonnet b/ci/common.jsonnet index ab2bdceb15af..c8ae2a46228f 100644 --- a/ci/common.jsonnet +++ b/ci/common.jsonnet @@ -316,7 +316,7 @@ local common_json = import "../common.json"; wasm:: { downloads+: { - WABT_DIR: {name: 'wabt', version: '1.0.36', platformspecific: true}, + WABT_DIR: {name: 'wabt', version: '1.0.37', platformspecific: true}, }, environment+: { WABT_DIR: '$WABT_DIR/bin', @@ -325,7 +325,7 @@ local common_json = import "../common.json"; wasm_ol8:: { downloads+: { - WABT_DIR: {name: 'wabt', version: '1.0.36-ol8', platformspecific: true}, + WABT_DIR: {name: 'wabt', version: '1.0.37-ol8', platformspecific: true}, }, environment+: { WABT_DIR: '$WABT_DIR/bin', From 7f20ae3327a4dedda890a3fcb20b686ba7a7d739 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Wed, 24 Sep 2025 01:12:48 +0200 Subject: [PATCH 03/12] Fix handling of exception table in instrumented bytecode. Move exceptionTableOffset field to WasmFunctionNode. No need to emit exception table offset in instrumented bytecode array. --- .../src/org/graalvm/wasm/BinaryParser.java | 22 +++++++++++-------- .../src/org/graalvm/wasm/WasmCodeEntry.java | 8 +------ .../org/graalvm/wasm/WasmInstantiator.java | 6 ++--- .../WasmFixedMemoryImplFunctionNode.java | 12 +++++----- .../graalvm/wasm/nodes/WasmFunctionNode.java | 13 ++++++----- .../nodes/WasmInstrumentableFunctionNode.java | 13 +++++++---- 6 files changed, 41 insertions(+), 33 deletions(-) diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java index 40bced228b60..dacef9a45429 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java @@ -1089,6 +1089,12 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy fail(Failure.SECTION_SIZE_MISMATCH, "END opcode expected"); } } + + if (offsetToLineIndexMap != null) { + // Make sure we notify a statement exit before leaving the function + bytecode.addNotify(-1, -1); + } + final int bytecodeEndOffset = bytecode.location(); final int exceptionTableOffset; @@ -1098,11 +1104,12 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy } else { exceptionTableOffset = 0; } - bytecode.add(exceptionTableOffset); - - final int functionEndOffset = bytecode.location(); if (offsetToLineIndexMap == null) { + bytecode.add(exceptionTableOffset); + + final int functionEndOffset = bytecode.location(); + bytecode.addCodeEntry(functionIndex, state.maxStackSize(), bytecodeEndOffset - bytecodeStartOffset, locals.length, resultTypes.length); for (byte local : locals) { bytecode.addByte(local); @@ -1119,9 +1126,6 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy // Do not override the code entry offset when rereading the function. module.setCodeEntryOffset(codeEntryIndex, functionEndOffset); - } else { - // Make sure we notify a statement exit before leaving the function - bytecode.addNotify(-1, -1); } return new CodeEntry(functionIndex, state.maxStackSize(), locals, resultTypes, callNodes, bytecodeStartOffset, bytecodeEndOffset, state.usesMemoryZero(), exceptionTableOffset); } @@ -3405,13 +3409,13 @@ private Vector128 readUnsignedInt128() { * index map */ @TruffleBoundary - public byte[] createFunctionDebugBytecode(int functionIndex, EconomicMap offsetToLineIndexMap) { + public Pair createFunctionDebugBytecode(int functionIndex, EconomicMap offsetToLineIndexMap) { final RuntimeBytecodeGen bytecode = new RuntimeBytecodeGen(); final int codeEntryIndex = functionIndex - module.numImportedFunctions(); final CodeEntry codeEntry = BytecodeParser.readCodeEntry(module, module.bytecode(), codeEntryIndex); offset = module.functionSourceCodeInstructionOffset(functionIndex); final int endOffset = module.functionSourceCodeEndOffset(functionIndex); - readFunction(functionIndex, codeEntry.localTypes(), codeEntry.resultTypes(), endOffset, true, bytecode, codeEntryIndex, offsetToLineIndexMap); - return bytecode.toArray(); + final CodeEntry result = readFunction(functionIndex, codeEntry.localTypes(), codeEntry.resultTypes(), endOffset, true, bytecode, codeEntryIndex, offsetToLineIndexMap); + return Pair.create(result, bytecode.toArray()); } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmCodeEntry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmCodeEntry.java index e3161b57bd6d..6dd87c4a6a35 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmCodeEntry.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmCodeEntry.java @@ -54,9 +54,8 @@ public final class WasmCodeEntry { private final int numLocals; private final int resultCount; private final boolean usesMemoryZero; - private final int exceptionTableOffset; - public WasmCodeEntry(WasmFunction function, byte[] bytecode, byte[] localTypes, byte[] resultTypes, boolean usesMemoryZero, int exceptionTableOffset) { + public WasmCodeEntry(WasmFunction function, byte[] bytecode, byte[] localTypes, byte[] resultTypes, boolean usesMemoryZero) { this.function = function; this.bytecode = bytecode; this.localTypes = localTypes; @@ -64,7 +63,6 @@ public WasmCodeEntry(WasmFunction function, byte[] bytecode, byte[] localTypes, this.resultTypes = resultTypes; this.resultCount = resultTypes.length; this.usesMemoryZero = usesMemoryZero; - this.exceptionTableOffset = exceptionTableOffset; } public WasmFunction function() { @@ -107,10 +105,6 @@ public boolean usesMemoryZero() { return usesMemoryZero; } - public int exceptionTableOffset() { - return exceptionTableOffset; - } - @Override public String toString() { return "wasm-code-entry:" + functionIndex(); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java index 8231729cb088..562d634c88c1 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java @@ -527,11 +527,11 @@ private CallTarget instantiateCodeEntry(WasmStore store, WasmModule module, Wasm } private WasmFunctionRootNode instantiateCodeEntryRootNode(WasmStore store, WasmModule module, CodeEntry codeEntry, WasmFunction function) { - final WasmCodeEntry wasmCodeEntry = new WasmCodeEntry(function, module.bytecode(), codeEntry.localTypes(), codeEntry.resultTypes(), codeEntry.usesMemoryZero(), - codeEntry.exceptionTableOffset()); + final WasmCodeEntry wasmCodeEntry = new WasmCodeEntry(function, module.bytecode(), codeEntry.localTypes(), codeEntry.resultTypes(), codeEntry.usesMemoryZero()); final FrameDescriptor frameDescriptor = createFrameDescriptor(codeEntry.localTypes(), codeEntry.maxStackSize()); final Node[] callNodes = setupCallNodes(module, codeEntry); - final WasmFixedMemoryImplFunctionNode functionNode = WasmFixedMemoryImplFunctionNode.create(module, wasmCodeEntry, codeEntry.bytecodeStartOffset(), codeEntry.bytecodeEndOffset(), callNodes); + final WasmFixedMemoryImplFunctionNode functionNode = WasmFixedMemoryImplFunctionNode.create(module, wasmCodeEntry, codeEntry.bytecodeStartOffset(), codeEntry.bytecodeEndOffset(), + codeEntry.exceptionTableOffset(), callNodes); final WasmFunctionRootNode rootNode; if (store.getContextOptions().memoryOverheadMode()) { rootNode = new WasmMemoryOverheadModeFunctionRootNode(language, frameDescriptor, module, functionNode, wasmCodeEntry); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFixedMemoryImplFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFixedMemoryImplFunctionNode.java index 99f4cc6b99eb..ec176ef7b6b5 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFixedMemoryImplFunctionNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFixedMemoryImplFunctionNode.java @@ -70,22 +70,24 @@ public abstract class WasmFixedMemoryImplFunctionNode extends Node { private final WasmCodeEntry codeEntry; private final int bytecodeStartOffset; private final int bytecodeEndOffset; + private final int exceptionTableOffset; private final Node[] callNodes; private static final WasmFunctionBaseNode[] EMPTY_FUNCTION_BASE_NODES = new WasmFunctionBaseNode[0]; @Children private WasmFunctionBaseNode[] functionBaseNodes = EMPTY_FUNCTION_BASE_NODES; - protected WasmFixedMemoryImplFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, Node[] callNodes) { + protected WasmFixedMemoryImplFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, int exceptionTableOffset, Node[] callNodes) { this.module = module; this.codeEntry = codeEntry; this.bytecodeStartOffset = bytecodeStartOffset; this.bytecodeEndOffset = bytecodeEndOffset; + this.exceptionTableOffset = exceptionTableOffset; this.callNodes = callNodes; } - public static WasmFixedMemoryImplFunctionNode create(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, Node[] callNodes) { - return WasmFixedMemoryImplFunctionNodeGen.create(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, callNodes); + public static WasmFixedMemoryImplFunctionNode create(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, int exceptionTableOffset, Node[] callNodes) { + return WasmFixedMemoryImplFunctionNodeGen.create(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, exceptionTableOffset, callNodes); } @Specialization(guards = {"memoryCount() == 1"}, limit = "3") @@ -115,7 +117,7 @@ protected int memoryCount() { @NeverDefault protected WasmFunctionBaseNode createSpecializedFunctionNode(WasmMemoryLibrary[] memoryLibs) { CompilerAsserts.neverPartOfCompilation(); - WasmInstrumentableFunctionNode instrumentableFunctionNode = new WasmInstrumentableFunctionNode(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, callNodes, memoryLibs); + var instrumentableFunctionNode = new WasmInstrumentableFunctionNode(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, exceptionTableOffset, callNodes, memoryLibs); WasmFunctionBaseNode baseNode = new WasmFunctionBaseNode(instrumentableFunctionNode); functionBaseNodes = Arrays.copyOf(functionBaseNodes, functionBaseNodes.length + 1); functionBaseNodes[functionBaseNodes.length - 1] = insert(baseNode); @@ -130,7 +132,7 @@ protected WasmFunctionBaseNode createDispatchedFunctionNode() { for (int memoryIndex = 0; memoryIndex < module.memoryCount(); memoryIndex++) { memoryLibs[memoryIndex] = insert(WasmMemoryLibrary.getFactory().createDispatched(3)); } - WasmInstrumentableFunctionNode instrumentableFunctionNode = new WasmInstrumentableFunctionNode(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, callNodes, memoryLibs); + var instrumentableFunctionNode = new WasmInstrumentableFunctionNode(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, exceptionTableOffset, callNodes, memoryLibs); WasmFunctionBaseNode baseNode = new WasmFunctionBaseNode(instrumentableFunctionNode); functionBaseNodes = new WasmFunctionBaseNode[]{insert(baseNode)}; notifyInserted(instrumentableFunctionNode); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java index 3cb0ae135f0c..0faeeaadada3 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java @@ -132,16 +132,18 @@ public final class WasmFunctionNode extends Node implements BytecodeOSRNod private final int bytecodeStartOffset; private final int bytecodeEndOffset; + private final int exceptionTableOffset; @CompilationFinal(dimensions = 1) private final byte[] bytecode; @CompilationFinal private WasmNotifyFunction notifyFunction; @Children private final WasmMemoryLibrary[] memoryLibs; - public WasmFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, Node[] callNodes, WasmMemoryLibrary[] memoryLibs) { + public WasmFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, int exceptionTableOffset, Node[] callNodes, WasmMemoryLibrary[] memoryLibs) { this.module = module; this.codeEntry = codeEntry; this.bytecodeStartOffset = bytecodeStartOffset; this.bytecodeEndOffset = bytecodeEndOffset; + this.exceptionTableOffset = exceptionTableOffset; this.bytecode = codeEntry.bytecode(); this.callNodes = new Node[callNodes.length]; for (int childIndex = 0; childIndex < callNodes.length; childIndex++) { @@ -160,11 +162,12 @@ public WasmFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecode * @param notifyFunction The callback used by {@link Bytecode#NOTIFY} instructions to inform * instruments about statements in the bytecode */ - WasmFunctionNode(WasmFunctionNode node, byte[] bytecode, WasmNotifyFunction notifyFunction) { + WasmFunctionNode(WasmFunctionNode node, byte[] bytecode, int bytecodeStartOffset, int bytecodeEndOffset, int exceptionTableOffset, WasmNotifyFunction notifyFunction) { this.module = node.module; this.codeEntry = node.codeEntry; - this.bytecodeStartOffset = 0; - this.bytecodeEndOffset = bytecode.length; + this.bytecodeStartOffset = bytecodeStartOffset; + this.bytecodeEndOffset = bytecodeEndOffset; + this.exceptionTableOffset = exceptionTableOffset; this.bytecode = bytecode; this.callNodes = new Node[node.callNodes.length]; for (int childIndex = 0; childIndex < callNodes.length; childIndex++) { @@ -1731,7 +1734,7 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i */ final int source = offset; - offset = codeEntry.exceptionTableOffset(); + offset = this.exceptionTableOffset; if (offset == 0) { // no exception table, directly throw to the next function on the call stack diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java index 1f6b42657bc9..f7942d704745 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java @@ -56,6 +56,7 @@ import org.graalvm.wasm.debugging.representation.DebugScopeDisplayValue; import org.graalvm.wasm.memory.WasmMemory; import org.graalvm.wasm.memory.WasmMemoryLibrary; +import org.graalvm.wasm.parser.ir.CodeEntry; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -89,10 +90,11 @@ public class WasmInstrumentableFunctionNode extends Node implements Instrumentab @Child private WasmMemoryLibrary zeroMemoryLib; - public WasmInstrumentableFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, Node[] callNodes, WasmMemoryLibrary[] memoryLibs) { + public WasmInstrumentableFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, int exceptionTableOffset, Node[] callNodes, + WasmMemoryLibrary[] memoryLibs) { this.module = module; this.codeEntry = codeEntry; - this.functionNode = new WasmFunctionNode<>(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, callNodes, memoryLibs); + this.functionNode = new WasmFunctionNode<>(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, exceptionTableOffset, callNodes, memoryLibs); this.functionSourceLocation = module.functionSourceCodeStartOffset(codeEntry.functionIndex()); this.zeroMemoryLib = module.memoryCount() > 0 ? memoryLibs[0] : null; } @@ -201,8 +203,11 @@ public InstrumentableNode materializeInstrumentableNodes(Set functionNodeDuplicate = new WasmFunctionNode<>(functionNode, bytecode, support::notifyLine); + final var bytecodePair = binaryParser.createFunctionDebugBytecode(functionIndex, debugLineSection.offsetToLineIndexMap()); + final CodeEntry bcInfo = bytecodePair.getLeft(); + final byte[] bytecode = bytecodePair.getRight(); + final WasmFunctionNode functionNodeDuplicate = new WasmFunctionNode<>(functionNode, bytecode, + bcInfo.bytecodeStartOffset(), bcInfo.bytecodeEndOffset(), bcInfo.exceptionTableOffset(), support::notifyLine); return new WasmInstrumentableFunctionNode(this, functionNodeDuplicate, support); } } finally { From b4d59eb746114f238ad09da21360097bd1e8adbb Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Wed, 24 Sep 2025 01:18:18 +0200 Subject: [PATCH 04/12] Directly use WasmInstrumentationSupportNode as WasmNotifyFunction. --- .../org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java | 2 +- .../org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java index f7942d704745..56b5d534e75f 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java @@ -207,7 +207,7 @@ public InstrumentableNode materializeInstrumentableNodes(Set functionNodeDuplicate = new WasmFunctionNode<>(functionNode, bytecode, - bcInfo.bytecodeStartOffset(), bcInfo.bytecodeEndOffset(), bcInfo.exceptionTableOffset(), support::notifyLine); + bcInfo.bytecodeStartOffset(), bcInfo.bytecodeEndOffset(), bcInfo.exceptionTableOffset(), support); return new WasmInstrumentableFunctionNode(this, functionNodeDuplicate, support); } } finally { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.java index 7104e3ece103..832612711f6d 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.java @@ -58,7 +58,7 @@ * Represents the statements in the source file of a wasm binary. Provides some helper methods to * interact with the instrumentation system. */ -public final class WasmInstrumentationSupportNode extends Node { +public final class WasmInstrumentationSupportNode extends Node implements WasmNotifyFunction { @Children private final WasmBaseStatementNode[] statementNodes; private int sourceLocation; @@ -78,6 +78,7 @@ public WasmInstrumentationSupportNode(DebugLineSection lineSection, Source sourc } } + @Override public void notifyLine(VirtualFrame frame, int currentLineIndex, int nextLineIndex, int currentSourceLocation) { CompilerAsserts.partialEvaluationConstant(currentLineIndex); CompilerAsserts.partialEvaluationConstant(nextLineIndex); From 0587fd074c372ee4ae0c5780b4a09a168d782951 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Wed, 24 Sep 2025 14:51:34 +0200 Subject: [PATCH 05/12] Update owners of GraalWasm. --- wasm/src/org.graalvm.wasm/OWNERS.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/src/org.graalvm.wasm/OWNERS.toml b/wasm/src/org.graalvm.wasm/OWNERS.toml index 904bdcd3d23d..ec03484c6b46 100644 --- a/wasm/src/org.graalvm.wasm/OWNERS.toml +++ b/wasm/src/org.graalvm.wasm/OWNERS.toml @@ -3,5 +3,5 @@ files = "*" all = [ "aleksandar.prokopec@oracle.com", "andreas.woess@oracle.com", - "florian.huemer@oracle.com", -] \ No newline at end of file + "jiri.marsik@oracle.com", +] From ab1d6363ae189da97ae7fcefccb0bc8e0883411a Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Wed, 24 Sep 2025 02:17:42 +0200 Subject: [PATCH 06/12] Validate tag attribute field and empty tag result type. Throw on tag import and export identifiers if exception handling is disabled. --- .../suites/validation/ValidationSuite.java | 21 +++++++++++++++++-- .../src/org/graalvm/wasm/BinaryParser.java | 15 ++++++++----- .../org/graalvm/wasm/BinaryStreamParser.java | 20 +++++++++++++++++- .../src/org/graalvm/wasm/SymbolTable.java | 1 + .../org/graalvm/wasm/exception/Failure.java | 1 + 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java index a661439f0199..1e4c35c44603 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java @@ -205,7 +205,7 @@ public static Collection data() { Failure.Type.MALFORMED), binaryCase( "Global - invalid modified", - "Invalid mutability flag: 2", + "Invalid mutability flag: 0x02", "00 61 73 6D 01 00 00 00 06 06 01 7F 02 41 00 0B", Failure.Type.MALFORMED), binaryCase( @@ -993,7 +993,24 @@ public static Collection data() { "Data Count Section - not supported", "invalid section ID: 12", "00 61 73 6D 01 00 00 00 0C 00", - Failure.Type.MALFORMED)); + Failure.Type.MALFORMED), + + // Tag section + binaryCase( + "Tag section - invalid attribute", + "Invalid tag attribute: 0x01", + "00 61 73 6d 01 00 00 00 " + + "01 04 01 60 00 00 " + // type section + "0d 03 01 01 00", // tag section + Failure.Type.MALFORMED), + binaryCase( + "Tag section - non-empty result type", + "non-empty tag result type: 1 should = 0", + // (module (tag (result i32))) + "00 61 73 6d 01 00 00 00 " + + "01 05 01 60 00 01 7f " + // type section + "0d 03 01 00 00", // tag section + Failure.Type.INVALID)); } private final String expectedErrorMessage; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java index dacef9a45429..952a5e1cb2e2 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java @@ -450,14 +450,17 @@ private void readImportSection() { break; } case ImportIdentifier.TAG: { - final byte attribute = read1(); + if (!exceptions) { + fail(Failure.MALFORMED_IMPORT_KIND, "Invalid import type identifier: 0x%02x", importType); + } + final byte attribute = readTagAttribute(); final int typeIndex = readTypeIndex(); final int tagIndex = module.symbolTable().tagCount(); module.symbolTable().importTag(moduleName, memberName, tagIndex, attribute, typeIndex); break; } default: { - fail(Failure.MALFORMED_IMPORT_KIND, "Invalid import type identifier: 0x%02X", importType); + fail(Failure.MALFORMED_IMPORT_KIND, "Invalid import type identifier: 0x%02x", importType); } } } @@ -2932,12 +2935,15 @@ private void readExportSection() { break; } case ExportIdentifier.TAG: { + if (!exceptions) { + fail(Failure.UNSPECIFIED_MALFORMED, "Invalid export type identifier: 0x%02x", exportType); + } final int index = readTagIndex(); module.symbolTable().exportTag(index, exportName); break; } default: { - fail(Failure.UNSPECIFIED_MALFORMED, "Invalid export type identifier: 0x%02X", exportType); + fail(Failure.UNSPECIFIED_MALFORMED, "Invalid export type identifier: 0x%02x", exportType); } } } @@ -2950,8 +2956,7 @@ private void readTagSection() { for (int tagIndex = startingTagIndex; tagIndex != startingTagIndex + tagCount; tagIndex++) { assertTrue(!isEOF(), Failure.LENGTH_OUT_OF_BOUNDS); // 0x00 means exception - final byte attribute = read1(); - assertByteEqual(attribute, (byte) WasmTag.Attribute.EXCEPTION, Failure.MALFORMED_TAG_ATTRIBUTE); + final byte attribute = readTagAttribute(); final int type = readTypeIndex(); module.symbolTable().allocateTag(tagIndex, attribute, type); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryStreamParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryStreamParser.java index aefcc4f50679..5ba793ba08e2 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryStreamParser.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryStreamParser.java @@ -204,7 +204,25 @@ protected byte peekMutability() { } else if (mut == GlobalModifier.MUTABLE) { return mut; } else { - throw Assert.fail(Failure.MALFORMED_MUTABILITY, "Invalid mutability flag: " + mut); + throw Assert.fail(Failure.MALFORMED_MUTABILITY, "Invalid mutability flag: 0x%02x", mut); + } + } + + /** + * Reads the attribute of a tag (uint8). + */ + protected byte readTagAttribute() { + final byte attribute = peekTagAttribute(); + offset++; + return attribute; + } + + protected byte peekTagAttribute() { + final byte attribute = peek1(); + if (attribute == WasmTag.Attribute.EXCEPTION) { + return attribute; + } else { + throw Assert.fail(Failure.MALFORMED_TAG_ATTRIBUTE, "Invalid tag attribute: 0x%02x", attribute); } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java index 619457de6b57..c47940f17676 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java @@ -1184,6 +1184,7 @@ public void importTag(String moduleName, String tagName, int index, byte attribu } void addTag(int index, byte attribute, int typeIndex) { + assertIntEqual(functionTypeResultCount(typeIndex), 0, Failure.NON_EMPTY_TAG_RESULT_TYPE); ensureTagCapacity(index); final TagInfo tag = new TagInfo(attribute, typeIndex); tags[index] = tag; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/Failure.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/Failure.java index d09b0a1d1b3f..11d25cc18f8f 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/Failure.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/exception/Failure.java @@ -76,6 +76,7 @@ public enum Failure { UNSPECIFIED_INVALID(Type.INVALID, "unspecified"), TYPE_MISMATCH(Type.INVALID, "type mismatch"), INVALID_RESULT_ARITY(Type.INVALID, "invalid result arity"), + NON_EMPTY_TAG_RESULT_TYPE(Type.INVALID, "non-empty tag result type"), MULTIPLE_MEMORIES(Type.INVALID, "multiple memories"), MULTIPLE_TABLES(Type.INVALID, "multiple tables"), LOOP_INPUT(Type.INVALID, "non-empty loop input type"), From 64af7fbd9395a269aa7b1978e8224d826b6284b5 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 26 Sep 2025 01:18:20 +0200 Subject: [PATCH 07/12] Add tag members to wasm instance exports. --- .../src/org/graalvm/wasm/WasmInstanceExports.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstanceExports.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstanceExports.java index d8f35d63de3b..65a6a14321a2 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstanceExports.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstanceExports.java @@ -90,6 +90,10 @@ public Object readMember(String member) throws UnknownIdentifierException { if (globalIndex != null) { return instance.externalGlobal(globalIndex); } + final Integer tagIndex = symbolTable.exportedTags().get(member); + if (tagIndex != null) { + return instance.tag(tagIndex); + } throw UnknownIdentifierException.create(member); } @@ -100,7 +104,8 @@ boolean isMemberReadable(String member) { return symbolTable.exportedFunctions().containsKey(member) || symbolTable.exportedMemories().containsKey(member) || symbolTable.exportedTables().containsKey(member) || - symbolTable.exportedGlobals().containsKey(member); + symbolTable.exportedGlobals().containsKey(member) || + symbolTable.exportedTags().containsKey(member); } @ExportMessage @@ -145,6 +150,9 @@ Object getMembers(@SuppressWarnings("unused") boolean includeInternal) { for (String globalName : symbolTable.exportedGlobals().getKeys()) { exportNames.add(globalName); } + for (String globalName : symbolTable.exportedTags().getKeys()) { + exportNames.add(globalName); + } return new Sequence<>(exportNames); } From d29db5388004bf1fd6a8e913763c839b7f900e49 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 26 Sep 2025 01:34:00 +0200 Subject: [PATCH 08/12] Address review feedback and fix bugs in exception handling. Fix reading more than one exception handler. Fix exn_read: should return exception fields, not the tag. Add exn_tag. Fix tag count limit. Fix THROW bytecode length. Simplify ParserState.exit(), moving out TryTableFrame handling. --- .../bytecode/MultiInstantiationSuite.java | 10 +++- .../exceptions/exceptions_with_params.wat | 2 +- .../src/test/exceptions/throw_ref.wat | 1 - .../src/org/graalvm/wasm/BinaryParser.java | 6 ++- .../src/org/graalvm/wasm/ModuleLimits.java | 4 +- .../src/org/graalvm/wasm/SymbolTable.java | 2 +- .../src/org/graalvm/wasm/api/FuncType.java | 33 +++++-------- .../src/org/graalvm/wasm/api/JsConstants.java | 2 +- .../src/org/graalvm/wasm/api/WebAssembly.java | 17 +++++-- .../wasm/constants/BytecodeBitEncoding.java | 4 ++ .../graalvm/wasm/constants/Instructions.java | 2 +- .../graalvm/wasm/nodes/WasmFunctionNode.java | 48 +++++++++++-------- .../wasm/parser/bytecode/BytecodeParser.java | 8 ++-- .../parser/bytecode/RuntimeBytecodeGen.java | 6 ++- .../wasm/parser/validation/ParserState.java | 8 +--- .../wasm/parser/validation/TryTableFrame.java | 9 ++++ 16 files changed, 95 insertions(+), 67 deletions(-) diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java index 637e5e76fdbe..8257acf5c412 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java @@ -232,10 +232,16 @@ public void testImportsAndExports() throws IOException, InterruptedException { final Object e = WebAssembly.instanceExport(i, "e"); final Object exception = WebAssembly.instanceExport(i, "exception"); final Object eInstance = lib.execute(exception); - final Object tagRead = wasm.readMember("exn_read"); - final Object eInstanceTag = lib.execute(tagRead, eInstance); + + final Object exnTag = wasm.readMember("exn_tag"); + final Object eInstanceTag = lib.execute(exnTag, eInstance); Assert.assertSame("Exception tag does not match", e, eInstanceTag); + final Object exnRead = wasm.readMember("exn_read"); + final Object eInstanceFields = lib.execute(exnRead, eInstance); + Assert.assertTrue("Exception fields is not an array", lib.hasArrayElements(eInstanceFields)); + Assert.assertEquals("Exception fields array size", 0, lib.getArraySize(eInstanceFields)); + final Object test = WebAssembly.instanceExport(i, "test"); final int result = lib.asInt(lib.execute(test)); Assert.assertEquals("Invalid test value", 64, result); diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.wat index 80585fadfa85..bf8374835c15 100644 --- a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.wat +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params.wat @@ -47,7 +47,7 @@ block $h (result i32) try_table (result i32) (catch $e0 $h) block $h1 (result i32) - try_table (result i32) (catch $e1 $h) + try_table (result i32) (catch $e1 $h1) i32.const 4 throw $e0 i32.const 42 diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.wat index 1ce94f5e5819..cc56d3556dac 100644 --- a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.wat +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/throw_ref.wat @@ -42,7 +42,6 @@ (type $t0 (func)) (tag $e0 (type $t0)) - (tag $e1 (type $t0)) (func $throw_e0 (param i32) local.get 0 diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java index 952a5e1cb2e2..c2609ef80789 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java @@ -78,6 +78,7 @@ import org.graalvm.wasm.api.Vector128Shape; import org.graalvm.wasm.collection.ByteArrayList; import org.graalvm.wasm.constants.Bytecode; +import org.graalvm.wasm.constants.BytecodeBitEncoding; import org.graalvm.wasm.constants.ExceptionHandlerType; import org.graalvm.wasm.constants.ExportIdentifier; import org.graalvm.wasm.constants.GlobalModifier; @@ -1103,9 +1104,10 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy final int exceptionTableOffset; if (state.needsExceptionTable()) { exceptionTableOffset = bytecode.location(); + assert exceptionTableOffset != BytecodeBitEncoding.INVALID_EXCEPTION_TABLE_OFFSET; state.generateExceptionTable(); } else { - exceptionTableOffset = 0; + exceptionTableOffset = BytecodeBitEncoding.INVALID_EXCEPTION_TABLE_OFFSET; } if (offsetToLineIndexMap == null) { @@ -2504,7 +2506,7 @@ private ExceptionHandler[] readExceptionHandlers(ParserState state) { final int length = readLength(); final ExceptionHandler[] handlers = new ExceptionHandler[length]; - for (int i = 0; i < length; i += 2) { + for (int i = 0; i < length; i++) { final int opcode = read1() & 0xFF; switch (opcode) { case ExceptionHandlerType.CATCH -> { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java index d998cd662db3..4a2e739e6084 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java @@ -201,8 +201,8 @@ public void checkMemoryInstanceSize(long size, boolean indexType64) { } } - public void checkTagCount(int size) { - assertUnsignedIntLessOrEqual(size, tagCountLimit, Failure.TAG_COUNT_LIMIT_EXCEEDED); + public void checkTagCount(int count) { + assertUnsignedIntLessOrEqual(count, tagCountLimit, Failure.TAG_COUNT_LIMIT_EXCEEDED); } public int tableInstanceSizeLimit() { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java index c47940f17676..b114ff98bcc4 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java @@ -1199,7 +1199,7 @@ public void exportTag(int tagIndex, String name) { checkNotParsed(); exportSymbol(name); if (!checkExistingTagIndex(tagIndex)) { - throw WasmException.create(Failure.UNSPECIFIED_INVALID, "No tag with the specified index has been declared or imported, so it cannot be exported."); + throw WasmException.create(Failure.UNKNOWN_TAG, "No tag with the specified index has been declared or imported, so it cannot be exported."); } exportedTags.put(name, tagIndex); module().addLinkAction((context, store, instance, imports) -> { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/FuncType.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/FuncType.java index b4cbb603842d..560eedc8eb81 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/FuncType.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/FuncType.java @@ -66,29 +66,22 @@ public static FuncType fromString(String s) { if (leftPar == -1 || rightPar == -1) { throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "Invalid function type format"); } - final String paramTypesString = s.substring(leftPar + 1, rightPar); - final ValueType[] params; - if (paramTypesString.isEmpty()) { - params = EMTPY; - } else { - final String[] paramTypes = s.substring(leftPar + 1, rightPar).split("\\s"); - params = new ValueType[paramTypes.length]; - for (int i = 0; i < paramTypes.length; i++) { - params[i] = ValueType.valueOf(paramTypes[i]); - } - } - final String resultTypesString = s.substring(rightPar + 1); - final ValueType[] results; - if (resultTypesString.isEmpty()) { - results = EMTPY; + final ValueType[] params = parseTypeString(s, leftPar + 1, rightPar); + final ValueType[] results = parseTypeString(s, rightPar + 1, s.length()); + return new FuncType(params, results); + } + + private static ValueType[] parseTypeString(String typesString, int start, int end) { + if (start >= end) { + return EMTPY; } else { - final String[] resultTypes = s.substring(rightPar + 1).split("\\s"); - results = new ValueType[resultTypes.length]; - for (int i = 0; i < resultTypes.length; i++) { - results[i] = ValueType.valueOf(resultTypes[i]); + String[] typeNames = typesString.substring(start, end).split(" "); + ValueType[] types = new ValueType[typeNames.length]; + for (int i = 0; i < typeNames.length; i++) { + types[i] = ValueType.valueOf(typeNames[i]); } + return types; } - return new FuncType(params, results); } public static FuncType fromFunctionType(SymbolTable.FunctionType functionType) { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java index ba602c5772d9..4b37d7e3e951 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java @@ -64,7 +64,7 @@ private JsConstants() { private static final int LOCAL_COUNT_LIMIT = 50000; private static final int TABLE_SIZE_LIMIT = 10000000; private static final int MEMORY_SIZE_LIMIT = 65536; - private static final int TAG_COUNT_LIMIT = 10000000; + private static final int TAG_COUNT_LIMIT = 1_000_000; public static final ModuleLimits JS_LIMITS = new ModuleLimits( MODULE_SIZE_LIMIT, diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java index b2e899fbf8a7..b6d6916d920c 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java @@ -119,6 +119,7 @@ public WebAssembly(WasmContext currentContext) { addMember("tag_type", new Executable(WebAssembly::tagType)); addMember("exn_alloc", new Executable(this::exnAlloc)); + addMember("exn_tag", new Executable(WebAssembly::exnTag)); addMember("exn_read", new Executable(WebAssembly::exnRead)); addMember("module_imports", new Executable(WebAssembly::moduleImports)); @@ -445,7 +446,7 @@ public WasmTable tableAlloc(int initial, int maximum, TableKind elemKind, Object throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "Element type must be a reftype"); } if (!refTypes && (elemKind == TableKind.externref || elemKind == TableKind.exnref)) { - throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "Element type must be anyfunc. Enable reference types to support externref"); + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "Element type must be anyfunc. Enable wasm.BulkMemoryAndRefTypes to support other reference types"); } final int maxAllowedSize = minUnsigned(maximum, JS_LIMITS.tableInstanceSizeLimit()); return new WasmTable(initial, maximum, maxAllowedSize, elemKind.byteValue(), initialValue); @@ -1004,14 +1005,24 @@ public Object exnAlloc(Object[] args) { return new WasmRuntimeException(null, tag, fields); } - public static Object exnRead(Object[] args) { + public static Object exnTag(Object[] args) { checkArgumentCount(args, 1); if (!(args[0] instanceof WasmRuntimeException exn)) { - throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be a wasm tag"); + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be a wasm exception"); } return exn.tag(); } + public static Object exnRead(Object[] args) { + checkArgumentCount(args, 1); + if (!(args[0] instanceof WasmRuntimeException exn)) { + throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be a wasm exception"); + } + // Should return exn.fields. + // WasmRuntimeException already exposes its fields as array elements. + return exn; + } + private static Object instanceExport(Object[] args) { checkArgumentCount(args, 2); if (!(args[0] instanceof WasmInstance instance)) { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/BytecodeBitEncoding.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/BytecodeBitEncoding.java index 18d81b85860d..76fc7ee6ac92 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/BytecodeBitEncoding.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/BytecodeBitEncoding.java @@ -180,4 +180,8 @@ public class BytecodeBitEncoding { public static final int CODE_ENTRY_LOCALS_FLAG = 0b0000_0010; public static final int CODE_ENTRY_RESULT_FLAG = 0b0000_0001; + + // Exception handlers + + public static final int INVALID_EXCEPTION_TABLE_OFFSET = -1; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Instructions.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Instructions.java index 4e9d4a1a9bea..e0a7b68bad5b 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Instructions.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/constants/Instructions.java @@ -55,7 +55,7 @@ public final class Instructions { public static final int ELSE = 0x05; public static final int THROW = 0x08; - public static final int THROW_REF = 0xA; + public static final int THROW_REF = 0x0A; public static final int END = 0x0B; diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java index 0faeeaadada3..6ec30f636330 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java @@ -391,9 +391,10 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i } case Bytecode.BR_U8: { final int offsetDelta = rawPeekU8(bytecode, offset); - // BR_U8 encodes the back jump value as a positive byte value. BR_U8 can - // never - // perform a forward jump. + /* + * BR_U8 encodes the back jump value as a positive byte value. BR_U8 can + * never perform a forward jump. + */ offset -= offsetDelta; break; } @@ -406,9 +407,10 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i stackPointer--; if (profileCondition(bytecode, offset + 1, popBoolean(frame, stackPointer))) { final int offsetDelta = rawPeekU8(bytecode, offset); - // BR_IF_U8 encodes the back jump value as a positive byte value. - // BR_IF_U8 - // can never perform a forward jump. + /* + * BR_IF_U8 encodes the back jump value as a positive byte value. + * BR_IF_U8 can never perform a forward jump. + */ offset -= offsetDelta; } else { offset += 3; @@ -444,14 +446,15 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i offset = indexOffset + offsetDelta; break; } else { - // This loop is implemented to create a separate path for every index. - // This - // guarantees that all values inside the if statement are treated as - // compile - // time constants, since the loop is unrolled. - // We keep track of the sum of the preceding profiles to adjust the - // independent probabilities to conditional ones. This gets explained in - // profileBranchTable(). + /* + * This loop is implemented to create a separate path for every index. + * This guarantees that all values inside the if statement are treated + * as compile time constants, since the loop is unrolled. + * + * We keep track of the sum of the preceding profiles to adjust the + * independent probabilities to conditional ones. This gets explained in + * profileBranchTable(). + */ int precedingSum = 0; for (int i = 0; i < size; i++) { final int indexOffset = offset + 3 + i * 6; @@ -485,9 +488,11 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i offset = indexOffset + offsetDelta; break; } else { - // This loop is implemented to create a separate path for every index. - // This guarantees that all values inside the if statement are treated - // as compile time constants, since the loop is unrolled. + /* + * This loop is implemented to create a separate path for every index. + * This guarantees that all values inside the if statement are treated + * as compile time constants, since the loop is unrolled. + */ int precedingSum = 0; for (int i = 0; i < size; i++) { final int indexOffset = offset + 6 + i * 6; @@ -1658,11 +1663,12 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i final int numFields = module.functionTypeParamCount(functionTypeIndex); final Object[] fields = createFieldsForException(frame, functionTypeIndex, numFields, stackPointer); stackPointer -= numFields; + offset += 4; throw createException(instance.tag(tagIndex), fields); } case Bytecode.THROW_REF: { codeEntry.exceptionBranch(); - final Object exception = frame.getObjectStatic(stackPointer - 1); + final Object exception = popReference(frame, stackPointer - 1); stackPointer--; assert exception != null : "Exception object has to be a valid exception or wasm null"; if (exception == WasmConstant.NULL) { @@ -1732,11 +1738,11 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i * The source of the exception, i.e., the throw/throw_ref raising the exception or * the call that forwarded the exception. */ - final int source = offset; + final int sourceOffset = offset; offset = this.exceptionTableOffset; - if (offset == 0) { + if (offset == BytecodeBitEncoding.INVALID_EXCEPTION_TABLE_OFFSET) { // no exception table, directly throw to the next function on the call stack throw e; } @@ -1770,7 +1776,7 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i offset += 4; final int to = rawPeekI32(bytecode, offset); offset += 4; - if (from < source && source <= to) { + if (from < sourceOffset && sourceOffset <= to) { final int type = rawPeekU8(bytecode, offset); offset++; switch (type) { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/BytecodeParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/BytecodeParser.java index 381b8ee59e80..cafab53c9de7 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/BytecodeParser.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/BytecodeParser.java @@ -428,7 +428,7 @@ public static CodeEntry readCodeEntry(WasmModule module, byte[] bytecode, int co results = Bytecode.EMPTY_BYTES; } final int endOffset; - if (exceptionTableOffset == 0) { + if (exceptionTableOffset == BytecodeBitEncoding.INVALID_EXCEPTION_TABLE_OFFSET) { // no exception table endOffset = (codeEntryOffset - 4); } else { @@ -798,7 +798,8 @@ private static List readCallNodes(byte[] bytecode, int startOffset, in case Bytecode.ELEM_DROP: case Bytecode.TABLE_GROW: case Bytecode.TABLE_SIZE: - case Bytecode.TABLE_FILL: { + case Bytecode.TABLE_FILL: + case Bytecode.THROW: { offset += 4; break; } @@ -809,8 +810,7 @@ private static List readCallNodes(byte[] bytecode, int startOffset, in case Bytecode.MEMORY64_COPY_D64_S32: case Bytecode.MEMORY64_COPY_D64_S64: case Bytecode.TABLE_INIT: - case Bytecode.TABLE_COPY: - case Bytecode.THROW: { + case Bytecode.TABLE_COPY: { offset += 8; break; } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/RuntimeBytecodeGen.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/RuntimeBytecodeGen.java index 3e67faa0b8b7..6c000c36d1ca 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/RuntimeBytecodeGen.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/bytecode/RuntimeBytecodeGen.java @@ -482,9 +482,11 @@ public int addBranchTableItemLocation() { * Adds an exception handler that catches a specific exception type (tag), formatted as below. * *
-     * type (1 byte) | tag (4 byte) | target (4 byte)
+     * from (4 byte) | to (4 byte) | type (1 byte) | tag (4 byte) | target (4 byte)
      * 
- * + * + * @param from start offset of the bytecode range caught by the exception handler (exclusive) + * @param to end offset of the bytecode range caught by the exception handler (inclusive) * @param type The opcode of the exception handler (see * {@link org.graalvm.wasm.constants.ExceptionHandlerType}). * @param tag The tag of the exception handler. diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ParserState.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ParserState.java index 05aed33ca6de..f2d4d3f5b307 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ParserState.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/ParserState.java @@ -329,6 +329,8 @@ public void enterElse() { public void enterTryTable(byte[] paramTypes, byte[] resultTypes, ExceptionHandler[] handlers) { final TryTableFrame frame = new TryTableFrame(paramTypes, resultTypes, valueStack.size(), false, bytecode.location(), handlers); controlStack.push(frame); + + exceptionTables.add(frame.table()); } /** @@ -638,12 +640,6 @@ public byte[] exit(boolean multiValue) { frame.exit(bytecode); checkStackAfterFrameExit(frame, resultTypes); - if (frame instanceof TryTableFrame e) { - final ExceptionTable t = e.table(); - t.setTo(bytecode.location()); - exceptionTables.add(t); - } - controlStack.pop(); if (!multiValue) { Assert.assertIntLessOrEqual(resultTypes.length, 1, "A block cannot return more than one value.", Failure.INVALID_RESULT_ARITY); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/TryTableFrame.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/TryTableFrame.java index cc9b6f29a753..271b33f0c4be 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/TryTableFrame.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/parser/validation/TryTableFrame.java @@ -41,6 +41,8 @@ package org.graalvm.wasm.parser.validation; +import org.graalvm.wasm.parser.bytecode.RuntimeBytecodeGen; + /** * Representation of a wasm try table during module validation. */ @@ -55,4 +57,11 @@ public class TryTableFrame extends BlockFrame { ExceptionTable table() { return table; } + + @Override + void exit(RuntimeBytecodeGen bytecode) { + super.exit(bytecode); + + table.setTo(bytecode.location()); + } } From e0d27787ed1d1e4e5b85b62d41d26fa7f2a6efa0 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 26 Sep 2025 16:55:20 +0200 Subject: [PATCH 09/12] Simplify exception handler dispatch. Use separate local for exceptionTableOffset. Outline pushing exception fields and/or reference onto the stack. --- .../graalvm/wasm/nodes/WasmFunctionNode.java | 109 +++++++++--------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java index 6ec30f636330..8f78969b2cb4 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java @@ -1734,20 +1734,13 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i CompilerAsserts.partialEvaluationConstant(stackPointer); CompilerAsserts.partialEvaluationConstant(offset); - /* - * The source of the exception, i.e., the throw/throw_ref raising the exception or - * the call that forwarded the exception. - */ - final int sourceOffset = offset; - - offset = this.exceptionTableOffset; + int exceptionTableOffset = this.exceptionTableOffset; - if (offset == BytecodeBitEncoding.INVALID_EXCEPTION_TABLE_OFFSET) { + if (exceptionTableOffset == BytecodeBitEncoding.INVALID_EXCEPTION_TABLE_OFFSET) { // no exception table, directly throw to the next function on the call stack throw e; } - final WasmTag actualTag = e.tag(); /* * The exception table is encoded in the following format: * @@ -1757,9 +1750,11 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i * inside this range, the entry defines a possible exception handler for the * exception. If the type of the exception handler is catch or catch_ref, we check * whether the expected tag defined by the tag index and the tag of the exception - * object match. For catch_all and catch_all_ref we don't have this check. The catch - * and catch_all types push the values of the exception onto the stack, while - * catch_ref and catch_all_ref also push a reference to the exception itself. + * object match. For catch_all and catch_all_ref we don't have this check. + * + * The catch and catch_ref types push the fields of the exception onto the stack, + * catch_ref also pushes a reference to the exception itself, while catch_all_ref + * only pushes the reference, and catch_all doesn't push anything onto the stack. * * If we find a matching entry, execution continues at the label defined by target. * @@ -1768,56 +1763,44 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i * exception to the next function on the call stack. */ while (true) { - final int from = rawPeekI32(bytecode, offset); + final int from = rawPeekI32(bytecode, exceptionTableOffset); if (from == -1) { // we reached the end of the table break; } - offset += 4; - final int to = rawPeekI32(bytecode, offset); - offset += 4; - if (from < sourceOffset && sourceOffset <= to) { - final int type = rawPeekU8(bytecode, offset); - offset++; - switch (type) { - case ExceptionHandlerType.CATCH, ExceptionHandlerType.CATCH_REF -> { - final int tagIndex = rawPeekI32(bytecode, offset); - offset += 4; - final WasmTag expectedTag = instance.tag(tagIndex); - if (expectedTag == actualTag) { - final int functionTypeIndex = module.tagTypeIndex(tagIndex); - final int numFields = module.functionTypeParamCount(functionTypeIndex); - if (numFields != 0) { - pushExceptionFields(frame, e, functionTypeIndex, numFields, stackPointer); - stackPointer += numFields; - } - if (type == ExceptionHandlerType.CATCH_REF) { - pushReference(frame, stackPointer, e); - stackPointer++; - } - // load target - offset = rawPeekI32(bytecode, offset); - continue loop; - } else { - // skip target - offset += 4; - } - } - case ExceptionHandlerType.CATCH_ALL, ExceptionHandlerType.CATCH_ALL_REF -> { - // skip 0 tag - offset += 4; - if (type == ExceptionHandlerType.CATCH_ALL_REF) { - pushReference(frame, stackPointer, e); - stackPointer++; - } - // load target - offset = rawPeekI32(bytecode, offset); - continue loop; + exceptionTableOffset += 4; + final int to = rawPeekI32(bytecode, exceptionTableOffset); + exceptionTableOffset += 4; + + /* + * offset is the source of the exception, i.e., the throw or throw_ref raising + * the exception or the call that forwarded the exception. + */ + if (from < offset && offset <= to) { + final int catchType = rawPeekU8(bytecode, exceptionTableOffset); + exceptionTableOffset++; + final int tagIndex; + if (catchType == ExceptionHandlerType.CATCH || catchType == ExceptionHandlerType.CATCH_REF) { + tagIndex = rawPeekI32(bytecode, exceptionTableOffset); + exceptionTableOffset += 4; + if (e.tag() != instance.tag(tagIndex)) { + // skip target + exceptionTableOffset += 4; + continue; } + } else { + assert catchType == ExceptionHandlerType.CATCH_ALL || catchType == ExceptionHandlerType.CATCH_ALL_REF : catchType; + // skip 0 tag + tagIndex = -1; + exceptionTableOffset += 4; } + stackPointer = pushExceptionFieldsAndReference(frame, e, stackPointer, catchType, tagIndex); + // load target + offset = rawPeekI32(bytecode, exceptionTableOffset); + continue loop; } else { // skip the entry - offset += 9; + exceptionTableOffset += 9; } } throw e; @@ -1826,6 +1809,24 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i return WasmConstant.RETURN_VALUE; } + private int pushExceptionFieldsAndReference(VirtualFrame frame, WasmRuntimeException e, int sourceStackPointer, int catchType, int tagIndex) { + int stackPointer = sourceStackPointer; + if (catchType == ExceptionHandlerType.CATCH || catchType == ExceptionHandlerType.CATCH_REF) { + final int functionTypeIndex = module.tagTypeIndex(tagIndex); + final int numFields = module.functionTypeParamCount(functionTypeIndex); + if (numFields != 0) { + pushExceptionFields(frame, e, functionTypeIndex, numFields, stackPointer); + stackPointer += numFields; + } + } + if (catchType == ExceptionHandlerType.CATCH_REF || catchType == ExceptionHandlerType.CATCH_ALL_REF) { + pushReference(frame, stackPointer, e); + stackPointer++; + } + CompilerAsserts.partialEvaluationConstant(stackPointer); + return stackPointer; + } + @TruffleBoundary private void failFunctionTypeCheck(WasmFunction function, int expectedFunctionTypeIndex) { throw WasmException.format(Failure.INDIRECT_CALL_TYPE__MISMATCH, this, From ee0c136e8c27c8981267369f25c3e58f745b98b8 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Fri, 26 Sep 2025 18:54:19 +0200 Subject: [PATCH 10/12] Add more exception-with-param tests using catch_all and catch_ref. --- .../exceptions_with_params_catch_all.opts | 1 + .../exceptions_with_params_catch_all.result | 1 + .../exceptions_with_params_catch_all.wat | 67 +++++++++++++++++ .../exceptions_with_params_catch_ref.opts | 1 + .../exceptions_with_params_catch_ref.result | 1 + .../exceptions_with_params_catch_ref.wat | 71 +++++++++++++++++++ .../src/test/exceptions/wasm_test_index | 2 + 7 files changed, 144 insertions(+) create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.wat create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.opts create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.result create mode 100644 wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.wat diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.opts new file mode 100644 index 000000000000..4d21d303794a --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.opts @@ -0,0 +1 @@ +wasm.Exceptions=true diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.result new file mode 100644 index 000000000000..26b7ec045e92 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.result @@ -0,0 +1 @@ +int 14 diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.wat new file mode 100644 index 000000000000..64b770c48b7e --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_all.wat @@ -0,0 +1,67 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func (param i32) (param i64))) + (tag $e0 (type $t0)) + (tag $e1 (type $t0)) + + (func (export "_main") (result i32) + (block $h0 + (try_table (catch_all $h0) + (block $h1 (result i32 i64) + (try_table (result i32 i64) (catch $e1 $h1) + i32.const 1984 + i64.const 4398046511104 + throw $e0 + ) + drop ;; i64 + return + ) + drop ;; i64 + return + ) + i32.const -1 + return + ) + ;; caught exception, but no access to its fields + i32.const 14 + ) +) diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.opts b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.opts new file mode 100644 index 000000000000..4d21d303794a --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.opts @@ -0,0 +1 @@ +wasm.Exceptions=true diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.result b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.result new file mode 100644 index 000000000000..7aa3c269eebd --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.result @@ -0,0 +1 @@ +int 4 diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.wat b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.wat new file mode 100644 index 000000000000..8d6acf108a26 --- /dev/null +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/exceptions_with_params_catch_ref.wat @@ -0,0 +1,71 @@ +;; +;; Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +;; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +;; +;; The Universal Permissive License (UPL), Version 1.0 +;; +;; Subject to the condition set forth below, permission is hereby granted to any +;; person obtaining a copy of this software, associated documentation and/or +;; data (collectively the "Software"), free of charge and under any and all +;; copyright rights in the Software, and any and all patent rights owned or +;; freely licensable by each licensor hereunder covering either (i) the +;; unmodified Software as contributed to or provided by such licensor, or (ii) +;; the Larger Works (as defined below), to deal in both +;; +;; (a) the Software, and +;; +;; (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +;; one is included with the Software each a "Larger Work" to which the Software +;; is contributed by such licensors), +;; +;; without restriction, including without limitation the rights to copy, create +;; derivative works of, display, perform, and distribute the Software and make, +;; use, sell, offer for sale, import, export, have made, and have sold the +;; Software and the Larger Work(s), and to sublicense the foregoing rights on +;; either these or other terms. +;; +;; This license is subject to the following condition: +;; +;; The above copyright notice and either this complete permission notice or at a +;; minimum a reference to the UPL must be included in all copies or substantial +;; portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +(module + (type $t0 (func (param i32))) + (tag $e0 (type $t0)) + (tag $e1 (type $t0)) + + (func (export "_main") (result i32) + block $h0 (result i32 exnref) + try_table (result i32 exnref) (catch_ref $e0 $h0) + block $h1 (result i32 exnref) + try_table (result i32 exnref) (catch_ref $e1 $h1) + i32.const 4 + throw $e0 + end + drop ;; exnref + drop ;; i32 + i32.const 42 + return + end + drop ;; exnref + drop ;; i32 + i32.const 21 + return + end + drop ;; exnref + drop ;; i32 + i32.const 10 + return + end + drop ;; exnref + ) +) diff --git a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/wasm_test_index b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/wasm_test_index index fb58064bdfc5..d631debfba28 100644 --- a/wasm/src/org.graalvm.wasm.test/src/test/exceptions/wasm_test_index +++ b/wasm/src/org.graalvm.wasm.test/src/test/exceptions/wasm_test_index @@ -3,5 +3,7 @@ exceptions_indirect_call exceptions_local_unwind exceptions_single_func exceptions_with_params +exceptions_with_params_catch_all +exceptions_with_params_catch_ref multi_exception_types throw_ref \ No newline at end of file From e8f457f70e05b5e5063c6138a0ffdeab153833db Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Wed, 1 Oct 2025 15:41:52 +0200 Subject: [PATCH 11/12] Remove tag registry. --- .../src/org/graalvm/wasm/Linker.java | 3 +- .../src/org/graalvm/wasm/SymbolTable.java | 4 +- .../src/org/graalvm/wasm/TagRegistry.java | 80 ------------------- .../org/graalvm/wasm/WasmInstantiator.java | 4 +- .../src/org/graalvm/wasm/WasmStore.java | 6 -- 5 files changed, 3 insertions(+), 94 deletions(-) delete mode 100644 wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TagRegistry.java diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java index c9df221f452d..8479286032aa 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java @@ -549,8 +549,7 @@ void resolveTagImport(WasmStore store, WasmInstance instance, ImportDescriptor i final WasmTag importedTag; final WasmTag externalTag = lookupImportObject(instance, importDescriptor, imports, WasmTag.class); if (externalTag != null) { - final int contextTagIndex = store.tags().register(externalTag); - importedTag = store.tags().tag(contextTagIndex); + importedTag = externalTag; assert tagIndex == importDescriptor.targetIndex(); } else { final WasmInstance importedInstance = store.lookupModuleInstance(importedModuleName); diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java index b114ff98bcc4..1119e5483b42 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/SymbolTable.java @@ -1165,9 +1165,7 @@ public void allocateTag(int index, byte attribute, int typeIndex) { addTag(index, attribute, typeIndex); module().addLinkAction((context, store, instance, imports) -> { final WasmTag tag = new WasmTag(typeAt(typeIndex)); - final int tagAddress = store.tags().register(tag); - final WasmTag allocatedTag = store.tags().tag(tagAddress); - instance.setTag(index, allocatedTag); + instance.setTag(index, tag); }); } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TagRegistry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TagRegistry.java deleted file mode 100644 index a7b0c645337d..000000000000 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/TagRegistry.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.graalvm.wasm; - -import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; - -public class TagRegistry { - private static final int INITIAL_TAG_SIZE = 2; - - @CompilationFinal(dimensions = 1) private WasmTag[] tags; - private int numTags; - - public TagRegistry() { - this.tags = new WasmTag[INITIAL_TAG_SIZE]; - this.numTags = 0; - } - - private void ensureCapacity() { - if (numTags == tags.length) { - final WasmTag[] updatedTags = new WasmTag[tags.length * 2]; - System.arraycopy(tags, 0, updatedTags, 0, numTags); - tags = updatedTags; - } - } - - public int count() { - return numTags; - } - - public int register(WasmTag tag) { - ensureCapacity(); - final int index = numTags; - tags[index] = tag; - numTags++; - return index; - } - - public WasmTag tag(int index) { - assert index < numTags; - return tags[index]; - } -} diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java index 562d634c88c1..107f9085a63c 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java @@ -210,9 +210,7 @@ static List recreateLinkActions(WasmModule module) { } else { linkActions.add((context, store, instance, imports) -> { final WasmTag tag = new WasmTag(type); - final int address = store.tags().register(tag); - final WasmTag allocatedTag = store.tags().tag(address); - instance.setTag(tagIndex, allocatedTag); + instance.setTag(tagIndex, tag); }); } } diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmStore.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmStore.java index 43f684d6700b..9fad7f7c479f 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmStore.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmStore.java @@ -70,7 +70,6 @@ public final class WasmStore implements TruffleObject { private final WasmLanguage language; private final MemoryRegistry memoryRegistry; private final TableRegistry tableRegistry; - private final TagRegistry tagRegistry; private final Linker linker; private final Map moduleInstances; private final FdManager filesManager; @@ -82,7 +81,6 @@ public WasmStore(WasmContext context, WasmLanguage language) { this.contextOptions = context.getContextOptions(); this.tableRegistry = new TableRegistry(); this.memoryRegistry = new MemoryRegistry(); - this.tagRegistry = new TagRegistry(); this.moduleInstances = new LinkedHashMap<>(); this.linker = new Linker(); this.filesManager = context.fdManager(); @@ -108,10 +106,6 @@ public TableRegistry tables() { return tableRegistry; } - public TagRegistry tags() { - return tagRegistry; - } - public Linker linker() { return linker; } From bd252e2e76b3c91d8cac41ce807e335adb42f3b4 Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Wed, 1 Oct 2025 15:53:15 +0200 Subject: [PATCH 12/12] Update wasm changelog. --- wasm/CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wasm/CHANGELOG.md b/wasm/CHANGELOG.md index 69b9c361a0c0..ae0151d1fb8e 100644 --- a/wasm/CHANGELOG.md +++ b/wasm/CHANGELOG.md @@ -2,6 +2,10 @@ This changelog summarizes major changes to the WebAssembly engine implemented in GraalVM (GraalWasm). +## Version 25.1.0 + +* Implemented the [exception handling](https://github.com/WebAssembly/exception-handling) proposal. This feature can be enabled with the experimental option `wasm.Exceptions=true`. + ## Version 25.0.0 * BREAKING: Changed Context.eval of _wasm_ sources to return a compiled, but not yet instantiated, module object instead of the module instance. @@ -27,14 +31,13 @@ This changelog summarizes major changes to the WebAssembly engine implemented in * Added an implementation of the [SIMD](https://github.com/WebAssembly/simd) proposal using the JDK's Vector API. This improves peak performance when running WebAssembly code which makes heavy use of the new instructions in the SIMD proposal. This new implementation is always used in native image. On the JVM, it is opt-in and requires setting `--add-modules=jdk.incubator.vector`. Use of the incubating Vector API will result in the following warning message being printed to stderr: ``` WARNING: Using incubator modules: jdk.incubator.vector - ``` - + ``` ## Version 24.2.0 * Updated developer metadata of Maven artifacts. * Deprecated the `--wasm.AsyncParsingBinarySize` and `--wasm.AsyncParsingStackSize` options. These options no longer have any effect and will be removed in a future release. -* Implemented the [Relaxed SIMD](https://github.com/WebAssembly/relaxed-simd) proposal. This feature can be enabled with the options `--wasm.RelaxedSIMD`. +* Implemented the [Relaxed SIMD](https://github.com/WebAssembly/relaxed-simd) proposal. This feature can be enabled with the option `--wasm.RelaxedSIMD`. ## Version 24.1.0