Skip to content

Commit 90acaae

Browse files
Acareldflohuemer
authored andcommitted
Implement WebAssembly exception handling proposal.
Co-authored-by: Florian Huemer <florian.huemer@oracle.com>
1 parent c588bdc commit 90acaae

File tree

91 files changed

+4185
-1602
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+4185
-1602
lines changed

wasm/docs/contributor/TestsAndBenchmarks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Building GraalWasm using the `mx build` command will also create the `wasm-tests.jar`, which contains the main test cases.
66
To run these tests, the WebAssembly binary toolkit is needed.
77

8-
1. Download the binary of the [WebAssembly binary toolkit(wabt)](https://github.com/WebAssembly/wabt) and extract it.
8+
1. Download the binary of the [WebAssembly binary toolkit(wabt)](https://github.com/WebAssembly/wabt) (**1.0.37** or higher) and extract it.
99

1010
2. Set `WABT_DIR`:
1111
```bash

wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,29 @@ private byte[] generateGlobalSection() {
339339
}
340340
}
341341

342+
private static final class BinaryTags {
343+
private final ByteArrayList attributes = new ByteArrayList();
344+
private final ByteArrayList typeIndices = new ByteArrayList();
345+
346+
private void add(byte attribute, byte typeIndex) {
347+
attributes.add(attribute);
348+
typeIndices.add(typeIndex);
349+
}
350+
351+
private byte[] generateTagSection() {
352+
ByteArrayList b = new ByteArrayList();
353+
b.add(getByte("0d"));
354+
b.add((byte) 0); // length is patched at the end
355+
b.add((byte) attributes.size());
356+
for (int i = 0; i < attributes.size(); i++) {
357+
b.add(attributes.get(i));
358+
b.add(typeIndices.get(i));
359+
}
360+
b.set(1, (byte) (b.size() - 2));
361+
return b.toArray();
362+
}
363+
}
364+
342365
private static final class BinaryCustomSections {
343366
private final List<byte[]> names = new ArrayList<>();
344367
private final List<byte[]> sections = new ArrayList<>();
@@ -373,6 +396,7 @@ protected static class BinaryBuilder {
373396
private final BinaryElements binaryElements = new BinaryElements();
374397
private final BinaryDatas binaryDatas = new BinaryDatas();
375398
private final BinaryGlobals binaryGlobals = new BinaryGlobals();
399+
private final BinaryTags binaryTags = new BinaryTags();
376400

377401
private final BinaryCustomSections binaryCustomSections = new BinaryCustomSections();
378402

@@ -416,6 +440,11 @@ public BinaryBuilder addGlobal(byte mutability, byte valueType, String hexCode)
416440
return this;
417441
}
418442

443+
public BinaryBuilder addTag(byte attribute, byte typeIndex) {
444+
binaryTags.add(attribute, typeIndex);
445+
return this;
446+
}
447+
419448
public BinaryBuilder addCustomSection(String name, byte[] section) {
420449
binaryCustomSections.add(name, section);
421450
return this;
@@ -443,8 +472,9 @@ public byte[] build() {
443472
final byte[] codeSection = binaryFunctions.generateCodeSection();
444473
final byte[] dataSection = binaryDatas.generateDataSection();
445474
final byte[] customSections = binaryCustomSections.generateCustomSections();
475+
final byte[] tagSection = binaryTags.generateTagSection();
446476
final int totalLength = preamble.length + typeSection.length + functionSection.length + tableSection.length + memorySection.length + globalSection.length + exportSection.length +
447-
elementSection.length + dataCountSection.length + codeSection.length + dataSection.length + customSections.length;
477+
elementSection.length + dataCountSection.length + codeSection.length + dataSection.length + customSections.length + tagSection.length;
448478
final byte[] binary = new byte[totalLength];
449479
int length = 0;
450480
System.arraycopy(preamble, 0, binary, length, preamble.length);
@@ -457,6 +487,8 @@ public byte[] build() {
457487
length += tableSection.length;
458488
System.arraycopy(memorySection, 0, binary, length, memorySection.length);
459489
length += memorySection.length;
490+
System.arraycopy(tagSection, 0, binary, length, tagSection.length);
491+
length += tagSection.length;
460492
System.arraycopy(globalSection, 0, binary, length, globalSection.length);
461493
length += globalSection.length;
462494
System.arraycopy(exportSection, 0, binary, length, exportSection.length);

wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,13 +399,17 @@ private WasmTestStatus runTestCase(WasmCase testCase, Engine sharedEngine) {
399399

400400
EnumSet<WasmBinaryTools.WabtOption> options = EnumSet.noneOf(WasmBinaryTools.WabtOption.class);
401401
String threadsOption = testCase.options().getProperty("wasm.Threads");
402-
if (threadsOption != null && threadsOption.equals("true")) {
402+
if ("true".equals(threadsOption)) {
403403
options.add(WasmBinaryTools.WabtOption.THREADS);
404404
}
405405
String multiMemoryOption = testCase.options().getProperty("wasm.MultiMemory");
406-
if (multiMemoryOption != null && multiMemoryOption.equals("true")) {
406+
if ("true".equals(multiMemoryOption)) {
407407
options.add(WasmBinaryTools.WabtOption.MULTI_MEMORY);
408408
}
409+
String exceptionsOption = testCase.options().getProperty("wasm.Exceptions");
410+
if ("true".equals(exceptionsOption)) {
411+
options.add(WasmBinaryTools.WabtOption.EXCEPTIONS);
412+
}
409413
ArrayList<Source> sources = testCase.getSources(options);
410414

411415
runInContexts(testCase, contextBuilder, sources, sharedEngine, testOut);

wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -854,11 +854,11 @@ public void testExportCountsLimit() throws IOException {
854854
context.readModule(binaryWithMixedExports, limits);
855855

856856
final int noLimit = Integer.MAX_VALUE;
857-
limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 6, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit);
857+
limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 6, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit);
858858
context.readModule(binaryWithMixedExports, limits);
859859

860860
try {
861-
limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 5, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit);
861+
limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 5, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit);
862862
context.readModule(binaryWithMixedExports, limits);
863863
Assert.fail("Should have failed - export count exceeds the limit");
864864
} catch (WasmException ex) {

wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestSuite.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -50,6 +50,7 @@
5050
import org.graalvm.wasm.test.suites.bytecode.MultiInstantiationSuite;
5151
import org.graalvm.wasm.test.suites.control.BlockWithLocalsSuite;
5252
import org.graalvm.wasm.test.suites.control.BranchBlockSuite;
53+
import org.graalvm.wasm.test.suites.control.ExceptionSuite;
5354
import org.graalvm.wasm.test.suites.control.IfThenElseSuite;
5455
import org.graalvm.wasm.test.suites.control.LoopBlockSuite;
5556
import org.graalvm.wasm.test.suites.control.MultiValueSuite;
@@ -62,9 +63,9 @@
6263
import org.graalvm.wasm.test.suites.memory.MemorySuite;
6364
import org.graalvm.wasm.test.suites.memory.MultiMemorySuite;
6465
import org.graalvm.wasm.test.suites.memory.ThreadsSuite;
65-
import org.graalvm.wasm.test.suites.validation.ReferenceTypesValidationSuite;
6666
import org.graalvm.wasm.test.suites.table.TableSuite;
6767
import org.graalvm.wasm.test.suites.validation.MultiValueValidationSuite;
68+
import org.graalvm.wasm.test.suites.validation.ReferenceTypesValidationSuite;
6869
import org.graalvm.wasm.test.suites.validation.ValidationSuite;
6970
import org.graalvm.wasm.test.suites.wasi.WasiSuite;
7071
import org.graalvm.wasm.test.suites.webassembly.EmscriptenSuite;
@@ -105,6 +106,7 @@
105106
MultiInstantiationSuite.class,
106107
MultiMemorySuite.class,
107108
ThreadsSuite.class,
109+
ExceptionSuite.class,
108110
DebugValidationSuite.class,
109111
DebugObjectFactorySuite.class
110112
})

wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/BytecodeSuite.java

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
import org.graalvm.wasm.WasmType;
4747
import org.graalvm.wasm.constants.Bytecode;
48+
import org.graalvm.wasm.constants.ExceptionHandlerType;
4849
import org.graalvm.wasm.constants.SegmentMode;
4950
import org.graalvm.wasm.parser.bytecode.RuntimeBytecodeGen;
5051
import org.junit.Assert;
@@ -168,7 +169,7 @@ public void testBrU8Max() {
168169
expected[255] = (byte) 0xFF;
169170
test(b -> {
170171
for (int i = 0; i < 254; i++) {
171-
b.add(0);
172+
b.addOp(0);
172173
}
173174
b.addBranch(0);
174175
}, expected);
@@ -194,7 +195,7 @@ public void testBrI32MinBackward() {
194195
expected[259] = (byte) 0xFF;
195196
test(b -> {
196197
for (int i = 0; i < 255; i++) {
197-
b.add(0);
198+
b.addOp(0);
198199
}
199200
b.addBranch(0);
200201
}, expected);
@@ -212,7 +213,7 @@ public void testBrIfU8Max() {
212213
expected[255] = (byte) 0xFF;
213214
test(b -> {
214215
for (int i = 0; i < 254; i++) {
215-
b.add(0);
216+
b.addOp(0);
216217
}
217218
b.addBranchIf(0);
218219
}, expected);
@@ -238,7 +239,7 @@ public void testBrIfI32MinBackward() {
238239
expected[259] = (byte) 0xFF;
239240
test(b -> {
240241
for (int i = 0; i < 255; i++) {
241-
b.add(0);
242+
b.addOp(0);
242243
}
243244
b.addBranchIf(0);
244245
}, expected);
@@ -448,52 +449,52 @@ public void testMemoryInstructionInvalidOpcodeI32() {
448449

449450
@Test
450451
public void testAddMin() {
451-
test(b -> b.add(0x00), new byte[]{0x00});
452+
test(b -> b.addOp(0x00), new byte[]{0x00});
452453
}
453454

454455
@Test
455456
public void testAddMax() {
456-
test(b -> b.add(0xFF), new byte[]{(byte) 0xFF});
457+
test(b -> b.addOp(0xFF), new byte[]{(byte) 0xFF});
457458
}
458459

459460
@Test
460461
public void testInvalidAdd() {
461-
testAssertion(b -> b.add(256), "opcode does not fit into byte");
462+
testAssertion(b -> b.addOp(256), "opcode does not fit into byte");
462463
}
463464

464465
@Test
465466
public void testAddImmediateMin() {
466-
test(b -> b.add(0x01, 0), new byte[]{0x01, 0x00, 0x00, 0x00, 0x00});
467+
test(b -> b.addOp(0x01, 0), new byte[]{0x01, 0x00, 0x00, 0x00, 0x00});
467468
}
468469

469470
@Test
470471
public void testAddImmediateMax() {
471-
test(b -> b.add(0x01, 0xFFFFFFFF), new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
472+
test(b -> b.addOp(0x01, 0xFFFFFFFF), new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
472473
}
473474

474475
@Test
475476
public void testInvalidAddImmediate() {
476-
testAssertion(b -> b.add(256, 0), "opcode does not fit into byte");
477+
testAssertion(b -> b.addOp(256, 0), "opcode does not fit into byte");
477478
}
478479

479480
@Test
480481
public void testAddImmediate64Max() {
481-
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});
482+
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});
482483
}
483484

484485
@Test
485486
public void testInvalidAddImmediate64() {
486-
testAssertion(b -> b.add(256, 0xFFL), "opcode does not fit into byte");
487+
testAssertion(b -> b.addOp(256, 0xFFL), "opcode does not fit into byte");
487488
}
488489

489490
@Test
490491
public void testAddImmediateMax2() {
491-
test(b -> b.add(0x01, 0, 0xFFFFFFFF), new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
492+
test(b -> b.addOp(0x01, 0, 0xFFFFFFFF), new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
492493
}
493494

494495
@Test
495496
public void testInvalidAddImmediate2() {
496-
testAssertion(b -> b.add(256, 0, 0), "opcode does not fit into byte");
497+
testAssertion(b -> b.addOp(256, 0, 0), "opcode does not fit into byte");
497498
}
498499

499500
@Test
@@ -686,6 +687,11 @@ public void testElemHeaderExternref() {
686687
test(b -> b.addElemHeader(SegmentMode.ACTIVE, 8, WasmType.EXTERNREF_TYPE, 0, null, -1), new byte[]{0x40, 0x20, 0x08});
687688
}
688689

690+
@Test
691+
public void testElemHeaderExnref() {
692+
test(b -> b.addElemHeader(SegmentMode.ACTIVE, 8, WasmType.EXNREF_TYPE, 0, null, -1), new byte[]{0x40, 0x30, 0x08});
693+
}
694+
689695
@Test
690696
public void testElemHeaderMinU8TableIndex() {
691697
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() {
915921
public void testCodeEntryResults() {
916922
test(b -> b.addCodeEntry(0, 0, 0, 0, 1), new byte[]{0x05, 0x00});
917923
}
924+
925+
@Test
926+
public void testCatchExceptionHandler() {
927+
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});
928+
}
929+
930+
@Test
931+
public void testCatchRefExceptionHandler() {
932+
test(b -> b.addExceptionHandler(0, 12, ExceptionHandlerType.CATCH_REF, 1, 256),
933+
new byte[]{0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00});
934+
}
918935
}

wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
package org.graalvm.wasm.test.suites.bytecode;
4343

4444
import java.io.IOException;
45+
import java.util.EnumSet;
4546
import java.util.List;
4647
import java.util.Objects;
4748
import java.util.function.BiConsumer;
@@ -56,8 +57,10 @@
5657
import org.graalvm.wasm.WasmLanguage;
5758
import org.graalvm.wasm.WasmModule;
5859
import org.graalvm.wasm.WasmTable;
60+
import org.graalvm.wasm.WasmTag;
5961
import org.graalvm.wasm.api.Dictionary;
6062
import org.graalvm.wasm.api.Executable;
63+
import org.graalvm.wasm.api.FuncType;
6164
import org.graalvm.wasm.api.Sequence;
6265
import org.graalvm.wasm.api.TableKind;
6366
import org.graalvm.wasm.api.ValueType;
@@ -85,6 +88,7 @@ public class MultiInstantiationSuite {
8588
private static void test(byte[] testSource, Function<WebAssembly, Object> importFun, BiConsumer<WebAssembly, WasmInstance> check) throws IOException {
8689
final Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID);
8790
contextBuilder.option("wasm.Builtins", "testutil:testutil");
91+
contextBuilder.option("wasm.Exceptions", "true");
8892
try (Context context = contextBuilder.build()) {
8993
Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(binaryWithExports), "main");
9094
Source source = sourceBuilder.build();
@@ -146,10 +150,12 @@ public void testImportsAndExports() throws IOException, InterruptedException {
146150
final byte[] source = WasmBinaryTools.compileWat("main", """
147151
(module
148152
(type (;0;) (func (result i32)))
153+
(type (;1;) (func))
149154
(import "a" "f" (func (type 0)))
150155
(import "a" "t" (table 2 2 funcref))
151156
(import "a" "m" (memory 1 1))
152157
(import "a" "g" (global i32))
158+
(import "a" "e" (tag (type 1)))
153159
(func (type 0) i32.const 13)
154160
(func (export "main") (type 0) i32.const 0)
155161
(func (export "test") (type 0)
@@ -163,13 +169,22 @@ public void testImportsAndExports() throws IOException, InterruptedException {
163169
i32.load
164170
i32.add
165171
)
172+
(func (export "exception") (result exnref)
173+
block (result exnref)
174+
try_table (catch_ref 0 0)
175+
throw 0
176+
end
177+
unreachable
178+
end
179+
)
166180
(export "f" (func 0))
167181
(export "t" (table 0))
168182
(export "m" (memory 0))
169183
(export "g" (global 0))
184+
(export "e" (tag 0))
170185
(elem (i32.const 1) func 1)
171186
)
172-
""");
187+
""", EnumSet.of(WasmBinaryTools.WabtOption.EXCEPTIONS));
173188
final Executable tableFun = new Executable(args -> 13);
174189
test(source, wasm -> {
175190
final Dictionary imports = new Dictionary();
@@ -188,6 +203,9 @@ public void testImportsAndExports() throws IOException, InterruptedException {
188203
final WasmGlobal g = wasm.globalAlloc(ValueType.i32, false, 4);
189204
a.addMember("g", g);
190205

206+
final WasmTag e = WebAssembly.tagAlloc(FuncType.fromString("()"));
207+
a.addMember("e", e);
208+
191209
imports.addMember("a", a);
192210
return imports;
193211
}, (wasm, i) -> {
@@ -211,6 +229,13 @@ public void testImportsAndExports() throws IOException, InterruptedException {
211229
final int gValue = lib.asInt(lib.execute(globalRead, g));
212230
Assert.assertEquals("Global value does not match", 4, gValue);
213231

232+
final Object e = WebAssembly.instanceExport(i, "e");
233+
final Object exception = WebAssembly.instanceExport(i, "exception");
234+
final Object eInstance = lib.execute(exception);
235+
final Object tagRead = wasm.readMember("exn_read");
236+
final Object eInstanceTag = lib.execute(tagRead, eInstance);
237+
Assert.assertSame("Exception tag does not match", e, eInstanceTag);
238+
214239
final Object test = WebAssembly.instanceExport(i, "test");
215240
final int result = lib.asInt(lib.execute(test));
216241
Assert.assertEquals("Invalid test value", 64, result);

0 commit comments

Comments
 (0)