diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/ByteArrayMemory.java b/runtime/src/main/java/com/dylibso/chicory/runtime/ByteArrayMemory.java index 819b8005f..1c21353e2 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/ByteArrayMemory.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/ByteArrayMemory.java @@ -352,4 +352,15 @@ public void fill(byte value, int fromIndex, int toIndex) { public void drop(int segment) { dataSegments[segment] = PassiveDataSegment.EMPTY; } + + @Override + public ByteArrayMemory copy() { + ByteArrayMemory copy = new ByteArrayMemory(this.limits, this.allocStrategy); + copy.nPages = this.nPages; + copy.buffer = this.buffer.clone(); + if (this.dataSegments != null) { + copy.dataSegments = this.dataSegments.clone(); + } + return copy; + } } diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/ByteBufferMemory.java b/runtime/src/main/java/com/dylibso/chicory/runtime/ByteBufferMemory.java index c9753ee0c..5ccf1ab4b 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/ByteBufferMemory.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/ByteBufferMemory.java @@ -346,4 +346,23 @@ public void fill(byte value, int fromIndex, int toIndex) { public void drop(int segment) { dataSegments[segment] = PassiveDataSegment.EMPTY; } + + @Override + public ByteBufferMemory copy() { + ByteBufferMemory copy = new ByteBufferMemory(this.limits, this.allocStrategy); + copy.nPages = this.nPages; + + // Copy the ByteBuffer content + copy.buffer = ByteBuffer.allocate(this.buffer.capacity()).order(ByteOrder.LITTLE_ENDIAN); + int originalPosition = this.buffer.position(); + this.buffer.position(0); + copy.buffer.put(this.buffer); + this.buffer.position(originalPosition); + copy.buffer.position(originalPosition); + + if (this.dataSegments != null) { + copy.dataSegments = this.dataSegments.clone(); + } + return copy; + } } diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/GlobalInstance.java b/runtime/src/main/java/com/dylibso/chicory/runtime/GlobalInstance.java index 548f8183f..9138218e8 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/GlobalInstance.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/GlobalInstance.java @@ -88,4 +88,13 @@ public void setInstance(Instance instance) { public MutabilityType getMutabilityType() { return mutabilityType; } + + public GlobalInstance copy() { + GlobalInstance copy = + new GlobalInstance( + this.valueLow, this.valueHigh, this.valType, this.mutabilityType); + // The instance field will be set by the calling code + copy.instance = null; + return copy; + } } diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/ImportValues.java b/runtime/src/main/java/com/dylibso/chicory/runtime/ImportValues.java index 1b00f0948..a424c8e71 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/ImportValues.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/ImportValues.java @@ -191,4 +191,11 @@ public ImportValues build() { return importValues; } } + + public ImportValues copy() { + // Create a new instance using the existing constructor + // which already clones the arrays properly + return new ImportValues( + this.functions, this.globals, this.memories, this.tables, this.tags); + } } 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 693ec5324..e5ae47aa2 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java @@ -10,6 +10,7 @@ import static java.util.Objects.requireNonNullElse; import static java.util.Objects.requireNonNullElseGet; +import com.dylibso.chicory.runtime.internal.ExperimentalCopier; import com.dylibso.chicory.wasm.ChicoryException; import com.dylibso.chicory.wasm.InvalidException; import com.dylibso.chicory.wasm.UninstantiableException; @@ -44,30 +45,34 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import java.util.stream.Collectors; public class Instance { public static final String START_FUNCTION_NAME = "_start"; - - private final WasmModule module; - private final Machine machine; - private final FunctionBody[] functions; - private final Memory memory; - private final DataSegment[] dataSegments; - private final Global[] globalInitializers; - private final GlobalInstance[] globals; - private final FunctionType[] types; - private final int[] functionTypes; - private final ImportValues imports; - private final TableInstance[] tables; - private final Element[] elements; - private final TagInstance[] tags; - private final Map exports; - private final ExecutionListener listener; - private final Exports fluentExports; - - private final Map exnRefs; + private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(1); + + private final long id = NEXT_INSTANCE_ID.getAndIncrement(); + protected final WasmModule module; + protected final Machine machine; + protected final FunctionBody[] functions; + protected final Memory memory; + protected final DataSegment[] dataSegments; + protected final Global[] globalInitializers; + protected final GlobalInstance[] globals; + protected final FunctionType[] types; + protected final int[] functionTypes; + protected final ImportValues imports; + protected final TableInstance[] tables; + protected final Element[] elements; + protected final TagInstance[] tags; + protected final Map exports; + protected final ExecutionListener listener; + protected final Exports fluentExports; + + protected final Map exnRefs; + protected final Function machineFactory; Instance( WasmModule module, @@ -95,6 +100,7 @@ public class Instance { this.types = types.clone(); this.functionTypes = functionTypes.clone(); this.imports = imports; + this.machineFactory = machineFactory; this.machine = machineFactory.apply(this); this.tables = new TableInstance[tables.length]; this.elements = elements.clone(); @@ -119,6 +125,76 @@ public class Instance { } } + protected Instance(Instance original, ExperimentalCopier.Options options) { + + this.imports = options.imports() != null ? options.imports() : original.imports.copy(); + this.listener = options.listener() != null ? options.listener() : original.listener; + + this.module = original.module; + this.globalInitializers = original.globalInitializers.clone(); + this.functions = original.functions.clone(); + this.memory = (original.memory != null) ? original.memory.copy() : null; + this.dataSegments = original.dataSegments.clone(); + this.types = original.types.clone(); + this.functionTypes = original.functionTypes.clone(); + this.elements = original.elements.clone(); + this.exports = original.exports; + this.fluentExports = new Exports(this); + + // Copy the current state of tags + this.tags = new TagInstance[original.tags.length]; + for (int i = 0; i < original.tags.length; i++) { + TagType type = original.tags[i].tagType(); + this.tags[i] = new TagInstance(type); + this.tags[i].setType(types[(type.typeIdx())]); + } + + // Copy the current state of globals + this.globals = new GlobalInstance[original.globals.length]; + for (int i = 0; i < original.globals.length; i++) { + if (original.globals[i] != null) { + this.globals[i] = original.globals[i].copy(); + this.globals[i].setInstance(this); + } + } + + // Copy the current state of tables and update instance references + this.tables = new TableInstance[original.tables.length]; + for (int i = 0; i < original.tables.length; i++) { + if (original.tables[i] != null) { + this.tables[i] = original.tables[i].copy(); + // Update Instance references in the table to point to the copied instance + for (int j = 0; j < this.tables[i].size(); j++) { + if (this.tables[i].instance(j) == original) { + this.tables[i].setRef(j, this.tables[i].ref(j), this); + } + } + } + } + + // Copy the current state of tags + for (int i = 0; i < original.tags.length; i++) { + if (original.tags[i] != null) { + this.tags[i] = original.tags[i].copy(); + } + } + + // Copy exception references + this.exnRefs = new HashMap<>(); + for (var entry : original.exnRefs.entrySet()) { + WasmException value = entry.getValue(); + if (value.instance() == original) { + // If the exception is from the original instance, copy it + value = new WasmException(this, value.tagIdx(), value.args()); + } + this.exnRefs.put(entry.getKey(), value); + } + + // Create a new machine instance using the original's machine factory + this.machineFactory = original.machineFactory; + this.machine = this.machineFactory.apply(this); + } + public Instance initialize(boolean start) { for (var el : elements) { if (el instanceof ActiveElement) { @@ -182,6 +258,10 @@ public Instance initialize(boolean start) { return this; } + public long id() { + return id; + } + public FunctionType exportType(String name) { return type(functionType(exports.get(name).index())); } @@ -384,7 +464,8 @@ public Builder withMemoryFactory(Function memoryFactory) { } /* - * This method is experimental and might be dropped without notice in future releases. + * This method is experimental and might be dropped without notice in future + * releases. */ public Builder withUnsafeExecutionListener(ExecutionListener listener) { this.listener = listener; @@ -527,7 +608,8 @@ private void validateHostMemoryType(MemoryImport i, ImportMemory m) { ? Memory.RUNTIME_MAX_PAGES : i.limits().maximumPages(); - // HostMem bounds [x,y] must be within the import bounds [a, b]; i.e., a <= x, y >= b. + // HostMem bounds [x,y] must be within the import bounds [a, b]; i.e., a <= x, y + // >= b. // In other words, the bounds are not valid when: // - HostMem current number of pages cannot be less than the import lower bound. // - HostMem upper bound cannot be larger than the given upper bound. diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/Memory.java b/runtime/src/main/java/com/dylibso/chicory/runtime/Memory.java index 59fa3956a..40d329b1d 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/Memory.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/Memory.java @@ -140,5 +140,7 @@ default void copy(int dest, int src, int size) { write(dest, readBytes(src, size)); } + Memory copy(); + void drop(int segment); } diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/TableInstance.java b/runtime/src/main/java/com/dylibso/chicory/runtime/TableInstance.java index ca54b252b..e437cfc91 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/TableInstance.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/TableInstance.java @@ -82,4 +82,14 @@ public void reset() { this.refs[i] = REF_NULL_VALUE; } } + + public TableInstance copy() { + TableInstance copy = new TableInstance(this.table, this.refs.length > 0 ? this.refs[0] : 0); + // Deep copy the mutable arrays + copy.instances = this.instances.clone(); + copy.refs = this.refs.clone(); + // Note: The Instance references in the instances array will need to be + // updated by the calling code to point to the copied instances + return copy; + } } diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/TagInstance.java b/runtime/src/main/java/com/dylibso/chicory/runtime/TagInstance.java index bd7188e1b..74a6d5658 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/TagInstance.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/TagInstance.java @@ -28,4 +28,8 @@ public void setType(FunctionType type) { public FunctionType type() { return type; } + + public TagInstance copy() { + return new TagInstance(this.tag, this.type); + } } diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/internal/ExperimentalCopier.java b/runtime/src/main/java/com/dylibso/chicory/runtime/internal/ExperimentalCopier.java new file mode 100644 index 000000000..3a93243e6 --- /dev/null +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/internal/ExperimentalCopier.java @@ -0,0 +1,88 @@ +package com.dylibso.chicory.runtime.internal; + +import com.dylibso.chicory.runtime.ExecutionListener; +import com.dylibso.chicory.runtime.ImportValues; +import com.dylibso.chicory.runtime.Instance; + +/** + * This class is experimental and may change in future releases. + * It provides a way to copy an Instance with options for execution listeners and import values. + * Copying an Instance is dangerous and should be done with caution. Potential issues include: + *
    + *
  • Linking breaks.
  • + *
  • Stateful host functions break: therefore it also breaks WASI. + *
+ */ +public final class ExperimentalCopier { + + private ExperimentalCopier() { + // Prevent instantiation + } + + /** + * Options for copying Instances. + * This class allows setting an execution listener and import values for the copy operation. + */ + public static class Options { + private ExecutionListener listener; + private ImportValues imports; + + /** + * Set this if you want to replace the execution listener set on the original Instance. + */ + public Options withListener(ExecutionListener listener) { + this.listener = listener; + return this; + } + + /** + * Set this if you want to replace the imports used on the original Instance. + * + * @param imports The import values to use when copying the Instance. + * @return This Options instance for method chaining. + */ + public Options withImports(ImportValues imports) { + this.imports = imports; + return this; + } + + public ExecutionListener listener() { + return listener; + } + + public ImportValues imports() { + return imports; + } + } + + /** + * Creates a new Options instance for copying Instances. + * + * @return A new Options instance with default settings. + */ + public static Options options() { + return new Options(); + } + + /** + * Copies an Instance with default options. + * + * @param original The original Instance to copy. + * @return A new Instance that is a copy of the original. + */ + public static Instance copy(Instance original) { + return copy(original, options()); + } + + /** + * Copies an Instance with the specified options. + * + * @param original The original Instance to copy. + * @param options The options for copying, including execution listener and import values. + * @return A new Instance that is a copy of the original. + */ + public static Instance copy(Instance original, Options options) { + // We have to subclass Instance to access this protected constructor + return new Instance(original, options) {}; + } +} diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/internal/ExperimentalCopierTest.java b/runtime/src/test/java/com/dylibso/chicory/runtime/internal/ExperimentalCopierTest.java new file mode 100644 index 000000000..dc03ec784 --- /dev/null +++ b/runtime/src/test/java/com/dylibso/chicory/runtime/internal/ExperimentalCopierTest.java @@ -0,0 +1,278 @@ +package com.dylibso.chicory.runtime.internal; + +import static com.dylibso.chicory.runtime.internal.ExperimentalCopier.copy; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import com.dylibso.chicory.runtime.GlobalInstance; +import com.dylibso.chicory.runtime.ImportGlobal; +import com.dylibso.chicory.runtime.ImportValues; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.runtime.WasmException; +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.WasmModule; +import com.dylibso.chicory.wasm.types.Value; +import org.junit.jupiter.api.Test; + +/** + * Tests for the {@link ExperimentalCopier} functionality. + * This class verifies that copying an instance creates a new instance with shared immutable modules, + * independent globals, memory, and tables. + */ +public class ExperimentalCopierTest { + + private static WasmModule loadModule(String fileName) { + return Parser.parse(ExperimentalCopierTest.class.getResourceAsStream("/" + fileName)); + } + + @Test + public void copyCreatesNewInstance() { + var module = loadModule("compiled/exports.wat.wasm"); + var originalInstance = Instance.builder(module).build(); + + var copiedInstance = copy(originalInstance); + + assertNotNull(copiedInstance); + assertNotSame(originalInstance, copiedInstance); + } + + @Test + public void copySharesImmutableModule() { + var module = loadModule("compiled/exports.wat.wasm"); + var originalInstance = Instance.builder(module).build(); + + var copiedInstance = copy(originalInstance); + + // Module should be shared (immutable) + assertSame(originalInstance.module(), copiedInstance.module()); + } + + @Test + public void copyCreatesIndependentGlobals() { + var module = loadModule("compiled/global-counter.wat.wasm"); + var originalInstance = Instance.builder(module).build(); + + // Test initial state + var initialValue = (int) originalInstance.export("get").apply()[0]; + assertEquals(0, initialValue); + + // Set a value in the original + originalInstance.export("set").apply(42); + assertEquals(42, (int) originalInstance.export("get").apply()[0]); + + // Create copy - should have same state + var copiedInstance = copy(originalInstance); + assertEquals(42, (int) copiedInstance.export("get").apply()[0]); + + // Modify original - copy should be unaffected + originalInstance.export("increment").apply(); // Now 43 + assertEquals(43, (int) originalInstance.export("get").apply()[0]); + assertEquals(42, (int) copiedInstance.export("get").apply()[0]); // Still 42 + + // Modify copy - original should be unaffected + copiedInstance.export("add").apply(10); // Now 52 + assertEquals(43, (int) originalInstance.export("get").apply()[0]); // Still 43 + assertEquals(52, (int) copiedInstance.export("get").apply()[0]); // Now 52 + } + + @Test + public void copyPreservesGlobalIndependence() { + var module = loadModule("compiled/global-counter.wat.wasm"); + var originalInstance = Instance.builder(module).build(); + + // Set different values and test independence + originalInstance.export("set").apply(100); + var copiedInstance = copy(originalInstance); + + // Both should start with same value + assertEquals(100, (int) originalInstance.export("get").apply()[0]); + assertEquals(100, (int) copiedInstance.export("get").apply()[0]); + + // Decrement original, increment copy + originalInstance.export("decrement").apply(); // 99 + copiedInstance.export("increment").apply(); // 101 + + // Verify they're truly independent + assertEquals(99, (int) originalInstance.export("get").apply()[0]); + assertEquals(101, (int) copiedInstance.export("get").apply()[0]); + + // Direct global access should also show independence + var originalGlobal = originalInstance.global(0); + var copiedGlobal = copiedInstance.global(0); + + assertEquals(99, (int) originalGlobal.getValue()); + assertEquals(101, (int) copiedGlobal.getValue()); + + // Test further independence - set different values via exported functions + originalInstance.export("set").apply(777); + copiedInstance.export("set").apply(888); + + // Verify via exported functions + assertEquals(777, (int) originalInstance.export("get").apply()[0]); + assertEquals(888, (int) copiedInstance.export("get").apply()[0]); + + // And verify via direct global access + assertEquals(777, (int) originalGlobal.getValue()); + assertEquals(888, (int) copiedGlobal.getValue()); + } + + @Test + public void copyCreatesIndependentMemory() { + var module = loadModule("compiled/memory.wat.wasm"); + var originalInstance = Instance.builder(module).build(); + + var copiedInstance = copy(originalInstance); + + var originalMemory = originalInstance.memory(); + var copiedMemory = copiedInstance.memory(); + + if (originalMemory != null && copiedMemory != null) { + assertNotSame(originalMemory, copiedMemory); + + // Write to original memory + originalMemory.writeByte(0, (byte) 42); + + // Copied memory should be independent + assertNotEquals(42, copiedMemory.read(0)); + } + } + + @Test + public void copyCreatesIndependentTables() { + var module = loadModule("compiled/call_indirect-export.wat.wasm"); + var originalInstance = Instance.builder(module).build(); + + var copiedInstance = copy(originalInstance); + + // Test table independence if tables exist + try { + var originalTable = originalInstance.table(0); + var copiedTable = copiedInstance.table(0); + + assertNotSame(originalTable, copiedTable); + assertEquals(originalTable.size(), copiedTable.size()); + + // Test that table entries point to the correct instance + for (int i = 0; i < originalTable.size(); i++) { + var originalRef = originalTable.instance(i); + var copiedRef = copiedTable.instance(i); + + if (originalRef == originalInstance) { + assertEquals(copiedInstance, copiedRef); + } else if (originalRef != null) { + assertEquals(originalRef, copiedRef); + } + } + } catch (com.dylibso.chicory.wasm.InvalidException e) { + // Skip if no tables or index issues + } + } + + @Test + public void copyWithImportValues() { + var module = loadModule("compiled/add.wat.wasm"); + + var importValues = + ImportValues.builder() + .addGlobal( + new ImportGlobal( + "env", "test_global", new GlobalInstance(Value.i32(123)))) + .build(); + + var originalInstance = Instance.builder(module).withImportValues(importValues).build(); + + var copiedInstance = copy(originalInstance); + + assertNotNull(copiedInstance); + assertNotSame(originalInstance, copiedInstance); + assertNotSame(originalInstance.imports(), copiedInstance.imports()); + } + + @Test + public void copyPreservesExceptions() { + var module = loadModule("compiled/add.wat.wasm"); + var originalInstance = Instance.builder(module).build(); + + // Register an exception + var exception = new WasmException(originalInstance, 0, new long[] {}); + var exnId = originalInstance.registerException(exception); + + var copiedInstance = copy(originalInstance); + + // Exception should be preserved in copy + var copiedExn = copiedInstance.exn(exnId); + assertEquals(exception.tagIdx(), copiedExn.tagIdx()); + assertEquals(copiedInstance, copiedExn.instance()); + } + + @Test + public void copyWithoutInitialization() { + var module = loadModule("compiled/add.wat.wasm"); + var originalInstance = Instance.builder(module).withInitialize(false).build(); + + var copiedInstance = copy(originalInstance); + + assertNotNull(copiedInstance); + assertNotSame(originalInstance, copiedInstance); + } + + @Test + public void copyPreservesExports() { + var module = loadModule("compiled/exports.wat.wasm"); + var originalInstance = Instance.builder(module).build(); + + assertEquals(0, originalInstance.exports().memory("mem").pages()); + assertEquals(10, originalInstance.exports().table("tab").size()); + assertEquals(42, originalInstance.exports().global("glob1").getValue()); + assertArrayEquals(new long[] {42}, originalInstance.exports().function("get-1").apply()); + + var copiedInstance = copy(originalInstance); + + // Both instances should have same exports available + var originalExports = originalInstance.exports(); + var copiedExports = copiedInstance.exports(); + + assertNotNull(originalExports); + assertNotNull(copiedExports); + // Exports should be independent objects but serve the same functionality + + assertNotSame(originalExports, copiedExports); + assertEquals(0, copiedInstance.exports().memory("mem").pages()); + assertEquals(10, copiedInstance.exports().table("tab").size()); + assertEquals(42, copiedInstance.exports().global("glob1").getValue()); + assertArrayEquals(new long[] {42}, copiedInstance.exports().function("get-1").apply()); + } + + @Test + public void copyPreservesGlobalCounter() { + var module = loadModule("compiled/global-counter.wat.wasm"); + var original = Instance.builder(module).build(); + + // Test initial state + assertEquals(0, (int) original.export("get").apply()[0]); + + // Modify the counter + original.export("set").apply(42); + assertEquals(42, (int) original.export("get").apply()[0]); + + // Create copy + var copy = copy(original); + + // Verify copy has same state + assertEquals(42, (int) copy.export("get").apply()[0]); + + // Modify original - copy should be unaffected + original.export("increment").apply(); // Now 43 + assertEquals(43, (int) original.export("get").apply()[0]); + assertEquals(42, (int) copy.export("get").apply()[0]); // Still 42! + + // Modify copy - original should be unaffected + copy.export("add").apply(10); // Now 52 + assertEquals(43, (int) original.export("get").apply()[0]); // Still 43 + assertEquals(52, (int) copy.export("get").apply()[0]); // Now 52 + } +} diff --git a/wasm-corpus/Dockerfile b/wasm-corpus/Dockerfile index 7c9acce40..e842a5228 100644 --- a/wasm-corpus/Dockerfile +++ b/wasm-corpus/Dockerfile @@ -59,6 +59,9 @@ RUN curl -L -O https://github.com/tinygo-org/tinygo/releases/download/v0.30.0/ti ENV PATH="/opt/wabt/bin:${PATH}" +# Install wasm-tools +RUN cargo install wasm-tools + WORKDIR /usr/code ENTRYPOINT ["./compile.sh"] \ No newline at end of file diff --git a/wasm-corpus/src/main/resources/compiled/global-counter.wat.wasm b/wasm-corpus/src/main/resources/compiled/global-counter.wat.wasm new file mode 100644 index 000000000..58db7260b Binary files /dev/null and b/wasm-corpus/src/main/resources/compiled/global-counter.wat.wasm differ diff --git a/wasm-corpus/src/main/resources/wat/global-counter.wat b/wasm-corpus/src/main/resources/wat/global-counter.wat new file mode 100644 index 000000000..f565afb51 --- /dev/null +++ b/wasm-corpus/src/main/resources/wat/global-counter.wat @@ -0,0 +1,42 @@ +(module + ;; Mutable global counter starting at 0 + (global $counter (mut i32) (i32.const 0)) + + ;; Function to set the counter value + (func $set (export "set") (param i32) + local.get 0 + global.set $counter + ) + + ;; Function to get the counter value + (func $get (export "get") (result i32) + global.get $counter + ) + + ;; Function to increment the counter and return new value + (func $increment (export "increment") (result i32) + global.get $counter + i32.const 1 + i32.add + global.set $counter + global.get $counter + ) + + ;; Function to decrement the counter and return new value + (func $decrement (export "decrement") (result i32) + global.get $counter + i32.const 1 + i32.sub + global.set $counter + global.get $counter + ) + + ;; Function to add a value to the counter and return new value + (func $add (export "add") (param i32) (result i32) + global.get $counter + local.get 0 + i32.add + global.set $counter + global.get $counter + ) +) \ No newline at end of file