From bd17cf349a4b4be7c2bfc221eaa0cc1dbcb3ecaf Mon Sep 17 00:00:00 2001 From: Andrea Peruffo Date: Wed, 14 Aug 2024 11:06:48 +0200 Subject: [PATCH] [api] Make Module and Sections immutable with Builders (#460) This is part of the effort toward #441 --------- Co-authored-by: David Phillips --- .github/workflows/ci.yaml | 7 + README.md | 7 +- aot/README.md | 4 +- .../chicory/approvals/ApprovalTest.java | 9 +- .../dylibso/chicory/testing/TestModule.java | 14 +- .../java/com/dylibso/chicory/cli/Cli.java | 4 +- .../com/dylibso/chicory/fuzz/FuzzTest.java | 8 +- .../dylibso/chicory/fuzz/RegressionTest.java | 4 +- .../dylibso/chicory/fuzz/SingleReproTest.java | 4 +- .../bench/BenchmarkFactorialExecution.java | 6 +- .../chicory/bench/BenchmarkParsing.java | 4 +- readmes/wasm/expected/parser-base.result | 1 + readmes/wasm/expected/parser-listener.result | 8 + .../dylibso/chicory/testing/TestModule.java | 14 +- .../com/dylibso/chicory/runtime/Instance.java | 21 +- .../chicory/runtime/TypeValidator.java | 5 +- .../chicory/runtime/InterruptionTest.java | 19 +- .../dylibso/chicory/runtime/ModuleTest.java | 3 +- .../dylibso/chicory/maven/JavaTestGen.java | 7 +- .../com/dylibso/chicory/wabt/Wast2Json.java | 3 +- .../com/dylibso/chicory/wabt/Wat2Wasm.java | 3 +- .../dylibso/chicory/wabt/Wat2WasmTest.java | 15 +- wasi/README.md | 5 +- .../chicory/wasi/WasiPreview1Test.java | 3 +- .../dylibso/chicory/wasi/WasiTestRunner.java | 4 +- wasm/README.md | 76 +++-- .../java/com/dylibso/chicory/wasm/Module.java | 299 +++++++++--------- .../com/dylibso/chicory/wasm/ModuleType.java | 6 - .../java/com/dylibso/chicory/wasm/Parser.java | 121 ++++--- .../chicory/wasm/types/CodeSection.java | 71 +++-- .../chicory/wasm/types/DataCountSection.java | 20 +- .../chicory/wasm/types/DataSection.java | 58 ++-- .../chicory/wasm/types/ElementSection.java | 56 ++-- .../chicory/wasm/types/ExportSection.java | 58 ++-- .../chicory/wasm/types/FunctionSection.java | 53 ++-- .../chicory/wasm/types/GlobalSection.java | 58 ++-- .../chicory/wasm/types/ImportSection.java | 58 ++-- .../chicory/wasm/types/MemorySection.java | 58 ++-- .../chicory/wasm/types/NameCustomSection.java | 139 ++------ .../chicory/wasm/types/StartSection.java | 21 +- .../chicory/wasm/types/TableSection.java | 58 ++-- .../chicory/wasm/types/TypeSection.java | 58 ++-- .../wasm/types/UnknownCustomSection.java | 27 +- .../com/dylibso/chicory/wasm/ParserTest.java | 35 +- 44 files changed, 737 insertions(+), 775 deletions(-) create mode 100644 readmes/wasm/expected/parser-base.result create mode 100644 readmes/wasm/expected/parser-listener.result delete mode 100644 wasm/src/main/java/com/dylibso/chicory/wasm/ModuleType.java diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ff0fa346e..37fe2b515 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -91,6 +91,13 @@ jobs: script: aot/README.md env: JBANG_REPO: "${{ github.workspace }}/repository" + # Test WASM Readme + - name: jbang + uses: jbangdev/jbang-action@9f8c55e0a2b6b297711162b20c209c5e07076e59 # tag=v0.117.1 + with: + script: wasm/README.md + env: + JBANG_REPO: "${{ github.workspace }}/repository" test-results: name: Test Results diff --git a/README.md b/README.md index 41732fe50..f43501a07 100644 --- a/README.md +++ b/README.md @@ -114,11 +114,12 @@ Now let's load this module and instantiate it: import com.dylibso.chicory.runtime.ExportFunction; import com.dylibso.chicory.wasm.types.Value; import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.runtime.Instance; import java.io.File; // point this to your path on disk -Module module = Module.builder(new File("./factorial.wasm")).build(); +Module module = Parser.parse(new File("./factorial.wasm")); Instance instance = Instance.builder(module).build(); ``` @@ -171,7 +172,7 @@ copyFileFromWasmCorpus("count_vowels.rs.wasm", "count_vowels.wasm"); Build and instantiate this module: ```java -Instance instance = Instance.builder(Module.builder(new File("./count_vowels.wasm")).build()).build(); +Instance instance = Instance.builder(Parser.parse(new File("./count_vowels.wasm"))).build(); ExportFunction countVowels = instance.export("count_vowels"); ``` @@ -278,7 +279,7 @@ Now we just need to pass this host function in during our instantiation phase: ```java import com.dylibso.chicory.runtime.HostImports; var imports = new HostImports(new HostFunction[] {func}); -var instance = Instance.builder(Module.builder(new File("./logger.wasm")).build()).withHostImports(imports).build(); +var instance = Instance.builder(Parser.parse(new File("./logger.wasm"))).withHostImports(imports).build(); var logIt = instance.export("logIt"); logIt.apply(); // should print "Hello, World!" 10 times diff --git a/aot/README.md b/aot/README.md index e7116afe2..273e6de83 100644 --- a/aot/README.md +++ b/aot/README.md @@ -14,10 +14,10 @@ To enable use the AotMachine factory when building the module: ```java // ... -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.aot.AotMachine; // ... var is = ClassLoader.getSystemClassLoader().getResourceAsStream("compiled/basic.c.wasm"); -Instance.builder(Module.builder(is).build()).withMachineFactory(AotMachine::new).build(); +Instance.builder(Parser.parse(is)).withMachineFactory(AotMachine::new).build(); ``` diff --git a/aot/src/test/java/com/dylibso/chicory/approvals/ApprovalTest.java b/aot/src/test/java/com/dylibso/chicory/approvals/ApprovalTest.java index 0702f0e1e..7ddc5cf06 100644 --- a/aot/src/test/java/com/dylibso/chicory/approvals/ApprovalTest.java +++ b/aot/src/test/java/com/dylibso/chicory/approvals/ApprovalTest.java @@ -2,7 +2,7 @@ import com.dylibso.chicory.aot.AotMachine; import com.dylibso.chicory.runtime.Instance; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import org.approvaltests.Approvals; @@ -65,10 +65,9 @@ public void verifyTrap() { private static void verifyGeneratedBytecode(String name) { var instance = Instance.builder( - Module.builder( - ClassLoader.getSystemClassLoader() - .getResourceAsStream("compiled/" + name)) - .build()) + Parser.parse( + ClassLoader.getSystemClassLoader() + .getResourceAsStream("compiled/" + name))) .withImportValidation(false) .withMachineFactory(AotMachine::new) .withStart(false) diff --git a/aot/src/test/java/com/dylibso/chicory/testing/TestModule.java b/aot/src/test/java/com/dylibso/chicory/testing/TestModule.java index 0c28cc36a..52e214ad6 100644 --- a/aot/src/test/java/com/dylibso/chicory/testing/TestModule.java +++ b/aot/src/test/java/com/dylibso/chicory/testing/TestModule.java @@ -5,7 +5,7 @@ import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.wabt.Wat2Wasm; import com.dylibso.chicory.wasm.Module; -import com.dylibso.chicory.wasm.ModuleType; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.exceptions.MalformedException; import java.io.File; @@ -21,10 +21,6 @@ public TestModule(Module module) { this.module = module; } - public static TestModule of(File file) { - return of(file, ModuleType.BINARY); - } - public static TestModule of(Module module) { return new TestModule(module); } @@ -54,8 +50,8 @@ public static TestModule of(Module module) { + "alignment " + "multiple start sections"; - public static TestModule of(File file, ModuleType moduleType) { - if (moduleType == ModuleType.TEXT) { + public static TestModule of(File file) { + if (file.getName().endsWith(".wat")) { byte[] parsed; try { parsed = Wat2Wasm.parse(file); @@ -63,9 +59,9 @@ public static TestModule of(File file, ModuleType moduleType) { throw new MalformedException( e.getMessage() + HACK_MATCH_ALL_MALFORMED_EXCEPTION_TEXT); } - return of(Module.builder(parsed).build()); + return of(Parser.parse(parsed)); } - return of(Module.builder(file).build()); + return of(Parser.parse(file)); } public Instance build() { diff --git a/cli/src/main/java/com/dylibso/chicory/cli/Cli.java b/cli/src/main/java/com/dylibso/chicory/cli/Cli.java index 376c4efb4..881aaac14 100644 --- a/cli/src/main/java/com/dylibso/chicory/cli/Cli.java +++ b/cli/src/main/java/com/dylibso/chicory/cli/Cli.java @@ -5,7 +5,7 @@ import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.wasi.WasiOptions; import com.dylibso.chicory.wasi.WasiPreview1; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.types.Value; import java.io.ByteArrayInputStream; import java.io.File; @@ -57,7 +57,7 @@ public void run() { throw new RuntimeException(e); } var logger = new SystemLogger(); - var module = Module.builder(file).build(); + var module = Parser.parse(file); var imports = wasi ? new HostImports( diff --git a/fuzz/src/test/java/com/dylibso/chicory/fuzz/FuzzTest.java b/fuzz/src/test/java/com/dylibso/chicory/fuzz/FuzzTest.java index 565ea685e..8a058edc4 100644 --- a/fuzz/src/test/java/com/dylibso/chicory/fuzz/FuzzTest.java +++ b/fuzz/src/test/java/com/dylibso/chicory/fuzz/FuzzTest.java @@ -8,7 +8,7 @@ import com.dylibso.chicory.log.Logger; import com.dylibso.chicory.log.SystemLogger; import com.dylibso.chicory.runtime.Instance; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.types.ExternalType; import java.io.File; import org.junit.jupiter.api.BeforeEach; @@ -29,7 +29,7 @@ File generateTestData(String prefix, int num, InstructionType... instructionType targetWasm = smith.run(prefix + num, "test.wasm", new InstructionTypes(instructionTypes)); - var exportSection = Module.builder(targetWasm).build().exportSection(); + var exportSection = Parser.parse(targetWasm).exportSection(); atLeastOneExportedFunction = false; for (int i = 0; i < exportSection.exportCount(); i++) { if (exportSection.getExport(i).exportType() == ExternalType.FUNCTION) { @@ -59,7 +59,7 @@ public void numericOnlyFuzz(RepetitionInfo repetitionInfo) throws Exception { var targetWasm = generateTestData( "numeric-", repetitionInfo.getCurrentRepetition(), InstructionType.NUMERIC); - var module = Module.builder(targetWasm).build(); + var module = Parser.parse(targetWasm); var instance = Instance.builder(module).withInitialize(true).withStart(false).build(); var results = testModule(targetWasm, module, instance); @@ -76,7 +76,7 @@ public void tableOnlyFuzz(RepetitionInfo repetitionInfo) throws Exception { var targetWasm = generateTestData( "table-", repetitionInfo.getCurrentRepetition(), InstructionType.TABLE); - var module = Module.builder(targetWasm).build(); + var module = Parser.parse(targetWasm); var instance = Instance.builder(module).withInitialize(true).withStart(false).build(); var results = testModule(targetWasm, module, instance); diff --git a/fuzz/src/test/java/com/dylibso/chicory/fuzz/RegressionTest.java b/fuzz/src/test/java/com/dylibso/chicory/fuzz/RegressionTest.java index 2f22e2b56..be870859e 100644 --- a/fuzz/src/test/java/com/dylibso/chicory/fuzz/RegressionTest.java +++ b/fuzz/src/test/java/com/dylibso/chicory/fuzz/RegressionTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.dylibso.chicory.runtime.Instance; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import java.io.File; import java.util.Arrays; import java.util.stream.Stream; @@ -24,7 +24,7 @@ private static Stream crashFolders() { @MethodSource("crashFolders") void regressionTests(File folder) throws Exception { var targetWasm = new File(folder.getAbsolutePath() + "/test.wasm"); - var module = Module.builder(targetWasm).build(); + var module = Parser.parse(targetWasm); var instance = Instance.builder(module).withInitialize(true).withStart(false).build(); var results = testModule(targetWasm, module, instance, false); diff --git a/fuzz/src/test/java/com/dylibso/chicory/fuzz/SingleReproTest.java b/fuzz/src/test/java/com/dylibso/chicory/fuzz/SingleReproTest.java index f905649e4..3513ab1e3 100644 --- a/fuzz/src/test/java/com/dylibso/chicory/fuzz/SingleReproTest.java +++ b/fuzz/src/test/java/com/dylibso/chicory/fuzz/SingleReproTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import com.dylibso.chicory.runtime.Instance; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import java.nio.file.Files; import java.nio.file.Paths; import org.junit.jupiter.api.Test; @@ -28,7 +28,7 @@ void singleReproducer() throws Exception { var targetWasm = smith.run(seed.substring(0, Math.min(seed.length(), 32)), "test.wasm", types); - var module = Module.builder(targetWasm).build(); + var module = Parser.parse(targetWasm); var instance = Instance.builder(module).withInitialize(true).withStart(false).build(); testModule(targetWasm, module, instance); diff --git a/jmh/src/main/java/com/dylibso/chicory/bench/BenchmarkFactorialExecution.java b/jmh/src/main/java/com/dylibso/chicory/bench/BenchmarkFactorialExecution.java index cf676efd0..84af92887 100644 --- a/jmh/src/main/java/com/dylibso/chicory/bench/BenchmarkFactorialExecution.java +++ b/jmh/src/main/java/com/dylibso/chicory/bench/BenchmarkFactorialExecution.java @@ -3,7 +3,7 @@ import com.dylibso.chicory.aot.AotMachine; import com.dylibso.chicory.runtime.ExportFunction; import com.dylibso.chicory.runtime.Instance; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.types.Value; import java.io.File; import java.util.concurrent.TimeUnit; @@ -38,11 +38,11 @@ public class BenchmarkFactorialExecution { @Setup public void setup() { - var factorialInt = Instance.builder(Module.builder(ITERFACT).build()).build(); + var factorialInt = Instance.builder(Parser.parse(ITERFACT)).build(); iterFactInt = factorialInt.export("iterFact"); var factorialAot = - Instance.builder(Module.builder(ITERFACT).build()) + Instance.builder(Parser.parse(ITERFACT)) .withMachineFactory(AotMachine::new) .build(); iterFactAot = factorialAot.export("iterFact"); diff --git a/jmh/src/main/java/com/dylibso/chicory/bench/BenchmarkParsing.java b/jmh/src/main/java/com/dylibso/chicory/bench/BenchmarkParsing.java index e84bb51f5..6afc9f4d5 100644 --- a/jmh/src/main/java/com/dylibso/chicory/bench/BenchmarkParsing.java +++ b/jmh/src/main/java/com/dylibso/chicory/bench/BenchmarkParsing.java @@ -1,6 +1,6 @@ package com.dylibso.chicory.bench; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; @@ -41,6 +41,6 @@ public void setup() throws IOException { @Benchmark @BenchmarkMode(Mode.Throughput) public void benchmark(Blackhole bh) { - bh.consume(Module.builder(memoryMappedFile).build()); + bh.consume(Parser.parse(memoryMappedFile)); } } diff --git a/readmes/wasm/expected/parser-base.result b/readmes/wasm/expected/parser-base.result new file mode 100644 index 000000000..9867bba75 --- /dev/null +++ b/readmes/wasm/expected/parser-base.result @@ -0,0 +1 @@ +target_features diff --git a/readmes/wasm/expected/parser-listener.result b/readmes/wasm/expected/parser-listener.result new file mode 100644 index 000000000..5fc6c1669 --- /dev/null +++ b/readmes/wasm/expected/parser-listener.result @@ -0,0 +1,8 @@ +.debug_abbrev +.debug_info +.debug_ranges +.debug_str +.debug_line +name +producers +target_features diff --git a/runtime-tests/src/test/java/com/dylibso/chicory/testing/TestModule.java b/runtime-tests/src/test/java/com/dylibso/chicory/testing/TestModule.java index 84dd54741..c837b8a69 100644 --- a/runtime-tests/src/test/java/com/dylibso/chicory/testing/TestModule.java +++ b/runtime-tests/src/test/java/com/dylibso/chicory/testing/TestModule.java @@ -4,7 +4,7 @@ import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.wabt.Wat2Wasm; import com.dylibso.chicory.wasm.Module; -import com.dylibso.chicory.wasm.ModuleType; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.exceptions.MalformedException; import java.io.File; @@ -15,10 +15,6 @@ public class TestModule { private HostImports imports; - public static TestModule of(File file) { - return of(file, ModuleType.BINARY); - } - public static TestModule of(Module module) { return new TestModule(module); } @@ -48,8 +44,8 @@ public static TestModule of(Module module) { + "alignment " + "multiple start sections"; - public static TestModule of(File file, ModuleType moduleType) { - if (moduleType == ModuleType.TEXT) { + public static TestModule of(File file) { + if (file.getName().endsWith(".wat")) { byte[] parsed; try { parsed = Wat2Wasm.parse(file); @@ -57,9 +53,9 @@ public static TestModule of(File file, ModuleType moduleType) { throw new MalformedException( e.getMessage() + HACK_MATCH_ALL_MALFORMED_EXCEPTION_TEXT); } - return of(Module.builder(parsed).build()); + return of(Parser.parse(parsed)); } - return of(Module.builder(file).build()); + return of(Parser.parse(file)); } public TestModule(Module module) { diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java b/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java index c8b40178d..b09e91d4e 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java @@ -627,8 +627,9 @@ private static void validateModule(Module module) { if (functionSectionSize != codeSectionSize) { throw new MalformedException("function and code section have inconsistent lengths"); } - if (module.dataCountSection() != null - && dataSectionSize != module.dataCountSection().dataCount()) { + if (module.dataCountSection() + .map(dcs -> dcs.dataCount() != dataSectionSize) + .orElse(false)) { throw new MalformedException( "data count and data section have inconsistent lengths"); } @@ -813,15 +814,13 @@ public Instance build() { mapHostImports( imports, (hostImports == null) ? new HostImports() : hostImports, - (module.memorySection() != null) - ? module.memorySection().memoryCount() - : 0); + module.memorySection().map(m -> m.memoryCount()).orElse(0)); - if (module.startSection() != null) { + if (module.startSection().isPresent()) { var export = new Export( START_FUNCTION_NAME, - (int) module.startSection().startIndex(), + (int) module.startSection().get().startIndex(), ExternalType.FUNCTION); exports.put(START_FUNCTION_NAME, export); } @@ -839,8 +838,8 @@ public Instance build() { Element[] elements = module.elementSection().elements(); Memory memory = null; - if (module.memorySection() != null) { - var memories = module.memorySection(); + if (module.memorySection().isPresent()) { + var memories = module.memorySection().get(); if (memories.memoryCount() > 0) { memory = new Memory(memories.getMemory(0).memoryLimits()); } @@ -912,9 +911,7 @@ public Instance build() { case MEMORY: { var memoryCount = - (module.memorySection() == null) - ? 0 - : module.memorySection().memoryCount(); + module.memorySection().map(m -> m.memoryCount()).orElse(0); if (e.index() >= memoryCount + memoryImportsOffset) { throw new InvalidException("unknown memory " + e); } diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/TypeValidator.java b/runtime/src/main/java/com/dylibso/chicory/runtime/TypeValidator.java index 97bf254c1..330493f3b 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/TypeValidator.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/TypeValidator.java @@ -958,9 +958,8 @@ public void validate( // to satisfy the check mentioned in the NOTE // https://webassembly.github.io/spec/core/binary/modules.html#data-count-section - if (instance.module().codeSection() != null - && instance.module().codeSection().isRequiresDataCount() - && instance.module().dataCountSection() == null) { + if (instance.module().codeSection().isRequiresDataCount() + && instance.module().dataCountSection().isEmpty()) { throw new MalformedException("data count section required"); } } diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/InterruptionTest.java b/runtime/src/test/java/com/dylibso/chicory/runtime/InterruptionTest.java index 87b3e9a6f..9960ffd6c 100644 --- a/runtime/src/test/java/com/dylibso/chicory/runtime/InterruptionTest.java +++ b/runtime/src/test/java/com/dylibso/chicory/runtime/InterruptionTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.exceptions.ChicoryException; import com.dylibso.chicory.wasm.types.Value; import java.util.concurrent.atomic.AtomicBoolean; @@ -17,11 +17,10 @@ public class InterruptionTest { public void shouldInterruptLoop() throws InterruptedException { var instance = Instance.builder( - Module.builder( - ClassLoader.getSystemClassLoader() - .getResourceAsStream( - "compiled/infinite-loop.c.wasm")) - .build()) + Parser.parse( + ClassLoader.getSystemClassLoader() + .getResourceAsStream( + "compiled/infinite-loop.c.wasm"))) .build(); var function = instance.export("run"); assertInterruption(function::apply); @@ -31,11 +30,9 @@ public void shouldInterruptLoop() throws InterruptedException { public void shouldInterruptCall() throws InterruptedException { var instance = Instance.builder( - Module.builder( - ClassLoader.getSystemClassLoader() - .getResourceAsStream( - "compiled/power.c.wasm")) - .build()) + Parser.parse( + ClassLoader.getSystemClassLoader() + .getResourceAsStream("compiled/power.c.wasm"))) .build(); var function = instance.export("run"); assertInterruption(() -> function.apply(Value.i32(100))); diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/ModuleTest.java b/runtime/src/test/java/com/dylibso/chicory/runtime/ModuleTest.java index 9e308fba2..ce8bac6e7 100644 --- a/runtime/src/test/java/com/dylibso/chicory/runtime/ModuleTest.java +++ b/runtime/src/test/java/com/dylibso/chicory/runtime/ModuleTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.exceptions.UninstantiableException; import com.dylibso.chicory.wasm.types.Instruction; import com.dylibso.chicory.wasm.types.MemoryLimits; @@ -20,7 +21,7 @@ public class ModuleTest { private static Module loadModule(String fileName) { - return Module.builder(ModuleTest.class.getResourceAsStream("/" + fileName)).build(); + return Parser.parse(ModuleTest.class.getResourceAsStream("/" + fileName)); } @Test diff --git a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/JavaTestGen.java b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/JavaTestGen.java index 10d6d8cb7..10d93674b 100644 --- a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/JavaTestGen.java +++ b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/JavaTestGen.java @@ -99,7 +99,6 @@ public CompilationUnit generate( cu.addImport("com.dylibso.chicory.testing.TestModule"); // runtime imports - cu.addImport("com.dylibso.chicory.wasm.ModuleType"); cu.addImport("com.dylibso.chicory.wasm.exceptions.ChicoryException"); cu.addImport("com.dylibso.chicory.runtime.ExportFunction"); cu.addImport("com.dylibso.chicory.runtime.Instance"); @@ -404,17 +403,13 @@ private static NameExpr generateModuleInstantiation( String importsName, String hostFuncs, boolean excludeInvalid) { - var additionalParam = - cmd.moduleType() == null ? "" : ", ModuleType." + cmd.moduleType().toUpperCase(); return new NameExpr( "TestModule.of(\n" + INDENT + TAB + "new File(\"" + wasmFile - + "\")" - + additionalParam - + ")\n" + + "\"))\n" + ((excludeInvalid) ? INDENT + ".withTypeValidation(false)\n" : "") + ((hostFuncs != null) ? INDENT diff --git a/wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java b/wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java index 2c1ca570e..66016ee82 100644 --- a/wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java +++ b/wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java @@ -10,6 +10,7 @@ import com.dylibso.chicory.wasi.WasiOptions; import com.dylibso.chicory.wasi.WasiPreview1; import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import java.io.ByteArrayOutputStream; @@ -32,7 +33,7 @@ public boolean isLoggable(Logger.Level level) { } }; private static final Module MODULE = - Module.builder(Wast2Json.class.getResourceAsStream("/wast2json")).build(); + Parser.parse(Wast2Json.class.getResourceAsStream("/wast2json")); private final File input; private final File output; diff --git a/wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java b/wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java index d5917854e..3155e82fb 100644 --- a/wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java +++ b/wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java @@ -9,6 +9,7 @@ import com.dylibso.chicory.wasi.WasiOptions; import com.dylibso.chicory.wasi.WasiPreview1; import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import java.io.ByteArrayInputStream; @@ -27,7 +28,7 @@ public final class Wat2Wasm { private static final Logger logger = new SystemLogger(); private static final Module MODULE = - Module.builder(Wat2Wasm.class.getResourceAsStream("/wat2wasm")).build(); + Parser.parse(Wat2Wasm.class.getResourceAsStream("/wat2wasm")); private Wat2Wasm() {} diff --git a/wabt/src/test/java/com/dylibso/chicory/wabt/Wat2WasmTest.java b/wabt/src/test/java/com/dylibso/chicory/wabt/Wat2WasmTest.java index f1fc7d352..35961128d 100644 --- a/wabt/src/test/java/com/dylibso/chicory/wabt/Wat2WasmTest.java +++ b/wabt/src/test/java/com/dylibso/chicory/wabt/Wat2WasmTest.java @@ -6,7 +6,7 @@ import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.wasi.WasiExitException; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.types.Value; import java.io.File; import org.junit.jupiter.api.Test; @@ -25,13 +25,12 @@ public void shouldRunWat2Wasm() throws Exception { public void shouldRunWat2WasmOnString() { var moduleInstance = Instance.builder( - Module.builder( - Wat2Wasm.parse( - "(module (func (export \"add\") (param $x" - + " i32) (param $y i32) (result i32)" - + " (i32.add (local.get $x) (local.get" - + " $y))))")) - .build()) + Parser.parse( + Wat2Wasm.parse( + "(module (func (export \"add\") (param $x" + + " i32) (param $y i32) (result i32)" + + " (i32.add (local.get $x) (local.get" + + " $y))))"))) .withTypeValidation(true) .withInitialize(true) .build(); diff --git a/wasi/README.md b/wasi/README.md index dc93dac86..127ccf169 100644 --- a/wasi/README.md +++ b/wasi/README.md @@ -155,6 +155,7 @@ import com.dylibso.chicory.log.SystemLogger; import com.dylibso.chicory.wasi.WasiOptions; import com.dylibso.chicory.wasi.WasiPreview1; import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.runtime.HostImports; import com.dylibso.chicory.runtime.Instance; @@ -169,7 +170,7 @@ var wasi = new WasiPreview1(logger, WasiOptions.builder().build()); var imports = new HostImports(wasi.toHostFunctions()); // create the module and connect imports // this will execute the module if it's a WASI command-pattern module -Instance.builder(Module.builder(new File("hello-wasi.wasm")).build()).withHostImports(imports).build(); +Instance.builder(Parser.parse(new File("hello-wasi.wasm"))).withHostImports(imports).build(); ``` > **Note**: Take note that we don't explicitly execute the module. The module will run when you instantiate it. This @@ -208,7 +209,7 @@ var imports = new HostImports(wasi.toHostFunctions()); // greet-wasi is a rust program that greets the string passed in stdin // instantiating will execute the module if it's a WASI command-pattern module -Instance.builder(Module.builder(new File("greet-wasi.wasm")).build()).withHostImports(imports).build(); +Instance.builder(Parser.parse(new File("greet-wasi.wasm"))).withHostImports(imports).build(); // check that we output the greeting assert(fakeStdout.toString().equals("Hello, Andrea!")); diff --git a/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java b/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java index 4cfe175b6..8e048d34f 100644 --- a/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java +++ b/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java @@ -8,6 +8,7 @@ import com.dylibso.chicory.runtime.HostImports; import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.types.Value; import java.io.ByteArrayInputStream; import java.util.List; @@ -17,7 +18,7 @@ public class WasiPreview1Test { private final Logger logger = new SystemLogger(); private static Module loadModule(String fileName) { - return Module.builder(WasiPreview1Test.class.getResourceAsStream("/" + fileName)).build(); + return Parser.parse(WasiPreview1Test.class.getResourceAsStream("/" + fileName)); } @Test diff --git a/wasi/src/test/java/com/dylibso/chicory/wasi/WasiTestRunner.java b/wasi/src/test/java/com/dylibso/chicory/wasi/WasiTestRunner.java index 2f1917e29..cf7bf4fa5 100644 --- a/wasi/src/test/java/com/dylibso/chicory/wasi/WasiTestRunner.java +++ b/wasi/src/test/java/com/dylibso/chicory/wasi/WasiTestRunner.java @@ -6,7 +6,7 @@ import com.dylibso.chicory.log.SystemLogger; import com.dylibso.chicory.runtime.HostImports; import com.dylibso.chicory.runtime.Instance; -import com.dylibso.chicory.wasm.Module; +import com.dylibso.chicory.wasm.Parser; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import java.io.File; @@ -83,7 +83,7 @@ public static void execute( private static int execute(File test, WasiOptions wasiOptions) { try (var wasi = new WasiPreview1(LOGGER, wasiOptions)) { - Instance.builder(Module.builder(test).build()) + Instance.builder(Parser.parse(test)) .withHostImports(new HostImports(wasi.toHostFunctions())) .build(); } catch (WasiExitException e) { diff --git a/wasm/README.md b/wasm/README.md index cb37f82e9..c1feffda9 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -7,46 +7,80 @@ for a beta we will publish to maven central. For now this is available as Github ## Usage There are two ways you can interface with this library. The simplest way is to parse the whole -module using `parseModule`: +module using `Parser.parse`: + + + + +```java +import com.dylibso.chicory.wasm.Parser; + +var is = ClassLoader.getSystemClassLoader().getResourceAsStream("compiled/count_vowels.rs.wasm"); +var module = Parser.parse(is); +var customSection = module.customSections().get(0); +System.out.println("First custom section: " + customSection.name()); ``` + + The second is to use the `ParserListener` interface and the `parse()` method. In this mode you can also call `includeSection(int sectionId)` for each section you wish to parse. It will skip all other sections. This is useful for performance if you only want to parse a piece of the module. If you don't call this method once it will parse all sections. ```java -var parser = new Parser(new SystemLogger()); +import com.dylibso.chicory.wasm.ParserListener; +import com.dylibso.chicory.wasm.types.CustomSection; +import com.dylibso.chicory.wasm.types.SectionId; + +var parser = new Parser(); // include the custom sections, don't call this to receive all sections parser.includeSection(SectionId.CUSTOM); // parser.includeSection(SectionId.CODE); // call for each section you want +String result = ""; // implement the listener -parser.setListener(section -> { - if (section.getSectionId() == SectionId.CUSTOM) { +ParserListener listener = section -> { + if (section.sectionId() == SectionId.CUSTOM) { var customSection = (CustomSection) section; - var name = customSection.getName(); + var name = customSection.name(); + result += name + "\n"; System.out.println("Got custom section with name: " + name); } else { - fail("Should not have received section with id: " + section.getSectionId()); + throw new RuntimeException("Should not have received section with id: " + section.sectionId()); } -}); - -// call parseModule() -try (var fis = new FileInputStream("/tmp/code.wasm")) { - parser.parseModule(fis); -} catch (Exception e) { - throw new RuntimeException(e); }; + +// call parse() +var is = ClassLoader.getSystemClassLoader().getResourceAsStream("compiled/count_vowels.rs.wasm"); +parser.parse(is, listener); +``` + diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/Module.java b/wasm/src/main/java/com/dylibso/chicory/wasm/Module.java index b1dd6319d..7cd57b83d 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/Module.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/Module.java @@ -2,8 +2,6 @@ import static java.util.Objects.requireNonNull; -import com.dylibso.chicory.wasm.exceptions.ChicoryException; -import com.dylibso.chicory.wasm.exceptions.InvalidException; import com.dylibso.chicory.wasm.types.CodeSection; import com.dylibso.chicory.wasm.types.CustomSection; import com.dylibso.chicory.wasm.types.DataCountSection; @@ -18,51 +16,58 @@ import com.dylibso.chicory.wasm.types.StartSection; import com.dylibso.chicory.wasm.types.TableSection; import com.dylibso.chicory.wasm.types.TypeSection; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Objects; -import java.util.function.Supplier; +import java.util.Map; +import java.util.Optional; public class Module { - private final HashMap customSections; - - private TypeSection typeSection = new TypeSection(); - private ImportSection importSection = new ImportSection(); - private FunctionSection functionSection = new FunctionSection(); - private TableSection tableSection = new TableSection(); - private MemorySection memorySection; - private GlobalSection globalSection = new GlobalSection(); - private ExportSection exportSection = new ExportSection(); - private StartSection startSection; - private ElementSection elementSection = new ElementSection(); - private CodeSection codeSection = new CodeSection(); - private DataSection dataSection = new DataSection(); - private DataCountSection dataCountSection; - - private final List ignoredSections = new ArrayList(); - - public Module() { - this.customSections = new HashMap<>(); - } - - public void setTypeSection(TypeSection typeSection) { + private final Map customSections; + + private final TypeSection typeSection; + private final ImportSection importSection; + private final FunctionSection functionSection; + private final TableSection tableSection; + private final Optional memorySection; + private final GlobalSection globalSection; + private final ExportSection exportSection; + private final Optional startSection; + private final ElementSection elementSection; + private final CodeSection codeSection; + private final DataSection dataSection; + private final Optional dataCountSection; + private final List ignoredSections; + + Module( + TypeSection typeSection, + ImportSection importSection, + FunctionSection functionSection, + TableSection tableSection, + Optional memorySection, + GlobalSection globalSection, + ExportSection exportSection, + Optional startSection, + ElementSection elementSection, + CodeSection codeSection, + DataSection dataSection, + Optional dataCountSection, + HashMap customSections, + List ignoredSections) { this.typeSection = requireNonNull(typeSection); - } - - public void setFunctionSection(FunctionSection functionSection) { + this.importSection = requireNonNull(importSection); this.functionSection = requireNonNull(functionSection); - } - - public void setExportSection(ExportSection exportSection) { + this.tableSection = requireNonNull(tableSection); + this.memorySection = memorySection; + this.globalSection = requireNonNull(globalSection); this.exportSection = requireNonNull(exportSection); + this.startSection = startSection; + this.elementSection = requireNonNull(elementSection); + this.codeSection = requireNonNull(codeSection); + this.dataSection = requireNonNull(dataSection); + this.dataCountSection = dataCountSection; + this.customSections = Map.copyOf(customSections); + this.ignoredSections = List.copyOf(ignoredSections); } public TypeSection typeSection() { @@ -77,7 +82,7 @@ public ExportSection exportSection() { return exportSection; } - public StartSection startSection() { + public Optional startSection() { return startSection; } @@ -85,66 +90,30 @@ public ImportSection importSection() { return importSection; } - public void setStartSection(StartSection startSection) { - this.startSection = startSection; - } - - public void setImportSection(ImportSection importSection) { - this.importSection = requireNonNull(importSection); - } - public CodeSection codeSection() { return codeSection; } - public void setCodeSection(CodeSection codeSection) { - this.codeSection = requireNonNull(codeSection); - } - - public void setDataSection(DataSection dataSection) { - this.dataSection = requireNonNull(dataSection); - } - - public void setDataCountSection(DataCountSection dataCountSection) { - this.dataCountSection = dataCountSection; - } - public DataSection dataSection() { return dataSection; } - public DataCountSection dataCountSection() { + public Optional dataCountSection() { return dataCountSection; } - public MemorySection memorySection() { + public Optional memorySection() { return memorySection; } - public void setMemorySection(MemorySection memorySection) { - this.memorySection = memorySection; - } - public GlobalSection globalSection() { return globalSection; } - public void setGlobalSection(GlobalSection globalSection) { - this.globalSection = requireNonNull(globalSection); - } - public TableSection tableSection() { return tableSection; } - public void setTableSection(TableSection tableSection) { - this.tableSection = requireNonNull(tableSection); - } - - public void addCustomSection(CustomSection customSection) { - this.customSections.put(customSection.name(), customSection); - } - public List customSections() { return new ArrayList<>(customSections.values()); } @@ -161,98 +130,120 @@ public ElementSection elementSection() { return elementSection; } - public void setElementSection(ElementSection elementSection) { - this.elementSection = requireNonNull(elementSection); + public List ignoredSections() { + return ignoredSections; } - public void addIgnoredSection(int id) { - ignoredSections.add(id); + public static Builder builder() { + return new Builder(); } - /** - * Creates a {@link Builder} for the specified {@link InputStream} - * - * @param input the input stream - * @return a {@link Builder} for reading the module definition from the specified input stream - */ - public static Builder builder(InputStream input) { - return new Builder(() -> input); - } + public static class Builder { + private TypeSection typeSection = TypeSection.builder().build(); + private ImportSection importSection = ImportSection.builder().build(); + private FunctionSection functionSection = FunctionSection.builder().build(); + private TableSection tableSection = TableSection.builder().build(); + private Optional memorySection = Optional.empty(); + private GlobalSection globalSection = GlobalSection.builder().build(); + private ExportSection exportSection = ExportSection.builder().build(); + private Optional startSection = Optional.empty(); + private ElementSection elementSection = ElementSection.builder().build(); + private CodeSection codeSection = CodeSection.builder().build(); + private DataSection dataSection = DataSection.builder().build(); + private Optional dataCountSection = Optional.empty(); + private HashMap customSections = new HashMap<>(); + private List ignoredSections = new ArrayList<>(); + + private Builder() {} + + public Builder setTypeSection(TypeSection ts) { + this.typeSection = requireNonNull(ts); + return this; + } - /** - * Creates a {@link Builder} for the specified {@link ByteBuffer} - * - * @param buffer the buffer - * @return a {@link Builder} for reading the module definition from the specified buffer - */ - public static Builder builder(ByteBuffer buffer) { - return builder(buffer.array()); - } + public Builder setImportSection(ImportSection is) { + this.importSection = requireNonNull(is); + return this; + } - /** - * Creates a {@link Builder} for the specified byte array - * - * @param buffer the buffer - * @return a {@link Builder} for reading the module definition from the specified buffer - */ - public static Builder builder(byte[] buffer) { - return new Builder(() -> new ByteArrayInputStream(buffer)); - } + public Builder setFunctionSection(FunctionSection fs) { + this.functionSection = requireNonNull(fs); + return this; + } - /** - * Creates a {@link Builder} for the specified {@link File} resource - * - * @param file the path of the resource - * @return a {@link Builder} for reading the module definition from the specified file - */ - public static Builder builder(File file) { - return builder(file.toPath()); - } + public Builder setTableSection(TableSection ts) { + this.tableSection = requireNonNull(ts); + return this; + } - /** - * Creates a {@link Builder} for the specified {@link Path} resource - * - * @param path the path of the resource - * @return a {@link Builder} for reading the module definition from the specified path - */ - public static Builder builder(Path path) { - return new Builder( - () -> { - try { - return Files.newInputStream(path); - } catch (IOException e) { - throw new IllegalArgumentException("Error opening file: " + path, e); - } - }); - } + public Builder setMemorySection(MemorySection ms) { + this.memorySection = Optional.ofNullable(ms); + return this; + } - public static class Builder { - private final Supplier inputStreamSupplier; - private ModuleType moduleType = ModuleType.BINARY; + public Builder setGlobalSection(GlobalSection gs) { + this.globalSection = requireNonNull(gs); + return this; + } + + public Builder setExportSection(ExportSection es) { + this.exportSection = requireNonNull(es); + return this; + } + + public Builder setStartSection(StartSection ss) { + this.startSection = Optional.ofNullable(ss); + return this; + } + + public Builder setElementSection(ElementSection es) { + this.elementSection = requireNonNull(es); + return this; + } - private Builder(Supplier inputStreamSupplier) { - this.inputStreamSupplier = Objects.requireNonNull(inputStreamSupplier); + public Builder setCodeSection(CodeSection cs) { + this.codeSection = requireNonNull(cs); + return this; + } + + public Builder setDataSection(DataSection ds) { + this.dataSection = requireNonNull(ds); + return this; + } + + public Builder setDataCountSection(DataCountSection dcs) { + this.dataCountSection = Optional.ofNullable(dcs); + return this; + } + + public Builder addCustomSection(String name, CustomSection cs) { + requireNonNull(name); + requireNonNull(cs); + this.customSections.put(name, cs); + return this; } - public Builder withType(ModuleType type) { - this.moduleType = type; + public Builder addIgnoredSection(int id) { + this.ignoredSections.add(id); return this; } public Module build() { - final Parser parser = new Parser(); - switch (this.moduleType) { - case BINARY: - try (final InputStream is = inputStreamSupplier.get()) { - return parser.parseModule(is); - } catch (IOException e) { - throw new ChicoryException(e); - } - default: - throw new InvalidException( - "Text format parsing is not implemented, but you can use wat2wasm" - + " through Chicory."); - } + return new Module( + typeSection, + importSection, + functionSection, + tableSection, + memorySection, + globalSection, + exportSection, + startSection, + elementSection, + codeSection, + dataSection, + dataCountSection, + customSections, + ignoredSections); } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/ModuleType.java b/wasm/src/main/java/com/dylibso/chicory/wasm/ModuleType.java deleted file mode 100644 index 91fb8718d..000000000 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/ModuleType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.dylibso.chicory.wasm; - -public enum ModuleType { - TEXT, - BINARY -} diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/Parser.java b/wasm/src/main/java/com/dylibso/chicory/wasm/Parser.java index af989d9c6..fc45354cc 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/Parser.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/Parser.java @@ -47,18 +47,23 @@ import com.dylibso.chicory.wasm.types.TypeSection; import com.dylibso.chicory.wasm.types.UnknownCustomSection; import com.dylibso.chicory.wasm.types.ValueType; +import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; /** * Parser for Web Assembly binaries. @@ -94,10 +99,11 @@ private ByteBuffer readByteBuffer(InputStream is) { } } - private void onSection(Module module, Section s) { + private static void onSection(Module.Builder module, Section s) { switch (s.sectionId()) { case SectionId.CUSTOM: - module.addCustomSection((CustomSection) s); + var customSection = (CustomSection) s; + module.addCustomSection(customSection.name(), customSection); break; case SectionId.TYPE: module.setTypeSection((TypeSection) s); @@ -141,17 +147,48 @@ private void onSection(Module module, Section s) { } } - public Module parseModule(InputStream in) { - Module module = new Module(); - try { - parse(in, (s) -> onSection(module, s)); + public static Module parse(InputStream input) { + return new Parser().parse(() -> input); + } + + public static Module parse(ByteBuffer buffer) { + return new Parser().parse(buffer.array()); + } + + public static Module parse(byte[] buffer) { + return new Parser().parse(() -> new ByteArrayInputStream(buffer)); + } + + public static Module parse(File file) { + return new Parser().parse(file.toPath()); + } + + public static Module parse(Path path) { + return new Parser() + .parse( + () -> { + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new IllegalArgumentException( + "Error opening file: " + path, e); + } + }); + } + + public Module parse(Supplier inputStreamSupplier) { + Module.Builder moduleBuilder = Module.builder(); + try (final InputStream is = inputStreamSupplier.get()) { + parse(is, (s) -> onSection(moduleBuilder, s)); + } catch (IOException e) { + throw new ChicoryException(e); } catch (MalformedException e) { throw new MalformedException( "section size mismatch, unexpected end of section or function, " + e.getMessage(), e); } - return module; + return moduleBuilder.build(); } private static int readInt(ByteBuffer buffer) { @@ -193,8 +230,7 @@ public void validateSectionType(byte sectionId) { } } - // package protected to make it visible for testing - void parse(InputStream in, ParserListener listener) { + public void parse(InputStream in, ParserListener listener) { requireNonNull(listener, "listener"); var validator = new SectionsValidator(); @@ -340,13 +376,15 @@ private CustomSection parseCustomSection( var bytes = new byte[(int) size]; readBytes(buffer, bytes); var parser = customParsers.get(name); - return parser == null ? new UnknownCustomSection(name, bytes) : parser.apply(bytes); + return parser == null + ? UnknownCustomSection.builder().withName(name).withBytes(bytes).build() + : parser.apply(bytes); } private static TypeSection parseTypeSection(ByteBuffer buffer) { var typeCount = readVarUInt32(buffer); - TypeSection typeSection = new TypeSection((int) typeCount); + TypeSection.Builder typeSection = TypeSection.builder(); // Parse individual types in the type section for (int i = 0; i < typeCount; i++) { @@ -382,13 +420,13 @@ private static TypeSection parseTypeSection(ByteBuffer buffer) { typeSection.addFunctionType(FunctionType.of(params, returns)); } - return typeSection; + return typeSection.build(); } private static ImportSection parseImportSection(ByteBuffer buffer) { var importCount = readVarUInt32(buffer); - ImportSection importSection = new ImportSection((int) importCount); + ImportSection.Builder importSection = ImportSection.builder(); // Parse individual imports in the import section for (int i = 0; i < importCount; i++) { @@ -459,13 +497,13 @@ private static ImportSection parseImportSection(ByteBuffer buffer) { } } - return importSection; + return importSection.build(); } private static FunctionSection parseFunctionSection(ByteBuffer buffer) { var functionCount = readVarUInt32(buffer); - FunctionSection functionSection = new FunctionSection((int) functionCount); + FunctionSection.Builder functionSection = FunctionSection.builder(); // Parse individual functions in the function section for (int i = 0; i < functionCount; i++) { @@ -473,13 +511,13 @@ private static FunctionSection parseFunctionSection(ByteBuffer buffer) { functionSection.addFunctionType((int) typeIndex); } - return functionSection; + return functionSection.build(); } private static TableSection parseTableSection(ByteBuffer buffer) { var tableCount = readVarUInt32(buffer); - TableSection tableSection = new TableSection((int) tableCount); + TableSection.Builder tableSection = TableSection.builder(); // Parse individual tables in the tables section for (int i = 0; i < tableCount; i++) { @@ -493,13 +531,13 @@ private static TableSection parseTableSection(ByteBuffer buffer) { tableSection.addTable(new Table(tableType, limits)); } - return tableSection; + return tableSection.build(); } private static MemorySection parseMemorySection(ByteBuffer buffer) { var memoryCount = readVarUInt32(buffer); - MemorySection memorySection = new MemorySection((int) memoryCount); + MemorySection.Builder memorySection = MemorySection.builder(); // Parse individual memories in the memory section for (int i = 0; i < memoryCount; i++) { @@ -507,7 +545,7 @@ private static MemorySection parseMemorySection(ByteBuffer buffer) { memorySection.addMemory(new Memory(limits)); } - return memorySection; + return memorySection.build(); } private static MemoryLimits parseMemoryLimits(ByteBuffer buffer) { @@ -529,7 +567,7 @@ private static MemoryLimits parseMemoryLimits(ByteBuffer buffer) { private static GlobalSection parseGlobalSection(ByteBuffer buffer) { var globalCount = readVarUInt32(buffer); - GlobalSection globalSection = new GlobalSection((int) globalCount); + GlobalSection.Builder globalSection = GlobalSection.builder(); // Parse individual globals for (int i = 0; i < globalCount; i++) { @@ -539,13 +577,13 @@ private static GlobalSection parseGlobalSection(ByteBuffer buffer) { globalSection.addGlobal(new Global(valueType, mutabilityType, List.of(init))); } - return globalSection; + return globalSection.build(); } private static ExportSection parseExportSection(ByteBuffer buffer) { var exportCount = readVarUInt32(buffer); - ExportSection exportSection = new ExportSection((int) exportCount); + ExportSection.Builder exportSection = ExportSection.builder(); // Parse individual functions in the function section for (int i = 0; i < exportCount; i++) { @@ -555,18 +593,18 @@ private static ExportSection parseExportSection(ByteBuffer buffer) { exportSection.addExport(new Export(name, index, exportType)); } - return exportSection; + return exportSection.build(); } private static StartSection parseStartSection(ByteBuffer buffer) { - return new StartSection(readVarUInt32(buffer)); + return StartSection.builder().setStartIndex(readVarUInt32(buffer)).build(); } private static ElementSection parseElementSection(ByteBuffer buffer, long sectionSize) { var initialPosition = buffer.position(); var elementCount = readVarUInt32(buffer); - ElementSection elementSection = new ElementSection((int) elementCount); + ElementSection.Builder elementSection = ElementSection.builder(); for (var i = 0; i < elementCount; i++) { elementSection.addElement(parseSingleElement(buffer)); @@ -575,7 +613,7 @@ private static ElementSection parseElementSection(ByteBuffer buffer, long sectio throw new MalformedException("section size mismatch"); } - return elementSection; + return elementSection.build(); } private static Element parseSingleElement(ByteBuffer buffer) { @@ -661,25 +699,6 @@ private static Element parseSingleElement(ByteBuffer buffer) { } } - private static long[] readFuncIndices(ByteBuffer buffer) { - var funcIndexCount = readVarUInt32(buffer); - var funcIndices = new long[(int) funcIndexCount]; - for (var j = 0; j < funcIndexCount; j++) { - funcIndices[j] = readVarUInt32(buffer); - } - return funcIndices; - } - - private static Instruction[][] readExprs(ByteBuffer buffer) { - var exprIndexCount = readVarUInt32(buffer); - var exprs = new Instruction[(int) exprIndexCount][]; - for (var j = 0; j < exprIndexCount; j++) { - var instr = parseExpression(buffer); - exprs[j] = instr; - } - return exprs; - } - private static List parseCodeSectionLocalTypes(ByteBuffer buffer) { var distinctTypesCount = readVarUInt32(buffer); var locals = new ArrayList(); @@ -702,7 +721,7 @@ private static CodeSection parseCodeSection(ByteBuffer buffer) { var funcBodyCount = readVarUInt32(buffer); var root = new ControlTree(); - var codeSection = new CodeSection((int) funcBodyCount); + var codeSection = CodeSection.builder(); // Parse individual function bodies in the code section for (int i = 0; i < funcBodyCount; i++) { @@ -853,13 +872,13 @@ private static CodeSection parseCodeSection(ByteBuffer buffer) { codeSection.addFunctionBody(functionBody); } - return codeSection; + return codeSection.build(); } private static DataSection parseDataSection(ByteBuffer buffer) { var dataSegmentCount = readVarUInt32(buffer); - DataSection dataSection = new DataSection((int) dataSegmentCount); + DataSection.Builder dataSection = DataSection.builder(); for (var i = 0; i < dataSegmentCount; i++) { var mode = readVarUInt32(buffer); @@ -883,12 +902,12 @@ private static DataSection parseDataSection(ByteBuffer buffer) { } } - return dataSection; + return dataSection.build(); } private static DataCountSection parseDataCountSection(ByteBuffer buffer) { var dataCount = readVarUInt32(buffer); - return new DataCountSection((int) dataCount); + return DataCountSection.builder().withDataCount((int) dataCount).build(); } private static Instruction parseInstruction(ByteBuffer buffer) { diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/CodeSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/CodeSection.java index 76c876dcd..93406a4d4 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/CodeSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/CodeSection.java @@ -1,31 +1,17 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class CodeSection extends Section { - private final ArrayList functionBodies; - private boolean requiresDataCount = false; - - /** - * Construct a new, empty section instance. - */ - public CodeSection() { - this(new ArrayList<>()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of functions to reserve space for - */ - public CodeSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } + private final List functionBodies; + private final boolean requiresDataCount; - private CodeSection(ArrayList functionBodies) { + private CodeSection(List functionBodies, boolean requiresDataCount) { super(SectionId.CODE); - this.functionBodies = functionBodies; + this.functionBodies = List.copyOf(functionBodies); + this.requiresDataCount = requiresDataCount; } public FunctionBody[] functionBodies() { @@ -40,24 +26,39 @@ public FunctionBody getFunctionBody(int idx) { return functionBodies.get(idx); } - /** - * Add a function body to this section. - * - * @param functionBody the function body to add to this section (must not be {@code null}) - * @return the index of the newly-added function body - */ - public int addFunctionBody(FunctionBody functionBody) { - Objects.requireNonNull(functionBody, "functionBody"); - int idx = functionBodies.size(); - functionBodies.add(functionBody); - return idx; + public boolean isRequiresDataCount() { + return requiresDataCount; } - public void setRequiresDataCount(boolean requiresDataCount) { - this.requiresDataCount = requiresDataCount; + public static Builder builder() { + return new Builder(); } - public boolean isRequiresDataCount() { - return requiresDataCount; + public static class Builder { + private List functionBodies = new ArrayList<>(); + private boolean requiresDataCount = false; + + private Builder() {} + + /** + * Add a function body to this section. + * + * @param functionBody the function body to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addFunctionBody(FunctionBody functionBody) { + Objects.requireNonNull(functionBody, "functionBody"); + functionBodies.add(functionBody); + return this; + } + + public Builder setRequiresDataCount(boolean requiresDataCount) { + this.requiresDataCount = requiresDataCount; + return this; + } + + public CodeSection build() { + return new CodeSection(functionBodies, requiresDataCount); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/DataCountSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/DataCountSection.java index 0e61545a4..292873946 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/DataCountSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/DataCountSection.java @@ -1,10 +1,9 @@ package com.dylibso.chicory.wasm.types; public class DataCountSection extends Section { - private final int dataCount; - public DataCountSection(int dataCount) { + private DataCountSection(int dataCount) { super(SectionId.DATA_COUNT); this.dataCount = dataCount; } @@ -12,4 +11,21 @@ public DataCountSection(int dataCount) { public int dataCount() { return dataCount; } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private int dataCount; + + public Builder withDataCount(int dataCount) { + this.dataCount = dataCount; + return this; + } + + public DataCountSection build() { + return new DataCountSection(dataCount); + } + } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/DataSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/DataSection.java index 0c160175e..75b7ca512 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/DataSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/DataSection.java @@ -1,30 +1,15 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class DataSection extends Section { - private final ArrayList dataSegments; + private final List dataSegments; - /** - * Construct a new, empty section instance. - */ - public DataSection() { - this(new ArrayList<>()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of data segments to reserve space for - */ - public DataSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private DataSection(ArrayList dataSegments) { + private DataSection(List dataSegments) { super(SectionId.DATA); - this.dataSegments = dataSegments; + this.dataSegments = List.copyOf(dataSegments); } public DataSegment[] dataSegments() { @@ -39,16 +24,29 @@ public DataSegment getDataSegment(int idx) { return dataSegments.get(idx); } - /** - * Add a data segment definition to this section. - * - * @param dataSegment the data segment to add to this section (must not be {@code null}) - * @return the index of the newly-added data segment - */ - public int addDataSegment(DataSegment dataSegment) { - Objects.requireNonNull(dataSegment, "dataSegment"); - int idx = dataSegments.size(); - dataSegments.add(dataSegment); - return idx; + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List dataSegments = new ArrayList<>(); + + private Builder() {} + + /** + * Add a data segment definition to this section. + * + * @param dataSegment the data segment to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addDataSegment(DataSegment dataSegment) { + Objects.requireNonNull(dataSegment, "dataSegment"); + dataSegments.add(dataSegment); + return this; + } + + public DataSection build() { + return new DataSection(dataSegments); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/ElementSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/ElementSection.java index 43b4a1a32..972a0a87a 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/ElementSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/ElementSection.java @@ -1,30 +1,15 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class ElementSection extends Section { - private final ArrayList elements; + private final List elements; - /** - * Construct a new, empty section instance. - */ - public ElementSection() { - this(new ArrayList<>()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of elements to reserve space for - */ - public ElementSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private ElementSection(ArrayList elements) { + private ElementSection(List elements) { super(SectionId.ELEMENT); - this.elements = elements; + this.elements = List.copyOf(elements); } public Element[] elements() { @@ -39,16 +24,27 @@ public Element getElement(int idx) { return elements.get(idx); } - /** - * Add an element definition to this section. - * - * @param element the element to add to this section (must not be {@code null}) - * @return the index of the newly-added element - */ - public int addElement(Element element) { - Objects.requireNonNull(element, "element"); - int idx = elements.size(); - elements.add(element); - return idx; + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List elements = new ArrayList<>(); + + /** + * Add an element definition to this section. + * + * @param element the element to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addElement(Element element) { + Objects.requireNonNull(element, "element"); + elements.add(element); + return this; + } + + public ElementSection build() { + return new ElementSection(elements); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/ExportSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/ExportSection.java index e6d8aa71a..08b3969e1 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/ExportSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/ExportSection.java @@ -1,30 +1,15 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class ExportSection extends Section { - private final ArrayList exports; + private final List exports; - /** - * Construct a new, empty section instance. - */ - public ExportSection() { - this(new ArrayList<>()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of exports to reserve space for - */ - public ExportSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private ExportSection(ArrayList exports) { + private ExportSection(List exports) { super(SectionId.EXPORT); - this.exports = exports; + this.exports = List.copyOf(exports); } public int exportCount() { @@ -35,16 +20,29 @@ public Export getExport(int idx) { return exports.get(idx); } - /** - * Add an export definition to this section. - * - * @param export the export to add to this section (must not be {@code null}) - * @return the index of the newly-added export - */ - public int addExport(Export export) { - Objects.requireNonNull(export, "export"); - int idx = exports.size(); - exports.add(export); - return idx; + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List exports = new ArrayList<>(); + + private Builder() {} + + /** + * Add an export definition to this section. + * + * @param export the export to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addExport(Export export) { + Objects.requireNonNull(export, "export"); + exports.add(export); + return this; + } + + public ExportSection build() { + return new ExportSection(exports); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/FunctionSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/FunctionSection.java index 4997fe5b6..ca4e1f629 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/FunctionSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/FunctionSection.java @@ -6,25 +6,9 @@ public class FunctionSection extends Section { private final List typeIndices; - /** - * Construct a new, empty section instance. - */ - public FunctionSection() { - this(new ArrayList()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of functions to reserve space for - */ - public FunctionSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private FunctionSection(ArrayList typeIndices) { + private FunctionSection(List typeIndices) { super(SectionId.FUNCTION); - this.typeIndices = typeIndices; + this.typeIndices = List.copyOf(typeIndices); } public int getFunctionType(int idx) { @@ -39,15 +23,28 @@ public FunctionType getFunctionType(int idx, TypeSection typeSection) { return typeSection.getType(getFunctionType(idx)); } - /** - * Add a function type index to this section. - * - * @param typeIndex the type index to add (should be a valid index into the type section) - * @return the index of the function whose type index was added - */ - public int addFunctionType(int typeIndex) { - int idx = typeIndices.size(); - typeIndices.add(typeIndex); - return idx; + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List typeIndices = new ArrayList<>(); + + private Builder() {} + + /** + * Add a function type index to this section. + * + * @param typeIndex the type index to add (should be a valid index into the type section) + * @return the Builder + */ + public Builder addFunctionType(int typeIndex) { + typeIndices.add(typeIndex); + return this; + } + + public FunctionSection build() { + return new FunctionSection(typeIndices); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/GlobalSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/GlobalSection.java index 204e79e87..ec6173da6 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/GlobalSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/GlobalSection.java @@ -1,30 +1,15 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class GlobalSection extends Section { - private final ArrayList globals; + private final List globals; - /** - * Construct a new, empty section instance. - */ - public GlobalSection() { - this(new ArrayList<>()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of globals to reserve space for - */ - public GlobalSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private GlobalSection(ArrayList globals) { + private GlobalSection(List globals) { super(SectionId.GLOBAL); - this.globals = globals; + this.globals = List.copyOf(globals); } public Global[] globals() { @@ -35,20 +20,29 @@ public int globalCount() { return globals.size(); } - public Global getGlobal(int idx) { - return globals.get(idx); + public static Builder builder() { + return new Builder(); } - /** - * Add a global variable definition to this section. - * - * @param global the global to add to this section (must not be {@code null}) - * @return the index of the newly-added global - */ - public int addGlobal(Global global) { - Objects.requireNonNull(global, "global"); - int idx = globals.size(); - globals.add(global); - return idx; + public static class Builder { + private List globals = new ArrayList<>(); + + private Builder() {} + + /** + * Add a global variable definition to this section. + * + * @param global the global to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addGlobal(Global global) { + Objects.requireNonNull(global, "global"); + globals.add(global); + return this; + } + + public GlobalSection build() { + return new GlobalSection(globals); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/ImportSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/ImportSection.java index e7f9e157d..df4f7398b 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/ImportSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/ImportSection.java @@ -1,31 +1,16 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.stream.Stream; public class ImportSection extends Section { - private final ArrayList imports; + private final List imports; - /** - * Construct a new, empty section instance. - */ - public ImportSection() { - this(new ArrayList<>()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of imports to reserve space for - */ - public ImportSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private ImportSection(ArrayList imports) { + private ImportSection(List imports) { super(SectionId.IMPORT); - this.imports = imports; + this.imports = List.copyOf(imports); } public int importCount() { @@ -44,16 +29,29 @@ public int count(ExternalType type) { return (int) imports.stream().filter(i -> i.importType() == type).count(); } - /** - * Add an import definition to this section. - * - * @param import_ the import to add to this section (must not be {@code null}) - * @return the index of the newly-added import - */ - public int addImport(Import import_) { - Objects.requireNonNull(import_, "import_"); - int idx = imports.size(); - imports.add(import_); - return idx; + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List imports = new ArrayList<>(); + + private Builder() {} + + /** + * Add an import definition to this section. + * + * @param import_ the import to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addImport(Import import_) { + Objects.requireNonNull(import_, "import_"); + imports.add(import_); + return this; + } + + public ImportSection build() { + return new ImportSection(imports); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/MemorySection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/MemorySection.java index 80d658f12..7683c3379 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/MemorySection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/MemorySection.java @@ -1,30 +1,15 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class MemorySection extends Section { - private final ArrayList memories; + private final List memories; - /** - * Construct a new, empty section instance. - */ - public MemorySection() { - this(1); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of memories to reserve space for - */ - public MemorySection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private MemorySection(ArrayList memories) { + private MemorySection(List memories) { super(SectionId.MEMORY); - this.memories = memories; + this.memories = List.copyOf(memories); } public int memoryCount() { @@ -35,16 +20,29 @@ public Memory getMemory(int idx) { return memories.get(idx); } - /** - * Add a memory definition to this section. - * - * @param memory the memory to add to this section (must not be {@code null}) - * @return the index of the newly-added memory - */ - public int addMemory(Memory memory) { - Objects.requireNonNull(memory, "memory"); - int idx = memories.size(); - memories.add(memory); - return idx; + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List memories = new ArrayList<>(); + + private Builder() {} + + /** + * Add a memory definition to this section. + * + * @param memory the memory to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addMemory(Memory memory) { + Objects.requireNonNull(memory, "memory"); + memories.add(memory); + return this; + } + + public MemorySection build() { + return new MemorySection(memories); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/NameCustomSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/NameCustomSection.java index d01d90733..95b9124ed 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/NameCustomSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/NameCustomSection.java @@ -1,10 +1,12 @@ package com.dylibso.chicory.wasm.types; +import static java.util.Objects.requireNonNull; + import com.dylibso.chicory.wasm.Parser; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.Objects; +import java.util.Optional; import java.util.function.ToIntFunction; /** @@ -12,7 +14,7 @@ */ public class NameCustomSection extends CustomSection { - private final String moduleName; + private final Optional moduleName; private final List funcNames; private final List> localNames; private final List> labelNames; @@ -26,8 +28,8 @@ public class NameCustomSection extends CustomSection { /** * Construct a section instance from the specified contents. */ - public NameCustomSection( - String moduleName, + private NameCustomSection( + Optional moduleName, List funcNames, List> localNames, List> labelNames, @@ -38,15 +40,15 @@ public NameCustomSection( List dataNames, List tagNames) { this.moduleName = moduleName; - this.funcNames = funcNames; - this.localNames = localNames; - this.labelNames = labelNames; - this.tableNames = tableNames; - this.memoryNames = memoryNames; - this.globalNames = globalNames; - this.elementNames = elementNames; - this.dataNames = dataNames; - this.tagNames = tagNames; + this.funcNames = List.copyOf(requireNonNull(funcNames)); + this.localNames = List.copyOf(requireNonNull(localNames)); + this.labelNames = List.copyOf(requireNonNull(labelNames)); + this.tableNames = List.copyOf(requireNonNull(tableNames)); + this.memoryNames = List.copyOf(requireNonNull(memoryNames)); + this.globalNames = List.copyOf(requireNonNull(globalNames)); + this.elementNames = List.copyOf(requireNonNull(elementNames)); + this.dataNames = List.copyOf(requireNonNull(dataNames)); + this.tagNames = List.copyOf(requireNonNull(tagNames)); } /** @@ -130,7 +132,7 @@ public static NameCustomSection parse(byte[] bytes) { } return new NameCustomSection( - moduleName, + Optional.ofNullable(moduleName), funcNames, localNames, labelNames, @@ -147,9 +149,9 @@ public String name() { } /** - * @return the module name, or null if none is set + * @return the optional module name */ - public String moduleName() { + public Optional moduleName() { return moduleName; } @@ -226,107 +228,6 @@ public String nameOfTag(int tagIdx) { return oneLevelSearch(tagNames, tagIdx); } - /** - * Add a function name to this section. - * - * @param functionIdx the index of the function to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addFunctionName(int functionIdx, String name) { - return oneLevelStore(funcNames, functionIdx, name); - } - - /** - * Add a local name to this section. - * - * @param functionIdx the index of the function containing the local - * @param localIdx the index of the local to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addLocalName(int functionIdx, int localIdx, String name) { - return twoLevelStore(localNames, functionIdx, localIdx, name); - } - - /** - * Add a label name to this section. - * - * @param functionIdx the index of the function containing the label - * @param labelIdx the index of the label to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addLabelName(int functionIdx, int labelIdx, String name) { - return twoLevelStore(labelNames, functionIdx, labelIdx, name); - } - - /** - * Add a table name to this section. - * - * @param tableIdx the index of the table to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addTableName(int tableIdx, String name) { - return oneLevelStore(funcNames, tableIdx, name); - } - - /** - * Add a memory name to this section. - * - * @param memoryIdx the index of the memory to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addMemoryName(int memoryIdx, String name) { - return oneLevelStore(funcNames, memoryIdx, name); - } - - /** - * Add a global name to this section. - * - * @param globalIdx the index of the global to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addGlobalName(int globalIdx, String name) { - return oneLevelStore(funcNames, globalIdx, name); - } - - /** - * Add an element name to this section. - * - * @param elementIdx the index of the element to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addElementName(int elementIdx, String name) { - return oneLevelStore(funcNames, elementIdx, name); - } - - /** - * Add a data segment name to this section. - * - * @param dataIdx the index of the data segment to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addDataName(int dataIdx, String name) { - return oneLevelStore(funcNames, dataIdx, name); - } - - /** - * Add a tag name to this section. - * - * @param tagIdx the index of the tag to name - * @param name the new name (must not be {@code null}) - * @return the previously set name, or {@code null} if there was none - */ - public String addTagName(int tagIdx, String name) { - return oneLevelStore(funcNames, tagIdx, name); - } - // parsing helpers private static void oneLevelParse(final ByteBuffer slice, final List list) { @@ -380,7 +281,7 @@ private static String twoLevelSearch( } private static String oneLevelStore(List list, int storeIdx, String name) { - Objects.requireNonNull(name); + requireNonNull(name); int idx = binarySearch(list, storeIdx, NameEntry::index); if (idx < 0) { // insert @@ -394,7 +295,7 @@ private static String oneLevelStore(List list, int storeIdx, String n private static String twoLevelStore( List> listList, int groupIdx, int subIdx, String name) { - Objects.requireNonNull(name); + requireNonNull(name); int fi = binarySearch(listList, groupIdx, ListEntry::index); ListEntry subList; if (fi < 0) { diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/StartSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/StartSection.java index 6696117c7..fa1213c7d 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/StartSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/StartSection.java @@ -3,7 +3,7 @@ public class StartSection extends Section { private final long startIndex; - public StartSection(long startIndex) { + private StartSection(long startIndex) { super(SectionId.START); this.startIndex = startIndex; } @@ -11,4 +11,23 @@ public StartSection(long startIndex) { public long startIndex() { return startIndex; } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private long startIndex; + + private Builder() {} + + public Builder setStartIndex(long startIndex) { + this.startIndex = startIndex; + return this; + } + + public StartSection build() { + return new StartSection(startIndex); + } + } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/TableSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/TableSection.java index 327be6c5d..1c1f5e9a1 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/TableSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/TableSection.java @@ -1,30 +1,15 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class TableSection extends Section { - private final ArrayList tables; + private final List
tables; - /** - * Construct a new, empty section instance. - */ - public TableSection() { - this(new ArrayList<>()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of functions to reserve space for - */ - public TableSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private TableSection(ArrayList
tables) { + private TableSection(List
tables) { super(SectionId.TABLE); - this.tables = tables; + this.tables = List.copyOf(tables); } public int tableCount() { @@ -35,16 +20,29 @@ public Table getTable(int idx) { return tables.get(idx); } - /** - * Add a table definition to this section. - * - * @param table the table to add to this section (must not be {@code null}) - * @return the index of the newly-added table - */ - public int addTable(Table table) { - Objects.requireNonNull(table, "table"); - int idx = tables.size(); - tables.add(table); - return idx; + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List
tables = new ArrayList<>(); + + private Builder() {} + + /** + * Add a table definition to this section. + * + * @param table the table to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addTable(Table table) { + Objects.requireNonNull(table, "table"); + tables.add(table); + return this; + } + + public TableSection build() { + return new TableSection(tables); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/TypeSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/TypeSection.java index 539a970e4..2d9de4ba5 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/TypeSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/TypeSection.java @@ -1,30 +1,15 @@ package com.dylibso.chicory.wasm.types; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class TypeSection extends Section { - private final ArrayList types; + private final List types; - /** - * Construct a new, empty section instance. - */ - public TypeSection() { - this(new ArrayList<>()); - } - - /** - * Construct a new, empty section instance. - * - * @param estimatedSize the estimated number of types to reserve space for - */ - public TypeSection(int estimatedSize) { - this(new ArrayList<>(estimatedSize)); - } - - private TypeSection(ArrayList types) { + private TypeSection(List types) { super(SectionId.TYPE); - this.types = types; + this.types = List.copyOf(types); } public FunctionType[] types() { @@ -39,16 +24,29 @@ public FunctionType getType(int idx) { return types.get(idx); } - /** - * Add a function type definition to this section. - * - * @param functionType the function type to add to this section (must not be {@code null}) - * @return the index of the newly-added function type - */ - public int addFunctionType(FunctionType functionType) { - Objects.requireNonNull(functionType, "functionType"); - int idx = types.size(); - types.add(functionType); - return idx; + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List types = new ArrayList<>(); + + private Builder() {} + + /** + * Add a function type definition to this section. + * + * @param functionType the function type to add to this section (must not be {@code null}) + * @return the Builder + */ + public Builder addFunctionType(FunctionType functionType) { + Objects.requireNonNull(functionType, "functionType"); + types.add(functionType); + return this; + } + + public TypeSection build() { + return new TypeSection(types); + } } } diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/UnknownCustomSection.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/UnknownCustomSection.java index 85314d88f..42ab8987e 100644 --- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/UnknownCustomSection.java +++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/UnknownCustomSection.java @@ -15,7 +15,7 @@ public final class UnknownCustomSection extends CustomSection { * @param name the name of the section (must not be {@code null}) * @param bytes the section contents (must not be {@code null}) */ - public UnknownCustomSection(final String name, final byte[] bytes) { + private UnknownCustomSection(final String name, final byte[] bytes) { super(); this.name = Objects.requireNonNull(name, "name"); this.bytes = bytes.clone(); @@ -28,4 +28,29 @@ public String name() { public byte[] bytes() { return bytes; } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String name; + private byte[] bytes; + + private Builder() {} + + public Builder withName(String name) { + this.name = name; + return this; + } + + public Builder withBytes(byte[] bytes) { + this.bytes = bytes; + return this; + } + + public UnknownCustomSection build() { + return new UnknownCustomSection(name, bytes); + } + } } diff --git a/wasm/src/test/java/com/dylibso/chicory/wasm/ParserTest.java b/wasm/src/test/java/com/dylibso/chicory/wasm/ParserTest.java index 4ec56b22f..dddb692bc 100644 --- a/wasm/src/test/java/com/dylibso/chicory/wasm/ParserTest.java +++ b/wasm/src/test/java/com/dylibso/chicory/wasm/ParserTest.java @@ -27,10 +27,8 @@ public class ParserTest { @Test public void shouldParseFile() throws IOException { - var parser = new Parser(); - try (InputStream is = getClass().getResourceAsStream("/compiled/start.wat.wasm")) { - var module = parser.parseModule(is); + var module = Parser.parse(is); // check types section var typeSection = module.typeSection(); @@ -56,7 +54,7 @@ public void shouldParseFile() throws IOException { // check start section var startSection = module.startSection(); - assertEquals(1, startSection.startIndex()); + assertEquals(1, startSection.get().startIndex()); // check function section var funcSection = module.functionSection(); @@ -70,9 +68,9 @@ public void shouldParseFile() throws IOException { // check memory section var memorySection = module.memorySection(); - assertEquals(1, memorySection.memoryCount()); - assertEquals(1, memorySection.getMemory(0).memoryLimits().initialPages()); - assertEquals(65536, memorySection.getMemory(0).memoryLimits().maximumPages()); + assertEquals(1, memorySection.get().memoryCount()); + assertEquals(1, memorySection.get().getMemory(0).memoryLimits().initialPages()); + assertEquals(65536, memorySection.get().getMemory(0).memoryLimits().maximumPages()); var codeSection = module.codeSection(); assertEquals(1, codeSection.functionBodyCount()); @@ -92,10 +90,8 @@ public void shouldParseFile() throws IOException { @Test public void shouldParseIterfact() throws IOException { - var parser = new Parser(); - try (InputStream is = getClass().getResourceAsStream("/compiled/iterfact.wat.wasm")) { - var module = parser.parseModule(is); + var module = Parser.parse(is); // check types section var typeSection = module.typeSection(); @@ -134,9 +130,8 @@ public void shouldParseAllFiles() { } for (var f : files) { - var parser = new Parser(); try (InputStream is = new FileInputStream(f)) { - parser.parseModule(is); + Parser.parse(is); } catch (Exception e) { throw new RuntimeException(String.format("Failed to parse file %s", f), e); } @@ -165,10 +160,8 @@ public void shouldSupportCustomListener() throws IOException { @Test public void shouldParseFloats() throws IOException { - var parser = new Parser(); - try (InputStream is = getClass().getResourceAsStream("/compiled/float.wat.wasm")) { - var module = parser.parseModule(is); + var module = Parser.parse(is); var codeSection = module.codeSection(); var fbody = codeSection.getFunctionBody(0); var f32 = Float.intBitsToFloat((int) fbody.instructions().get(0).operands()[0]); @@ -180,10 +173,8 @@ public void shouldParseFloats() throws IOException { @Test public void shouldProperlyParseSignedValue() throws IOException { - var parser = new Parser(); - try (InputStream is = getClass().getResourceAsStream("/compiled/i32.wat.wasm")) { - var module = parser.parseModule(is); + var module = Parser.parse(is); var codeSection = module.codeSection(); var fbody = codeSection.getFunctionBody(0); assertEquals(-2147483648L, fbody.instructions().get(0).operands()[0]); @@ -205,10 +196,8 @@ public void shouldProperlyParseSignedValue() throws IOException { @Test public void shouldParseLocalDefinitions() throws Exception { - var parser = new Parser(); - try (InputStream is = getClass().getResourceAsStream("/compiled/define-locals.wat.wasm")) { - var module = parser.parseModule(is); + var module = Parser.parse(is); var codeSection = module.codeSection(); var fbody = codeSection.getFunctionBody(0); assertEquals(fbody.localTypes().get(0), ValueType.I32); @@ -218,10 +207,8 @@ public void shouldParseLocalDefinitions() throws Exception { @Test public void shouldParseNamesSection() throws IOException { - var parser = new Parser(); - try (InputStream is = getClass().getResourceAsStream("/compiled/count_vowels.rs.wasm")) { - var module = parser.parseModule(is); + var module = Parser.parse(is); var nameSec = module.nameSection(); assertEquals(module.codeSection().functionBodyCount(), nameSec.functionNameCount()); assertEquals("__stack_pointer", nameSec.nameOfGlobal(0));