diff --git a/wasm/docs/user/GraalWasmAPIMigration.md b/wasm/docs/user/GraalWasmAPIMigration.md
new file mode 100644
index 000000000000..60ac4f6260ae
--- /dev/null
+++ b/wasm/docs/user/GraalWasmAPIMigration.md
@@ -0,0 +1,171 @@
+# GraalWasm Polyglot API Migration and Usage Guide
+
+In GraalWasm version 25.0, the GraalWasm Polyglot Embedding API has been changed to more closely align with the WebAssembly JS API and its usage patterns.
+This document outlines the main differences and serves as a migration guide.
+
+## Overview of the Differences between the Old and New API
+
+| Difference | **New API (since 25.0)** | **Old API (before 25.0)** |
+|------------------------------------|-----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
+| **Key Differences** | Explicit module instantiation. Exports are accessed via the `exports` member of the instance | Implicit module instantiation. Exports are direct members of the instance |
+| **Result of `context.eval(src)`** | Returns a compiled _module_ object (not yet instantiated) [^3] | Returns an already instantiated module _instance_ [^3] |
+| **Module Instantiation** | Explicitly instantiated by calling `newInstance()` on the _module_ returned by `context.eval` | Implicitly instantiated by `context.eval()` |
+| **Repeated Instantiation** | Same module can be instantiated multiple times per context | Modules can be instantiated only once per context (cached by name) |
+| **Import Linking** | Linking is explicit and eager, performed by `module.newInstance()` | Linking is implicit and lazy, occurs on first export access |
+| **Start Function Invocation** | Runs during `module.newInstance()` | Runs on first export access if not yet linked |
+| **Accessing Exports** | Via `instance.getMember("exports").getMember("exportName")` | Via `instance.getMember("exportName")` |
+| **Invoking a Function[^1]** | `instance.getMember("exports").invokeMember("functionName", ...args);`, or: | `instance.invokeMember("functionName", ...args)`, or: |
+| **Invoking a Function[^2]** | `Value fn = instance.getMember("exports").getMember("functionName"); fn.execute(...args)` | `Value fn = instance.getMember("functionName"); fn.execute(...args)` |
+| **Module Instance Members** | The `"exports"` object member | The exports of the module _instance_ |
+| **Importing Host Functions** | Supported via optional import object passed to `module.newInstance(importObject)` | Not supported |
+| **Cyclic Import Dependencies** | Not allowed | Allowed |
+
+[^1] Recommended approach for invoking a function once or only a few times: Simply use `exports.invokeMember("functionName", ...args)` (combines lookup and function call).
+[^2] Recommended approach for invoking a function repeatedly: First, use `Value function = exports.getMember("functionName");` to look up the function, then call it using `function.execute(...args)`.
+[^3] Note: To ease migration, you can set the option `wasm.EvalReturnsInstance=true` to revert to the old `Context.eval` behavior for the time being.
+
+## Module Instantiation
+
+Old approach ≤24.2 (implicit instantiation), or with `wasm.EvalReturnsInstance=true`[^3]:
+```java
+Source source = Source.newBuilder("wasm", ByteSequence.create(bytes), "example").build();
+
+// Evaluate the Source named "example", returns a module instance (cf. WebAssembly.Instance).
+Value instance = context.eval(source);
+```
+
+New approach ≥25.0 (explicit instantiation):
+```java
+Source source = Source.newBuilder("wasm", ByteSequence.create(bytes), "example").build();
+
+// Evaluate the Source named "example", returns a module object (cf. WebAssembly.Module).
+Value module = context.eval(source);
+
+// Instantiate the example module (optional: provide an import object).
+Value instance = module.newInstance();
+Value anotherInstance = module.newInstance();
+```
+
+## Accessing Exports
+
+Old approach ≤24.2 (no _exports_ member indirection):
+```java
+// Compile and instantiate the module.
+Value instance = context.eval(source);
+
+// Invoke an exported function.
+assert instance.invokeMember("factorial", 6).asInt() == 720;
+
+// Or if you need to call a function multiple times:
+Value factorial = instance.getMember("factorial");
+assert factorial.execute(7).asInt() == 5040;
+assert factorial.execute(8).asInt() == 40320;
+```
+
+New approach ≥25.0 (via _exports_ member indirection):
+```java
+// Compile and instantiate the module
+Value module = context.eval(source);
+Value instance = module.newInstance();
+
+// Get the exports member from the module instance.
+Value exports = instance.getMember("exports");
+
+// Invoke an exported function.
+assert exports.invokeMember("factorial", 6).asInt() == 720;
+
+// Or if you need to call a function multiple times:
+Value factorial = exports.getMember("factorial");
+assert factorial.execute(7).asInt() == 5040;
+assert factorial.execute(8).asInt() == 40320;
+```
+
+## Importing Host Functions
+
+Old approach ≤24.2: N/A
+
+New approach ≥25.0 (using importObject argument to `newInstance()`):
+```java
+// Evaluate the example Source, returns a module object (cf. WebAssembly.Module).
+Value module = context.eval(exampleSource);
+
+var imports = ProxyObject.fromMap(Map.of(
+ "system", ProxyObject.fromMap(Map.of(
+ "println",
+ (ProxyExecutable) (args) -> {
+ String text = (/* read string from memory */);
+ System.out.println(text);
+ return null; /* for void functions, simply return null */
+ }
+ ))
+ ));
+
+// Instantiate the example module with imports obtained from the `imports` object.
+Value instance = module.newInstance(imports);
+```
+
+
+## Comparison with the WebAssembly JS API
+
+| Aspect | **WebAssembly JS API** | **GraalWasm Polyglot API (since 25.0)** |
+|----------------------------|-------------------------------------------------------------------------|-------------------------------------------------------------|
+| **Compilation** | `WebAssembly.compile(buffer)` or `new WebAssembly.Module(buffer)` | `context.eval(source)` → returns a compiled *Module* object |
+| **Instantiation** | `WebAssembly.instantiate(module)` or `new WebAssembly.Instance(module)` | `module.newInstance()` |
+| **Optional Import Object** | Passed as second argument to `WebAssembly.instantiate(module, imports)` | Passed as first argument to `module.newInstance(imports)` |
+| **Exports Access** | `instance.exports.exportName` | `instance.getMember("exports").getMember("exportName")` |
+| **Function Invocation** | `instance.exports.mul(3, 14)` | `instance.getMember("exports").invokeMember("mul", 3, 14)` |
+
+### Examples
+
+**Compile, instantiate, and call a function of a wasm module:**
+
+JS:
+```js
+const arrayBuffer = /*...*/;
+const module = await WebAssembly.compile(arrayBuffer); // Compile
+const instance = await WebAssembly.instantiate(module); // Instantiate
+
+const exports = instance.exports;
+let theAnswer = exports.mul(3, 14);
+```
+
+Java (GraalWasm ≥25.0):
+```java
+Source source = Source.newBuilder("wasm", wasmBinary, "example").build();
+Value module = context.eval(source); // Compile
+Value instance = module.newInstance(); // Instantiate
+
+Value exports = instance.getMember("exports");
+int theAnswer = exports.invokeMember("mul", 3, 14).asInt();
+
+// or, alternatively:
+Value mul = exports.getMember("mul");
+assert mul.execute(6, 7).asInt() == mul.execute(7, 6).asInt();
+```
+
+**Instantiate a wasm module with an import object:**
+
+JS:
+```js
+const imports = {
+ console: {
+ log: (value) => console.log("The answer is: " + value),
+ },
+};
+
+const instance = await WebAssembly.instantiate(module, imports);
+instance.exports.callLog(42);
+```
+
+Java (GraalWasm ≥25.0):
+```java
+ProxyExecutable log = args -> {
+ System.out.println("The answer is: " + args[0].asInt());
+ return null;
+};
+var console = ProxyObject.fromMap(Map.of("log", log));
+var imports = ProxyObject.fromMap(Map.of("console", console));
+
+Value instance = module.newInstance(imports);
+instance.getMember("exports").invokeMember("callLog", 42);
+```
diff --git a/wasm/docs/user/README.md b/wasm/docs/user/README.md
index 2be896d05f53..4e3f53a7b061 100644
--- a/wasm/docs/user/README.md
+++ b/wasm/docs/user/README.md
@@ -15,37 +15,54 @@ A valid [Source](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Source
Here is one way you can build a WebAssembly `Source`:
```java
-Source source = Source.newBuilder("wasm", new File("example.wasm")).name("example").build();
+Source source = Source.newBuilder("wasm", new File("example.wasm")).build();
```
-When you evaluate a WebAssembly `Source`, the module is parsed, and validated, and a module instance is returned as the resulting value.
-The module instance can also later be retrieved from the top-level bindings.
-The name of the binding in the top-level scope is the same as the name of the `Source` that was evaluated.
+Or directly from a byte array:
```java
-// Evaluate the Source named "example".
+Source source = Source.newBuilder("wasm", ByteSequence.create(in.readAllBytes()), "example").build();
+```
+
+When you evaluate a WebAssembly `Source`, the module is parsed and validated, and a module object is returned as the result value.
+This module can then be instantiated using `module.`[`newInstance()`](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#newInstance(java.lang.Object...)).
+To access exported functions and bindings, first get the _exports_ member of the instance using `instance.getMember("exports")`, which returns an object containing all the exported members.
+You can then use `exports.getMember("bindingName")` to read an exported binding, and either `exports.getMember("functionName").execute(...args)` or `exports.invokeMember("functionName", ...args)` to call an exported function.
+
+```java
+// Evaluate the Source.
Value exampleModule = context.eval(source);
-// It is now accessible under the binding name "example".
-assert context.getBindings("wasm").getMember("example") == exampleModule;
+// Instantiate the example module (optional: provide an import object).
+Value exampleInstance = exampleModule.newInstance();
+// Get the exports of the module instance.
+Value exampleExports = exampleInstance.getMember("exports");
+// Invoke an exported function.
+assert exampleExports.invokeMember("factorial", 8).asInt() == 40320;
+// Get the value of an exported global.
+assert exampleExports.getMember("theAnswerToLife").asInt() == 42;
```
[Source names](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Source.html#getName()) are important in GraalWasm because they are also used to resolve module imports.
If a module tries to import a symbol from module `foo`, then GraalWasm looks for that symbol in the module whose `Source` was named `foo`.
-These imports are not resolved until when a WebAssembly module instance's members are accessed or executed for the first time in a given context.
+These imports are resolved from the arguments passed to `module.newInstance()` (either the import object or a module argument).
+When using the `wasm.EvalReturnsInstance=true` option, the imports are not resolved until the module instance's exports are accessed or executed for the first time.
### Module Instance Objects
-By evaluating WebAssembly modules through the Polyglot API, you get access to module instance objects.
-Module instance objects expose members for every symbol that was exported from the WebAssembly module.
+By calling [`newInstance()`](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#newInstance(java.lang.Object...)) on the WebAssembly module value returned by [`Context.eval(Source)`](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Context.html#eval(org.graalvm.polyglot.Source)), you instantiate the module and create new module instance objects.
+Module instance objects expose a read-only "exports" member, an object that contains a member for every symbol that was exported from the WebAssembly module.
You can get a list of all exported symbols using [getMemberKeys](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#getMemberKeys()), access individual exports using [getMember](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#getMember(java.lang.String)) and, in the case of mutable globals, use [putMember](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#putMember(java.lang.String,java.lang.Object)) to set their values.
+You can also call functions using [invokeMember](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#invokeMember(java.lang.String,java.lang.Object...)).
Here is how the various kinds of WebAssembly exports map to polyglot values:
* Functions
Functions are exported as executable values, which you can call using [execute](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#execute(java.lang.Object...)).
+ Alternatively, you can use [invokeMember](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#invokeMember(java.lang.String,java.lang.Object...)) on the _exports_ object.
Function arguments and return values are mapped between WebAssembly value types and polyglot values using the [type mapping](#type-mapping).
If a function returns multiple values, these are wrapped in an interop array.
+ Use [getArrayElement](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#getArrayElement(long)) to extract the individual return values.
* Globals
@@ -61,7 +78,7 @@ Here is how the various kinds of WebAssembly exports map to polyglot values:
* Tables
- Exported tables are opaque and cannot be queried or modified.
+ Exported tables are opaque and cannot be queried or modified currently.
## Type Mapping
diff --git a/wasm/src/org.graalvm.wasm.benchmark/src/org/graalvm/wasm/benchmark/MemoryFootprintBenchmarkRunner.java b/wasm/src/org.graalvm.wasm.benchmark/src/org/graalvm/wasm/benchmark/MemoryFootprintBenchmarkRunner.java
index 79a521c95cc1..ce9827055694 100644
--- a/wasm/src/org.graalvm.wasm.benchmark/src/org/graalvm/wasm/benchmark/MemoryFootprintBenchmarkRunner.java
+++ b/wasm/src/org.graalvm.wasm.benchmark/src/org/graalvm/wasm/benchmark/MemoryFootprintBenchmarkRunner.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -124,6 +124,7 @@ public static void main(String[] args) throws IOException, InterruptedException
contextBuilder.allowExperimentalOptions(true);
contextBuilder.option("wasm.Builtins", "go");
contextBuilder.option("wasm.MemoryOverheadMode", "true");
+ contextBuilder.option("wasm.EvalReturnsInstance", "true");
final List results = new ArrayList<>();
diff --git a/wasm/src/org.graalvm.wasm.benchmark/src/org/graalvm/wasm/benchmark/WasmBenchmarkSuiteBase.java b/wasm/src/org.graalvm.wasm.benchmark/src/org/graalvm/wasm/benchmark/WasmBenchmarkSuiteBase.java
index 6caf376a8b9a..aa336728f984 100644
--- a/wasm/src/org.graalvm.wasm.benchmark/src/org/graalvm/wasm/benchmark/WasmBenchmarkSuiteBase.java
+++ b/wasm/src/org.graalvm.wasm.benchmark/src/org/graalvm/wasm/benchmark/WasmBenchmarkSuiteBase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -87,6 +87,7 @@ public void setup() throws IOException, InterruptedException {
final Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID);
contextBuilder.option("wasm.Builtins", "testutil,env:emscripten,wasi_snapshot_preview1");
+ contextBuilder.option("wasm.EvalReturnsInstance", "true");
contextBuilder.allowExperimentalOptions(true);
if (!Objects.isNull(DISABLE_COMPILATION_FLAG)) {
contextBuilder.option("engine.Compilation", "false");
@@ -106,7 +107,7 @@ public void setup() throws IOException, InterruptedException {
sources.forEach(context::eval);
String mainModuleName = benchmarkCase.name();
- Value benchmarkModule = context.getBindings(WasmLanguage.ID).getMember(mainModuleName);
+ Value benchmarkModule = context.getBindings(WasmLanguage.ID).getMember(mainModuleName).getMember("exports");
Value benchmarkSetupOnce = benchmarkModule.getMember("benchmarkSetupOnce");
benchmarkSetupEach = benchmarkModule.getMember("benchmarkSetupEach");
benchmarkTeardownEach = benchmarkModule.getMember("benchmarkTeardownEach");
diff --git a/wasm/src/org.graalvm.wasm.launcher/src/org/graalvm/wasm/launcher/WasmLauncher.java b/wasm/src/org.graalvm.wasm.launcher/src/org/graalvm/wasm/launcher/WasmLauncher.java
index 0024e7f171da..f80d57ded266 100644
--- a/wasm/src/org.graalvm.wasm.launcher/src/org/graalvm/wasm/launcher/WasmLauncher.java
+++ b/wasm/src/org.graalvm.wasm.launcher/src/org/graalvm/wasm/launcher/WasmLauncher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -40,13 +40,6 @@
*/
package org.graalvm.wasm.launcher;
-import org.graalvm.launcher.AbstractLanguageLauncher;
-import org.graalvm.options.OptionCategory;
-import org.graalvm.polyglot.Context;
-import org.graalvm.polyglot.PolyglotException;
-import org.graalvm.polyglot.Source;
-import org.graalvm.polyglot.Value;
-
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -55,6 +48,13 @@
import java.util.Map;
import java.util.Set;
+import org.graalvm.launcher.AbstractLanguageLauncher;
+import org.graalvm.options.OptionCategory;
+import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.PolyglotException;
+import org.graalvm.polyglot.Source;
+import org.graalvm.polyglot.Value;
+
public class WasmLauncher extends AbstractLanguageLauncher {
private File file = null;
private VersionAction versionAction = VersionAction.None;
@@ -146,7 +146,7 @@ private int execute(Context.Builder contextBuilder) {
try (Context context = contextBuilder.build()) {
runVersionAction(versionAction, context.getEngine());
- Value mainModule = context.eval(Source.newBuilder(getLanguageId(), file).build());
+ Value mainModule = context.eval(Source.newBuilder(getLanguageId(), file).build()).newInstance();
Value entryPoint = detectEntryPoint(mainModule);
if (entryPoint == null) {
@@ -172,9 +172,10 @@ private Value detectEntryPoint(Value mainModule) {
if (customEntryPoint != null) {
return mainModule.getMember(customEntryPoint);
}
- Value candidate = mainModule.getMember("_start");
+ Value exports = mainModule.getMember("exports");
+ Value candidate = exports.getMember("_start");
if (candidate == null) {
- candidate = mainModule.getMember("_main");
+ candidate = exports.getMember("_main");
}
return candidate;
}
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java
index 9472fea99cc8..b52ae37f9fab 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/AbstractBinarySuite.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -66,8 +66,7 @@ protected static void runRuntimeTest(byte[] binary, Consumer op
try (Context context = contextBuilder.build()) {
Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(binary), "main");
Source source = sourceBuilder.build();
- context.eval(source);
- testCase.accept(context.getBindings(WasmLanguage.ID).getMember("main"));
+ testCase.accept(context.eval(source).newInstance().getMember("exports"));
}
}
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java
index 9a615f39af82..a608178c1401 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmFileSuite.java
@@ -165,11 +165,25 @@ private static boolean isPoorShell() {
return inCI() || inWindows();
}
- private static Value findMain(WasmStore wasmStore) {
- for (final WasmInstance instance : wasmStore.moduleInstances().values()) {
- final WasmFunctionInstance function = instance.inferEntryPoint();
+ private static WasmInstance toWasmInstance(Value instance) {
+ final WasmContext wasmContext = WasmContext.get(null);
+ return (WasmInstance) wasmContext.environment().asGuestValue(instance);
+ }
+
+ private static Value inferEntryPoint(Value moduleInstance) {
+ final WasmInstance wasmInstance = toWasmInstance(moduleInstance);
+ final WasmFunctionInstance function = wasmInstance.inferEntryPoint();
+ if (function != null) {
+ return Value.asValue(function);
+ }
+ return null;
+ }
+
+ private static Value findMain(List moduleInstances) {
+ for (final Value instance : moduleInstances) {
+ final Value function = inferEntryPoint(instance);
if (function != null) {
- return Value.asValue(function);
+ return function;
}
}
throw new AssertionFailedError("No start function exported.");
@@ -185,16 +199,18 @@ private void runInContext(WasmCase testCase, Context context, List sourc
// This is needed so that we can call WasmContext.getCurrent().
context.enter();
+ final List moduleInstances;
try {
- sources.forEach(context::eval);
+ moduleInstances = sources.stream().map(context::eval).toList();
} catch (PolyglotException e) {
validateThrown(testCase.data(), WasmCaseData.ErrorType.Validation, e);
return;
}
final WasmContext wasmContext = WasmContext.get(null);
- final var contextStore = wasmContext.contextStore();
- final Value mainFunction = findMain(contextStore);
+ final Value mainFunction = findMain(moduleInstances);
+ final List instanceList = moduleInstances.stream().map(i -> toWasmInstance(i)).toList();
+ final var contextStore = instanceList.get(0).store();
resetStatus(System.out, phaseIcon, phaseLabel);
@@ -211,7 +227,7 @@ private void runInContext(WasmCase testCase, Context context, List sourc
// If no exception is expected and the program returns with success exit status,
// then we check stdout.
if (e.isExit() && testCase.data().expectedErrorMessage() == null) {
- Assert.assertEquals("Program exited with non-zero return code.", e.getExitStatus(), 0);
+ Assert.assertEquals("Program exited with non-zero return code.", 0, e.getExitStatus());
WasmCase.validateResult(testCase.data().resultValidator(), null, testOut);
} else if (testCase.data().expectedErrorTime() == WasmCaseData.ErrorType.Validation) {
validateThrown(testCase.data(), WasmCaseData.ErrorType.Validation, e);
@@ -246,11 +262,10 @@ private void runInContext(WasmCase testCase, Context context, List sourc
contextStore.tables().table(j).reset();
}
}
- List instanceList = new ArrayList<>(contextStore.moduleInstances().values());
- instanceList.sort(Comparator.comparingInt(RuntimeState::startFunctionIndex));
- for (WasmInstance instance : instanceList) {
- if (!instance.isBuiltin()) {
- contextStore.reinitInstance(instance, reinitMemory);
+
+ for (WasmInstance instance : instanceList.stream().sorted(Comparator.comparingInt(RuntimeState::startFunctionIndex)).toList()) {
+ if (!instance.module().isBuiltin()) {
+ instance.store().reinitInstance(instance, reinitMemory);
}
}
@@ -391,7 +406,7 @@ private void runInContexts(WasmCase testCase, Context.Builder contextBuilder, Ar
interpreterIterations = Math.min(interpreterIterations, 1);
}
- context = contextBuilder.options(getInterpretedNoInline()).build();
+ context = contextBuilder.options(getInterpretedNoInline()).option("wasm.EvalReturnsInstance", "true").build();
runInContext(testCase, context, sources, interpreterIterations, PHASE_INTERPRETER_ICON, "interpreter", testOut);
// Run in synchronous compiled mode, with inlining turned off.
@@ -401,7 +416,7 @@ private void runInContexts(WasmCase testCase, Context.Builder contextBuilder, Ar
if (WasmTestOptions.COVERAGE_MODE) {
syncNoinlineIterations = Math.min(syncNoinlineIterations, 1);
}
- context = contextBuilder.options(getSyncCompiledNoInline()).build();
+ context = contextBuilder.options(getSyncCompiledNoInline()).option("wasm.EvalReturnsInstance", "true").build();
runInContext(testCase, context, sources, syncNoinlineIterations, PHASE_SYNC_NO_INLINE_ICON, "sync,no-inl", testOut);
// Run in synchronous compiled mode, with inlining turned on.
@@ -411,7 +426,7 @@ private void runInContexts(WasmCase testCase, Context.Builder contextBuilder, Ar
if (WasmTestOptions.COVERAGE_MODE) {
syncInlineIterations = Math.min(syncInlineIterations, 1);
}
- context = contextBuilder.options(getSyncCompiledWithInline()).build();
+ context = contextBuilder.options(getSyncCompiledWithInline()).option("wasm.EvalReturnsInstance", "true").build();
runInContext(testCase, context, sources, syncInlineIterations, PHASE_SYNC_INLINE_ICON, "sync,inl", testOut);
// Run with normal, asynchronous compilation.
@@ -419,7 +434,7 @@ private void runInContexts(WasmCase testCase, Context.Builder contextBuilder, Ar
if (WasmTestOptions.COVERAGE_MODE) {
asyncIterations = Math.min(asyncIterations, 1);
}
- context = contextBuilder.options(getAsyncCompiled()).build();
+ context = contextBuilder.options(getAsyncCompiled()).option("wasm.EvalReturnsInstance", "true").build();
runInContext(testCase, context, sources, asyncIterations, PHASE_ASYNC_ICON, "async,multi", testOut);
} else {
int asyncSharedIterations = testCase.options().containsKey("async-iterations") && !testCase.options().containsKey("async-shared-iterations")
@@ -428,7 +443,7 @@ private void runInContexts(WasmCase testCase, Context.Builder contextBuilder, Ar
if (WasmTestOptions.COVERAGE_MODE) {
asyncSharedIterations = Math.min(asyncSharedIterations, 1);
}
- context = contextBuilder.build();
+ context = contextBuilder.option("wasm.EvalReturnsInstance", "true").build();
runInContext(testCase, context, sources, asyncSharedIterations, PHASE_SHARED_ENGINE_ICON, "async,shared", testOut);
}
}
@@ -546,7 +561,7 @@ public void test() throws IOException {
}
System.err.printf("\uD83D\uDCA5\u001B[31m %d/%d Wasm tests passed.\u001B[0m%n", qualifyingTestCases.size() - errors.size(), qualifyingTestCases.size());
System.out.println();
- fail();
+ fail("Some tests failed. See errors above.");
} else {
System.out.printf("\uD83C\uDF40\u001B[32m %d/%d Wasm tests passed.\u001B[0m%n", qualifyingTestCases.size() - errors.size(), qualifyingTestCases.size());
System.out.println();
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java
index b2e7ca50378b..9f2ab7b23458 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java
@@ -47,6 +47,7 @@
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
@@ -82,7 +83,6 @@
import org.graalvm.wasm.globals.WasmGlobal;
import org.graalvm.wasm.memory.WasmMemory;
import org.graalvm.wasm.memory.WasmMemoryLibrary;
-import org.graalvm.wasm.predefined.testutil.TestutilModule;
import org.junit.Assert;
import org.junit.Test;
@@ -95,7 +95,6 @@
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.InvalidBufferOffsetException;
-import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
@@ -2381,7 +2380,7 @@ public void testReimportWasmFunctionViaImportObject() throws IOException, Interr
}),
});
WasmInstance instance1 = moduleInstantiate(wasm, sourceBytesMod1, importObj1);
- var mod1Sum = instance1.readMember("sum");
+ var mod1Sum = WebAssembly.instanceExport(instance1, "sum");
Dictionary importObj2 = Dictionary.create(new Object[]{
"mod1", Dictionary.create(new Object[]{
"sum", mod1Sum,
@@ -2527,24 +2526,10 @@ public static void runTest(Consumer options, Consumer, TruffleObject {
- private final Consumer testCase;
-
- private GuestCode(Consumer testCase) {
- this.testCase = testCase;
- }
-
- @Override
- public void accept(WasmContext context) {
- testCase.accept(context);
+ WasmTestUtils.runInWasmContext(context, testCase);
}
}
@@ -2916,6 +2901,6 @@ public void accept(WasmContext context) {
public static WasmInstance moduleInstantiate(WebAssembly wasm, byte[] source, Object importObject) {
final WasmModule module = wasm.moduleDecode(source);
- return wasm.moduleInstantiate(module, importObject);
+ return wasm.moduleInstantiate(module, Objects.requireNonNullElse(importObject, WasmConstant.NULL));
}
}
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmLateLinkingSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmLateLinkingSuite.java
index b4ee0b8c34c4..c10658768203 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmLateLinkingSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmLateLinkingSuite.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -62,9 +62,9 @@ public class WasmLateLinkingSuite {
@Test
public void testLateMemoryLink() throws IOException {
runTest(binaryWithMemoryExport, context -> {
- Value main = context.getBindings(WasmLanguage.ID).getMember("main").getMember("main");
+ Value main = context.getBindings(WasmLanguage.ID).getMember("main").getMember("exports").getMember("main");
main.execute();
- Value memory = context.getBindings(WasmLanguage.ID).getMember("main").getMember("memory");
+ Value memory = context.getBindings(WasmLanguage.ID).getMember("main").getMember("exports").getMember("memory");
memory.setArrayElement(0, 11);
Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(binaryWithMemoryImport), "aux");
try {
@@ -72,7 +72,7 @@ public void testLateMemoryLink() throws IOException {
} catch (IOException e) {
throw new RuntimeException(e);
}
- Value loadZero = context.getBindings(WasmLanguage.ID).getMember("aux").getMember("loadZero");
+ Value loadZero = context.getBindings(WasmLanguage.ID).getMember("aux").getMember("exports").getMember("loadZero");
Value result = loadZero.execute();
Assert.assertEquals(11, result.asInt());
});
@@ -84,10 +84,10 @@ public void linkingTooLate() throws IOException, InterruptedException {
final ByteSequence binaryMain = ByteSequence.create(compileWat("file1", textWithImportFunExportFun));
final Source sourceAux = Source.newBuilder(WasmLanguage.ID, binaryAux, "m1").build();
final Source sourceMain = Source.newBuilder(WasmLanguage.ID, binaryMain, "main").build();
- try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.EvalReturnsInstance", "true").build()) {
context.eval(sourceMain); // main
context.eval(sourceAux); // m1
- final Value g = context.getBindings(WasmLanguage.ID).getMember("main").getMember("g");
+ final Value g = context.getBindings(WasmLanguage.ID).getMember("main").getMember("exports").getMember("g");
Assert.assertEquals(42, g.execute().asInt());
}
}
@@ -100,12 +100,12 @@ public void linkingTooLateShared() throws IOException, InterruptedException {
final Source sourceMain = Source.newBuilder(WasmLanguage.ID, binaryMain, "main").build();
try (Engine engine = Engine.create()) {
for (int i = 0; i < N_CONTEXTS; i++) {
- try (Context context = Context.newBuilder(WasmLanguage.ID).engine(engine).build()) {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.EvalReturnsInstance", "true").engine(engine).allowExperimentalOptions(true).build()) {
Value main = context.eval(sourceMain); // main
Value main2 = context.eval(sourceMain); // main
Assert.assertEquals(main, main2);
context.eval(sourceAux); // m1
- final Value g = context.getBindings(WasmLanguage.ID).getMember("main").getMember("g");
+ final Value g = context.getBindings(WasmLanguage.ID).getMember("main").getMember("exports").getMember("g");
Assert.assertEquals(42, g.execute().asInt());
}
}
@@ -122,7 +122,7 @@ public void linkingTooLateDifferentImportedFunctions() throws IOException, Inter
final Source sourceAux2 = Source.newBuilder(WasmLanguage.ID, binaryAux2, "m1").build();
try (Engine engine = Engine.create()) {
for (int i = 0; i < N_CONTEXTS; i++) {
- try (Context context = Context.newBuilder(WasmLanguage.ID).engine(engine).build()) {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.EvalReturnsInstance", "true").engine(engine).allowExperimentalOptions(true).build()) {
Value mainModule = context.eval(sourceMain); // main
int expected;
if (i < N_CONTEXTS - 1) {
@@ -133,10 +133,10 @@ public void linkingTooLateDifferentImportedFunctions() throws IOException, Inter
expected = 43;
}
// call g(), which is the reexported f() from m1.
- final Value g = mainModule.getMember("g");
+ final Value g = mainModule.getMember("exports").getMember("g");
Assert.assertEquals(expected, g.execute().asInt());
// call f() via call_f() function in main module.
- final Value f = mainModule.getMember("call_f");
+ final Value f = mainModule.getMember("exports").getMember("call_f");
Assert.assertEquals(expected, f.execute().asInt());
}
}
@@ -145,7 +145,7 @@ public void linkingTooLateDifferentImportedFunctions() throws IOException, Inter
@Test
public void linkingFailureInvertedOnlyAux() throws IOException, InterruptedException {
- try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.EvalReturnsInstance", "true").build()) {
final ByteSequence binaryAux = ByteSequence.create(compileWat("file1", "(import \"non_existing\" \"f\" (func))"));
final ByteSequence binaryMain = ByteSequence.create(compileWat("file2", "(func (export \"g\") (result i32) (i32.const 42))"));
final Source sourceAux = Source.newBuilder(WasmLanguage.ID, binaryAux, "module1").build();
@@ -157,7 +157,7 @@ public void linkingFailureInvertedOnlyAux() throws IOException, InterruptedExcep
// referenced by the import 'f' in the module 'module1',
// does not exist."
try {
- final Value g = module2Instance.getMember("g");
+ final Value g = module2Instance.getMember("exports").getMember("g");
g.execute();
Assert.assertFalse("Should not reach here.", true);
} catch (Throwable e) {
@@ -169,7 +169,7 @@ public void linkingFailureInvertedOnlyAux() throws IOException, InterruptedExcep
}
try {
- final Value g2 = module2Instance.getMember("g");
+ final Value g2 = module2Instance.getMember("exports").getMember("g");
final Value result2 = g2.execute();
Assert.assertEquals(42, result2.asInt());
} catch (Throwable e) {
@@ -180,7 +180,7 @@ public void linkingFailureInvertedOnlyAux() throws IOException, InterruptedExcep
@Test
public void linkingFailureOnlyAux() throws IOException, InterruptedException {
- try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.EvalReturnsInstance", "true").build()) {
final ByteSequence binaryAux = ByteSequence.create(compileWat("file1", "(import \"non_existing\" \"f\" (func))"));
final ByteSequence binaryMain = ByteSequence.create(compileWat("file2", "(func (export \"g\") (result i32) (i32.const 42))"));
final Source sourceAux = Source.newBuilder(WasmLanguage.ID, binaryAux, "module1").build();
@@ -192,7 +192,7 @@ public void linkingFailureOnlyAux() throws IOException, InterruptedException {
// referenced by the import 'f' in the module 'module1',
// does not exist."
try {
- final Value g = module2Instance.getMember("g");
+ final Value g = module2Instance.getMember("exports").getMember("g");
g.execute();
Assert.assertFalse("Should not reach here.", true);
} catch (Throwable e) {
@@ -204,7 +204,7 @@ public void linkingFailureOnlyAux() throws IOException, InterruptedException {
}
try {
- final Value g2 = module2Instance.getMember("g");
+ final Value g2 = module2Instance.getMember("exports").getMember("g");
final Value result2 = g2.execute();
Assert.assertEquals(42, result2.asInt());
} catch (Throwable e) {
@@ -215,7 +215,7 @@ public void linkingFailureOnlyAux() throws IOException, InterruptedException {
@Test
public void linkingFailureDueToDependency() throws IOException, InterruptedException {
- try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.EvalReturnsInstance", "true").build()) {
final ByteSequence binaryAux = ByteSequence.create(compileWat("file1", "(import \"non_existing\" \"f\" (func)) (func (export \"h\") (result i32) (i32.const 42))"));
final ByteSequence binaryMain = ByteSequence.create(compileWat("file2", "(import \"main\" \"h\" (func)) (func (export \"g\") (result i32) (i32.const 42))"));
final Source sourceAux = Source.newBuilder(WasmLanguage.ID, binaryAux, "module1").build();
@@ -224,7 +224,7 @@ public void linkingFailureDueToDependency() throws IOException, InterruptedExcep
final Value module2Instance = context.eval(sourceMain);
try {
- final Value g = module2Instance.getMember("g");
+ final Value g = module2Instance.getMember("exports").getMember("g");
g.execute();
Assert.fail("Should not reach here.");
} catch (Throwable e) {
@@ -233,7 +233,7 @@ public void linkingFailureDueToDependency() throws IOException, InterruptedExcep
}
try {
- final Value g2 = module2Instance.getMember("g");
+ final Value g2 = module2Instance.getMember("exports").getMember("g");
g2.execute();
Assert.fail("Should not reach here.");
} catch (Throwable e) {
@@ -266,15 +266,15 @@ public void lazyLinkEquivalenceClasses() throws IOException, InterruptedExceptio
)
""");
- try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.EvalReturnsInstance", "true").build()) {
final ByteSequence exportByteSeq = ByteSequence.create(exportBytes);
final ByteSequence importByteSeq = ByteSequence.create(importBytes);
final Source exportSource = Source.newBuilder(WasmLanguage.ID, exportByteSeq, "exportModule").build();
final Source importSource = Source.newBuilder(WasmLanguage.ID, importByteSeq, "importModule").build();
- final Value exportModuleInstance = context.eval(exportSource);
+ final Value exportModuleInstance = context.eval(exportSource).getMember("exports");
exportModuleInstance.getMember("table");
// Linking of the first module was triggered by this point.
- final Value importModuleInstance = context.eval(importSource);
+ final Value importModuleInstance = context.eval(importSource).getMember("exports");
importModuleInstance.getMember("testFunc").execute(0);
// Linking of the second module was triggered by this point.
}
@@ -283,6 +283,7 @@ public void lazyLinkEquivalenceClasses() throws IOException, InterruptedExceptio
private static void runTest(byte[] firstBinary, Consumer testCase) throws IOException {
final Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID);
contextBuilder.option("wasm.Builtins", "testutil:testutil");
+ contextBuilder.option("wasm.EvalReturnsInstance", "true");
try (Context context = contextBuilder.build()) {
Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(firstBinary), "main");
Source source = sourceBuilder.build();
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmMemoryLeakSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmMemoryLeakSuite.java
index c2d45694e79a..4b8ccb78c6e1 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmMemoryLeakSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmMemoryLeakSuite.java
@@ -85,9 +85,10 @@ public void testMemoryLeak() throws IOException, InterruptedException {
var strongInstances = new ArrayList<>();
for (int iter = 0; iter < 16; iter++) {
var instance = wasm.moduleInstantiate(module, importObject);
- Object mem = lib.readMember(instance, "mem");
- Object fun = lib.readMember(instance, "fun");
- Object tab = lib.readMember(instance, "tab");
+ Object exports = lib.readMember(instance, "exports");
+ Object mem = lib.readMember(exports, "mem");
+ Object fun = lib.readMember(exports, "fun");
+ Object tab = lib.readMember(exports, "tab");
weakStores.add(new WeakReference<>(instance.store()));
weakInstances.add(new WeakReference<>(instance));
@@ -113,7 +114,8 @@ public void testMemoryLeak() throws IOException, InterruptedException {
switch (i % 4) {
case 0 -> {
// WasmInstance
- Assert.assertTrue(lib.isMemberReadable(instance, "fun"));
+ Object exports = lib.readMember(instance, "exports");
+ Assert.assertTrue(lib.isMemberReadable(exports, "fun"));
}
case 1 -> {
// WasmFunction
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmPolyglotTestSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmPolyglotTestSuite.java
index 3ea3d3ff1e49..e6538d66e1b3 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmPolyglotTestSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmPolyglotTestSuite.java
@@ -45,13 +45,19 @@
import java.io.IOException;
import java.nio.ByteOrder;
+import java.util.Collections;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.ByteSequence;
+import org.graalvm.polyglot.proxy.Proxy;
+import org.graalvm.polyglot.proxy.ProxyExecutable;
+import org.graalvm.polyglot.proxy.ProxyObject;
import org.graalvm.wasm.WasmContext;
import org.graalvm.wasm.WasmLanguage;
import org.graalvm.wasm.exception.WasmException;
@@ -59,6 +65,7 @@
import org.graalvm.wasm.memory.UnsafeWasmMemory;
import org.graalvm.wasm.memory.WasmMemoryLibrary;
import org.junit.Assert;
+import org.junit.BeforeClass;
import org.junit.Test;
import com.oracle.truffle.api.TruffleLanguage;
@@ -77,13 +84,10 @@ public void testEmpty() throws IOException {
@Test
public void test42() throws IOException {
Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID);
- Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID,
- ByteSequence.create(binaryReturnConst),
- "main");
+ Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(binaryReturnConst), "main");
Source source = sourceBuilder.build();
try (Context context = contextBuilder.build()) {
- context.eval(source);
- Value mainFunction = context.getBindings(WasmLanguage.ID).getMember("main").getMember("main");
+ Value mainFunction = context.eval(source).newInstance().getMember("exports").getMember("main");
Value result = mainFunction.execute();
Assert.assertEquals("Should be equal: ", 42, result.asInt());
}
@@ -92,9 +96,7 @@ public void test42() throws IOException {
@Test
public void unsafeMemoryFreed() throws IOException {
Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID);
- Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID,
- ByteSequence.create(binaryReturnConst),
- "main");
+ Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(binaryReturnConst), "main");
Source source = sourceBuilder.build();
contextBuilder.allowExperimentalOptions(true);
contextBuilder.option("wasm.UseUnsafeMemory", "true");
@@ -102,11 +104,11 @@ public void unsafeMemoryFreed() throws IOException {
contextBuilder.option("wasm.DirectByteBufferMemoryAccess", "true");
Context context = contextBuilder.build();
context.enter();
- context.eval(source);
- final Value mainModule = context.getBindings(WasmLanguage.ID).getMember("main");
- mainModule.getMember("main").execute();
+
+ final Value mainExports = context.eval(source).newInstance().getMember("exports");
+ mainExports.getMember("main").execute();
final TruffleLanguage.Env env = WasmContext.get(null).environment();
- final Value memoryValue = mainModule.getMember("memory");
+ final Value memoryValue = mainExports.getMember("memory");
final UnsafeWasmMemory memory = (UnsafeWasmMemory) env.asGuestValue(memoryValue);
Assert.assertFalse("Memory should have been allocated.", WasmMemoryLibrary.getUncached().freed(memory));
context.leave();
@@ -124,20 +126,17 @@ public void unsafeMemoryFreed() throws IOException {
@Test
public void nativeMemoryFreed() throws IOException {
Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID);
- Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID,
- ByteSequence.create(binaryReturnConst),
- "main");
+ Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(binaryReturnConst), "main");
Source source = sourceBuilder.build();
contextBuilder.allowExperimentalOptions(true);
contextBuilder.option("wasm.UseUnsafeMemory", "true");
contextBuilder.option("wasm.DirectByteBufferMemoryAccess", "false");
Context context = contextBuilder.build();
context.enter();
- context.eval(source);
- final Value mainModule = context.getBindings(WasmLanguage.ID).getMember("main");
- mainModule.getMember("main").execute();
+ final Value mainExports = context.eval(source).newInstance().getMember("exports");
+ mainExports.getMember("main").execute();
final TruffleLanguage.Env env = WasmContext.get(null).environment();
- final Value memoryValue = mainModule.getMember("memory");
+ final Value memoryValue = mainExports.getMember("memory");
final NativeWasmMemory memory = (NativeWasmMemory) env.asGuestValue(memoryValue);
Assert.assertFalse("Memory should have been allocated.", WasmMemoryLibrary.getUncached().freed(memory));
context.leave();
@@ -159,8 +158,7 @@ public void overwriteElement() throws IOException, InterruptedException {
Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, test, "main");
Source source = sourceBuilder.build();
try (Context context = contextBuilder.build()) {
- context.eval(source);
- Value mainFunction = context.getBindings(WasmLanguage.ID).getMember("main").getMember("main");
+ Value mainFunction = context.eval(source).newInstance().getMember("exports").getMember("main");
Value result = mainFunction.execute();
Assert.assertEquals("Should be equal: ", 11, result.asInt());
}
@@ -171,9 +169,8 @@ public void divisionByZeroStressTest() throws IOException, InterruptedException
String divisionByZeroWAT = "(module (func (export \"main\") (result i32) i32.const 1 i32.const 0 i32.div_s))";
ByteSequence test = ByteSequence.create(compileWat("test", divisionByZeroWAT));
Source source = Source.newBuilder(WasmLanguage.ID, test, "main").build();
- try (Context context = Context.create(WasmLanguage.ID)) {
- context.eval(source);
- Value mainFunction = context.getBindings(WasmLanguage.ID).getMember("main").getMember("main");
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ Value mainFunction = context.eval(source).newInstance().getMember("exports").getMember("main");
for (int iteration = 0; iteration < 20000; iteration++) {
try {
@@ -190,10 +187,9 @@ public void divisionByZeroStressTest() throws IOException, InterruptedException
public void extractKeys() throws IOException {
ByteSequence test = ByteSequence.create(binaryReturnConst);
Source source = Source.newBuilder(WasmLanguage.ID, test, "main").build();
- try (Context context = Context.create(WasmLanguage.ID)) {
- context.eval(source);
- Value instance = context.getBindings(WasmLanguage.ID).getMember("main");
- Set keys = instance.getMemberKeys();
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ Value instance = context.eval(source).newInstance();
+ Set keys = instance.getMember("exports").getMemberKeys();
Assert.assertTrue("Should contain function 'main'", keys.contains("main"));
Assert.assertTrue("Should contain memory 'memory'", keys.contains("memory"));
Assert.assertTrue("Should contain global '__heap_base'", keys.contains("__heap_base"));
@@ -213,9 +209,8 @@ public void deeplyNestedBrIf() throws IOException, InterruptedException {
ByteSequence bytes = ByteSequence.create(compileWat("test", wat));
Source source = Source.newBuilder(WasmLanguage.ID, bytes, "main").build();
- try (Context context = Context.create(WasmLanguage.ID)) {
- context.eval(source);
- Value mainFunction = context.getBindings(WasmLanguage.ID).getMember("main").getMember("main");
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ Value mainFunction = context.eval(source).newInstance().getMember("exports").getMember("main");
Value result = mainFunction.execute();
Assert.assertEquals("Should be equal: ", 42, result.asInt());
}
@@ -265,4 +260,337 @@ public void deeplyNestedBrIf() throws IOException, InterruptedException {
(elem (i32.const 3) $g)
)
""";
+
+ private static final String simpleTestModule = """
+ (module
+ (func (export "main") (result i32)
+ i32.const 13
+ )
+ )
+ """;
+
+ private static final String simpleImportModule = """
+ (module
+ (import "main" "main" (func $m (result i32)))
+ (func (export "test") (result i32)
+ call $m
+ )
+ )
+ """;
+
+ private static Source simpleTestModuleSource;
+ private static Source simpleImportModuleSource;
+
+ @BeforeClass
+ public static void setup() throws IOException, InterruptedException {
+ final ByteSequence simpleTestModuleData = ByteSequence.create(compileWat("simpleTestModule", simpleTestModule));
+ simpleTestModuleSource = Source.newBuilder(WasmLanguage.ID, simpleTestModuleData, "main").build();
+
+ final ByteSequence simpleImportModuleData = ByteSequence.create(compileWat("simpleImportModule", simpleImportModule));
+ simpleImportModuleSource = Source.newBuilder(WasmLanguage.ID, simpleImportModuleData, "test").build();
+ }
+
+ @Test
+ public void instantiateModuleWithImportObject() {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ final Proxy executable = (ProxyExecutable) args -> 42;
+ final Proxy function = ProxyObject.fromMap(Map.of("main", executable));
+ final Proxy importObject = ProxyObject.fromMap(Map.of("main", function));
+
+ final Value importModule = context.eval(simpleImportModuleSource);
+
+ final Value instance = importModule.newInstance(importObject);
+
+ final Value result = instance.getMember("exports").invokeMember("test");
+
+ Assert.assertEquals(42, result.asInt());
+ }
+ }
+
+ @Test
+ public void instantiateModuleWithMissingImportObject() {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ final Value importModule = context.eval(simpleImportModuleSource);
+
+ try {
+ importModule.newInstance();
+ Assert.fail("Should have failed because of missing import");
+ } catch (PolyglotException e) {
+ Assert.assertFalse(e.isExit());
+ }
+ }
+ }
+
+ @Test
+ public void instantiateModuleWithMissingFunction() {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ final Proxy importObject = ProxyObject.fromMap(Map.of("main", ProxyObject.fromMap(Collections.emptyMap())));
+
+ final Value importModule = context.eval(simpleImportModuleSource);
+
+ try {
+ importModule.newInstance(importObject);
+ Assert.fail("Should have failed because of incorrect import");
+ } catch (PolyglotException e) {
+ Assert.assertFalse(e.isExit());
+ Assert.assertTrue(e.getMessage().contains("does not contain \"main\""));
+ }
+ }
+ }
+
+ @Test
+ public void instantiateModuleWithNonExecutableFunction() {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ final Proxy importObject = ProxyObject.fromMap(Map.of("main", ProxyObject.fromMap(Map.of("main", 5))));
+
+ final Value importModule = context.eval(simpleImportModuleSource);
+
+ try {
+ importModule.newInstance(importObject);
+ Assert.fail("Should have failed because of incorrect import");
+ } catch (PolyglotException e) {
+ Assert.assertFalse(e.isExit());
+ Assert.assertTrue(e.getMessage().contains("is not callable"));
+ }
+ }
+ }
+
+ @Test
+ public void instantiateModuleWithTwoImportObjects() {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ final Proxy importObject = ProxyObject.fromMap(Map.of("main", ProxyObject.fromMap(Map.of("main", 5))));
+ final Proxy importObject2 = ProxyObject.fromMap(Map.of("main2", ProxyObject.fromMap(Map.of("main2", 6))));
+
+ final Value importModule = context.eval(simpleImportModuleSource);
+
+ try {
+ importModule.newInstance(importObject, importObject2);
+ Assert.fail("Should have failed because of incorrect number of import objects");
+ } catch (PolyglotException e) {
+ Assert.assertFalse(e.isExit());
+ Assert.assertTrue(e.getMessage().contains("single import object"));
+ }
+ }
+ }
+
+ @Test
+ public void instantiateModuleWithExportsFromAnotherModule() {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ final Value testModule = context.eval(simpleTestModuleSource);
+ final Value importModule = context.eval(simpleImportModuleSource);
+
+ final Value testInstance = testModule.newInstance();
+ final Value importInstance = importModule.newInstance(ProxyObject.fromMap(Map.of(
+ "main", testInstance.getMember("exports"))));
+
+ final Value result = importInstance.getMember("exports").getMember("test").execute();
+
+ Assert.assertEquals(13, result.asInt());
+ }
+ }
+
+ @Test
+ public void instantiateModuleWithGlobalImport() throws IOException, InterruptedException {
+ final ByteSequence supplierBytes = ByteSequence.create(compileWat("supplier", """
+ (module
+ (global (export "g") i32 (i32.const 42))
+ )
+ """));
+ final ByteSequence consumerBytes = ByteSequence.create(compileWat("consumer", """
+ (module
+ (import "supplier" "g" (global $g i32))
+ (export "gg" (global $g))
+ (func (export "main") (result i32)
+ (global.get $g)
+ )
+ )
+ """));
+
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ final Value supplierModule = context.eval(Source.newBuilder(WasmLanguage.ID, supplierBytes, "supplier").build());
+ final Value consumerModule = context.eval(Source.newBuilder(WasmLanguage.ID, consumerBytes, "consumer").build());
+
+ final Value supplierInstance = supplierModule.newInstance();
+ final Value consumerInstance = consumerModule.newInstance(ProxyObject.fromMap(Map.of(
+ "supplier", supplierInstance.getMember("exports"))));
+
+ final Value consumerExports = consumerInstance.getMember("exports");
+ final Value result = consumerExports.invokeMember("main");
+ final Value global = consumerExports.getMember("gg");
+
+ Assert.assertEquals(42, result.asInt());
+ Assert.assertEquals(42, global.getMember("value").asInt());
+
+ Assert.assertThrows(UnsupportedOperationException.class, () -> global.putMember("value", 43));
+ }
+ }
+
+ @Test
+ public void mutableGlobals() throws IOException, InterruptedException {
+ final ByteSequence globalsBytes = ByteSequence.create(compileWat("mutable-globals", """
+ (module
+ (global (export "global-i32") (mut i32) (i32.const 42))
+ (global (export "global-i64") (mut i64) (i64.const 0x1_ffff_ffff))
+ (global (export "global-f32") (mut f32) (f32.const 3.14))
+ (global (export "global-f64") (mut f64) (f64.const 3.14))
+ )
+ """));
+
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ final Source source = Source.newBuilder(WasmLanguage.ID, globalsBytes, "mutable-globals").build();
+ final Value globals = context.eval(source).newInstance().getMember("exports");
+
+ final Value globalI32 = globals.getMember("global-i32");
+ Assert.assertEquals(42, globalI32.getMember("value").asInt());
+ globalI32.putMember("value", 43);
+ Assert.assertEquals(43, globalI32.getMember("value").asInt());
+
+ final Value globalI64 = globals.getMember("global-i64");
+ Assert.assertEquals(0x1_ffff_ffffL, globalI64.getMember("value").asLong());
+ globalI64.putMember("value", -1L);
+ Assert.assertEquals(-1L, globalI64.getMember("value").asLong());
+
+ final Value globalF32 = globals.getMember("global-f32");
+ Assert.assertEquals(3.14f, globalF32.getMember("value").asFloat(), 0.0f);
+ globalF32.putMember("value", 13.37f);
+ Assert.assertEquals(13.37f, globalF32.getMember("value").asFloat(), 0.0f);
+
+ final Value globalF64 = globals.getMember("global-f64");
+ Assert.assertEquals(3.14, globalF64.getMember("value").asDouble(), 0.0);
+ globalF64.putMember("value", 13.37);
+ Assert.assertEquals(13.37, globalF64.getMember("value").asDouble(), 0.0);
+ }
+ }
+
+ @Test
+ public void newInstanceWASI() throws IOException, InterruptedException {
+ final ByteSequence mainModuleBytes = ByteSequence.create(compileWat("main", """
+ (module
+ (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
+ (memory 1)
+ (export "memory" (memory 0))
+ (func (export "main") (result i32)
+ (i32.const 13)
+ )
+ )
+ """));
+ final ByteSequence importModuleBytes = ByteSequence.create(compileWat("test", """
+ (module
+ (func (export "f") (result i32)
+ (i32.const 42)
+ )
+ )
+ """));
+
+ final Source mainModuleSource = Source.newBuilder(WasmLanguage.ID, mainModuleBytes, "main-mod").build();
+ final Source importModuleSource = Source.newBuilder(WasmLanguage.ID, importModuleBytes, "import-mod").build();
+
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.Builtins", "wasi_snapshot_preview1").build()) {
+ final Value mainModule = context.eval(mainModuleSource);
+ final Value otherModule = context.eval(importModuleSource);
+
+ final Value mainInstance = mainModule.newInstance();
+ final Value mainExports = mainInstance.getMember("exports");
+
+ Assert.assertEquals(13, mainExports.invokeMember("main").asInt());
+
+ final Value otherInstance = otherModule.newInstance();
+ final Value otherExports = otherInstance.getMember("exports");
+ Assert.assertEquals(42, otherExports.invokeMember("f").asInt());
+ }
+ }
+
+ @Test
+ public void newInstanceWASIWithSupportModule() throws IOException, InterruptedException {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).option("wasm.Builtins", "wasi_snapshot_preview1").build()) {
+ Value mainModule = context.eval(Source.newBuilder("wasm", ByteSequence.create(compileWat("main", """
+ (module
+ ;; Import WASI function: args_sizes_get(argc_ptr, argv_buf_size_ptr)
+ (import "wasi_snapshot_preview1" "args_sizes_get"
+ (func $args_sizes_get (param i32 i32) (result i32)))
+
+ ;; Import printf function from support module
+ (import "env" "printf"
+ (func $printf (param i32)))
+
+ ;; Export memory 0 (used by WASI)
+ (memory $mem 1)
+ (export "memory" (memory $mem))
+
+ ;; Dummy start function that calls both imported functions
+ (func (export "_start")
+ ;; Write to 0 and 4 memory offsets (pretend we use these as argc/argv)
+ (call $args_sizes_get
+ (i32.const 0) ;; argc pointer
+ (i32.const 4)) ;; argv_buf_size pointer
+ drop
+
+ ;; Call printf with a dummy i32 pointer, say address 16 (assuming a string is there)
+ (call $printf (i32.const 16))
+ )
+ )
+ """)), "main").build());
+ Value envModule = context.eval(Source.newBuilder("wasm", ByteSequence.create(compileWat("env", """
+ (module
+ (func $printf (export "printf") (import "hostEnv" "printf") (param i32))
+ )
+ """)), "env").build());
+
+ AtomicBoolean printfCalled = new AtomicBoolean();
+ ProxyExecutable printf = (args) -> {
+ Assert.assertEquals(16, args[0].asInt());
+ Assert.assertFalse(printfCalled.getAndSet(true));
+ return null;
+ };
+ var hostEnvObject = ProxyObject.fromMap(Map.of("printf", printf));
+ Value envInstance = envModule.newInstance(ProxyObject.fromMap(Map.of(
+ "hostEnv", hostEnvObject)));
+ Value envExports = envInstance.getMember("exports");
+ Value mainInstance = mainModule.newInstance(ProxyObject.fromMap(Map.of(
+ "hostEnv", hostEnvObject,
+ "env", envExports)));
+ Value mainExports = mainInstance.getMember("exports");
+ mainExports.getMember("_start").execute();
+ Assert.assertTrue("printf called", printfCalled.get());
+ }
+ }
+
+ @Test
+ public void indirectImportOfHostFunction() throws IOException, InterruptedException {
+ try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
+ Value mainModule = context.eval(Source.newBuilder("wasm", ByteSequence.create(compileWat("main", """
+ (module
+ ;; Import printf function from support module
+ (import "env" "printf"
+ (func $printf (param i32)))
+
+ (func (export "_start")
+ ;; Call printf with a dummy i32 pointer, say address 16
+ (call $printf (i32.const 16))
+ )
+ )
+ """)), "main").build());
+ Value envModule = context.eval(Source.newBuilder("wasm", ByteSequence.create(compileWat("env", """
+ (module
+ (func $printf (export "printf") (import "hostEnv" "printf") (param i32))
+ )
+ """)), "env").build());
+
+ AtomicBoolean printfCalled = new AtomicBoolean();
+ ProxyExecutable printf = (args) -> {
+ Assert.assertEquals(16, args[0].asInt());
+ Assert.assertFalse(printfCalled.getAndSet(true));
+ return null;
+ };
+ var hostEnvObject = ProxyObject.fromMap(Map.of("printf", printf));
+ Value envModuleInstance = envModule.newInstance(ProxyObject.fromMap(Map.of(
+ "hostEnv", hostEnvObject)));
+ Value envExports = envModuleInstance.getMember("exports");
+ Value mainModuleInstance = mainModule.newInstance(ProxyObject.fromMap(Map.of(
+ "hostEnv", hostEnvObject,
+ "env", envExports)));
+ mainModuleInstance.getMember("exports").getMember("_start").execute();
+ Assert.assertTrue("printf called", printfCalled.get());
+ }
+ }
}
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmStackTraceTest.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmStackTraceTest.java
index e19854cc46cc..09c38baf12eb 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmStackTraceTest.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmStackTraceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -83,8 +83,7 @@ public void testStackTrace() throws IOException, InterruptedException {
final Source sourceMain = Source.newBuilder(WasmLanguage.ID, binaryMain, "main").build();
try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
- context.eval(sourceMain); // main
- final Value g = context.getBindings(WasmLanguage.ID).getMember("main").getMember("main");
+ final Value g = context.eval(sourceMain).newInstance().getMember("exports").getMember("main");
try {
g.execute();
} catch (PolyglotException e) {
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestUtils.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestUtils.java
index bff910af0c5f..5b8f74620340 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestUtils.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmTestUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -42,6 +42,11 @@
package org.graalvm.wasm.test;
import java.io.ByteArrayOutputStream;
+import java.util.function.Consumer;
+
+import org.graalvm.polyglot.Context;
+import org.graalvm.wasm.WasmContext;
+import org.graalvm.wasm.WasmLanguage;
public final class WasmTestUtils {
private WasmTestUtils() {
@@ -59,4 +64,11 @@ public static byte[] hexStringToByteArray(String... strings) {
}
return bytes.toByteArray();
}
+
+ public static void runInWasmContext(Context context, Consumer testCase) {
+ context.initialize(WasmLanguage.ID);
+ context.enter();
+ testCase.accept(WasmContext.get(null));
+ context.leave();
+ }
}
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/regress/GR54505Test.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/regress/GR54505Test.java
index 67d771ed0c8b..a1c6e1ffac06 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/regress/GR54505Test.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/regress/GR54505Test.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -70,9 +70,9 @@ public void testWrongArity() throws IOException, InterruptedException {
"""));
final Source sourceMain = Source.newBuilder(WasmLanguage.ID, binaryMain, "main").build();
- try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
- Value mainModule = context.eval(sourceMain); // main
- final Value main = mainModule.getMember("_main");
+ try (Context context = Context.create(WasmLanguage.ID)) {
+ Value mainExports = context.eval(sourceMain).newInstance().getMember("exports"); // main
+ final Value main = mainExports.getMember("_main");
// Too few arguments
try {
main.execute();
@@ -109,12 +109,12 @@ public void testDifferentArgumentType() throws IOException, InterruptedException
"""));
final Source sourceMain = Source.newBuilder(WasmLanguage.ID, binaryMain, "main").build();
- try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
- Value mainModule = context.eval(sourceMain); // main
- final Value main = mainModule.getMember("_main");
+ try (Context context = Context.create(WasmLanguage.ID)) {
+ Value mainExports = context.eval(sourceMain).newInstance().getMember("exports"); // main
+ final Value main = mainExports.getMember("_main");
// Expected argument types
- assertEquals(42, main.execute(0.0, 0, 0).asInt());
+ assertEquals(42, mainExports.execute(0.0, 0, 0).asInt());
// Invalid argument type(s)
try {
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/WasmImplementationLimitationsSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/WasmImplementationLimitationsSuite.java
index 70385adee8d9..f8356204c04d 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/WasmImplementationLimitationsSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/WasmImplementationLimitationsSuite.java
@@ -41,6 +41,12 @@
package org.graalvm.wasm.test.suites;
+import static org.graalvm.wasm.utils.WasmBinaryTools.compileWat;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Source;
@@ -53,12 +59,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-
-import static org.graalvm.wasm.utils.WasmBinaryTools.compileWat;
-
@RunWith(Parameterized.class)
public class WasmImplementationLimitationsSuite {
@Parameterized.Parameters(name = "{0}")
@@ -85,7 +85,7 @@ public void test() throws IOException {
final Context context = Context.newBuilder(WasmLanguage.ID).build();
final Source source = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(bytecode), "dummy_main").build();
try {
- context.eval(source).getMember("_main").execute();
+ context.eval(source).newInstance().getMember("exports").getMember("_main").execute();
} catch (final PolyglotException e) {
final Value actualFailureObject = e.getGuestObject();
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java
index 3eae2a0f0b6d..0b0293b26995 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java
@@ -43,8 +43,8 @@
import java.io.IOException;
import java.util.List;
+import java.util.Objects;
import java.util.function.BiConsumer;
-import java.util.function.Consumer;
import java.util.function.Function;
import org.graalvm.polyglot.Context;
@@ -52,7 +52,6 @@
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.ByteSequence;
import org.graalvm.wasm.WasmConstant;
-import org.graalvm.wasm.WasmContext;
import org.graalvm.wasm.WasmInstance;
import org.graalvm.wasm.WasmLanguage;
import org.graalvm.wasm.WasmModule;
@@ -67,7 +66,7 @@
import org.graalvm.wasm.globals.WasmGlobal;
import org.graalvm.wasm.memory.WasmMemory;
import org.graalvm.wasm.memory.WasmMemoryLibrary;
-import org.graalvm.wasm.predefined.testutil.TestutilModule;
+import org.graalvm.wasm.test.WasmTestUtils;
import org.graalvm.wasm.utils.WasmBinaryTools;
import org.junit.Assert;
import org.junit.Test;
@@ -75,7 +74,6 @@
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
-import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
@@ -84,42 +82,32 @@
* Tests that modules can be instantiated several times.
*/
public class MultiInstantiationSuite {
- private static void test(Function sourceFun, Function importFun, BiConsumer check) throws IOException {
+ private static void test(byte[] testSource, Function importFun, BiConsumer check) throws IOException {
final Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID);
contextBuilder.option("wasm.Builtins", "testutil:testutil");
try (Context context = contextBuilder.build()) {
Source.Builder sourceBuilder = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(binaryWithExports), "main");
Source source = sourceBuilder.build();
- context.eval(source);
- Value main = context.getBindings(WasmLanguage.ID).getMember("main").getMember("main");
+
+ Value mainModule = context.eval(source);
+ Value mainInstance = mainModule.newInstance();
+
+ Value main = mainInstance.getMember("exports").getMember("main");
main.execute();
- Value run = context.getBindings(WasmLanguage.ID).getMember("testutil").getMember(TestutilModule.Names.RUN_CUSTOM_INITIALIZATION);
- run.execute(new GuestCode(c -> {
+
+ WasmTestUtils.runInWasmContext(context, c -> {
WebAssembly wasm = new WebAssembly(c);
- WasmModule module = wasm.moduleDecode(sourceFun.apply(wasm));
- WasmInstance instance1 = wasm.moduleInstantiate(module, importFun.apply(wasm));
+ WasmModule module = wasm.moduleDecode(testSource);
+ WasmInstance instance1 = wasm.moduleInstantiate(module, Objects.requireNonNullElse(importFun.apply(wasm), WasmConstant.NULL));
Value v1 = Value.asValue(instance1);
// link module
v1.getMember("main");
- WasmInstance instance2 = wasm.moduleInstantiate(module, importFun.apply(wasm));
+ WasmInstance instance2 = wasm.moduleInstantiate(module, Objects.requireNonNullElse(importFun.apply(wasm), WasmConstant.NULL));
Value v2 = Value.asValue(instance2);
// link module
v2.getMember("main");
check.accept(wasm, instance2);
- }));
- }
- }
-
- private static final class GuestCode implements Consumer, TruffleObject {
- private final Consumer testCase;
-
- private GuestCode(Consumer testCase) {
- this.testCase = testCase;
- }
-
- @Override
- public void accept(WasmContext context) {
- testCase.accept(context);
+ });
}
}
@@ -183,7 +171,7 @@ public void testImportsAndExports() throws IOException, InterruptedException {
)
""");
final Executable tableFun = new Executable(args -> 13);
- test(wasm -> source, wasm -> {
+ test(source, wasm -> {
final Dictionary imports = new Dictionary();
final Dictionary a = new Dictionary();
@@ -246,7 +234,7 @@ public void testGlobalInitialization() throws IOException, InterruptedException
(global (export "g6") (mut funcref) (ref.func 0))
)
""");
- test(wasm -> source, wasm -> {
+ test(source, wasm -> {
WasmGlobal g = wasm.globalAlloc(ValueType.i32, false, 16);
return Dictionary.create(new Object[]{"a", Dictionary.create(new Object[]{"g", g})});
}, (wasm, i) -> {
@@ -318,7 +306,7 @@ public void testTableInitialization() throws IOException, InterruptedException {
(elem (i32.const 0) func 0)
)
""");
- test(wasm -> source, wasm -> null, (wasm, i) -> {
+ test(source, wasm -> null, (wasm, i) -> {
InteropLibrary lib = InteropLibrary.getUncached();
try {
final Object tableRead = wasm.readMember("table_read");
@@ -350,7 +338,7 @@ public void testMemoryInitialization() throws IOException, InterruptedException
(data (i32.const 0) "\\01\\02\\03\\04")
)
""");
- test(wasm -> source, wasm -> null, (wasm, i) -> {
+ test(source, wasm -> null, (wasm, i) -> {
final Value m = Value.asValue(WebAssembly.instanceExport(i, "m"));
final int b0 = m.getArrayElement(0).asByte();
@@ -387,7 +375,7 @@ public void testDataSections() throws IOException, InterruptedException {
(data "\\08\\09\\0A\\0B")
)
""");
- test(wasm -> source, wasm -> null, (wasm, i) -> {
+ test(source, wasm -> null, (wasm, i) -> {
final Value m = Value.asValue(WebAssembly.instanceExport(i, "m"));
final int b0 = m.getArrayElement(0).asByte();
@@ -457,7 +445,7 @@ public void testElemSections() throws IOException, InterruptedException {
(elem func 0 1 2 3)
)
""");
- test(wasm -> source, wasm -> null, (wasm, i) -> {
+ test(source, wasm -> null, (wasm, i) -> {
InteropLibrary lib = InteropLibrary.getUncached();
try {
final Object tableRead = wasm.readMember("table_read");
@@ -522,8 +510,7 @@ public void testMultiValueReturn() throws IOException, InterruptedException {
final Context.Builder contextBuilder = Context.newBuilder(WasmLanguage.ID);
contextBuilder.option("wasm.Builtins", "testutil:testutil");
try (Context context = contextBuilder.build()) {
- Value run = context.getBindings(WasmLanguage.ID).getMember("testutil").getMember(TestutilModule.Names.RUN_CUSTOM_INITIALIZATION);
- run.execute(new GuestCode(c -> {
+ WasmTestUtils.runInWasmContext(context, c -> {
WebAssembly wasm = new WebAssembly(c);
WasmModule module = wasm.moduleDecode(sourceCode);
@@ -542,17 +529,17 @@ public void testMultiValueReturn() throws IOException, InterruptedException {
WasmInstance instance2 = wasm.moduleInstantiate(module, imports2);
- Value v1 = context.asValue(instance1);
+ Value v1 = context.asValue(instance1).getMember("exports");
v1.getMember("main");
Value main1 = v1.getMember("main");
Value result1 = main1.execute();
Assert.assertEquals("Return value of main", List.of(42L, 1, 2, 3.14), List.copyOf(result1.as(List.class)));
- Value v2 = context.asValue(instance2);
+ Value v2 = context.asValue(instance2).getMember("exports");
Value main2 = v2.getMember("main");
Value result2 = main2.execute();
Assert.assertEquals("Return value of main", List.of(42L, 6, 8, 2.72), List.copyOf(result2.as(List.class)));
- }));
+ });
}
}
}
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/WasmOSRSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/WasmOSRSuite.java
index c0ca8a33f34c..4c45f419fa8b 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/WasmOSRSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/WasmOSRSuite.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -93,8 +93,8 @@ public void testOSR() throws IOException, InterruptedException {
try (Engine engine = eb.build()) {
for (int i = 0; i < N_CONTEXTS; i++) {
try (Context context = Context.newBuilder(WasmLanguage.ID).engine(engine).build()) {
- Value mainMod = context.eval(sourceMain);
- Value mainFun = mainMod.getMember("_main");
+ Value mainExports = context.eval(sourceMain).newInstance().getMember("exports");
+ Value mainFun = mainExports.getMember("_main");
Assert.assertEquals(0, mainFun.execute().asInt());
}
}
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/debugging/DebugValidationSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/debugging/DebugValidationSuite.java
index e517ef37c953..3063bf52f758 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/debugging/DebugValidationSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/debugging/DebugValidationSuite.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -789,7 +789,7 @@ private static void runTest(byte[] data) throws IOException {
DebuggerSession session = debugger.startSession(event -> {
});
try {
- Value val = context.eval(source);
+ Value val = context.eval(source).newInstance().getMember("exports");
val.getMember("_main").execute();
session.suspendNextExecution();
} finally {
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java
index 89f1b6d2f237..a661439f0199 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java
@@ -1022,7 +1022,7 @@ public void test() throws IOException {
final Source source = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(bytecode), "dummy_main").build();
final Context context = contextBuilder.build();
try {
- context.eval(source).getMember("_main").execute();
+ context.eval(source).newInstance().getMember("exports").getMember("_main").execute();
} catch (final PolyglotException e) {
final Value actualFailureObject = e.getGuestObject();
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/wasi/WasiOptionsSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/wasi/WasiOptionsSuite.java
index 273135f170b9..f480882a5e8b 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/wasi/WasiOptionsSuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/wasi/WasiOptionsSuite.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -189,8 +189,7 @@ public void test() throws IOException, InterruptedException {
contextBuilder.option("wasm.WasiMapDirs", dir);
try (Context context = contextBuilder.build()) {
final Source s = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(source), "main").build();
- context.eval(s);
- final Value main = context.getBindings(WasmLanguage.ID).getMember("main");
+ final Value main = context.eval(s).newInstance().getMember("exports");
final Value relative = main.getMember("relative");
final Value direct = main.getMember("direct");
final Value absolute = main.getMember("absolute");
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/GlobalRegistry.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/GlobalRegistry.java
index 74d50c8e24dd..75b88a7189c7 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/GlobalRegistry.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/GlobalRegistry.java
@@ -40,13 +40,13 @@
*/
package org.graalvm.wasm;
-import com.oracle.truffle.api.CompilerDirectives;
import org.graalvm.wasm.api.Vector128;
import org.graalvm.wasm.exception.Failure;
import org.graalvm.wasm.exception.WasmException;
import org.graalvm.wasm.globals.WasmGlobal;
import com.oracle.truffle.api.CompilerAsserts;
+import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
/**
@@ -59,12 +59,13 @@
public class GlobalRegistry {
private static final int INITIAL_GLOBALS_SIZE = 8;
- // If we support late linking, we need to ensure that methods accessing the global array
- // are compiled with assumptions on what this field points to.
- // Such an assumption can be invalidated if the late-linking causes this array
- // to be replaced with a larger array.
- @CompilationFinal(dimensions = 0) private long[] globals;
- @CompilationFinal(dimensions = 0) private Object[] objectGlobals;
+ /**
+ * If we support late linking, and the global arrays are @CompilationFinal, we need to ensure
+ * that methods accessing the global array are compiled with assumptions that will be
+ * invalidated if the late linking causes this array to be replaced with a larger array.
+ */
+ private long[] globals;
+ private Object[] objectGlobals;
@CompilationFinal(dimensions = 1) private WasmGlobal[] externalGlobals;
private int globalCount;
private int externalGlobalCount;
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ImportValueSupplier.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ImportValueSupplier.java
index 2d12f44da024..8ebdfb5e6302 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ImportValueSupplier.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ImportValueSupplier.java
@@ -54,4 +54,26 @@ public interface ImportValueSupplier {
* @param intoInstance importing module instance of the import to be resolved.
*/
Object get(ImportDescriptor importDesc, WasmInstance intoInstance);
+
+ /**
+ * Creates a union of this and another supplier.
+ */
+ default ImportValueSupplier andThen(ImportValueSupplier next) {
+ if (this == none()) {
+ return next;
+ } else if (next == none()) {
+ return this;
+ }
+ return (importDesc, intoInstance) -> {
+ Object found = this.get(importDesc, intoInstance);
+ if (found != null) {
+ return found;
+ }
+ return next.get(importDesc, intoInstance);
+ };
+ }
+
+ static ImportValueSupplier none() {
+ return (importDesc, intoInstance) -> null;
+ }
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java
index e552a03f9826..f2b2e12bc729 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/Linker.java
@@ -160,7 +160,7 @@ private static WasmException linkFailedError(WasmInstance instance) {
@CompilerDirectives.TruffleBoundary
private void tryLinkOutsidePartialEvaluation(WasmInstance entryPointInstance) {
- tryLinkOutsidePartialEvaluation(entryPointInstance, null);
+ tryLinkOutsidePartialEvaluation(entryPointInstance, ImportValueSupplier.none());
}
/**
@@ -188,9 +188,10 @@ private void tryLinkOutsidePartialEvaluation(WasmInstance entryPointInstance, Im
if (resolutionDag == null) {
resolutionDag = new ResolutionDag();
}
+ var importValues = imports.andThen(store.instantiateBuiltinInstances());
Map instances = store.moduleInstances();
ArrayList failures = new ArrayList<>();
- final int maxStartFunctionIndex = runLinkActions(store, instances, imports, failures);
+ final int maxStartFunctionIndex = runLinkActions(store, instances, importValues, failures);
linkTopologically(store, failures, maxStartFunctionIndex);
assignTypeEquivalenceClasses(store);
resolutionDag = null;
@@ -476,8 +477,6 @@ void resolveMemoryImport(WasmStore store, WasmInstance instance, ImportDescripto
boolean shared, ImportValueSupplier imports) {
final String importedModuleName = importDescriptor.moduleName();
final String importedMemoryName = importDescriptor.memberName();
- // Special import of main module memory into WASI built-in module.
- final boolean importsMainMemory = instance.module().isBuiltin() && importedModuleName.equals("main") && importedMemoryName.equals("memory");
final Runnable resolveAction = () -> {
final WasmMemory importedMemory;
final WasmMemory externalMemory = lookupImportObject(instance, importDescriptor, imports, WasmMemory.class);
@@ -486,7 +485,9 @@ void resolveMemoryImport(WasmStore store, WasmInstance instance, ImportDescripto
importedMemory = store.memories().memory(contextMemoryIndex);
assert memoryIndex == importDescriptor.targetIndex();
} else {
- final WasmInstance importedInstance = importsMainMemory ? store.lookupMainModule() : store.lookupModuleInstance(importedModuleName);
+ // WASIp1 memory import should have been resolved via ImportValueSupplier above.
+ assert !instance.module().isBuiltin() : importDescriptor;
+ final WasmInstance importedInstance = store.lookupModuleInstance(importedModuleName);
if (importedInstance == null) {
throw WasmException.create(Failure.UNKNOWN_IMPORT, String.format("The module '%s', referenced in the import of memory '%s' in module '%s', does not exist",
importedModuleName, importedMemoryName, instance.name()));
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContextOptions.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContextOptions.java
index 16b2abf4f8e3..3547f5dc99f4 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContextOptions.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContextOptions.java
@@ -65,6 +65,7 @@ public final class WasmContextOptions {
@CompilationFinal private boolean constantRandomGet;
@CompilationFinal private boolean directByteBufferMemoryAccess;
@CompilationFinal private boolean debugTestMode;
+ @CompilationFinal private boolean evalReturnsInstance;
private final OptionValues optionValues;
@@ -94,6 +95,7 @@ private void setOptionValues() {
this.constantRandomGet = readBooleanOption(WasmOptions.WasiConstantRandomGet);
this.directByteBufferMemoryAccess = readBooleanOption(WasmOptions.DirectByteBufferMemoryAccess);
this.debugTestMode = readBooleanOption(WasmOptions.DebugTestMode);
+ this.evalReturnsInstance = readBooleanOption(WasmOptions.EvalReturnsInstance);
}
private void checkOptionDependencies() {
@@ -173,6 +175,10 @@ public boolean debugTestMode() {
return debugTestMode;
}
+ public boolean evalReturnsInstance() {
+ return evalReturnsInstance;
+ }
+
@Override
public int hashCode() {
int hash = 5;
@@ -190,6 +196,7 @@ public int hashCode() {
hash = 53 * hash + (this.constantRandomGet ? 1 : 0);
hash = 53 * hash + (this.directByteBufferMemoryAccess ? 1 : 0);
hash = 53 * hash + (this.debugTestMode ? 1 : 0);
+ hash = 53 * hash + (this.evalReturnsInstance ? 1 : 0);
return hash;
}
@@ -246,6 +253,9 @@ public boolean equals(Object obj) {
if (this.debugTestMode != other.debugTestMode) {
return false;
}
+ if (this.evalReturnsInstance != other.evalReturnsInstance) {
+ return false;
+ }
return true;
}
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstance.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstance.java
index 6b91395e7c0d..f201403ac0fb 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstance.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstance.java
@@ -40,18 +40,12 @@
*/
package org.graalvm.wasm;
-import java.util.ArrayList;
import java.util.List;
-import org.graalvm.wasm.api.Sequence;
-import org.graalvm.wasm.constants.GlobalModifier;
-
-import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
-import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
@@ -123,6 +117,8 @@ protected WasmInstance instance() {
return this;
}
+ private static final String EXPORTS_MEMBER = "exports";
+
@ExportMessage
boolean hasMembers() {
return true;
@@ -130,132 +126,25 @@ boolean hasMembers() {
@ExportMessage
@TruffleBoundary
- public Object readMember(String member) throws UnknownIdentifierException {
- ensureLinked();
- final SymbolTable symbolTable = symbolTable();
- final WasmFunction function = symbolTable.exportedFunctions().get(member);
- if (function != null) {
- return functionInstance(function);
- }
- final Integer tableIndex = symbolTable.exportedTables().get(member);
- if (tableIndex != null) {
- return store().tables().table(tableAddress(tableIndex));
- }
- final Integer memoryIndex = symbolTable.exportedMemories().get(member);
- if (memoryIndex != null) {
- return memory(memoryIndex);
- }
- final Integer globalIndex = symbolTable.exportedGlobals().get(member);
- if (globalIndex != null) {
- return readGlobal(symbolTable, globalIndex);
- }
- throw UnknownIdentifierException.create(member);
+ boolean isMemberReadable(String member) {
+ return EXPORTS_MEMBER.equals(member);
}
@ExportMessage
@TruffleBoundary
- public void writeMember(String member, Object value) throws UnknownIdentifierException, UnsupportedMessageException {
- ensureLinked();
- // This method works only for mutable globals.
- final SymbolTable symbolTable = symbolTable();
- final Integer index = symbolTable.exportedGlobals().get(member);
- if (index == null) {
+ Object readMember(String member) throws UnknownIdentifierException {
+ if (!isMemberReadable(member)) {
throw UnknownIdentifierException.create(member);
}
- final int address = globalAddress(index);
- if (!(value instanceof Number)) {
- throw UnsupportedMessageException.create();
- }
- final boolean mutable = symbolTable.globalMutability(index) == GlobalModifier.MUTABLE;
- if (module().isParsed() && !mutable) {
- // Constant variables cannot be modified after linking.
- throw UnsupportedMessageException.create();
- }
- long longValue = ((Number) value).longValue();
- store().globals().storeLong(address, longValue);
- }
-
- @ExportMessage
- @TruffleBoundary
- boolean isMemberReadable(String member) {
ensureLinked();
- final SymbolTable symbolTable = symbolTable();
- try {
- return symbolTable.exportedFunctions().containsKey(member) ||
- symbolTable.exportedMemories().containsKey(member) ||
- symbolTable.exportedTables().containsKey(member) ||
- symbolTable.exportedGlobals().containsKey(member);
- } catch (NumberFormatException exc) {
- return false;
- }
- }
-
- @ExportMessage
- @TruffleBoundary
- boolean isMemberModifiable(String member) {
- ensureLinked();
- final SymbolTable symbolTable = symbolTable();
- final Integer index = symbolTable.exportedGlobals().get(member);
- if (index == null) {
- return false;
- }
- return symbolTable.globalMutability(index) == GlobalModifier.MUTABLE;
- }
-
- @ExportMessage
- @TruffleBoundary
- boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
- return false;
- }
-
- private Object readGlobal(SymbolTable symbolTable, int globalIndex) {
- final int address = globalAddress(globalIndex);
- final GlobalRegistry globals = store().globals();
- final byte type = symbolTable.globalValueType(globalIndex);
- switch (type) {
- case WasmType.I32_TYPE:
- return globals.loadAsInt(address);
- case WasmType.I64_TYPE:
- return globals.loadAsLong(address);
- case WasmType.F32_TYPE:
- return Float.intBitsToFloat(globals.loadAsInt(address));
- case WasmType.F64_TYPE:
- return Double.longBitsToDouble(globals.loadAsLong(address));
- case WasmType.V128_TYPE:
- return globals.loadAsVector128(address);
- case WasmType.FUNCREF_TYPE:
- case WasmType.EXTERNREF_TYPE:
- return globals.loadAsReference(address);
- default:
- CompilerDirectives.transferToInterpreter();
- throw new RuntimeException("Unknown type: " + type);
- }
+ assert EXPORTS_MEMBER.equals(member) : member;
+ return new WasmInstanceExports(this);
}
@ExportMessage
@TruffleBoundary
Object getMembers(@SuppressWarnings("unused") boolean includeInternal) {
- ensureLinked();
- // TODO: Handle includeInternal.
- final SymbolTable symbolTable = symbolTable();
- final List exportNames = new ArrayList<>();
- for (String functionName : symbolTable.exportedFunctions().getKeys()) {
- exportNames.add(functionName);
- }
- for (String tableName : symbolTable.exportedTables().getKeys()) {
- exportNames.add(tableName);
- }
- for (String memoryName : symbolTable.exportedMemories().getKeys()) {
- exportNames.add(memoryName);
- }
- for (String globalName : symbolTable.exportedGlobals().getKeys()) {
- exportNames.add(globalName);
- }
- return new Sequence<>(exportNames);
- }
-
- public boolean isBuiltin() {
- return module().isBuiltin();
+ return new WasmNamesObject(new String[]{EXPORTS_MEMBER});
}
@Override
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstanceExports.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstanceExports.java
new file mode 100644
index 000000000000..226408cd7f99
--- /dev/null
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstanceExports.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * The Universal Permissive License (UPL), Version 1.0
+ *
+ * Subject to the condition set forth below, permission is hereby granted to any
+ * person obtaining a copy of this software, associated documentation and/or
+ * data (collectively the "Software"), free of charge and under any and all
+ * copyright rights in the Software, and any and all patent rights owned or
+ * freely licensable by each licensor hereunder covering either (i) the
+ * unmodified Software as contributed to or provided by such licensor, or (ii)
+ * the Larger Works (as defined below), to deal in both
+ *
+ * (a) the Software, and
+ *
+ * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
+ * one is included with the Software each a "Larger Work" to which the Software
+ * is contributed by such licensors),
+ *
+ * without restriction, including without limitation the rights to copy, create
+ * derivative works of, display, perform, and distribute the Software and make,
+ * use, sell, offer for sale, import, export, have made, and have sold the
+ * Software and the Larger Work(s), and to sublicense the foregoing rights on
+ * either these or other terms.
+ *
+ * This license is subject to the following condition:
+ *
+ * The above copyright notice and either this complete permission notice or at a
+ * minimum a reference to the UPL must be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.graalvm.wasm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.graalvm.wasm.api.Sequence;
+import org.graalvm.wasm.api.ValueType;
+import org.graalvm.wasm.globals.ExportedWasmGlobal;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.interop.ArityException;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.interop.UnknownIdentifierException;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.interop.UnsupportedTypeException;
+import com.oracle.truffle.api.library.ExportLibrary;
+import com.oracle.truffle.api.library.ExportMessage;
+
+@ExportLibrary(InteropLibrary.class)
+@SuppressWarnings("static-method")
+public final class WasmInstanceExports implements TruffleObject {
+ private final WasmInstance instance;
+
+ public WasmInstanceExports(WasmInstance instance) {
+ this.instance = instance;
+ }
+
+ @ExportMessage
+ boolean hasMembers() {
+ return true;
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ public Object readMember(String member) throws UnknownIdentifierException {
+ final SymbolTable symbolTable = instance.symbolTable();
+ final WasmFunction function = symbolTable.exportedFunctions().get(member);
+ if (function != null) {
+ return instance.functionInstance(function);
+ }
+ final Integer tableIndex = symbolTable.exportedTables().get(member);
+ if (tableIndex != null) {
+ return instance.store().tables().table(instance.tableAddress(tableIndex));
+ }
+ final Integer memoryIndex = symbolTable.exportedMemories().get(member);
+ if (memoryIndex != null) {
+ return instance.memory(memoryIndex);
+ }
+ final Integer globalIndex = symbolTable.exportedGlobals().get(member);
+ if (globalIndex != null) {
+ return readGlobal(symbolTable, globalIndex);
+ }
+ throw UnknownIdentifierException.create(member);
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ boolean isMemberReadable(String member) {
+ final SymbolTable symbolTable = instance.symbolTable();
+ return symbolTable.exportedFunctions().containsKey(member) ||
+ symbolTable.exportedMemories().containsKey(member) ||
+ symbolTable.exportedTables().containsKey(member) ||
+ symbolTable.exportedGlobals().containsKey(member);
+ }
+
+ private Object readGlobal(SymbolTable symbolTable, int globalIndex) {
+ final int address = instance.globalAddress(globalIndex);
+ if (address < 0) {
+ return instance.store().globals().externalGlobal(address);
+ } else {
+ final ValueType valueType = ValueType.fromByteValue(symbolTable.globalValueType(globalIndex));
+ final boolean mutable = symbolTable.isGlobalMutable(globalIndex);
+ return new ExportedWasmGlobal(valueType, mutable, instance.store().globals(), address);
+ }
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ boolean isMemberInvocable(String member) {
+ final SymbolTable symbolTable = instance.symbolTable();
+ return symbolTable.exportedFunctions().containsKey(member);
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ Object invokeMember(String member, Object... arguments) throws UnknownIdentifierException, UnsupportedMessageException, UnsupportedTypeException, ArityException {
+ if (!isMemberInvocable(member)) {
+ throw UnknownIdentifierException.create(member);
+ }
+ final SymbolTable symbolTable = instance.symbolTable();
+ final WasmFunction function = symbolTable.exportedFunctions().get(member);
+ if (function != null) {
+ final WasmFunctionInstance functionInstance = instance.functionInstance(function);
+ final InteropLibrary lib = InteropLibrary.getUncached(functionInstance);
+ assert lib.isExecutable(functionInstance);
+ return lib.execute(functionInstance, arguments);
+ }
+ return WasmConstant.VOID;
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ Object getMembers(@SuppressWarnings("unused") boolean includeInternal) {
+ // TODO: Handle includeInternal.
+ final SymbolTable symbolTable = instance.symbolTable();
+ final List exportNames = new ArrayList<>();
+ for (String functionName : symbolTable.exportedFunctions().getKeys()) {
+ exportNames.add(functionName);
+ }
+ for (String tableName : symbolTable.exportedTables().getKeys()) {
+ exportNames.add(tableName);
+ }
+ for (String memoryName : symbolTable.exportedMemories().getKeys()) {
+ exportNames.add(memoryName);
+ }
+ for (String globalName : symbolTable.exportedGlobals().getKeys()) {
+ exportNames.add(globalName);
+ }
+ return new Sequence<>(exportNames);
+ }
+
+ @Override
+ public String toString() {
+ return "wasm-instance-exports(" + instance.name() + ")";
+ }
+}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java
index a192873ead2c..92c814252ff8 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstantiator.java
@@ -267,7 +267,8 @@ static List recreateLinkActions(WasmModule module) {
}
final byte[] dataOffsetBytecode;
final long dataOffsetAddress;
- if ((encoding & BytecodeBitEncoding.DATA_SEG_BYTECODE_OR_OFFSET_MASK) == BytecodeBitEncoding.DATA_SEG_BYTECODE) {
+ if ((encoding & BytecodeBitEncoding.DATA_SEG_BYTECODE_OR_OFFSET_MASK) == BytecodeBitEncoding.DATA_SEG_BYTECODE &&
+ ((encoding & BytecodeBitEncoding.DATA_SEG_VALUE_MASK) != BytecodeBitEncoding.DATA_SEG_VALUE_UNDEFINED)) {
int dataOffsetBytecodeLength = (int) value;
dataOffsetBytecode = Arrays.copyOfRange(bytecode, effectiveOffset, effectiveOffset + dataOffsetBytecodeLength);
effectiveOffset += dataOffsetBytecodeLength;
@@ -285,7 +286,7 @@ static List recreateLinkActions(WasmModule module) {
effectiveOffset++;
switch (memoryIndexEncoding & BytecodeBitEncoding.DATA_SEG_MEMORY_INDEX_MASK) {
case BytecodeBitEncoding.DATA_SEG_MEMORY_INDEX_U6:
- memoryIndex = encoding & BytecodeBitEncoding.DATA_SEG_MEMORY_INDEX_VALUE;
+ memoryIndex = memoryIndexEncoding & BytecodeBitEncoding.DATA_SEG_MEMORY_INDEX_VALUE;
break;
case BytecodeBitEncoding.DATA_SEG_MEMORY_INDEX_U8:
memoryIndex = BinaryStreamParser.rawPeekU8(bytecode, effectiveOffset);
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmLanguage.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmLanguage.java
index 8db365a5020c..0131277a35ad 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmLanguage.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmLanguage.java
@@ -163,21 +163,27 @@ private ParsedWasmModuleRootNode(WasmLanguage language, WasmModule module, Sourc
* The CallTarget returned by {@code parse} supports two calling conventions:
*
*
- *
(default) zero arguments provided: on the first call, instantiates the decoded module
- * and puts it in the context's module instance map; then returns the {@link WasmInstance}.
- *
first argument is {@code "module_decode"}: returns the decoded {@link WasmModule}
- * (i.e. behaves like {@link WebAssembly#moduleDecode module_decode}). Used by the JS API.
+ *
(default) returns the decoded {@link WasmModule} (i.e. behaves like
+ * {@link WebAssembly#moduleDecode module_decode} in the JS API).
+ *
(enabled with {@link WasmOptions#EvalReturnsInstance}), instantiates the decoded
+ * module and puts it in the context's module instance map; then returns the
+ * {@link WasmInstance}.
*
*/
@Override
public Object execute(VirtualFrame frame) {
if (frame.getArguments().length == 0) {
- final WasmStore contextStore = WasmContext.get(this).contextStore();
- WasmInstance instance = contextStore.lookupModuleInstance(module);
- if (instance == null) {
- instance = contextStore.readInstance(module);
+ final WasmContext context = WasmContext.get(this);
+ if (context.getContextOptions().evalReturnsInstance()) {
+ final WasmStore contextStore = context.contextStore();
+ WasmInstance instance = contextStore.lookupModuleInstance(module);
+ if (instance == null) {
+ instance = contextStore.readInstance(module);
+ }
+ return instance;
+ } else {
+ return module;
}
- return instance;
} else {
if (frame.getArguments()[0] instanceof String mode) {
if (mode.equals(MODULE_DECODE)) {
@@ -203,7 +209,7 @@ WasmModule getModule() {
/**
* Parses simple expressions required to modify values during debugging.
- *
+ *
* @param request request for parsing
*/
@Override
@@ -249,7 +255,7 @@ protected void finalizeContext(WasmContext context) {
super.finalizeContext(context);
context.memoryContext().close();
try {
- context.contextStore().fdManager().close();
+ context.fdManager().close();
} catch (IOException e) {
throw new RuntimeException("Error while closing WasmFilesManager.");
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmModule.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmModule.java
index 05229ad6f01f..ff61dd18f33a 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmModule.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmModule.java
@@ -44,22 +44,36 @@
import java.lang.invoke.VarHandle;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
import org.graalvm.collections.EconomicMap;
+import org.graalvm.wasm.constants.ImportIdentifier;
import org.graalvm.wasm.debugging.data.DebugFunction;
import org.graalvm.wasm.debugging.parser.DebugTranslator;
+import org.graalvm.wasm.exception.ExceptionProvider;
+import org.graalvm.wasm.exception.Failure;
+import org.graalvm.wasm.exception.WasmException;
+import org.graalvm.wasm.globals.WasmGlobal;
+import org.graalvm.wasm.memory.WasmMemory;
import org.graalvm.wasm.parser.ir.CodeEntry;
+import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.interop.UnknownIdentifierException;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.library.ExportLibrary;
+import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.source.Source;
/**
* Represents a parsed and validated WebAssembly module, which has not yet been instantiated.
*/
+@ExportLibrary(InteropLibrary.class)
@SuppressWarnings("static-method")
public final class WasmModule extends SymbolTable implements TruffleObject {
private final String name;
@@ -270,4 +284,134 @@ public EconomicMap debugFunctions() {
}
return debugFunctions;
}
+
+ @ExportMessage
+ boolean isInstantiable() {
+ return true;
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ Object instantiate(Object... arguments) {
+ final WasmContext context = WasmContext.get(null);
+ final Object importObject;
+ if (arguments.length == 0) {
+ importObject = WasmConstant.NULL;
+ } else if (arguments.length == 1) {
+ importObject = arguments[0];
+ } else {
+ throw WasmException.provider().createTypeError(Failure.TYPE_MISMATCH, "Can only provide a single import object.");
+ }
+ final WasmStore store = new WasmStore(context, context.language());
+ return createInstance(store, importObject, WasmException.provider(), false);
+ }
+
+ public WasmInstance createInstance(WasmStore store, Object importObject, ExceptionProvider exceptionProvider, boolean importsOnlyInImportObject) {
+ final WasmInstance instance = store.readInstance(this);
+ var imports = resolveModuleImports(importObject, exceptionProvider, importsOnlyInImportObject);
+ store.linker().tryLink(instance, imports);
+ return instance;
+ }
+
+ private ImportValueSupplier resolveModuleImports(Object importObject, ExceptionProvider exceptionProvider, boolean importsOnlyInImportObject) {
+ CompilerAsserts.neverPartOfCompilation();
+ Objects.requireNonNull(importObject);
+ List