Skip to content

Commit 90732e1

Browse files
committed
Fix importing globals from another module instance's exports.
Reading a global export now returns a WasmGlobal instance instead of its value.
1 parent a88f9ff commit 90732e1

File tree

4 files changed

+169
-64
lines changed

4 files changed

+169
-64
lines changed

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,79 @@ public void instantiateModuleWithExportsFromAnotherModule() {
389389
}
390390
}
391391

392+
@Test
393+
public void instantiateModuleWithGlobalImport() throws IOException, InterruptedException {
394+
final ByteSequence supplierBytes = ByteSequence.create(compileWat("supplier", """
395+
(module
396+
(global (export "g") i32 (i32.const 42))
397+
)
398+
"""));
399+
final ByteSequence consumerBytes = ByteSequence.create(compileWat("consumer", """
400+
(module
401+
(import "supplier" "g" (global $g i32))
402+
(export "gg" (global $g))
403+
(func (export "main") (result i32)
404+
(global.get $g)
405+
)
406+
)
407+
"""));
408+
409+
try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
410+
final Value supplierModule = context.eval(Source.newBuilder(WasmLanguage.ID, supplierBytes, "supplier").build());
411+
final Value consumerModule = context.eval(Source.newBuilder(WasmLanguage.ID, consumerBytes, "consumer").build());
412+
413+
final Value supplierInstance = supplierModule.newInstance();
414+
final Value consumerInstance = consumerModule.newInstance(ProxyObject.fromMap(Map.of(
415+
"supplier", supplierInstance.getMember("exports"))));
416+
417+
final Value consumerExports = consumerInstance.getMember("exports");
418+
final Value result = consumerExports.invokeMember("main");
419+
final Value global = consumerExports.getMember("gg");
420+
421+
Assert.assertEquals(42, result.asInt());
422+
Assert.assertEquals(42, global.getMember("value").asInt());
423+
424+
Assert.assertThrows(UnsupportedOperationException.class, () -> global.putMember("value", 43));
425+
}
426+
}
427+
428+
@Test
429+
public void mutableGlobals() throws IOException, InterruptedException {
430+
final ByteSequence globalsBytes = ByteSequence.create(compileWat("mutable-globals", """
431+
(module
432+
(global (export "global-i32") (mut i32) (i32.const 42))
433+
(global (export "global-i64") (mut i64) (i64.const 0x1_ffff_ffff))
434+
(global (export "global-f32") (mut f32) (f32.const 3.14))
435+
(global (export "global-f64") (mut f64) (f64.const 3.14))
436+
)
437+
"""));
438+
439+
try (Context context = Context.newBuilder(WasmLanguage.ID).build()) {
440+
final Source source = Source.newBuilder(WasmLanguage.ID, globalsBytes, "mutable-globals").build();
441+
final Value globals = context.eval(source).newInstance().getMember("exports");
442+
443+
final Value globalI32 = globals.getMember("global-i32");
444+
Assert.assertEquals(42, globalI32.getMember("value").asInt());
445+
globalI32.putMember("value", 43);
446+
Assert.assertEquals(43, globalI32.getMember("value").asInt());
447+
448+
final Value globalI64 = globals.getMember("global-i64");
449+
Assert.assertEquals(0x1_ffff_ffffL, globalI64.getMember("value").asLong());
450+
globalI64.putMember("value", -1L);
451+
Assert.assertEquals(-1L, globalI64.getMember("value").asLong());
452+
453+
final Value globalF32 = globals.getMember("global-f32");
454+
Assert.assertEquals(3.14f, globalF32.getMember("value").asFloat(), 0.0f);
455+
globalF32.putMember("value", 13.37f);
456+
Assert.assertEquals(13.37f, globalF32.getMember("value").asFloat(), 0.0f);
457+
458+
final Value globalF64 = globals.getMember("global-f64");
459+
Assert.assertEquals(3.14, globalF64.getMember("value").asDouble(), 0.0);
460+
globalF64.putMember("value", 13.37);
461+
Assert.assertEquals(13.37, globalF64.getMember("value").asDouble(), 0.0);
462+
}
463+
}
464+
392465
@Test
393466
public void newInstanceWASI() throws IOException, InterruptedException {
394467
final ByteSequence mainModuleBytes = ByteSequence.create(compileWat("main", """

wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmInstanceExports.java

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@
4545
import java.util.List;
4646

4747
import org.graalvm.wasm.api.Sequence;
48-
import org.graalvm.wasm.constants.GlobalModifier;
48+
import org.graalvm.wasm.api.ValueType;
49+
import org.graalvm.wasm.globals.ExportedWasmGlobal;
4950

50-
import com.oracle.truffle.api.CompilerDirectives;
5151
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
5252
import com.oracle.truffle.api.interop.ArityException;
5353
import com.oracle.truffle.api.interop.InteropLibrary;
@@ -60,7 +60,7 @@
6060

6161
@ExportLibrary(InteropLibrary.class)
6262
@SuppressWarnings("static-method")
63-
public class WasmInstanceExports implements TruffleObject {
63+
public final class WasmInstanceExports implements TruffleObject {
6464
private final WasmInstance instance;
6565

6666
public WasmInstanceExports(WasmInstance instance) {
@@ -95,29 +95,6 @@ public Object readMember(String member) throws UnknownIdentifierException {
9595
throw UnknownIdentifierException.create(member);
9696
}
9797

98-
@ExportMessage
99-
@TruffleBoundary
100-
public void writeMember(String member, Object value) throws UnknownIdentifierException, UnsupportedMessageException {
101-
// This method works only for mutable globals.
102-
final SymbolTable symbolTable = instance.symbolTable();
103-
final Integer index = symbolTable.exportedGlobals().get(member);
104-
if (index == null) {
105-
throw UnknownIdentifierException.create(member);
106-
}
107-
final int address = instance.globalAddress(index);
108-
if (!(value instanceof Number)) {
109-
throw UnsupportedMessageException.create();
110-
}
111-
final boolean mutable = symbolTable.globalMutability(index) == GlobalModifier.MUTABLE;
112-
assert instance.module().isParsed() : instance;
113-
if (!mutable) {
114-
// Constant variables cannot be modified after linking.
115-
throw UnsupportedMessageException.create();
116-
}
117-
long longValue = ((Number) value).longValue();
118-
instance.store().globals().storeLong(address, longValue);
119-
}
120-
12198
@ExportMessage
12299
@TruffleBoundary
123100
boolean isMemberReadable(String member) {
@@ -128,39 +105,15 @@ boolean isMemberReadable(String member) {
128105
symbolTable.exportedGlobals().containsKey(member);
129106
}
130107

131-
@ExportMessage
132-
@TruffleBoundary
133-
boolean isMemberModifiable(String member) {
134-
final SymbolTable symbolTable = instance.symbolTable();
135-
final Integer index = symbolTable.exportedGlobals().get(member);
136-
if (index == null) {
137-
return false;
138-
}
139-
return symbolTable.globalMutability(index) == GlobalModifier.MUTABLE;
140-
}
141-
142-
@ExportMessage
143-
@TruffleBoundary
144-
boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
145-
return false;
146-
}
147-
148108
private Object readGlobal(SymbolTable symbolTable, int globalIndex) {
149109
final int address = instance.globalAddress(globalIndex);
150-
final GlobalRegistry globals = instance.store().globals();
151-
final byte type = symbolTable.globalValueType(globalIndex);
152-
return switch (type) {
153-
case WasmType.I32_TYPE -> globals.loadAsInt(address);
154-
case WasmType.I64_TYPE -> globals.loadAsLong(address);
155-
case WasmType.F32_TYPE -> Float.intBitsToFloat(globals.loadAsInt(address));
156-
case WasmType.F64_TYPE -> Double.longBitsToDouble(globals.loadAsLong(address));
157-
case WasmType.V128_TYPE -> globals.loadAsVector128(address);
158-
case WasmType.FUNCREF_TYPE, WasmType.EXTERNREF_TYPE -> globals.loadAsReference(address);
159-
default -> {
160-
CompilerDirectives.transferToInterpreter();
161-
throw new RuntimeException("Unknown type: " + type);
162-
}
163-
};
110+
if (address < 0) {
111+
return instance.store().globals().externalGlobal(address);
112+
} else {
113+
final ValueType valueType = ValueType.fromByteValue(symbolTable.globalValueType(globalIndex));
114+
final boolean mutable = symbolTable.isGlobalMutable(globalIndex);
115+
return new ExportedWasmGlobal(valueType, mutable, instance.store().globals(), address);
116+
}
164117
}
165118

166119
@ExportMessage

wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmNamesObject.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,14 @@
5252
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
5353

5454
@ExportLibrary(InteropLibrary.class)
55-
public class WasmNamesObject implements TruffleObject {
55+
public final class WasmNamesObject implements TruffleObject {
5656
private final String[] names;
5757

58-
WasmNamesObject(String[] names) {
58+
public WasmNamesObject(String[] names) {
5959
this.names = names;
6060
}
6161

62+
@SuppressWarnings("static-method")
6263
@ExportMessage
6364
boolean hasArrayElements() {
6465
return true;

wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/globals/WasmGlobal.java

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -41,11 +41,22 @@
4141

4242
package org.graalvm.wasm.globals;
4343

44-
import com.oracle.truffle.api.interop.TruffleObject;
4544
import org.graalvm.wasm.EmbedderDataHolder;
45+
import org.graalvm.wasm.WasmNamesObject;
4646
import org.graalvm.wasm.api.ValueType;
4747
import org.graalvm.wasm.constants.GlobalModifier;
4848

49+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
50+
import com.oracle.truffle.api.interop.InteropLibrary;
51+
import com.oracle.truffle.api.interop.TruffleObject;
52+
import com.oracle.truffle.api.interop.UnknownIdentifierException;
53+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
54+
import com.oracle.truffle.api.library.CachedLibrary;
55+
import com.oracle.truffle.api.library.ExportLibrary;
56+
import com.oracle.truffle.api.library.ExportMessage;
57+
58+
@SuppressWarnings("static-method")
59+
@ExportLibrary(InteropLibrary.class)
4960
public abstract class WasmGlobal extends EmbedderDataHolder implements TruffleObject {
5061

5162
private final ValueType valueType;
@@ -56,15 +67,15 @@ protected WasmGlobal(ValueType valueType, boolean mutable) {
5667
this.mutable = mutable;
5768
}
5869

59-
public ValueType getValueType() {
70+
public final ValueType getValueType() {
6071
return valueType;
6172
}
6273

63-
public boolean isMutable() {
74+
public final boolean isMutable() {
6475
return mutable;
6576
}
6677

67-
public byte getMutability() {
78+
public final byte getMutability() {
6879
return mutable ? GlobalModifier.MUTABLE : GlobalModifier.CONSTANT;
6980
}
7081

@@ -79,4 +90,71 @@ public byte getMutability() {
7990
public abstract void storeLong(long value);
8091

8192
public abstract void storeObject(Object value);
93+
94+
private static final String VALUE_MEMBER = "value";
95+
96+
@ExportMessage
97+
final boolean hasMembers() {
98+
return true;
99+
}
100+
101+
@ExportMessage
102+
@TruffleBoundary
103+
final boolean isMemberReadable(String member) {
104+
return VALUE_MEMBER.equals(member);
105+
}
106+
107+
@ExportMessage
108+
@TruffleBoundary
109+
final Object readMember(String member) throws UnknownIdentifierException {
110+
if (!isMemberReadable(member)) {
111+
throw UnknownIdentifierException.create(member);
112+
}
113+
assert VALUE_MEMBER.equals(member) : member;
114+
return switch (getValueType()) {
115+
case i32 -> loadAsInt();
116+
case i64 -> loadAsLong();
117+
case f32 -> Float.intBitsToFloat(loadAsInt());
118+
case f64 -> Double.longBitsToDouble(loadAsLong());
119+
case v128, anyfunc, externref -> loadAsObject();
120+
};
121+
}
122+
123+
@ExportMessage
124+
@TruffleBoundary
125+
final boolean isMemberModifiable(String member) {
126+
return VALUE_MEMBER.equals(member) && isMutable();
127+
}
128+
129+
@ExportMessage
130+
@TruffleBoundary
131+
final boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
132+
return false;
133+
}
134+
135+
@ExportMessage
136+
@TruffleBoundary
137+
final void writeMember(String member, Object value,
138+
@CachedLibrary(limit = "4") InteropLibrary valueLibrary) throws UnknownIdentifierException, UnsupportedMessageException {
139+
if (!isMemberReadable(member)) {
140+
throw UnknownIdentifierException.create(member);
141+
}
142+
if (!mutable) {
143+
// Constant variables cannot be modified after linking.
144+
throw UnsupportedMessageException.create();
145+
}
146+
switch (getValueType()) {
147+
case i32 -> storeInt(valueLibrary.asInt(value));
148+
case i64 -> storeLong(valueLibrary.asLong(value));
149+
case f32 -> storeInt(Float.floatToRawIntBits(valueLibrary.asFloat(value)));
150+
case f64 -> storeLong(Double.doubleToRawLongBits(valueLibrary.asDouble(value)));
151+
default -> throw UnsupportedMessageException.create();
152+
}
153+
}
154+
155+
@ExportMessage
156+
@TruffleBoundary
157+
final Object getMembers(@SuppressWarnings("unused") boolean includeInternal) {
158+
return new WasmNamesObject(new String[]{VALUE_MEMBER});
159+
}
82160
}

0 commit comments

Comments
 (0)