diff --git a/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorDebugger.java b/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorDebugger.java
index d6e8a8205f2c..6fd2056bb319 100644
--- a/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorDebugger.java
+++ b/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorDebugger.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -55,6 +55,8 @@
import java.util.regex.PatternSyntaxException;
import org.graalvm.collections.Pair;
+import org.graalvm.shadowed.org.json.JSONArray;
+import org.graalvm.shadowed.org.json.JSONObject;
import com.oracle.truffle.api.debug.Breakpoint;
import com.oracle.truffle.api.debug.DebugException;
@@ -94,8 +96,6 @@
import com.oracle.truffle.tools.chromeinspector.types.Script;
import com.oracle.truffle.tools.chromeinspector.types.StackTrace;
import com.oracle.truffle.tools.chromeinspector.util.LineSearch;
-import org.graalvm.shadowed.org.json.JSONArray;
-import org.graalvm.shadowed.org.json.JSONObject;
public final class InspectorDebugger extends DebuggerDomain {
@@ -338,9 +338,15 @@ public Params getScriptSource(String scriptId) throws CommandProcessException {
if (scriptId == null) {
throw new CommandProcessException("A scriptId required.");
}
- CharSequence characters = getScript(scriptId).getCharacters();
+ Script script = getScript(scriptId);
JSONObject json = new JSONObject();
- json.put("scriptSource", characters.toString());
+ if (script.hasWasmSource()) {
+ CharSequence bytecode = script.getWasmBytecode();
+ json.put("bytecode", bytecode.toString());
+ } else {
+ CharSequence characters = script.getCharacters();
+ json.put("scriptSource", characters.toString());
+ }
return new Params(json);
}
diff --git a/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/types/Script.java b/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/types/Script.java
index 89d524765147..b8415b1a27c6 100644
--- a/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/types/Script.java
+++ b/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/types/Script.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,7 @@
package com.oracle.truffle.tools.chromeinspector.types;
import java.text.MessageFormat;
+import java.util.Base64;
import com.oracle.truffle.api.source.Source;
@@ -62,6 +63,10 @@ public Source getSourceLoaded() {
return sourceLoaded;
}
+ public boolean hasWasmSource() {
+ return source.hasBytes() && "application/wasm".equals(source.getMimeType());
+ }
+
public CharSequence getCharacters() {
if (source.hasCharacters()) {
return source.getCharacters();
@@ -72,6 +77,19 @@ public CharSequence getCharacters() {
}
}
+ /**
+ * @return A base64 encoded representation of the wasm bytecode inside this script.
+ */
+ public CharSequence getWasmBytecode() {
+ if (source.hasBytes()) {
+ return Base64.getEncoder().encodeToString(source.getBytes().toByteArray());
+ } else {
+ return MessageFormat.format("Can not load source from {0}\n" +
+ "Please use the --inspect.SourcePath option to point to the source locations.\n" +
+ "Example: --inspect.SourcePath=/home/joe/project/src\n", source.getURI().toString());
+ }
+ }
+
public String getHash() {
CharSequence code = getCharacters();
// See
diff --git a/wasm/CHANGELOG.md b/wasm/CHANGELOG.md
index 3729df379d0c..6b46c499693b 100644
--- a/wasm/CHANGELOG.md
+++ b/wasm/CHANGELOG.md
@@ -2,6 +2,10 @@
This changelog summarizes major changes to the WebAssembly engine implemented in GraalVM (GraalWasm).
+## Version 25.0.0
+
+* Implemented support for editing primitive values during debugging. Fixed several debugger-related issues.
+
## Version 24.2.0
* Updated developer metadata of Maven artifacts.
diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/debugging/DebugObjectFactorySuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/debugging/DebugObjectFactorySuite.java
index d938e044222e..e05bdb33e506 100644
--- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/debugging/DebugObjectFactorySuite.java
+++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/debugging/DebugObjectFactorySuite.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
@@ -41,9 +41,10 @@
package org.graalvm.wasm.test.suites.debugging;
-import com.oracle.truffle.api.source.Source;
+import java.nio.file.Path;
+import java.util.ArrayList;
+
import org.graalvm.collections.EconomicMap;
-import org.graalvm.wasm.WasmLanguage;
import org.graalvm.wasm.collection.LongArrayList;
import org.graalvm.wasm.debugging.DebugLineMap;
import org.graalvm.wasm.debugging.data.DebugFunction;
@@ -60,9 +61,6 @@
import org.junit.Assert;
import org.junit.Test;
-import java.nio.file.Path;
-import java.util.ArrayList;
-
/**
* Test suite for debug entries based on the specification in the
* DWARF Debug Information format.
@@ -118,7 +116,7 @@ private static DebugParserContext parseCompilationUnit(TestObjectFactory factory
return parseCompilationUnit(factory, null, null, children);
}
- private static DebugParserContext parseCompilationUnit(TestObjectFactory factory, DebugLineMap lineMap, Source source, DebugData... children) {
+ private static DebugParserContext parseCompilationUnit(TestObjectFactory factory, DebugLineMap lineMap, Path path, DebugData... children) {
final byte[] data = {};
final DebugData compUnit = getCompilationUnit(children);
final DebugLineMap[] lineMaps;
@@ -127,13 +125,13 @@ private static DebugParserContext parseCompilationUnit(TestObjectFactory factory
} else {
lineMaps = null;
}
- final Source[] sources;
- if (source != null) {
- sources = new Source[]{source};
+ final Path[] paths;
+ if (path != null) {
+ paths = new Path[]{path};
} else {
- sources = null;
+ paths = null;
}
- final DebugParserContext context = new DebugParserContext(data, 0, getEntryData(compUnit), lineMaps, sources);
+ final DebugParserContext context = new DebugParserContext(data, 0, getEntryData(compUnit), lineMaps, paths, "wasm", factory);
final DebugParserScope scope = DebugParserScope.createGlobalScope();
for (DebugData d : compUnit.children()) {
factory.parse(context, scope, d);
@@ -1073,29 +1071,29 @@ public void testEnumTypeMissingBaseType() {
@Test
public void testFunction() {
- final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
+ final Path p = Path.of("");
+ final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
- final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder funcAttr = AttributeBuilder.create().add(Attributes.DECL_FILE, 0x0F, 0).add(Attributes.NAME, 0x08, "func").add(Attributes.LOW_PC, 0x0F, 0).add(Attributes.HIGH_PC, 0x0F,
10).add(Attributes.FRAME_BASE, 0x09, new byte[]{});
final DebugData func = new DebugData(Tags.SUBPROGRAM, 1, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[0]);
final TestObjectFactory factory = new TestObjectFactory();
- final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, func);
+ final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, func);
Assert.assertEquals(1, context.functions().size());
}
@Test
public void testFunctionMissingLineMap() {
- final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
+ final Path p = Path.of("");
final AttributeBuilder funcAttr = AttributeBuilder.create().add(Attributes.DECL_FILE, 0x0F, 0).add(Attributes.NAME, 0x08, "func").add(Attributes.LOW_PC, 0x0F, 0).add(Attributes.HIGH_PC, 0x0F,
10).add(Attributes.FRAME_BASE, 0x09, new byte[]{});
final DebugData func = new DebugData(Tags.SUBPROGRAM, 1, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[0]);
final TestObjectFactory factory = new TestObjectFactory();
- final DebugParserContext context = parseCompilationUnit(factory, null, s, func);
+ final DebugParserContext context = parseCompilationUnit(factory, null, p, func);
Assert.assertEquals(0, context.functions().size());
}
@@ -1117,42 +1115,42 @@ public void testFunctionMissingSource() {
@Test
public void testInlinedFunction() {
- final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
+ final Path p = Path.of("");
+ final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
- final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder funcAttr = AttributeBuilder.create().add(Attributes.DECL_FILE, 0x0F, 0).add(Attributes.NAME, 0x08, "func").add(Attributes.LOW_PC, 0x0F, 0).add(Attributes.HIGH_PC, 0x0F,
10).add(Attributes.FRAME_BASE, 0x09, new byte[]{}).add(Attributes.INLINE, 0x0F, 1);
final DebugData func = new DebugData(Tags.SUBPROGRAM, 1, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[0]);
final TestObjectFactory factory = new TestObjectFactory();
- final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, func);
+ final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, func);
Assert.assertEquals(0, context.functions().size());
}
@Test
public void testFunctionMissingFrameBase() {
- final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
+ final Path p = Path.of("");
+ final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
- final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder funcAttr = AttributeBuilder.create().add(Attributes.DECL_FILE, 0x0F, 0).add(Attributes.NAME, 0x08, "func").add(Attributes.LOW_PC, 0x0F, 0).add(Attributes.HIGH_PC, 0x0F,
10);
final DebugData func = new DebugData(Tags.SUBPROGRAM, 1, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[0]);
final TestObjectFactory factory = new TestObjectFactory();
- final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, func);
+ final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, func);
Assert.assertEquals(0, context.functions().size());
}
@Test
public void testFunctionWithVariables() {
- final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
+ final Path p = Path.of("");
+ final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
- final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder baseAttr = AttributeBuilder.create().add(Attributes.NAME, 0x08, "int").add(Attributes.ENCODING, 0x0F, AttributeEncodings.SIGNED).add(Attributes.BYTE_SIZE, 0x0F, 4);
final DebugData baseType = new DebugData(Tags.BASE_TYPE, 1, baseAttr.attributeInfo(), baseAttr.attributeValues(), new DebugData[0]);
@@ -1172,7 +1170,7 @@ public void testFunctionWithVariables() {
final DebugData func = new DebugData(Tags.SUBPROGRAM, 3, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[]{var1, var2, var3});
final TestObjectFactory factory = new TestObjectFactory();
- final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, baseType, func);
+ final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, baseType, func);
Assert.assertEquals(1, context.functions().size());
final DebugFunction function = context.functions().get(0);
@@ -1181,10 +1179,10 @@ public void testFunctionWithVariables() {
@Test
public void testFunctionWithVariableMissingTypeAttribute() {
- final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
+ final Path p = Path.of("");
+ final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
- final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder baseAttr = AttributeBuilder.create().add(Attributes.NAME, 0x08, "int").add(Attributes.ENCODING, 0x0F, AttributeEncodings.SIGNED).add(Attributes.BYTE_SIZE, 0x0F, 4);
final DebugData baseType = new DebugData(Tags.BASE_TYPE, 1, baseAttr.attributeInfo(), baseAttr.attributeValues(), new DebugData[0]);
@@ -1198,7 +1196,7 @@ public void testFunctionWithVariableMissingTypeAttribute() {
final DebugData func = new DebugData(Tags.SUBPROGRAM, 3, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[]{var});
final TestObjectFactory factory = new TestObjectFactory();
- final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, baseType, func);
+ final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, baseType, func);
Assert.assertEquals(1, context.functions().size());
final DebugFunction function = context.functions().get(0);
@@ -1207,10 +1205,10 @@ public void testFunctionWithVariableMissingTypeAttribute() {
@Test
public void testFunctionWithVariableMissingType() {
- final DebugLineMap lineMap = new DebugLineMap(Path.of(""));
+ final Path p = Path.of("");
+ final DebugLineMap lineMap = new DebugLineMap(p);
lineMap.add(0, 1);
lineMap.add(10, 2);
- final Source s = Source.newBuilder(WasmLanguage.ID, "", "test").internal(true).build();
final AttributeBuilder varAttr = AttributeBuilder.create().add(Attributes.NAME, 0x08, "c").add(Attributes.TYPE, 0x0F, 1).add(Attributes.LOCATION, 0x09, new byte[0]).add(Attributes.DECL_FILE,
0x0F, 0).add(Attributes.DECL_LINE, 0x0F, 1);
@@ -1221,7 +1219,7 @@ public void testFunctionWithVariableMissingType() {
final DebugData func = new DebugData(Tags.SUBPROGRAM, 3, funcAttr.attributeInfo(), funcAttr.attributeValues(), new DebugData[]{var});
final TestObjectFactory factory = new TestObjectFactory();
- final DebugParserContext context = parseCompilationUnit(factory, lineMap, s, func);
+ final DebugParserContext context = parseCompilationUnit(factory, lineMap, p, func);
Assert.assertEquals(1, context.functions().size());
final DebugFunction function = context.functions().get(0);
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java
index 629872a3bd23..acca3e331874 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/BinaryParser.java
@@ -576,7 +576,7 @@ private static byte[] encapsulateResultType(int type) {
}
private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTypes, int sourceCodeEndOffset, boolean hasNextFunction, RuntimeBytecodeGen bytecode,
- int codeEntryIndex, EconomicMap sourceLocationToLineMap) {
+ int codeEntryIndex, EconomicMap offsetToLineIndexMap) {
final ParserState state = new ParserState(bytecode);
final ArrayList callNodes = new ArrayList<>();
final int bytecodeStartOffset = bytecode.location();
@@ -584,10 +584,11 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy
int opcode;
end: while (offset < sourceCodeEndOffset) {
- // Insert a debug instruction if a line mapping exists.
- if (sourceLocationToLineMap != null) {
- if (sourceLocationToLineMap.containsKey(offset)) {
- bytecode.addNotify(sourceLocationToLineMap.get(offset), offset);
+ // Insert a debug instruction if a line mapping (line index) exists.
+ if (offsetToLineIndexMap != null) {
+ final Integer lineIndex = offsetToLineIndexMap.get(offset);
+ if (lineIndex != null) {
+ bytecode.addNotify(lineIndex, offset);
}
}
@@ -713,6 +714,10 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy
break;
}
case Instructions.RETURN: {
+ if (offsetToLineIndexMap != null) {
+ // Make sure we exit the current statement before leaving the function
+ bytecode.addNotify(-1, -1);
+ }
state.addReturn(multiValue);
// This instruction is stack-polymorphic
@@ -1019,22 +1024,27 @@ private CodeEntry readFunction(int functionIndex, byte[] locals, byte[] resultTy
}
}
final int bytecodeEndOffset = bytecode.location();
- bytecode.addCodeEntry(functionIndex, state.maxStackSize(), bytecodeEndOffset - bytecodeStartOffset, locals.length, resultTypes.length);
- for (byte local : locals) {
- bytecode.addByte(local);
- }
- if (locals.length != 0) {
- bytecode.addByte((byte) 0);
- }
- for (byte result : resultTypes) {
- bytecode.addByte(result);
- }
- if (resultTypes.length != 0) {
- bytecode.addByte((byte) 0);
- }
- if (sourceLocationToLineMap == null) {
+
+ if (offsetToLineIndexMap == null) {
+ bytecode.addCodeEntry(functionIndex, state.maxStackSize(), bytecodeEndOffset - bytecodeStartOffset, locals.length, resultTypes.length);
+ for (byte local : locals) {
+ bytecode.addByte(local);
+ }
+ if (locals.length != 0) {
+ bytecode.addByte((byte) 0);
+ }
+ for (byte result : resultTypes) {
+ bytecode.addByte(result);
+ }
+ if (resultTypes.length != 0) {
+ bytecode.addByte((byte) 0);
+ }
+
// Do not override the code entry offset when rereading the function.
module.setCodeEntryOffset(codeEntryIndex, bytecodeEndOffset);
+ } else {
+ // Make sure we notify a statement exit before leaving the function
+ bytecode.addNotify(-1, -1);
}
return new CodeEntry(functionIndex, state.maxStackSize(), locals, resultTypes, callNodes, bytecodeStartOffset, bytecodeEndOffset, state.usesMemoryZero());
}
@@ -3248,17 +3258,17 @@ private Vector128 readUnsignedInt128() {
* Creates a runtime bytecode of a function with added debug opcodes.
*
* @param functionIndex the function index
- * @param sourceLocationToLineMap a mapping from source code locations to source code lines
- * numbers (extracted from debugging information)
+ * @param offsetToLineIndexMap a mapping from source code locations to line indices in the line
+ * index map
*/
@TruffleBoundary
- public byte[] createFunctionDebugBytecode(int functionIndex, EconomicMap sourceLocationToLineMap) {
+ public byte[] createFunctionDebugBytecode(int functionIndex, EconomicMap offsetToLineIndexMap) {
final RuntimeBytecodeGen bytecode = new RuntimeBytecodeGen();
final int codeEntryIndex = functionIndex - module.numImportedFunctions();
final CodeEntry codeEntry = BytecodeParser.readCodeEntry(module, module.bytecode(), codeEntryIndex);
offset = module.functionSourceCodeInstructionOffset(functionIndex);
final int endOffset = module.functionSourceCodeEndOffset(functionIndex);
- readFunction(functionIndex, codeEntry.localTypes(), codeEntry.resultTypes(), endOffset, true, bytecode, codeEntryIndex, sourceLocationToLineMap);
+ readFunction(functionIndex, codeEntry.localTypes(), codeEntry.resultTypes(), endOffset, true, bytecode, codeEntryIndex, offsetToLineIndexMap);
return bytecode.toArray();
}
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContext.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContext.java
index 536d79ba120c..78f45d9af4dd 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContext.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmContext.java
@@ -139,12 +139,6 @@ public Object getMemWaitCallback() {
return memWaitCallback;
}
- public void inheritCallbacksFromParentContext(WasmContext parent) {
- setMemGrowCallback(parent.getMemGrowCallback());
- setMemNotifyCallback(parent.getMemNotifyCallback());
- setMemWaitCallback(parent.getMemWaitCallback());
- }
-
public MemoryContext memoryContext() {
return memoryContext;
}
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 c4e1136b5b10..16b2abf4f8e3 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
@@ -64,8 +64,8 @@ public final class WasmContextOptions {
@CompilationFinal private boolean memoryOverheadMode;
@CompilationFinal private boolean constantRandomGet;
@CompilationFinal private boolean directByteBufferMemoryAccess;
+ @CompilationFinal private boolean debugTestMode;
- @CompilationFinal private String debugCompDirectory;
private final OptionValues optionValues;
WasmContextOptions(OptionValues optionValues) {
@@ -93,7 +93,7 @@ private void setOptionValues() {
this.memoryOverheadMode = readBooleanOption(WasmOptions.MemoryOverheadMode);
this.constantRandomGet = readBooleanOption(WasmOptions.WasiConstantRandomGet);
this.directByteBufferMemoryAccess = readBooleanOption(WasmOptions.DirectByteBufferMemoryAccess);
- this.debugCompDirectory = readStringOption(WasmOptions.DebugCompDirectory);
+ this.debugTestMode = readBooleanOption(WasmOptions.DebugTestMode);
}
private void checkOptionDependencies() {
@@ -109,10 +109,6 @@ private boolean readBooleanOption(OptionKey key) {
return key.getValue(optionValues);
}
- private String readStringOption(OptionKey key) {
- return key.getValue(optionValues);
- }
-
private static void failDependencyCheck(String option, String dependency) {
throw WasmException.format(Failure.INCOMPATIBLE_OPTIONS, "Incompatible WebAssembly options: %s requires %s to be enabled.", option, dependency);
}
@@ -173,8 +169,8 @@ public boolean directByteBufferMemoryAccess() {
return directByteBufferMemoryAccess;
}
- public String debugCompDirectory() {
- return debugCompDirectory;
+ public boolean debugTestMode() {
+ return debugTestMode;
}
@Override
@@ -193,7 +189,7 @@ public int hashCode() {
hash = 53 * hash + (this.memoryOverheadMode ? 1 : 0);
hash = 53 * hash + (this.constantRandomGet ? 1 : 0);
hash = 53 * hash + (this.directByteBufferMemoryAccess ? 1 : 0);
- hash = 53 * hash + (this.debugCompDirectory.hashCode());
+ hash = 53 * hash + (this.debugTestMode ? 1 : 0);
return hash;
}
@@ -247,7 +243,7 @@ public boolean equals(Object obj) {
if (this.directByteBufferMemoryAccess != other.directByteBufferMemoryAccess) {
return false;
}
- if (!this.debugCompDirectory.equals(other.debugCompDirectory)) {
+ if (this.debugTestMode != other.debugTestMode) {
return false;
}
return true;
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 79e316d152bd..8db365a5020c 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
@@ -51,6 +51,7 @@
import org.graalvm.wasm.api.InteropCallAdapterNode;
import org.graalvm.wasm.api.JsConstants;
import org.graalvm.wasm.api.WebAssembly;
+import org.graalvm.wasm.debugging.representation.DebugPrimitiveValue;
import org.graalvm.wasm.exception.WasmJsApiException;
import org.graalvm.wasm.predefined.BuiltinModule;
@@ -64,6 +65,7 @@
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.StandardTags;
+import com.oracle.truffle.api.nodes.ExecutableNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
@@ -75,7 +77,6 @@
byteMimeTypes = {WasmLanguage.WASM_MIME_TYPE}, //
contextPolicy = TruffleLanguage.ContextPolicy.SHARED, //
fileTypeDetectors = WasmFileDetector.class, //
- interactive = false, //
website = "https://www.graalvm.org/webassembly/", //
sandbox = SandboxPolicy.CONSTRAINED)
@ProvidedTags({StandardTags.RootTag.class, StandardTags.RootBodyTag.class, StandardTags.StatementTag.class})
@@ -200,6 +201,31 @@ WasmModule getModule() {
}
}
+ /**
+ * Parses simple expressions required to modify values during debugging.
+ *
+ * @param request request for parsing
+ */
+ @Override
+ protected ExecutableNode parse(InlineParsingRequest request) throws Exception {
+ final String expression = request.getSource().getCharacters().toString();
+ return new ParsePrimitiveExpressionRootNode(this, expression);
+ }
+
+ private static final class ParsePrimitiveExpressionRootNode extends ExecutableNode {
+ private final String expression;
+
+ private ParsePrimitiveExpressionRootNode(WasmLanguage language, String expression) {
+ super(language);
+ this.expression = expression;
+ }
+
+ @Override
+ public Object execute(VirtualFrame frame) {
+ return new DebugPrimitiveValue(expression);
+ }
+ }
+
public static WasmModule getParsedModule(CallTarget parseResult) {
if (parseResult instanceof RootCallTarget rct && rct.getRootNode() instanceof ParsedWasmModuleRootNode moduleRoot) {
return moduleRoot.getModule();
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 8cb0d52f8e10..05229ad6f01f 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
@@ -55,7 +55,6 @@
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.interop.TruffleObject;
-import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.Source;
/**
@@ -263,11 +262,10 @@ public String toString() {
}
@TruffleBoundary
- public EconomicMap debugFunctions(Node node) {
+ public EconomicMap debugFunctions() {
// lazily load debug information if needed.
if (debugFunctions == null && hasDebugInfo()) {
- WasmContext context = WasmContext.get(node);
- DebugTranslator translator = new DebugTranslator(customData, context.getContextOptions().debugCompDirectory(), context.environment());
+ DebugTranslator translator = new DebugTranslator(customData);
debugFunctions = translator.readCompilationUnits(customData, debugInfoOffset);
}
return debugFunctions;
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmOptions.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmOptions.java
index 9cc0b9017bc0..e82d7cb64d5f 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmOptions.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmOptions.java
@@ -40,12 +40,13 @@
*/
package org.graalvm.wasm;
-import com.oracle.truffle.api.Option;
import org.graalvm.options.OptionCategory;
import org.graalvm.options.OptionKey;
import org.graalvm.options.OptionStability;
import org.graalvm.options.OptionType;
+import com.oracle.truffle.api.Option;
+
@Option.Group(WasmLanguage.ID)
public class WasmOptions {
@Option(help = "A comma-separated list of builtin modules to use.", category = OptionCategory.USER, stability = OptionStability.STABLE, usageSyntax = "[:],[:],...")//
@@ -122,6 +123,6 @@ public enum ConstantsStorePolicy {
@Option(help = "Allows the embedder to access memories as direct byte buffers.", category = OptionCategory.INTERNAL, stability = OptionStability.EXPERIMENTAL, usageSyntax = "false|true") //
public static final OptionKey DirectByteBufferMemoryAccess = new OptionKey<>(false);
- @Option(help = "Test dir used for testing the debugger.", category = OptionCategory.INTERNAL, stability = OptionStability.EXPERIMENTAL, usageSyntax = "") //
- public static final OptionKey DebugCompDirectory = new OptionKey<>("");
+ @Option(help = "Support instrumentation for functions that do not have their source available. For testing purpose only.", category = OptionCategory.INTERNAL, stability = OptionStability.EXPERIMENTAL, usageSyntax = "false|true") //
+ public static final OptionKey DebugTestMode = new OptionKey<>(false);
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLineMap.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLineMap.java
index e44bb3c6dd83..1b903ece9e6a 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLineMap.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLineMap.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
@@ -41,69 +41,95 @@
package org.graalvm.wasm.debugging;
-import org.graalvm.collections.EconomicMap;
-
import java.nio.file.Path;
-import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.TreeMap;
+
+import org.graalvm.collections.EconomicMap;
+import org.graalvm.collections.EconomicSet;
/**
* Representation of a source location to source code line number mapping.
*/
-public class DebugLineMap {
+public final class DebugLineMap {
private final Path filePath;
- private final SortedSet lines;
- private final EconomicMap sourceLocationToLineMap;
- private final EconomicMap lineToSourceLocationMap;
+ private final TreeMap sourceOffsetToLineMap;
+ private final TreeMap lineToSourceOffsetMap;
public DebugLineMap(Path filePath) {
this.filePath = filePath;
- this.lines = new TreeSet<>();
- this.sourceLocationToLineMap = EconomicMap.create();
- this.lineToSourceLocationMap = EconomicMap.create();
+ this.sourceOffsetToLineMap = new TreeMap<>();
+ this.lineToSourceOffsetMap = new TreeMap<>();
}
- public void add(int sourceLocation, int line) {
- if (!lines.contains(line)) {
- lines.add(line);
- lineToSourceLocationMap.put(line, sourceLocation);
+ public void add(int sourceOffset, int line) {
+ if (line == 0) {
+ // invalid line
+ return;
}
- sourceLocationToLineMap.put(sourceLocation, line);
+ if (!lineToSourceOffsetMap.containsKey(line)) {
+ lineToSourceOffsetMap.put(line, sourceOffset);
+ }
+ sourceOffsetToLineMap.put(sourceOffset, line);
}
public Path getFilePath() {
return filePath;
}
- public int getLine(int sourceLocation) {
- if (!sourceLocationToLineMap.containsKey(sourceLocation)) {
+ /**
+ * @param startOffset The start offset
+ * @param endOffset The end offset
+ * @return The next line, if one exists in the given offset range, -1 otherwise.
+ */
+ public int getNextLine(int startOffset, int endOffset) {
+ final Integer location = sourceOffsetToLineMap.ceilingKey(startOffset);
+ if (location == null) {
return -1;
}
- return sourceLocationToLineMap.get(sourceLocation);
- }
-
- public int getSourceLocation(int line) {
- if (!lineToSourceLocationMap.containsKey(line)) {
- return -1;
+ if (location <= endOffset) {
+ return sourceOffsetToLineMap.get(location);
}
- return lineToSourceLocationMap.get(line);
- }
-
- public int size() {
- return lineToSourceLocationMap.size();
+ return -1;
}
/**
- * @return A set of all lines that are part of this line mapping.
+ *
+ * @param startOffset The start offset
+ * @param endOffset The end offset
+ * @return A {@link DebugLineSection} for the given offset range.
*/
- public SortedSet lines() {
- return lines;
+ public DebugLineSection getLineIndexMap(int startOffset, int endOffset) {
+ final EconomicSet uniqueLines = EconomicSet.create();
+ final EconomicMap offsetToLineIndexMap = EconomicMap.create();
+ final EconomicMap lineToLineIndexMap = EconomicMap.create();
+ int location = startOffset;
+ while (location <= endOffset) {
+ final Integer nextLocation = sourceOffsetToLineMap.ceilingKey(location);
+ if (nextLocation == null) {
+ break;
+ }
+ final int line = sourceOffsetToLineMap.get(nextLocation);
+ if (!uniqueLines.contains(line)) {
+ uniqueLines.add(line);
+ lineToLineIndexMap.put(line, lineToLineIndexMap.size());
+ }
+ final int lineIndex = lineToLineIndexMap.get(line);
+ offsetToLineIndexMap.put(nextLocation, lineIndex);
+ location = nextLocation + 1;
+ }
+ return new DebugLineSection(uniqueLines, offsetToLineIndexMap);
}
/**
- * @return A mapping from source location to line numbers.
+ *
+ * @param line The line
+ * @return The source offset of the line, if it exists, -1 otherwise.
*/
- public EconomicMap sourceLocationToLineMap() {
- return sourceLocationToLineMap;
+ public int getSourceOffset(int line) {
+ final Integer value = lineToSourceOffsetMap.get(line);
+ if (value == null) {
+ return -1;
+ }
+ return value;
}
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLineSection.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLineSection.java
new file mode 100644
index 000000000000..26aeb9d4dcbd
--- /dev/null
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLineSection.java
@@ -0,0 +1,59 @@
+/*
+ * 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.debugging;
+
+import org.graalvm.collections.EconomicMap;
+import org.graalvm.collections.EconomicSet;
+
+/**
+ * Represents the lines of a given source offset range and a mapping of source offsets to line
+ * indices. The line indices are assigned in their order of appearance in the source code. Offsets
+ * associated with the same line number have the same line number index.
+ *
+ * @param uniqueLines
+ * @param offsetToLineIndexMap
+ */
+public record DebugLineSection(EconomicSet uniqueLines, EconomicMap offsetToLineIndexMap) {
+ public boolean isEmpty() {
+ return uniqueLines.isEmpty();
+ }
+}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLocation.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLocation.java
index 31530c7a20ce..5494a909f285 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLocation.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/DebugLocation.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
@@ -41,13 +41,14 @@
package org.graalvm.wasm.debugging;
-import com.oracle.truffle.api.frame.MaterializedFrame;
+import java.util.Objects;
+
import org.graalvm.wasm.debugging.data.DebugAddressSize;
import org.graalvm.wasm.debugging.data.DebugConstants;
import org.graalvm.wasm.debugging.parser.DebugParser;
import org.graalvm.wasm.nodes.WasmDataAccess;
-import java.util.Objects;
+import com.oracle.truffle.api.frame.MaterializedFrame;
/**
* Represents a location descriptor. The value consist of a type (local variable, global variable,
@@ -353,6 +354,31 @@ public int loadU8() {
return loadU8(-1, 0);
}
+ /**
+ * Stores an uninterpreted byte at this location. Does not support storing values split into
+ * multiple locations.
+ *
+ * @see #isValidLocation(int)
+ */
+ public void store8(int bitSize, byte value) {
+ if (!isValidLocation(1) || bitSize >= 0) {
+ return;
+ }
+ final int index = (int) address;
+ if (isStack()) {
+ dataAccess.storeI32IntoStack(frame, index, value);
+ }
+ if (isLocal()) {
+ dataAccess.storeI32IntoLocals(frame, index, value);
+ }
+ if (isGlobal()) {
+ dataAccess.storeI32IntoGlobals(frame, index, value);
+ }
+ if (isMemory()) {
+ dataAccess.storeI8IntoMemory(frame, address, value);
+ }
+ }
+
/**
* Loads the signed short value at this location.
*
@@ -422,6 +448,31 @@ public int loadU16(int bitSize, int bitOffset) {
}
}
+ /**
+ * Stores an uninterpreted short at this location. Does not support storing values split into
+ * multiple locations.
+ *
+ * @see #isValidLocation(int)
+ */
+ public void store16(int bitSize, short value) {
+ if (!isValidLocation(1) || bitSize >= 0) {
+ return;
+ }
+ final int index = (int) address;
+ if (isStack()) {
+ dataAccess.storeI32IntoStack(frame, index, value);
+ }
+ if (isLocal()) {
+ dataAccess.storeI32IntoLocals(frame, index, value);
+ }
+ if (isGlobal()) {
+ dataAccess.storeI32IntoGlobals(frame, index, value);
+ }
+ if (isMemory()) {
+ dataAccess.storeI16IntoMemory(frame, address, value);
+ }
+ }
+
/**
* Loads the signed integer at this location.
*
@@ -504,6 +555,31 @@ public long loadU32() {
return loadU32(-1, 0);
}
+ /**
+ * Stores an uninterpreted integer at this location. Does not support storing values split into
+ * multiple locations.
+ *
+ * @see #isValidLocation(int)
+ */
+ public void store32(int bitSize, int value) {
+ if (!isValidLocation(1) || bitSize >= 0) {
+ return;
+ }
+ final int index = (int) address;
+ if (isStack()) {
+ dataAccess.storeI32IntoStack(frame, index, value);
+ }
+ if (isLocal()) {
+ dataAccess.storeI32IntoLocals(frame, index, value);
+ }
+ if (isGlobal()) {
+ dataAccess.storeI32IntoGlobals(frame, index, value);
+ }
+ if (isMemory()) {
+ dataAccess.storeI32IntoMemory(frame, address, value);
+ }
+ }
+
/**
* Loads the signed long at this location.
*
@@ -574,6 +650,31 @@ public String loadU64(int bitSize, int bitOffset) {
}
}
+ /**
+ * Stores an uninterpreted long at this location. Does not support storing values split into
+ * multiple locations.
+ *
+ * @see #isValidLocation(int)
+ */
+ public void store64(int bitSize, long value) {
+ if (!isValidLocation(1) || bitSize >= 0) {
+ return;
+ }
+ final int index = (int) address;
+ if (isStack()) {
+ dataAccess.storeI64IntoStack(frame, index, value);
+ }
+ if (isLocal()) {
+ dataAccess.storeI64IntoLocals(frame, index, value);
+ }
+ if (isGlobal()) {
+ dataAccess.storeI64IntoGlobals(frame, index, value);
+ }
+ if (isMemory()) {
+ dataAccess.storeI64IntoMemory(frame, address, value);
+ }
+ }
+
/**
* Loads the float at this location.
*
@@ -600,6 +701,30 @@ public float loadF32() {
return DebugConstants.DEFAULT_F32;
}
+ /**
+ * Stores the float at this location.
+ *
+ * @see #isValidLocation(int)
+ */
+ public void storeF32(float value) {
+ if (!isValidLocation(4)) {
+ return;
+ }
+ final int index = (int) address;
+ if (isStack()) {
+ dataAccess.storeF32IntoStack(frame, index, value);
+ }
+ if (isLocal()) {
+ dataAccess.storeF32IntoLocals(frame, index, value);
+ }
+ if (isGlobal()) {
+ dataAccess.storeF32IntoGlobals(frame, index, value);
+ }
+ if (isMemory()) {
+ dataAccess.storeF32IntoMemory(frame, address, value);
+ }
+ }
+
/**
* Loads the double at this location.
*
@@ -626,6 +751,30 @@ public double loadF64() {
return DebugConstants.DEFAULT_F64;
}
+ /**
+ * Stores the double at this location.
+ *
+ * @see #isValidLocation(int)
+ */
+ public void storeF64(double value) {
+ if (!isValidLocation(8)) {
+ return;
+ }
+ final int index = (int) address;
+ if (isStack()) {
+ dataAccess.storeF64IntoStack(frame, index, value);
+ }
+ if (isLocal()) {
+ dataAccess.storeF64IntoLocals(frame, index, value);
+ }
+ if (isGlobal()) {
+ dataAccess.storeF64IntoGlobals(frame, index, value);
+ }
+ if (isMemory()) {
+ dataAccess.storeF64IntoMemory(frame, address, value);
+ }
+ }
+
/**
* Loads the string at this location.
*
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugDataUtil.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugDataUtil.java
index 130d975ce899..052ebcfa7f9d 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugDataUtil.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugDataUtil.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
@@ -42,9 +42,9 @@
package org.graalvm.wasm.debugging.data;
import org.graalvm.wasm.collection.IntArrayList;
-import org.graalvm.wasm.debugging.parser.DebugParserContext;
-import org.graalvm.wasm.debugging.parser.DebugData;
import org.graalvm.wasm.debugging.encoding.Attributes;
+import org.graalvm.wasm.debugging.parser.DebugData;
+import org.graalvm.wasm.debugging.parser.DebugParserContext;
/**
* Utility class used for resolving the data of a debug entry.
@@ -61,17 +61,17 @@ private DebugDataUtil() {
* @return the pc values int an int array or null, if the pcs are not present or malformed.
*/
public static int[] readPcsOrNull(DebugData data, DebugParserContext context) {
- int lowPc = data.asI32OrDefault(Attributes.LOW_PC, -1);
+ int lowPc = data.asU32OrDefault(Attributes.LOW_PC, -1);
if (lowPc == -1) {
return null;
}
- int highPc = data.asI32OrDefault(Attributes.HIGH_PC, -1);
+ int highPc = data.asU32OrDefault(Attributes.HIGH_PC, -1);
if (highPc != -1) {
if (data.isConstant(Attributes.HIGH_PC)) {
highPc = lowPc + highPc;
}
} else {
- final int rangeOffset = data.asI32OrDefault(Attributes.RANGES, -1);
+ final int rangeOffset = data.asU32OrDefault(Attributes.RANGES, -1);
if (rangeOffset == -1) {
return null;
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugFunction.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugFunction.java
index a54d3702caa1..7ab270ffa29f 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugFunction.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugFunction.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
@@ -41,14 +41,19 @@
package org.graalvm.wasm.debugging.data;
+import java.nio.file.Path;
import java.util.List;
import org.graalvm.wasm.debugging.DebugLineMap;
import org.graalvm.wasm.debugging.DebugLocation;
import org.graalvm.wasm.debugging.data.objects.DebugScopeValue;
+import org.graalvm.wasm.debugging.parser.DebugSourceLoader;
import org.graalvm.wasm.nodes.WasmDataAccess;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
/**
@@ -57,19 +62,24 @@
public class DebugFunction extends DebugType {
private final String name;
private final DebugLineMap lineMap;
- private final SourceSection sourceSection;
+ private final Path filePath;
+ private final String language;
+ private final int startLine;
+ private SourceSection sourceSection;
private final byte[] frameBaseExpression;
private final List variables;
private final List globals;
- public DebugFunction(String name, DebugLineMap lineMap, SourceSection sourceSection, byte[] frameBaseExpression, List variables, List globals) {
+ public DebugFunction(String name, DebugLineMap lineMap, Path filePath, String language, int startLine, byte[] frameBaseExpression, List variables, List globals) {
assert lineMap != null : "the source code to bytecode line map of a debug function must not be null";
assert frameBaseExpression != null : "the expression for calculating the frame base of a debug function must not be null";
assert variables != null : "the list of variables of a debug function must not be null";
assert globals != null : "the list of globals of a debug function must not be null";
this.name = name;
this.lineMap = lineMap;
- this.sourceSection = sourceSection;
+ this.filePath = filePath;
+ this.language = language;
+ this.startLine = startLine;
this.frameBaseExpression = frameBaseExpression;
this.variables = variables;
this.globals = globals;
@@ -89,7 +99,7 @@ public int valueLength() {
* @return Whether globals are defined or not.
*/
public boolean hasGlobals() {
- return globals.size() != 0;
+ return !globals.isEmpty();
}
/**
@@ -117,12 +127,68 @@ public DebugLocation frameBaseOrNull(MaterializedFrame frame, WasmDataAccess dat
}
/**
- * @return The source section of the function.
+ * @return The file path of the function.
*/
- public SourceSection sourceSection() {
+ public Path filePath() {
+ return filePath;
+ }
+
+ /**
+ * @return The source language of the function.
+ */
+ public String language() {
+ return language;
+ }
+
+ /**
+ * @return Whether the source section of this function has already been computed.
+ * @see #createSourceSection(TruffleLanguage.Env)
+ * @see #loadSourceSection(TruffleLanguage.Env)
+ */
+ public boolean hasSourceSection() {
+ return sourceSection != null;
+ }
+
+ /**
+ * @return The already computed source section of the function. null
, if it has not
+ * been computed, yet.
+ * @see #hasSourceSection()
+ */
+ public SourceSection getSourceSection() {
+ assert hasSourceSection();
+ return sourceSection;
+ }
+
+ /**
+ * Computes a pseudo source section for the function. Returns an existing source section, if
+ * present.
+ */
+ @TruffleBoundary
+ public SourceSection createSourceSection(TruffleLanguage.Env env) {
+ if (sourceSection == null) {
+ final Source source = DebugSourceLoader.create(filePath, language, env);
+ if (source != null) {
+ sourceSection = source.createSection(startLine);
+ }
+ }
return sourceSection;
}
+ /**
+ * Tries to load the source section for this function. Does not return an existing source
+ * section, if present.
+ */
+ @TruffleBoundary
+ public SourceSection loadSourceSection(TruffleLanguage.Env env) {
+ final Source source = DebugSourceLoader.load(filePath, language, env);
+ if (source != null) {
+ final SourceSection section = source.createSection(startLine);
+ sourceSection = section;
+ return section;
+ }
+ return null;
+ }
+
/**
* @return The line map associated with the function.
*/
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugObjectFactory.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugObjectFactory.java
index 642b508f59d0..afb79bae5a84 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugObjectFactory.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugObjectFactory.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
@@ -41,16 +41,14 @@
package org.graalvm.wasm.debugging.data;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
-import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import org.graalvm.collections.EconomicMap;
import org.graalvm.wasm.collection.IntArrayList;
import org.graalvm.wasm.debugging.DebugLineMap;
-import org.graalvm.wasm.debugging.parser.DebugParserContext;
-import org.graalvm.wasm.debugging.parser.DebugParserScope;
import org.graalvm.wasm.debugging.data.objects.DebugConstantObject;
import org.graalvm.wasm.debugging.data.objects.DebugMember;
import org.graalvm.wasm.debugging.data.objects.DebugParameter;
@@ -68,12 +66,13 @@
import org.graalvm.wasm.debugging.encoding.Attributes;
import org.graalvm.wasm.debugging.encoding.Tags;
import org.graalvm.wasm.debugging.parser.DebugData;
-
-import com.oracle.truffle.api.source.Source;
-import com.oracle.truffle.api.source.SourceSection;
+import org.graalvm.wasm.debugging.parser.DebugParserContext;
+import org.graalvm.wasm.debugging.parser.DebugParserScope;
import org.graalvm.wasm.debugging.parser.DebugUtil;
import org.graalvm.wasm.debugging.representation.DebugConstantDisplayValue;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+
/**
* Represents a factory that creates the internal representation of all types and objects in the
* debug information.
@@ -158,7 +157,7 @@ private DebugType parseArrayType(DebugParserContext context, DebugParserScope sc
return null;
}
final IntArrayList dimensionLengths = new IntArrayList();
- forEachChild(data, Tags.SUBRANGE_TYPE, s -> dimensionLengths.add(s.asI32OrDefault(Attributes.COUNT, 0)));
+ forEachChild(data, Tags.SUBRANGE_TYPE, s -> dimensionLengths.add(s.asU32OrDefault(Attributes.COUNT, 0)));
return createArrayType(name, elementType, dimensionLengths.toArray());
}
@@ -376,15 +375,15 @@ private DebugType parseSubprogram(DebugParserContext context, DebugData data) {
final String name;
final DebugData specData = context.dataOrNull(data.asI32OrDefault(Attributes.SPECIFICATION));
if (specData != null) {
- final int fileIndexValue = specData.asI32OrDefault(Attributes.DECL_FILE);
+ final int fileIndexValue = specData.asU32OrDefault(Attributes.DECL_FILE);
if (fileIndexValue != DebugUtil.DEFAULT_I32) {
fileIndex = fileIndexValue;
} else {
- fileIndex = data.asI32OrDefault(Attributes.DECL_FILE, -1);
+ fileIndex = data.asU32OrDefault(Attributes.DECL_FILE, -1);
}
name = specData.asStringOrNull(Attributes.NAME);
} else {
- fileIndex = data.asI32OrDefault(Attributes.DECL_FILE, -1);
+ fileIndex = data.asU32OrDefault(Attributes.DECL_FILE, -1);
name = data.asStringOrNull(Attributes.NAME);
}
@@ -408,16 +407,13 @@ private DebugType parseSubprogram(DebugParserContext context, DebugData data) {
assert pcs.length == 2 : "the pc range of a debug subprogram (function) must contain exactly two values (start pc and end pc)";
final int scopeStartPc = pcs[0];
final int scopeEndPc = pcs[1];
- final int startLine = lineMap.getLine(scopeStartPc);
-
- final Source source = context.sourceOrNull(fileIndex);
- final SourceSection sourceSection;
- if (source != null) {
- sourceSection = source.createSection(startLine);
- } else {
- sourceSection = null;
+ final int startLine = lineMap.getNextLine(scopeStartPc, scopeEndPc);
+ if (startLine == -1) {
+ return null;
}
+ final Path path = context.pathOrNull(fileIndex);
+
final byte[] frameBaseExpression = data.asByteArrayOrNull(Attributes.FRAME_BASE);
if (frameBaseExpression == null) {
return null;
@@ -427,7 +423,7 @@ private DebugType parseSubprogram(DebugParserContext context, DebugData data) {
parseChildren(context, functionScope, data);
final List globals = context.globals();
final List variables = functionScope.variables();
- final DebugFunction function = new DebugFunction(name, lineMap, sourceSection, frameBaseExpression, variables, globals);
+ final DebugFunction function = new DebugFunction(name, lineMap, path, context.language(), startLine, frameBaseExpression, variables, globals);
if (name != null) {
context.addFunction(scopeStartPc, function);
}
@@ -467,12 +463,32 @@ private DebugObject parseVariable(DebugParserContext context, DebugParserScope s
locationExpression = context.readLocationListOrNull(location);
}
}
- final int fileIndex = data.asI32OrDefault(Attributes.DECL_FILE, scope.fileIndex());
- final int startLineNumber = data.asI32OrDefault(Attributes.DECL_LINE, -1);
- final int startLocation = context.sourceLocationOrDefault(fileIndex, startLineNumber, -1);
+ // check if the spec data contains the relevant information instead
+ if (locationExpression == null && specData != null) {
+ locationExpression = specData.asByteArrayOrNull(Attributes.LOCATION);
+ if (locationExpression == null) {
+ final int location = data.asI32OrDefault(Attributes.LOCATION);
+ if (location != DebugUtil.DEFAULT_I32) {
+ locationExpression = context.readLocationListOrNull(location);
+ }
+ }
+ }
+ int fileIndex = data.asU32OrDefault(Attributes.DECL_FILE, -1);
+ if (fileIndex == -1) {
+ if (specData != null) {
+ fileIndex = specData.asU32OrDefault(Attributes.DECL_FILE, scope.fileIndex());
+ } else {
+ fileIndex = scope.fileIndex();
+ }
+ }
+ int startLineNumber = data.asU32OrDefault(Attributes.DECL_LINE, -1);
+ if (startLineNumber == -1 && specData != null) {
+ startLineNumber = specData.asU32OrDefault(Attributes.DECL_LINE, -1);
+ }
+ int startLocation = context.sourceOffsetOrDefault(fileIndex, startLineNumber, scope.startLocation());
final int endLocation = scope.endLocation();
final DebugObject variable;
- if (locationExpression != null) {
+ if (startLocation != -1 && locationExpression != null) {
variable = new DebugVariable(name, type, locationExpression, startLocation, endLocation);
} else {
variable = DebugConstantObject.UNDEFINED;
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugType.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugType.java
index d9e37052c980..e0aa4d72af91 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugType.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugType.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
@@ -45,6 +45,8 @@
import org.graalvm.wasm.debugging.data.objects.DebugConstantObject;
import org.graalvm.wasm.debugging.representation.DebugConstantDisplayValue;
+import com.oracle.truffle.api.interop.InteropLibrary;
+
/**
* Represents a type in the debug information.
*/
@@ -61,7 +63,7 @@ public abstract class DebugType {
public abstract int valueLength();
/**
- * Returns true
if the type represents a basic values like a string or an int, else
+ * Returns true
if the type represents a basic value like a string or an int, else
* false
.
*
* @see #asValue(DebugContext, DebugLocation)
@@ -81,6 +83,25 @@ public Object asValue(DebugContext context, DebugLocation location) {
return DebugConstantDisplayValue.UNDEFINED;
}
+ /**
+ * Returns true
if the type represents a basic value that can be edited like an
+ * int, else false
.
+ *
+ * @see #setValue(DebugContext, DebugLocation, Object, InteropLibrary)
+ */
+ public boolean isModifiableValue() {
+ return false;
+ }
+
+ /**
+ * Changes a value if the type represents a {@link #isModifiableValue()} like value.
+ *
+ * @see #isModifiableValue()
+ */
+ @SuppressWarnings("unused")
+ public void setValue(DebugContext context, DebugLocation location, Object value, InteropLibrary lib) {
+ }
+
/**
* Returns true
if the type represents a {@link DebugObject}, else
* false
.
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugTypeRef.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugTypeRef.java
index 55408708800a..eda7095751ff 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugTypeRef.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/DebugTypeRef.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
@@ -43,6 +43,8 @@
import org.graalvm.wasm.debugging.DebugLocation;
import org.graalvm.wasm.debugging.data.objects.DebugConstantObject;
+import com.oracle.truffle.api.interop.InteropLibrary;
+
/**
* Represents a reference to a {@link DebugType}. This is necessary to resolve circular dependencies
* while parsing the debug information.
@@ -86,6 +88,21 @@ public Object asValue(DebugContext context, DebugLocation location) {
return delegate.asValue(context, location);
}
+ @Override
+ public boolean isModifiableValue() {
+ if (delegate == null) {
+ return false;
+ }
+ return delegate.isModifiableValue();
+ }
+
+ @Override
+ public void setValue(DebugContext context, DebugLocation location, Object value, InteropLibrary lib) {
+ if (delegate != null) {
+ delegate.setValue(context, location, value, lib);
+ }
+ }
+
@Override
public boolean fitsIntoInt() {
if (delegate == null) {
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/objects/DebugBinding.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/objects/DebugBinding.java
index d27707409f3e..1fbeae7023d7 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/objects/DebugBinding.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/objects/DebugBinding.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
@@ -46,6 +46,8 @@
import org.graalvm.wasm.debugging.data.DebugObject;
import org.graalvm.wasm.debugging.data.DebugType;
+import com.oracle.truffle.api.interop.InteropLibrary;
+
/**
* Represents a debug object that binds a value of a specific type to a name. This could be a
* variable, a member of a struct, etc. This object forwards calls to its methods to its underlying
@@ -74,6 +76,16 @@ public Object asValue(DebugContext context, DebugLocation location) {
return type.asValue(context, location);
}
+ @Override
+ public boolean isModifiableValue() {
+ return type.isModifiableValue();
+ }
+
+ @Override
+ public void setValue(DebugContext context, DebugLocation location, Object value, InteropLibrary lib) {
+ type.setValue(context, location, value, lib);
+ }
+
@Override
public boolean isDebugObject() {
return type.isDebugObject();
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/objects/DebugLink.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/objects/DebugLink.java
index b8e2767cdeb1..c779b5010303 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/objects/DebugLink.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/objects/DebugLink.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
@@ -45,6 +45,8 @@
import org.graalvm.wasm.debugging.data.DebugContext;
import org.graalvm.wasm.debugging.data.DebugObject;
+import com.oracle.truffle.api.interop.InteropLibrary;
+
/**
* Represents a debug object that assigns a different type name to a given object. This allows for
* type definitions to be represented. All calls except for the type name are forwarded to the
@@ -100,6 +102,16 @@ public Object asValue(DebugContext context, DebugLocation location) {
return reference.asValue(context, location);
}
+ @Override
+ public boolean isModifiableValue() {
+ return reference.isModifiableValue();
+ }
+
+ @Override
+ public void setValue(DebugContext context, DebugLocation location, Object value, InteropLibrary lib) {
+ reference.setValue(context, location, value, lib);
+ }
+
@Override
public boolean isDebugObject() {
return reference.isDebugObject();
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugBaseType.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugBaseType.java
index fe55716a9912..7b06f3e85e3c 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugBaseType.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugBaseType.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
@@ -42,10 +42,14 @@
package org.graalvm.wasm.debugging.data.types;
import org.graalvm.wasm.debugging.DebugLocation;
-import org.graalvm.wasm.debugging.representation.DebugConstantDisplayValue;
import org.graalvm.wasm.debugging.data.DebugContext;
import org.graalvm.wasm.debugging.data.DebugType;
import org.graalvm.wasm.debugging.encoding.AttributeEncodings;
+import org.graalvm.wasm.debugging.representation.DebugConstantDisplayValue;
+
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
/**
* Represents a debug type that is a base type like int or float.
@@ -154,6 +158,62 @@ public Object asValue(DebugContext context, DebugLocation location) {
return DebugConstantDisplayValue.UNSUPPORTED;
}
+ @Override
+ public boolean isModifiableValue() {
+ return encoding == AttributeEncodings.BOOLEAN ||
+ encoding == AttributeEncodings.FLOAT ||
+ encoding == AttributeEncodings.SIGNED ||
+ (encoding == AttributeEncodings.UNSIGNED && byteSize <= 4) ||
+ encoding == AttributeEncodings.SIGNED_CHAR ||
+ encoding == AttributeEncodings.UNSIGNED_CHAR;
+ }
+
+ @Override
+ public void setValue(DebugContext context, DebugLocation location, Object value, InteropLibrary lib) {
+ if (!isModifiableValue()) {
+ return;
+ }
+ final int effectiveBitSize = context.memberBitSizeOrDefault(bitSize);
+ try {
+ if (encoding == AttributeEncodings.BOOLEAN) {
+ if (lib.isBoolean(value)) {
+ location.store8(effectiveBitSize, (byte) (lib.asBoolean(value) ? 1 : 0));
+ }
+ }
+ if (encoding == AttributeEncodings.FLOAT) {
+ if (byteSize == 4 && lib.fitsInFloat(value)) {
+ location.storeF32(lib.asFloat(value));
+ }
+ if (byteSize == 8 && lib.fitsInDouble(value)) {
+ location.storeF64(lib.asDouble(value));
+ }
+ }
+ if (encoding == AttributeEncodings.SIGNED || encoding == AttributeEncodings.UNSIGNED) {
+ if (byteSize == 1 && lib.fitsInByte(value)) {
+ location.store8(effectiveBitSize, lib.asByte(value));
+ }
+ if (byteSize == 2 && lib.fitsInShort(value)) {
+ location.store16(effectiveBitSize, lib.asShort(value));
+ }
+ if (byteSize == 4 && lib.fitsInInt(value)) {
+ location.store32(effectiveBitSize, lib.asInt(value));
+ }
+ if (byteSize == 8 && lib.fitsInLong(value)) {
+ location.store64(effectiveBitSize, lib.asLong(value));
+ }
+ }
+ if (encoding == AttributeEncodings.SIGNED_CHAR || encoding == AttributeEncodings.UNSIGNED_CHAR) {
+ if (lib.fitsInByte(value)) {
+ location.store8(effectiveBitSize, lib.asByte(value));
+ } else if (lib.isString(value)) {
+ location.store8(effectiveBitSize, (byte) lib.asString(value).charAt(0));
+ }
+ }
+ } catch (UnsupportedMessageException e) {
+ throw CompilerDirectives.shouldNotReachHere(e);
+ }
+ }
+
@Override
public boolean fitsIntoInt() {
if (encoding == AttributeEncodings.SIGNED) {
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugInheritance.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugInheritance.java
index 152de5a15fde..85518a0c9764 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugInheritance.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugInheritance.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
@@ -47,6 +47,8 @@
import org.graalvm.wasm.debugging.data.DebugType;
import org.graalvm.wasm.debugging.parser.DebugParser;
+import com.oracle.truffle.api.interop.InteropLibrary;
+
/**
* Represents an inheritance relationship in the debug information. Forwards all method calls except
* for the location resolution to the underlying reference type.
@@ -99,6 +101,16 @@ public Object asValue(DebugContext context, DebugLocation location) {
return referenceType.asValue(context, offsetLocation(location));
}
+ @Override
+ public boolean isModifiableValue() {
+ return referenceType.isModifiableValue();
+ }
+
+ @Override
+ public void setValue(DebugContext context, DebugLocation location, Object value, InteropLibrary lib) {
+ referenceType.setValue(context, offsetLocation(location), value, lib);
+ }
+
@Override
public boolean isDebugObject() {
return referenceType.isDebugObject();
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugTypeDef.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugTypeDef.java
index ed060830af5d..57dc32aaf9e8 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugTypeDef.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/data/types/DebugTypeDef.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
@@ -46,6 +46,8 @@
import org.graalvm.wasm.debugging.data.DebugObject;
import org.graalvm.wasm.debugging.data.DebugType;
+import com.oracle.truffle.api.interop.InteropLibrary;
+
/**
* Represents a debug type that represents a type definition.
*/
@@ -87,6 +89,23 @@ public Object asValue(DebugContext context, DebugLocation location) {
return type.asValue(context, location);
}
+ @Override
+ public boolean isModifiableValue() {
+ if (type == null) {
+ return super.isModifiableValue();
+ }
+ return type.isModifiableValue();
+ }
+
+ @Override
+ public void setValue(DebugContext context, DebugLocation location, Object value, InteropLibrary lib) {
+ if (type == null) {
+ super.setValue(context, location, value, lib);
+ } else {
+ type.setValue(context, location, value, lib);
+ }
+ }
+
@Override
public boolean isDebugObject() {
if (type == null) {
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugData.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugData.java
index f48069321d24..b0ce340f2092 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugData.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugData.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
@@ -127,6 +127,43 @@ public int asI32OrDefault(int attribute) {
return asI32OrDefault(attribute, DebugUtil.DEFAULT_I32);
}
+ /**
+ * Converts the underlying attribute value to an integer. Interprets byte and short encodings as
+ * unsigned integer values.
+ *
+ * @return the integer value or the given default value, if the attribute does not exist or the
+ * value does not fit into an integer.
+ */
+ public int asU32OrDefault(int attribute, int defaultValue) {
+ final int index = attributeIndex(attribute);
+ if (index == -1) {
+ return defaultValue;
+ }
+ final int encoding = attributeEncoding(attributeInfo[index]);
+ final int value;
+ if (DataEncoding.isByte(encoding)) {
+ value = Byte.toUnsignedInt((byte) attributes[index]);
+ } else if (DataEncoding.isShort(encoding)) {
+ value = Short.toUnsignedInt((short) attributes[index]);
+ } else if (DataEncoding.isInt(encoding)) {
+ value = (int) attributes[index];
+ } else {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ /**
+ * Converts the underlying attribute value to an integer. Interprets byte and short encodings as
+ * unsigned integer values.
+ *
+ * @return the integer value or {@link DebugUtil#DEFAULT_I32}, if the attribute does not exist
+ * or the value does not fit into an integer.
+ */
+ public int asU32OrDefault(int attribute) {
+ return asU32OrDefault(attribute, DebugUtil.DEFAULT_I32);
+ }
+
/**
* Converts the underlying attribute value to a long.
*
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParseUnit.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParseUnit.java
index 71696c5a4fbd..984922301672 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParseUnit.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParseUnit.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
@@ -46,13 +46,19 @@
/**
* Represents the result of parsing debug information.
*/
-public class DebugParseUnit {
+public final class DebugParseUnit {
private final DebugData rootData;
private final EconomicMap entries;
+ private final EconomicMap abbreviationTable;
+ private final int entryOffset;
+ private final int compilationUnitOffset;
- public DebugParseUnit(DebugData rootData, EconomicMap entries) {
+ public DebugParseUnit(DebugData rootData, EconomicMap entries, EconomicMap abbreviationTable, int entryOffset, int compilationUnitOffset) {
this.rootData = rootData;
this.entries = entries;
+ this.abbreviationTable = abbreviationTable;
+ this.entryOffset = entryOffset;
+ this.compilationUnitOffset = compilationUnitOffset;
}
public DebugData rootData() {
@@ -62,4 +68,16 @@ public DebugData rootData() {
public EconomicMap entries() {
return entries;
}
+
+ public EconomicMap abbreviationTable() {
+ return abbreviationTable;
+ }
+
+ public int entryOffset() {
+ return entryOffset;
+ }
+
+ public int compilationUnitOffset() {
+ return compilationUnitOffset;
+ }
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParser.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParser.java
index 454b36d26470..5e519ee2fc86 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParser.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParser.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
@@ -82,74 +82,33 @@ public DebugParser(byte[] data) {
this.data = data;
}
- /**
- * Reads a compilation unit and all its child entries based on a given unit offset.
- *
- * @param debugInfoOffset the offset of the debug information in the custom data.
- * @param unitOffset the unit offset.
- * @return A {@link DebugParseUnit} or null, if the debug information is malformed.
- */
- @TruffleBoundary
- public DebugParseUnit readEntries(int debugInfoOffset, int unitOffset) {
+ private boolean initializeAndCheckOffsets(int debugInfoOffset, int unitOffset) {
final int infoOffset = DebugUtil.getInfoOffsetOrUndefined(data, debugInfoOffset);
final int infoLength = DebugUtil.getInfoLengthOrUndefined(data, debugInfoOffset);
- final int abbrevOffset = DebugUtil.getAbbrevOffsetOrUndefined(data, debugInfoOffset);
- final int abbrevLength = DebugUtil.getAbbrevLengthOrUndefined(data, debugInfoOffset);
- if (infoOffset == DebugUtil.UNDEFINED || infoLength == DebugUtil.UNDEFINED || abbrevOffset == DebugUtil.UNDEFINED || abbrevLength == DebugUtil.UNDEFINED) {
- return null;
+ if (infoOffset == DebugUtil.UNDEFINED || infoLength == DebugUtil.UNDEFINED) {
+ return false;
}
if (unitOffset == 0) {
offset = infoOffset;
} else {
offset = unitOffset;
}
- final int unitEndOffset = offset + infoLength;
-
- endOffset = unitEndOffset;
- if (offset < unitEndOffset) {
- int unitStartOffset = offset - infoOffset;
- try {
- // read header
- if (is64Bit()) {
- return null;
- }
-
- final int unitLength = readInitialLength();
- if (unitLength == -1) {
- // 64 bit length
- return null;
- }
- final int version = readUnsigned2();
- if (version != SUPPORTED_VERSION) {
- // Unsupported version
- return null;
- }
-
- final int debugAbbrevOffset = read4();
- if (Integer.compareUnsigned(debugAbbrevOffset, abbrevLength) >= 0) {
- // abbrev offset outside abbrev section
- return null;
- }
- // read address size
- final int addressSize = read1();
- if (addressSize != SUPPORTED_ADDRESS_LENGTH) {
- // unsupported address size
- return null;
- }
+ endOffset = infoOffset + infoLength;
+ return offset < endOffset;
+ }
- endOffset = unitStartOffset + unitLength + UNIT_HEADER_LENGTH;
+ @TruffleBoundary
+ public DebugData readCompilationUnitChildren(DebugParseUnit unit, int debugInfoOffset) {
+ final EconomicMap abbreviationTable = unit.abbreviationTable();
+ final EconomicMap entries = unit.entries();
+ final int compilationUnitOffset = unit.compilationUnitOffset();
- final EconomicMap abbreviationTable = readAbbrevSection(abbrevOffset + debugAbbrevOffset, abbrevLength);
- final EconomicMap entries = EconomicMap.create();
- final DebugData compilationUnit = readDebugEntry(abbreviationTable, debugInfoOffset, unitStartOffset, entries, true);
- if (compilationUnit != null && Integer.compareUnsigned(endOffset, offset) == 0) {
- return new DebugParseUnit(compilationUnit, entries);
- }
- } catch (WasmDebugException e) {
- return null;
- }
+ offset = unit.entryOffset();
+ try {
+ return readDebugEntry(abbreviationTable, debugInfoOffset, compilationUnitOffset, entries, true);
+ } catch (WasmDebugException e) {
+ return null;
}
- return null;
}
/**
@@ -161,18 +120,7 @@ public DebugParseUnit readEntries(int debugInfoOffset, int unitOffset) {
*/
@TruffleBoundary
public int getNextCompilationUnitOffset(int debugInfoOffset, int unitOffset) {
- final int infoOffset = DebugUtil.getInfoOffsetOrUndefined(data, debugInfoOffset);
- final int infoLength = DebugUtil.getInfoLengthOrUndefined(data, debugInfoOffset);
- if (infoOffset == DebugUtil.UNDEFINED || infoLength == DebugUtil.UNDEFINED) {
- return -1;
- }
- offset = infoOffset;
- endOffset = offset + infoLength;
- if (unitOffset != 0) {
- offset = unitOffset;
- }
-
- if (offset < endOffset) {
+ if (initializeAndCheckOffsets(debugInfoOffset, unitOffset)) {
try {
if (is64Bit()) {
return -1;
@@ -195,21 +143,12 @@ public int getNextCompilationUnitOffset(int debugInfoOffset, int unitOffset) {
*/
@TruffleBoundary
public DebugParseUnit readCompilationUnit(int debugInfoOffset, int unitOffset) {
- final int infoOffset = DebugUtil.getInfoOffsetOrUndefined(data, debugInfoOffset);
- final int infoLength = DebugUtil.getInfoLengthOrUndefined(data, debugInfoOffset);
final int abbrevOffset = DebugUtil.getAbbrevOffsetOrUndefined(data, debugInfoOffset);
final int abbrevLength = DebugUtil.getAbbrevLengthOrUndefined(data, debugInfoOffset);
- if (infoOffset == DebugUtil.UNDEFINED || infoLength == DebugUtil.UNDEFINED || abbrevOffset == DebugUtil.UNDEFINED || abbrevLength == DebugUtil.UNDEFINED) {
+ if (abbrevOffset == DebugUtil.UNDEFINED || abbrevLength == DebugUtil.UNDEFINED) {
return null;
}
- offset = infoOffset;
- final int unitEndOffset = offset + infoLength;
- if (unitOffset != 0) {
- offset = unitOffset;
- }
- endOffset = unitEndOffset;
-
- if (offset < unitEndOffset) {
+ if (initializeAndCheckOffsets(debugInfoOffset, unitOffset)) {
int unitStartOffset = offset;
try {
// read header
@@ -244,9 +183,10 @@ public DebugParseUnit readCompilationUnit(int debugInfoOffset, int unitOffset) {
final EconomicMap abbreviationTable = readAbbrevSection(abbrevOffset + debugAbbrevOffset, abbrevLength);
final EconomicMap entries = EconomicMap.create();
+ final int entryOffset = offset;
final DebugData compilationUnit = readDebugEntry(abbreviationTable, debugInfoOffset, unitStartOffset, entries, false);
if (compilationUnit != null && compilationUnit.tag() == Tags.COMPILATION_UNIT) {
- return new DebugParseUnit(compilationUnit, entries);
+ return new DebugParseUnit(compilationUnit, entries, abbreviationTable, entryOffset, unitStartOffset);
}
} catch (WasmDebugException e) {
return null;
@@ -295,12 +235,11 @@ private DebugData readDebugEntry(EconomicMap a
final int startOffset = offset;
final int abbrevDeclarationIndex = readUnsignedInt();
final AbbreviationDeclaration declaration = abbrevTable.get(abbrevDeclarationIndex);
- final int infoOffset = DebugUtil.getInfoOffsetOrUndefined(data, debugInfoOffset);
- if (declaration == null || infoOffset == DebugUtil.UNDEFINED) {
+ if (declaration == null) {
// Malformed abbreviation table. Declaration not found
return null;
}
- final int entryOffset = startOffset - infoOffset - compilationUnitOffset;
+ final int entryOffset = startOffset - compilationUnitOffset;
final long[] attributeInfo = new long[declaration.attributeCount()];
final Object[] attributes = new Object[declaration.attributeCount()];
@@ -549,8 +488,8 @@ public DebugLineMap[] readLineSectionOrNull(int debugLineOffset, int debugLineLe
if (state == null) {
return null;
}
- final int sectionLength = debugLineOffset + state.length();
- while (offset < sectionLength) {
+ final int sectionEndOffset = debugLineOffset + state.length();
+ while (offset < sectionEndOffset) {
try {
final int opcode = readUnsigned1();
switch (opcode) {
@@ -566,6 +505,10 @@ public DebugLineMap[] readLineSectionOrNull(int debugLineOffset, int debugLineLe
break;
case Opcodes.LNE_SET_ADDRESS:
final int address = read4();
+ if (address == -1) {
+ // function was optimized away by the compiler
+ state.setIgnore();
+ }
state.setAddress(address);
break;
case Opcodes.LNE_DEFINE_FILE:
@@ -662,19 +605,36 @@ private DebugState readLineSectionHeader(String compilationPath) throws WasmDebu
read1();
}
- final List paths = new ArrayList<>();
+ final Path cPath;
+ try {
+ cPath = Path.of(compilationPath);
+ } catch (InvalidPathException e) {
+ throw new WasmDebugException(e.getMessage());
+ }
+
+ final List paths = new ArrayList<>();
+ paths.add(cPath);
// read included directories
byte lastByte = peek1();
while (lastByte != 0) {
- paths.add(readString());
+ try {
+ final Path dirPath = Path.of(readString());
+ if (dirPath.isAbsolute()) {
+ paths.add(dirPath);
+ } else {
+ paths.add(cPath.resolve(dirPath));
+ }
+ } catch (InvalidPathException e) {
+ throw new WasmDebugException(e.getMessage());
+ }
lastByte = peek1();
}
read1();
final List filePaths = new ArrayList<>();
try {
- filePaths.add(Paths.get(compilationPath));
+ filePaths.add(cPath);
} catch (InvalidPathException e) {
throw new WasmDebugException(e.getMessage());
}
@@ -686,12 +646,12 @@ private DebugState readLineSectionHeader(String compilationPath) throws WasmDebu
readUnsignedInt();
readUnsignedInt();
lastByte = peek1();
- final String containingPath = pathIndex == 0 ? compilationPath : paths.get(pathIndex - 1);
try {
- if (containingPath.startsWith(compilationPath)) {
- filePaths.add(Paths.get(containingPath, name));
+ final Path filePath = Paths.get(name);
+ if (filePath.isAbsolute()) {
+ filePaths.add(filePath);
} else {
- filePaths.add(Paths.get(compilationPath, containingPath, name));
+ filePaths.add(paths.get(pathIndex).resolve(filePath));
}
} catch (InvalidPathException e) {
throw new WasmDebugException(e.getMessage());
@@ -873,16 +833,6 @@ private short read2() throws WasmDebugException {
}
}
- /**
- * Reads two bytes as an unsigned int value from the internal byte array and advances the offset
- * pointer.
- *
- * @throws WasmDebugException if the data is beyond the current endOffset.
- */
- private int readUnsigned2() throws WasmDebugException {
- return read2() & 0xffff;
- }
-
/**
* Reads four bytes as an int value from the internal byte array without advancing the offset
* pointer.
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParserContext.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParserContext.java
index cc730123a315..09e5fad9aa40 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParserContext.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugParserContext.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
@@ -40,15 +40,15 @@
*/
package org.graalvm.wasm.debugging.parser;
+import java.nio.file.Path;
import java.util.List;
import org.graalvm.collections.EconomicMap;
import org.graalvm.wasm.collection.IntArrayList;
import org.graalvm.wasm.debugging.DebugLineMap;
-import org.graalvm.wasm.debugging.data.DebugObject;
import org.graalvm.wasm.debugging.data.DebugFunction;
-
-import com.oracle.truffle.api.source.Source;
+import org.graalvm.wasm.debugging.data.DebugObject;
+import org.graalvm.wasm.debugging.data.DebugObjectFactory;
/**
* Context during debug information parsing.
@@ -60,9 +60,12 @@ public class DebugParserContext {
private final EconomicMap functions;
private final DebugParserScope globalScope;
private final DebugLineMap[] fileLineMaps;
- private final Source[] fileSources;
+ private final Path[] filePaths;
+ private final String language;
+ private final DebugObjectFactory objectFactory;
- public DebugParserContext(byte[] data, int debugInfoOffset, EconomicMap entryData, DebugLineMap[] fileLineMaps, Source[] fileSources) {
+ public DebugParserContext(byte[] data, int debugInfoOffset, EconomicMap entryData, DebugLineMap[] fileLineMaps, Path[] filePaths, String language,
+ DebugObjectFactory objectFactory) {
assert data != null : "the reference to the array containing the debug information (data) must not be null";
assert entryData != null : "the mapping of locations in the bytecode to debug entries (entryData) must not be null";
this.data = data;
@@ -71,7 +74,9 @@ public DebugParserContext(byte[] data, int debugInfoOffset, EconomicMap= fileSources.length || fileIndex < 0) {
+ if (fileIndex >= filePaths.length || fileIndex < 0) {
return null;
}
- return fileSources[fileIndex];
+ return filePaths[fileIndex];
}
/**
@@ -198,4 +206,18 @@ public List globals() {
public DebugParserScope globalScope() {
return globalScope;
}
+
+ /**
+ * @return The object factory of the current context.
+ */
+ public DebugObjectFactory objectFactory() {
+ return objectFactory;
+ }
+
+ /**
+ * @return The source language of the current context.
+ */
+ public String language() {
+ return language;
+ }
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugSourceLoader.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugSourceLoader.java
index a5c257d4e31d..5489dd9ad087 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugSourceLoader.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugSourceLoader.java
@@ -42,14 +42,10 @@
package org.graalvm.wasm.debugging.parser;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.source.Source;
@@ -57,45 +53,45 @@
* Source loader for loading the source files of the debug information.
*/
public class DebugSourceLoader {
- private final Map cache = new HashMap<>();
-
/**
* Loads the source at the given path.
*
* @param path the path of the source
* @param language the source language
- * @param testMode if true, load the source as a resource instead of from the file system.
- * @param env Truffle environment used to access source files
*/
@TruffleBoundary
- public Source load(Path path, String language, boolean testMode, TruffleLanguage.Env env) {
+ public static Source create(Path path, String language, TruffleLanguage.Env env) {
if (path == null || language == null) {
return null;
}
- Path fileName = path.getFileName();
- if (fileName == null) {
- return null;
+ Source source = null;
+ try {
+ // we create a pseudo source that does not read the content of the actual file, since we
+ // are not allowed to perform any IO at this point.
+ // Source.CONTENT_NONE enforces this behavior.
+ source = Source.newBuilder(language, "", path.toString()).content(Source.CONTENT_NONE).build();
+ } catch (SecurityException e) {
+ // source not available or not accessible
+ if (env != null) {
+ env.getLogger("").warning("Debug source file could not be loaded or accessed: " + path);
+ }
}
- if (cache.containsKey(path)) {
- return cache.get(path);
+ return source;
+ }
+
+ @TruffleBoundary
+ public static Source load(Path path, String language, TruffleLanguage.Env env) {
+ if (path == null || language == null) {
+ return null;
}
- Source s;
+ Source source = null;
try {
- Reader reader;
- if (testMode) {
- InputStream stream = DebugSourceLoader.class.getResourceAsStream(path.toString());
- if (stream == null) {
- return null;
- }
- reader = new InputStreamReader(stream);
- } else {
- reader = env.getPublicTruffleFile(path.toString()).newBufferedReader();
- }
- s = Source.newBuilder(language, reader, fileName.toString()).build();
+ final TruffleFile file = env.getInternalTruffleFile(path.toString());
+ source = Source.newBuilder(language, file).build();
} catch (IOException | SecurityException e) {
- return null;
+ // source not available or not accessible
+ env.getLogger("").warning("Debug source file could not be loaded or accessed: " + path);
}
- cache.put(path, s);
- return s;
+ return source;
}
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugState.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugState.java
index 3c8c3371e8b2..f95b54114d53 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugState.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugState.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
@@ -63,6 +63,7 @@ public class DebugState {
private boolean epilogueBegin = false;
private int isa = 0;
private int discriminator = 0;
+ private boolean ignore = false;
private final int lineBase;
private final int lineRange;
@@ -108,10 +109,13 @@ private void reset() {
epilogueBegin = false;
isa = 0;
discriminator = 0;
+ ignore = false;
}
public void addRow() {
- lineMaps[file].add(pc, line);
+ if (!ignore) {
+ lineMaps[file].add(pc, line);
+ }
discriminator = 0;
basicBlock = false;
prologueEnd = false;
@@ -182,6 +186,10 @@ public void setDiscriminator(int d) {
discriminator = d;
}
+ public void setIgnore() {
+ ignore = true;
+ }
+
public void specialOpcode(int opcode) {
int adjustedOpcode = opcode - opcodeBase;
advance(Integer.divideUnsigned(adjustedOpcode, lineRange));
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugTranslator.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugTranslator.java
index eaa5ce543661..c955ec947e25 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugTranslator.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/parser/DebugTranslator.java
@@ -43,7 +43,6 @@
import java.nio.file.Path;
-import com.oracle.truffle.api.TruffleLanguage;
import org.graalvm.collections.EconomicMap;
import org.graalvm.wasm.debugging.DebugLineMap;
import org.graalvm.wasm.debugging.data.DebugDataUtil;
@@ -53,45 +52,38 @@
import org.graalvm.wasm.debugging.languages.DebugLanguageSupport;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.source.Source;
/**
* Extracts the debug information and converts it to an internal representation of values.
*/
public class DebugTranslator {
private final DebugParser parser;
- private final DebugSourceLoader sourceLoader;
- private final String testCompDir;
- private final TruffleLanguage.Env env;
- public DebugTranslator(byte[] data, String testCompDir, TruffleLanguage.Env env) {
+ public DebugTranslator(byte[] data) {
this.parser = new DebugParser(data);
- this.sourceLoader = new DebugSourceLoader();
- this.testCompDir = testCompDir;
- this.env = env;
}
@TruffleBoundary
public EconomicMap readCompilationUnits(byte[] customData, int debugInfoOffset) {
assert customData != null : "the array containing the debug information must not be null when trying to parse the information";
assert debugInfoOffset != DebugUtil.UNDEFINED : "the offset of the debug information must be valid";
- EconomicMap debugFunctions = EconomicMap.create();
+ final EconomicMap debugFunctions = EconomicMap.create();
int unitOffset = 0;
- DebugParseUnit entryUnit = parser.readCompilationUnit(debugInfoOffset, unitOffset);
- while (entryUnit != null) {
+ DebugParseUnit unit = parser.readCompilationUnit(debugInfoOffset, unitOffset);
+ while (unit != null) {
// Only read compilation units for which sources are available
- if (parseCompilationUnit(entryUnit, customData, debugInfoOffset) != null) {
- final DebugParseUnit unit = parser.readEntries(debugInfoOffset, unitOffset);
- if (unit != null) {
- final DebugParserContext context = parseCompilationUnit(unit, customData, debugInfoOffset);
- if (context != null) {
+ final DebugParserContext context = parseCompilationUnit(unit, customData, debugInfoOffset);
+ if (context != null) {
+ final DebugData compilationUnit = parser.readCompilationUnitChildren(unit, debugInfoOffset);
+ if (compilationUnit != null) {
+ if (DebugTranslator.parseFunctions(context, compilationUnit)) {
debugFunctions.putAll(context.functions());
}
}
}
unitOffset = parser.getNextCompilationUnitOffset(debugInfoOffset, unitOffset);
if (unitOffset != -1) {
- entryUnit = parser.readCompilationUnit(debugInfoOffset, unitOffset);
+ unit = parser.readCompilationUnit(debugInfoOffset, unitOffset);
}
}
return debugFunctions;
@@ -115,9 +107,6 @@ private DebugParserContext parseCompilationUnit(DebugParseUnit parseUnit, byte[]
if (compDir == null) {
return null;
}
- if (!testCompDir.isEmpty()) {
- compDir = testCompDir;
- }
final int lineOffset = DebugUtil.getLineOffsetOrUndefined(customData, debugInfoOffset);
final int lineLength = DebugUtil.getLineLengthOrUndefined(customData, debugInfoOffset);
if (lineOffset == DebugUtil.UNDEFINED || lineLength == DebugUtil.UNDEFINED) {
@@ -128,32 +117,34 @@ private DebugParserContext parseCompilationUnit(DebugParseUnit parseUnit, byte[]
return null;
}
int nullSources = 0;
- Source[] fileSources = new Source[fileLineMaps.length];
- for (int i = 0; i < fileSources.length; i++) {
+ final Path[] filePaths = new Path[fileLineMaps.length];
+ for (int i = 0; i < filePaths.length; i++) {
final DebugLineMap lineMap = fileLineMaps[i];
if (lineMap != null) {
- final Path path = lineMap.getFilePath();
- fileSources[i] = sourceLoader.load(path, languageName, !testCompDir.isEmpty(), env);
+ filePaths[i] = lineMap.getFilePath();
}
- if (fileSources[i] == null) {
+ if (filePaths[i] == null) {
nullSources++;
}
}
- if (nullSources == fileSources.length) {
+ if (nullSources == filePaths.length) {
return null;
}
- final DebugParserContext context = new DebugParserContext(customData, debugInfoOffset, entries, fileLineMaps, fileSources);
+ return new DebugParserContext(customData, debugInfoOffset, entries, fileLineMaps, filePaths, languageName, objectFactory);
+ }
+
+ private static boolean parseFunctions(DebugParserContext context, DebugData data) {
final int[] pcs = DebugDataUtil.readPcsOrNull(data, context);
if (pcs == null) {
- return null;
+ return false;
}
assert pcs.length == 2 : "the pc range of a debug compilation unit must contain exactly two values (start pc and end pc)";
final int scopeStart = pcs[0];
final int scopeEnd = pcs[1];
final DebugParserScope scope = context.globalScope().with(null, scopeStart, scopeEnd);
for (DebugData child : data.children()) {
- objectFactory.parse(context, scope, child);
+ context.objectFactory().parse(context, scope, child);
}
- return context;
+ return true;
}
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugArrayDisplayValue.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugArrayDisplayValue.java
index ceb60ac48e7a..76a2a634bbac 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugArrayDisplayValue.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugArrayDisplayValue.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
@@ -41,15 +41,18 @@
package org.graalvm.wasm.debugging.representation;
+import org.graalvm.wasm.WasmLanguage;
import org.graalvm.wasm.debugging.DebugLocation;
import org.graalvm.wasm.debugging.data.DebugContext;
import org.graalvm.wasm.debugging.data.DebugObject;
import org.graalvm.wasm.debugging.data.DebugType;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.TruffleLanguage;
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.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
@@ -83,6 +86,16 @@ public static DebugArrayDisplayValue fromDebugObject(DebugObject object, DebugCo
return new DebugArrayDisplayValue(context, object.toString(), 0, 0, location, object);
}
+ @ExportMessage
+ public boolean hasLanguage() {
+ return true;
+ }
+
+ @ExportMessage
+ public Class extends TruffleLanguage>> getLanguage() {
+ return WasmLanguage.class;
+ }
+
@ExportMessage
public boolean hasArrayElements() {
return true;
@@ -95,9 +108,11 @@ public long getArraySize() {
}
@ExportMessage
+ @ExportMessage(name = "isArrayElementModifiable")
@TruffleBoundary
public boolean isArrayElementReadable(long index) {
- return index >= 0 && index < getArraySize();
+ // TODO Limit the number of displayed values to 255 until GR-62691 is implemented
+ return index >= 0 && index < Math.max(getArraySize(), 255);
}
@ExportMessage
@@ -115,6 +130,23 @@ public Object readArrayElement(long index) throws InvalidArrayIndexException {
return resolveDebugObject(object, context, location);
}
+ @ExportMessage
+ @TruffleBoundary
+ public boolean isArrayElementInsertable(@SuppressWarnings("unused") long index) {
+ return false;
+ }
+
+ @ExportMessage(limit = "5")
+ @TruffleBoundary
+ public void writeArrayElement(long index, Object value, @CachedLibrary("value") InteropLibrary lib) throws InvalidArrayIndexException {
+ if (!isArrayElementReadable(index)) {
+ throw InvalidArrayIndexException.create(index);
+ }
+ final int offset = indexOffset + (int) index;
+ final DebugObject object = array.readArrayElement(context, location, offset);
+ writeDebugObject(object, context, location, value, lib);
+ }
+
@ExportMessage
Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) {
return name != null ? name : "";
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugConstantDisplayValue.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugConstantDisplayValue.java
index 90ae8c20cb4b..e82e8085f89d 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugConstantDisplayValue.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugConstantDisplayValue.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
@@ -41,10 +41,12 @@
package org.graalvm.wasm.debugging.representation;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
+import org.graalvm.wasm.WasmLanguage;
/**
* Represents a constant value in the debug environment.
@@ -62,6 +64,16 @@ public DebugConstantDisplayValue(String value) {
this.value = value;
}
+ @ExportMessage
+ public boolean hasLanguage() {
+ return true;
+ }
+
+ @ExportMessage
+ public Class extends TruffleLanguage>> getLanguage() {
+ return WasmLanguage.class;
+ }
+
@ExportMessage
@TruffleBoundary
Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) {
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugDisplayValue.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugDisplayValue.java
index 89ac221ef31e..c0fbffeff1de 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugDisplayValue.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugDisplayValue.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
@@ -46,6 +46,7 @@
import org.graalvm.wasm.debugging.data.DebugObject;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.interop.InteropLibrary;
public abstract class DebugDisplayValue {
@TruffleBoundary
@@ -66,4 +67,22 @@ protected Object resolveDebugObject(DebugObject object, DebugContext context, De
}
return DebugConstantDisplayValue.UNSUPPORTED;
}
+
+ @TruffleBoundary
+ protected void writeDebugObject(DebugObject object, DebugContext context, DebugLocation objectLocation, Object value, InteropLibrary lib) {
+ assert object.isDebugObject() || object.isValue();
+ final DebugLocation loc = object.getLocation(objectLocation);
+ final DebugContext ctx = object.getContext(context);
+ if (object.isValue()) {
+ if (object.isModifiableValue()) {
+ object.setValue(ctx, loc, value, lib);
+ } else {
+ // non-modifiable value
+ return;
+ }
+ }
+ if (object.isDebugObject()) {
+ writeDebugObject(object.asDebugObject(ctx, loc), ctx, loc, value, lib);
+ }
+ }
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugObjectDisplayValue.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugObjectDisplayValue.java
index c0afa49bd573..1319f59b592f 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugObjectDisplayValue.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugObjectDisplayValue.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
@@ -44,24 +44,20 @@
import java.util.ArrayList;
import java.util.List;
-import com.oracle.truffle.api.frame.MaterializedFrame;
import org.graalvm.collections.EconomicMap;
import org.graalvm.wasm.WasmLanguage;
-import org.graalvm.wasm.WasmType;
+import org.graalvm.wasm.debugging.DebugLocation;
+import org.graalvm.wasm.debugging.data.DebugContext;
+import org.graalvm.wasm.debugging.data.DebugObject;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLanguage;
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.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
-import org.graalvm.wasm.debugging.DebugLocation;
-import org.graalvm.wasm.debugging.data.DebugContext;
-import org.graalvm.wasm.debugging.data.DebugObject;
-import org.graalvm.wasm.debugging.data.DebugFunction;
-import org.graalvm.wasm.nodes.WasmDataAccess;
/**
* Represents an object scope in the debug environment.
@@ -97,20 +93,6 @@ public static Object fromDebugObject(DebugObject object, DebugContext context, D
return new DebugObjectDisplayValue(context, location, object.toString(), members);
}
- @TruffleBoundary
- public static Object fromDebugFunction(DebugFunction function, DebugContext context, MaterializedFrame frame, WasmDataAccess dataAccess, boolean testMode) {
- final DebugLocation frameBase = function.frameBaseOrNull(frame, dataAccess);
- if (frameBase == null) {
- return DebugConstantDisplayValue.UNDEFINED;
- }
- if (function.hasGlobals() || testMode) {
- final EconomicMap members = EconomicMap.of("globals", function.globals(), "locals", function.locals());
- return new DebugObjectDisplayValue(context, frameBase, "", members);
- } else {
- return fromDebugObject(function.locals(), context, frameBase);
- }
- }
-
@ExportMessage
boolean hasMembers() {
return true;
@@ -139,10 +121,7 @@ Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) {
@ExportMessage
@TruffleBoundary
Object readMember(String member) throws UnknownIdentifierException {
- if (members == null) {
- return WasmType.VOID_TYPE;
- }
- if (!members.containsKey(member)) {
+ if (!isMemberReadable(member)) {
throw UnknownIdentifierException.create(member);
}
final DebugObject memberObject = members.get(member);
@@ -152,51 +131,33 @@ Object readMember(String member) throws UnknownIdentifierException {
@ExportMessage
@TruffleBoundary
Object getMembers(@SuppressWarnings("unused") boolean includeInternal) {
- return new WasmVariableNamesObject(members.getKeys());
+ final List names = new ArrayList<>(members.size());
+ for (String member : members.getKeys()) {
+ names.add(member);
+ }
+ return new WasmVariableNamesObject(names);
}
@ExportMessage
+ @ExportMessage(name = "isMemberModifiable")
@TruffleBoundary
boolean isMemberReadable(String member) {
return members.containsKey(member);
}
- @SuppressWarnings("static-method")
- @ExportLibrary(InteropLibrary.class)
- static final class WasmVariableNamesObject implements TruffleObject {
- final List names;
-
- WasmVariableNamesObject(Iterable names) {
- this.names = new ArrayList<>(0);
- for (String name : names) {
- this.names.add(name);
- }
- }
-
- @ExportMessage
- boolean hasArrayElements() {
- return true;
- }
-
- @ExportMessage
- @TruffleBoundary
- long getArraySize() {
- return names.size();
- }
-
- @ExportMessage
- @TruffleBoundary
- Object readArrayElement(long index) throws InvalidArrayIndexException {
- if (!isArrayElementReadable(index)) {
- throw InvalidArrayIndexException.create(index);
- }
- return names.get((int) index);
+ @ExportMessage(limit = "5")
+ @TruffleBoundary
+ void writeMember(String member, Object value, @CachedLibrary("value") InteropLibrary lib) throws UnknownIdentifierException {
+ if (!isMemberReadable(member)) {
+ throw UnknownIdentifierException.create(member);
}
+ final DebugObject memberObject = members.get(member);
+ writeDebugObject(memberObject, context, location, value, lib);
+ }
- @ExportMessage
- @TruffleBoundary
- boolean isArrayElementReadable(long index) {
- return index >= 0 && index < names.size();
- }
+ @ExportMessage
+ @TruffleBoundary
+ boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
+ return false;
}
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugPrimitiveValue.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugPrimitiveValue.java
new file mode 100644
index 000000000000..f6b00c986b3d
--- /dev/null
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugPrimitiveValue.java
@@ -0,0 +1,236 @@
+/*
+ * 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.debugging.representation;
+
+import java.math.BigInteger;
+
+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.UnsupportedMessageException;
+import com.oracle.truffle.api.library.ExportLibrary;
+import com.oracle.truffle.api.library.ExportMessage;
+
+/**
+ * Represents a primitive expression needed to override values during debugging. Supports primitive
+ * value expressions and strings.
+ */
+@ExportLibrary(InteropLibrary.class)
+public class DebugPrimitiveValue implements TruffleObject {
+ private final String expression;
+
+ public DebugPrimitiveValue(String expression) {
+ this.expression = expression;
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean isBoolean() {
+ return "true".equalsIgnoreCase(expression) || "false".equalsIgnoreCase(expression);
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean asBoolean() {
+ return Boolean.parseBoolean(expression);
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean isNumber() {
+ // We return false here, so the debugger does not interpret this value as a number.
+ // If it were a number, the chrome debugger would interpret floats as BigDecimal
+ // values, which would lead to errors during debugging and would not allow us to change
+ // floats and doubles.
+ return false;
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean fitsInByte() {
+ try {
+ Byte.parseByte(expression);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public byte asByte() {
+ try {
+ return Byte.parseByte(expression);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean fitsInShort() {
+ try {
+ Short.parseShort(expression);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public short asShort() {
+ try {
+ return Short.parseShort(expression);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean fitsInInt() {
+ try {
+ Integer.parseInt(expression);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public int asInt() {
+ try {
+ return Integer.parseInt(expression);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean fitsInLong() {
+ try {
+ Long.parseLong(expression);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public long asLong() {
+ try {
+ return Long.parseLong(expression);
+ } catch (NumberFormatException e) {
+ return 0L;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean fitsInFloat() {
+ try {
+ Float.parseFloat(expression);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public float asFloat() {
+ try {
+ return Float.parseFloat(expression);
+ } catch (NumberFormatException e) {
+ return 0f;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean fitsInDouble() {
+ try {
+ Double.parseDouble(expression);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public double asDouble() {
+ try {
+ return Double.parseDouble(expression);
+ } catch (NumberFormatException e) {
+ return 0d;
+ }
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean fitsInBigInteger() {
+ return false;
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public BigInteger asBigInteger() throws UnsupportedMessageException {
+ throw UnsupportedMessageException.create();
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public boolean isString() {
+ return !expression.isBlank();
+ }
+
+ @TruffleBoundary
+ @ExportMessage
+ public String asString() {
+ return expression;
+ }
+}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugScopeDisplayValue.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugScopeDisplayValue.java
new file mode 100644
index 000000000000..a017070c1685
--- /dev/null
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/DebugScopeDisplayValue.java
@@ -0,0 +1,218 @@
+/*
+ * 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.debugging.representation;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import org.graalvm.collections.EconomicMap;
+import org.graalvm.wasm.WasmLanguage;
+import org.graalvm.wasm.debugging.DebugLocation;
+import org.graalvm.wasm.debugging.data.DebugContext;
+import org.graalvm.wasm.debugging.data.DebugFunction;
+import org.graalvm.wasm.debugging.data.DebugObject;
+import org.graalvm.wasm.nodes.WasmDataAccess;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.TruffleLanguage;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+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.CachedLibrary;
+import com.oracle.truffle.api.library.ExportLibrary;
+import com.oracle.truffle.api.library.ExportMessage;
+import com.oracle.truffle.api.source.SourceSection;
+
+@ExportLibrary(InteropLibrary.class)
+@SuppressWarnings("static-method")
+public final class DebugScopeDisplayValue extends DebugDisplayValue implements TruffleObject {
+ private final String name;
+ private final DebugContext context;
+ private final DebugLocation location;
+ private final EconomicMap members;
+ private final DebugScopeDisplayValue parentScope;
+ private final SourceSection sourceSection;
+
+ public DebugScopeDisplayValue(String name, DebugContext context, DebugLocation location, EconomicMap members, DebugScopeDisplayValue parentScope,
+ SourceSection sourceSection) {
+ this.name = Objects.requireNonNull(name);
+ this.context = context;
+ this.location = location;
+ this.members = members;
+ this.parentScope = parentScope;
+ this.sourceSection = sourceSection;
+ }
+
+ private static DebugScopeDisplayValue createScope(String name, DebugContext context, DebugLocation location, DebugObject object, DebugScopeDisplayValue parentScope, SourceSection sourceSection) {
+ final EconomicMap members = EconomicMap.create();
+ final int count = object.memberCount();
+ for (int i = 0; i < count; i++) {
+ final DebugObject member = object.readMember(context, location, i);
+ if (member.isVisible(context.sourceCodeLocation())) {
+ members.put(member.toDisplayString(), member);
+ }
+ }
+ return new DebugScopeDisplayValue(name, context, location, members, parentScope, sourceSection);
+ }
+
+ @TruffleBoundary
+ public static Object fromDebugFunction(DebugFunction function, DebugContext context, MaterializedFrame frame, WasmDataAccess dataAccess, SourceSection sourceSection) {
+ final DebugLocation frameBase = function.frameBaseOrNull(frame, dataAccess);
+ if (frameBase == null) {
+ return DebugConstantDisplayValue.UNDEFINED;
+ }
+ DebugScopeDisplayValue scope = null;
+ if (function.hasGlobals()) {
+ scope = createScope("Globals", context, frameBase, function.globals(), scope, null);
+ }
+ return createScope("Locals", context, frameBase, function.locals(), scope, sourceSection);
+ }
+
+ @ExportMessage
+ boolean isScope() {
+ return true;
+ }
+
+ @ExportMessage
+ boolean hasScopeParent() {
+ return parentScope != null;
+ }
+
+ @ExportMessage
+ Object getScopeParent() throws UnsupportedMessageException {
+ if (parentScope == null) {
+ throw UnsupportedMessageException.create();
+ }
+ return parentScope;
+ }
+
+ @ExportMessage
+ boolean hasSourceLocation() {
+ return sourceSection != null;
+ }
+
+ @ExportMessage
+ SourceSection getSourceLocation() {
+ return sourceSection;
+ }
+
+ @ExportMessage
+ boolean hasLanguage() {
+ return true;
+ }
+
+ @ExportMessage
+ Class extends TruffleLanguage>> getLanguage() {
+ return WasmLanguage.class;
+ }
+
+ @ExportMessage
+ Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) {
+ return name;
+ }
+
+ @ExportMessage
+ boolean hasMembers() {
+ return true;
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ Object readMember(String member) throws UnknownIdentifierException {
+ if (!isMemberReadable(member)) {
+ throw UnknownIdentifierException.create(member);
+ }
+ final DebugObject memberObject = members.get(member);
+ return resolveDebugObject(memberObject, context, location);
+ }
+
+ private List memberNames() {
+ final List names = new ArrayList<>(0);
+ for (String member : members.getKeys()) {
+ names.add(member);
+ }
+ return names;
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ Object getMembers(@SuppressWarnings("unused") boolean includeInternal) {
+ final List names = memberNames();
+ if (parentScope != null) {
+ names.addAll(parentScope.memberNames());
+ }
+ return new WasmVariableNamesObject(names);
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ boolean isMemberReadable(String member) {
+ return members.containsKey(member);
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ boolean isMemberModifiable(String member) {
+ return members.containsKey(member);
+ }
+
+ @ExportMessage(limit = "5")
+ @TruffleBoundary
+ void writeMember(String member, Object value, @CachedLibrary("value") InteropLibrary lib) throws UnknownIdentifierException {
+ if (!isMemberModifiable(member)) {
+ throw UnknownIdentifierException.create(member);
+ }
+ final DebugObject memberObject = members.get(member);
+ if (memberObject.isModifiableValue()) {
+ memberObject.setValue(context, location, value, lib);
+ }
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
+ return false;
+ }
+}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/WasmVariableNamesObject.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/WasmVariableNamesObject.java
new file mode 100644
index 000000000000..4e3bc219f089
--- /dev/null
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/debugging/representation/WasmVariableNamesObject.java
@@ -0,0 +1,87 @@
+/*
+ * 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.debugging.representation;
+
+import java.util.List;
+
+import com.oracle.truffle.api.CompilerDirectives;
+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.library.ExportLibrary;
+import com.oracle.truffle.api.library.ExportMessage;
+
+@SuppressWarnings("static-method")
+@ExportLibrary(InteropLibrary.class)
+public final class WasmVariableNamesObject implements TruffleObject {
+ final List names;
+
+ WasmVariableNamesObject(List names) {
+ this.names = names;
+ }
+
+ @ExportMessage
+ boolean hasArrayElements() {
+ return true;
+ }
+
+ @ExportMessage
+ @CompilerDirectives.TruffleBoundary
+ long getArraySize() {
+ return names.size();
+ }
+
+ @ExportMessage
+ @CompilerDirectives.TruffleBoundary
+ Object readArrayElement(long index) throws InvalidArrayIndexException {
+ if (!isArrayElementReadable(index)) {
+ throw InvalidArrayIndexException.create(index);
+ }
+ return names.get((int) index);
+ }
+
+ @ExportMessage
+ @CompilerDirectives.TruffleBoundary
+ boolean isArrayElementReadable(long index) {
+ return index >= 0 && index < names.size();
+ }
+}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmDataAccess.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmDataAccess.java
index f6603976cb0e..ef87116f79d3 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmDataAccess.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmDataAccess.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
@@ -52,45 +52,81 @@ public interface WasmDataAccess {
int loadI32FromStack(MaterializedFrame frame, int index);
+ void storeI32IntoStack(MaterializedFrame frame, int index, int value);
+
long loadI64FromStack(MaterializedFrame frame, int index);
+ void storeI64IntoStack(MaterializedFrame frame, int index, long value);
+
float loadF32FromStack(MaterializedFrame frame, int index);
+ void storeF32IntoStack(MaterializedFrame frame, int index, float value);
+
double loadF64FromStack(MaterializedFrame frame, int index);
+ void storeF64IntoStack(MaterializedFrame frame, int index, double value);
+
boolean isValidLocalIndex(MaterializedFrame frame, int index);
int loadI32FromLocals(MaterializedFrame frame, int index);
+ void storeI32IntoLocals(MaterializedFrame frame, int index, int value);
+
long loadI64FromLocals(MaterializedFrame frame, int index);
+ void storeI64IntoLocals(MaterializedFrame frame, int index, long value);
+
float loadF32FromLocals(MaterializedFrame frame, int index);
+ void storeF32IntoLocals(MaterializedFrame frame, int index, float value);
+
double loadF64FromLocals(MaterializedFrame frame, int index);
+ void storeF64IntoLocals(MaterializedFrame frame, int index, double value);
+
boolean isValidGlobalIndex(int index);
int loadI32FromGlobals(MaterializedFrame frame, int index);
+ void storeI32IntoGlobals(MaterializedFrame frame, int index, int value);
+
long loadI64FromGlobals(MaterializedFrame frame, int index);
+ void storeI64IntoGlobals(MaterializedFrame frame, int index, long value);
+
float loadF32FromGlobals(MaterializedFrame frame, int index);
+ void storeF32IntoGlobals(MaterializedFrame frame, int index, float value);
+
double loadF64FromGlobals(MaterializedFrame frame, int index);
+ void storeF64IntoGlobals(MaterializedFrame frame, int index, double value);
+
boolean isValidMemoryAddress(MaterializedFrame frame, long address, int length);
byte loadI8FromMemory(MaterializedFrame frame, long address);
+ void storeI8IntoMemory(MaterializedFrame frame, long address, byte value);
+
short loadI16FromMemory(MaterializedFrame frame, long address);
+ void storeI16IntoMemory(MaterializedFrame frame, long address, short value);
+
int loadI32FromMemory(MaterializedFrame frame, long address);
+ void storeI32IntoMemory(MaterializedFrame frame, long address, int value);
+
long loadI64FromMemory(MaterializedFrame frame, long address);
+ void storeI64IntoMemory(MaterializedFrame frame, long address, long value);
+
float loadF32FromMemory(MaterializedFrame frame, long address);
+ void storeF32IntoMemory(MaterializedFrame frame, long address, float value);
+
double loadF64FromMemory(MaterializedFrame frame, long address);
+ void storeF64IntoMemory(MaterializedFrame frame, long address, double value);
+
String loadStringFromMemory(MaterializedFrame frame, long address, int length);
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFixedMemoryImplFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFixedMemoryImplFunctionNode.java
index b989c070ef6a..99f4cc6b99eb 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFixedMemoryImplFunctionNode.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFixedMemoryImplFunctionNode.java
@@ -72,9 +72,9 @@ public abstract class WasmFixedMemoryImplFunctionNode extends Node {
private final int bytecodeEndOffset;
private final Node[] callNodes;
- private static final WasmInstrumentableFunctionNode[] EMPTY_INSTRUMENTABLE_FUNCTION_NODES = new WasmInstrumentableFunctionNode[0];
+ private static final WasmFunctionBaseNode[] EMPTY_FUNCTION_BASE_NODES = new WasmFunctionBaseNode[0];
- @Children private WasmInstrumentableFunctionNode[] instrumentableFunctionNodes = EMPTY_INSTRUMENTABLE_FUNCTION_NODES;
+ @Children private WasmFunctionBaseNode[] functionBaseNodes = EMPTY_FUNCTION_BASE_NODES;
protected WasmFixedMemoryImplFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecodeStartOffset, int bytecodeEndOffset, Node[] callNodes) {
this.module = module;
@@ -92,13 +92,13 @@ public static WasmFixedMemoryImplFunctionNode create(WasmModule module, WasmCode
protected void doFixedMemoryImpl(VirtualFrame frame, WasmInstance instance,
@CachedLibrary(value = "instance.memory(0)") @SuppressWarnings("unused") WasmMemoryLibrary cachedMemoryLib0,
@Cached("createMemoryLibs1(cachedMemoryLib0)") @SuppressWarnings("unused") WasmMemoryLibrary[] cachedMemoryLibs,
- @Cached(value = "createSpecializedFunctionNode(cachedMemoryLibs)", adopt = false) WasmInstrumentableFunctionNode specializedFunctionNode) {
+ @Cached(value = "createSpecializedFunctionNode(cachedMemoryLibs)", adopt = false) WasmFunctionBaseNode specializedFunctionNode) {
specializedFunctionNode.execute(frame, instance);
}
@Specialization(replaces = "doFixedMemoryImpl")
protected void doDispatched(VirtualFrame frame, WasmInstance instance,
- @Cached(value = "createDispatchedFunctionNode()", adopt = false) WasmInstrumentableFunctionNode dispatchedFunctionNode) {
+ @Cached(value = "createDispatchedFunctionNode()", adopt = false) WasmFunctionBaseNode dispatchedFunctionNode) {
dispatchedFunctionNode.execute(frame, instance);
}
@@ -113,26 +113,28 @@ protected int memoryCount() {
}
@NeverDefault
- protected WasmInstrumentableFunctionNode createSpecializedFunctionNode(WasmMemoryLibrary[] memoryLibs) {
+ protected WasmFunctionBaseNode createSpecializedFunctionNode(WasmMemoryLibrary[] memoryLibs) {
CompilerAsserts.neverPartOfCompilation();
WasmInstrumentableFunctionNode instrumentableFunctionNode = new WasmInstrumentableFunctionNode(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, callNodes, memoryLibs);
- instrumentableFunctionNodes = Arrays.copyOf(instrumentableFunctionNodes, instrumentableFunctionNodes.length + 1);
- instrumentableFunctionNodes[instrumentableFunctionNodes.length - 1] = insert(instrumentableFunctionNode);
+ WasmFunctionBaseNode baseNode = new WasmFunctionBaseNode(instrumentableFunctionNode);
+ functionBaseNodes = Arrays.copyOf(functionBaseNodes, functionBaseNodes.length + 1);
+ functionBaseNodes[functionBaseNodes.length - 1] = insert(baseNode);
notifyInserted(instrumentableFunctionNode);
- return instrumentableFunctionNode;
+ return baseNode;
}
@NeverDefault
- protected WasmInstrumentableFunctionNode createDispatchedFunctionNode() {
+ protected WasmFunctionBaseNode createDispatchedFunctionNode() {
CompilerAsserts.neverPartOfCompilation();
WasmMemoryLibrary[] memoryLibs = new WasmMemoryLibrary[module.memoryCount()];
for (int memoryIndex = 0; memoryIndex < module.memoryCount(); memoryIndex++) {
memoryLibs[memoryIndex] = insert(WasmMemoryLibrary.getFactory().createDispatched(3));
}
WasmInstrumentableFunctionNode instrumentableFunctionNode = new WasmInstrumentableFunctionNode(module, codeEntry, bytecodeStartOffset, bytecodeEndOffset, callNodes, memoryLibs);
- instrumentableFunctionNodes = new WasmInstrumentableFunctionNode[]{insert(instrumentableFunctionNode)};
+ WasmFunctionBaseNode baseNode = new WasmFunctionBaseNode(instrumentableFunctionNode);
+ functionBaseNodes = new WasmFunctionBaseNode[]{insert(baseNode)};
notifyInserted(instrumentableFunctionNode);
- return instrumentableFunctionNode;
+ return baseNode;
}
public abstract void execute(VirtualFrame frame, WasmInstance instance);
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionBaseNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionBaseNode.java
new file mode 100644
index 000000000000..0507182ba090
--- /dev/null
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionBaseNode.java
@@ -0,0 +1,63 @@
+/*
+ * 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.nodes;
+
+import org.graalvm.wasm.WasmInstance;
+
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.Node;
+
+/**
+ * Intermediate node to support instrumentation. Due to caching, instrumented nodes cannot replace
+ * themselves at {@link WasmFixedMemoryImplFunctionNode}. Therefore, this intermediate node is
+ * needed.
+ */
+public final class WasmFunctionBaseNode extends Node {
+ @Child private WasmInstrumentableFunctionNode functionNode;
+
+ public WasmFunctionBaseNode(WasmInstrumentableFunctionNode functionNode) {
+ this.functionNode = functionNode;
+ }
+
+ public void execute(VirtualFrame frame, WasmInstance instance) {
+ functionNode.execute(frame, instance);
+ }
+}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java
index cd35a104a525..e657e51fece6 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionNode.java
@@ -150,9 +150,9 @@ public final class WasmFunctionNode extends Node implements BytecodeOSRNode {
@Children private Node[] callNodes;
@CompilationFinal private Object osrMetadata;
- @CompilationFinal private int bytecodeStartOffset;
- @CompilationFinal private int bytecodeEndOffset;
- @CompilationFinal(dimensions = 1) private byte[] bytecode;
+ private final int bytecodeStartOffset;
+ private final int bytecodeEndOffset;
+ @CompilationFinal(dimensions = 1) private final byte[] bytecode;
@CompilationFinal private WasmNotifyFunction notifyFunction;
@Children private WasmMemoryLibrary[] memoryLibs;
@@ -170,18 +170,34 @@ public WasmFunctionNode(WasmModule module, WasmCodeEntry codeEntry, int bytecode
this.memoryLibs = memoryLibs;
}
- private void enterErrorBranch() {
- codeEntry.errorBranch();
- }
-
- @SuppressWarnings("hiding")
- void updateBytecode(byte[] bytecode, int bytecodeStartOffset, int bytecodeEndOffset, WasmNotifyFunction notifyFunction) {
+ /**
+ * Copies a function node with instrumentation enabled in the bytecode
. This should
+ * only be called by {@link WasmInstrumentableFunctionNode} when an instrument attaches and the
+ * bytecode needs to be rewritten to include instrumentation instructions.
+ *
+ * @param node The existing {@link WasmFunctionNode} used for copying most information
+ * @param bytecode The instrumented bytecode
+ * @param notifyFunction The callback used by {@link Bytecode#NOTIFY} instructions to inform
+ * instruments about statements in the bytecode
+ */
+ WasmFunctionNode(WasmFunctionNode node, byte[] bytecode, WasmNotifyFunction notifyFunction) {
+ this.module = node.module;
+ this.codeEntry = node.codeEntry;
+ this.bytecodeStartOffset = 0;
+ this.bytecodeEndOffset = bytecode.length;
this.bytecode = bytecode;
- this.bytecodeStartOffset = bytecodeStartOffset;
- this.bytecodeEndOffset = bytecodeEndOffset;
+ this.callNodes = new Node[node.callNodes.length];
+ for (int childIndex = 0; childIndex < callNodes.length; childIndex++) {
+ this.callNodes[childIndex] = insert(node.callNodes[childIndex].deepCopy());
+ }
+ this.memoryLibs = node.memoryLibs;
this.notifyFunction = notifyFunction;
}
+ private void enterErrorBranch() {
+ codeEntry.errorBranch();
+ }
+
private WasmMemory memory(WasmInstance instance, int index) {
return instance.memory(index).checkSize(memoryLib(index), module.memoryInitialSize(index));
}
@@ -253,7 +269,7 @@ public void execute(VirtualFrame frame, WasmInstance instance) {
@BytecodeInterpreterSwitch
@ExplodeLoop(kind = ExplodeLoop.LoopExplosionKind.MERGE_EXPLODE)
@SuppressWarnings({"UnusedAssignment", "hiding"})
- public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, int startOffset, int startStackPointer, int startLine) {
+ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, int startOffset, int startStackPointer, int startLineIndex) {
final int localCount = codeEntry.localCount();
final byte[] bytecode = this.bytecode;
@@ -265,7 +281,7 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i
int offset = startOffset;
int stackPointer = startStackPointer;
- int line = startLine;
+ int lineIndex = startLineIndex;
final WasmStore store = instance.store();
// Note: The module may not have any memories.
@@ -300,9 +316,6 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i
final int resultCount = codeEntry.resultCount();
unwindStack(frame, stackPointer, localCount, resultCount);
dropStack(frame, stackPointer, localCount + resultCount);
- if (notifyFunction != null) {
- notifyFunction.notifyLine(frame, line, -1, line);
- }
return WasmConstant.RETURN_VALUE;
}
case Bytecode.LABEL_U8: {
@@ -378,7 +391,7 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i
backEdgeCounter.count = 0;
}
if (CompilerDirectives.inInterpreter() && BytecodeOSRNode.pollOSRBackEdge(this)) {
- Object result = BytecodeOSRNode.tryOSR(this, offset, new WasmOSRInterpreterState(stackPointer, line), null, frame);
+ Object result = BytecodeOSRNode.tryOSR(this, offset, new WasmOSRInterpreterState(stackPointer, lineIndex), null, frame);
if (result != null) {
if (backEdgeCounter.count > 0) {
LoopNode.reportLoopCount(this, backEdgeCounter.count);
@@ -1695,13 +1708,12 @@ public Object executeBodyFromOffset(WasmInstance instance, VirtualFrame frame, i
break;
}
case Bytecode.NOTIFY: {
- final int nextLine = rawPeekI32(bytecode, offset);
+ final int nextLineIndex = rawPeekI32(bytecode, offset);
final int sourceCodeLocation = rawPeekI32(bytecode, offset + 4);
offset += 8;
- if (notifyFunction != null) {
- notifyFunction.notifyLine(frame, line, nextLine, sourceCodeLocation);
- }
- line = nextLine;
+ assert notifyFunction != null;
+ notifyFunction.notifyLine(frame, lineIndex, nextLineIndex, sourceCodeLocation);
+ lineIndex = nextLineIndex;
break;
}
default:
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionRootNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionRootNode.java
index 9fd8fb5d286c..8fbed9ae3784 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionRootNode.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmFunctionRootNode.java
@@ -53,10 +53,13 @@
import static org.graalvm.wasm.nodes.WasmFrame.pushReference;
import static org.graalvm.wasm.nodes.WasmFrame.pushVector128;
+import java.util.Set;
+
import org.graalvm.collections.EconomicMap;
import org.graalvm.wasm.WasmArguments;
import org.graalvm.wasm.WasmCodeEntry;
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;
@@ -277,7 +280,7 @@ private void initializeLocals(VirtualFrame frame) {
private DebugFunction debugFunction() {
if (module().hasDebugInfo()) {
int functionSourceLocation = module().functionSourceCodeStartOffset(codeEntry.functionIndex());
- final EconomicMap debugFunctions = module().debugFunctions(this);
+ final EconomicMap debugFunctions = module().debugFunctions();
if (debugFunctions.containsKey(functionSourceLocation)) {
return debugFunctions.get(functionSourceLocation);
}
@@ -303,7 +306,31 @@ protected boolean isInstrumentable() {
if (debugFunction == null) {
return false;
}
- return debugFunction.sourceSection() != null;
+ return debugFunction.filePath() != null;
+ }
+
+ @Override
+ protected void prepareForInstrumentation(Set> tags) {
+ if (sourceSection == null) {
+ final DebugFunction debugFunction = debugFunction();
+ if (debugFunction == null) {
+ sourceSection = module().source().createUnavailableSection();
+ return;
+ }
+ if (debugFunction.hasSourceSection()) {
+ sourceSection = debugFunction.getSourceSection();
+ return;
+ }
+ WasmContext context = WasmContext.get(this);
+ if (context != null) {
+ if (!context.getContextOptions().debugTestMode()) {
+ sourceSection = debugFunction.loadSourceSection(context.environment());
+ }
+ }
+ if (sourceSection == null) {
+ sourceSection = debugFunction.createSourceSection(context == null ? null : context.environment());
+ }
+ }
}
@Override
@@ -311,11 +338,16 @@ protected boolean isInstrumentable() {
public final SourceSection getSourceSection() {
if (sourceSection == null) {
final DebugFunction debugFunction = debugFunction();
- if (debugFunction != null) {
- sourceSection = debugFunction.sourceSection();
- } else {
+ if (debugFunction == null) {
sourceSection = module().source().createUnavailableSection();
+ return sourceSection;
}
+ if (debugFunction.hasSourceSection()) {
+ sourceSection = debugFunction.getSourceSection();
+ return sourceSection;
+ }
+ WasmContext context = WasmContext.get(this);
+ sourceSection = debugFunction.createSourceSection(context == null ? null : context.environment());
}
return sourceSection;
}
@@ -323,4 +355,9 @@ public final SourceSection getSourceSection() {
public final Node[] getCallNodes() {
return functionNode.getCallNodes();
}
+
+ @Override
+ public boolean isInternal() {
+ return false;
+ }
}
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java
index 50e35a80fcef..289656d2340d 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentableFunctionNode.java
@@ -50,12 +50,14 @@
import org.graalvm.wasm.WasmContext;
import org.graalvm.wasm.WasmInstance;
import org.graalvm.wasm.WasmModule;
+import org.graalvm.wasm.debugging.DebugLineSection;
import org.graalvm.wasm.debugging.data.DebugContext;
import org.graalvm.wasm.debugging.data.DebugFunction;
-import org.graalvm.wasm.debugging.representation.DebugObjectDisplayValue;
+import org.graalvm.wasm.debugging.representation.DebugScopeDisplayValue;
import org.graalvm.wasm.memory.WasmMemory;
import org.graalvm.wasm.memory.WasmMemoryLibrary;
+import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.MaterializedFrame;
@@ -104,6 +106,15 @@ protected WasmInstrumentableFunctionNode(WasmInstrumentableFunctionNode node) {
this.zeroMemoryLib = node.zeroMemoryLib;
}
+ private WasmInstrumentableFunctionNode(WasmInstrumentableFunctionNode node, WasmFunctionNode functionNode, WasmInstrumentationSupportNode instrumentation) {
+ this.module = node.module;
+ this.codeEntry = node.codeEntry;
+ this.functionNode = functionNode;
+ this.functionSourceLocation = node.functionSourceLocation;
+ this.instrumentation = instrumentation;
+ this.zeroMemoryLib = node.zeroMemoryLib;
+ }
+
private WasmInstance instance(VirtualFrame frame) {
return ((WasmRootNode) getRootNode()).instance(frame);
}
@@ -129,7 +140,7 @@ public boolean isInstrumentable() {
@TruffleBoundary
private DebugFunction debugFunction() {
if (module.hasDebugInfo()) {
- final EconomicMap debugFunctions = module.debugFunctions(this);
+ final EconomicMap debugFunctions = module.debugFunctions();
if (debugFunctions.containsKey(functionSourceLocation)) {
return debugFunctions.get(functionSourceLocation);
}
@@ -137,10 +148,6 @@ private DebugFunction debugFunction() {
return null;
}
- protected void notifyLine(VirtualFrame frame, int line, int nextLine, int sourceLocation) {
- instrumentation.notifyLine(frame, line, nextLine, sourceLocation);
- }
-
@Override
public boolean hasTag(Class extends Tag> tag) {
return tag == StandardTags.RootBodyTag.class || tag == StandardTags.RootTag.class;
@@ -151,7 +158,13 @@ public boolean hasTag(Class extends Tag> tag) {
public SourceSection getSourceSection() {
final DebugFunction debugFunction = debugFunction();
if (debugFunction != null) {
- return debugFunction.sourceSection();
+ if (debugFunction.hasSourceSection()) {
+ return debugFunction.getSourceSection();
+ } else {
+ // fallback solution, if the source was not loaded by the root node
+ WasmContext context = WasmContext.get(this);
+ return debugFunction.createSourceSection(context == null ? null : context.environment());
+ }
}
return null;
}
@@ -159,31 +172,42 @@ public SourceSection getSourceSection() {
@Override
@TruffleBoundary
public InstrumentableNode materializeInstrumentableNodes(Set> materializedTags) {
- WasmInstrumentationSupportNode info = this.instrumentation;
- // We need to check if linking is completed. Else the call nodes might not have been
- // resolved yet.
- WasmContext context = WasmContext.get(this);
- if (info == null && module.hasDebugInfo() && materializedTags.contains(StandardTags.StatementTag.class)) {
- Lock lock = getLock();
- lock.lock();
- try {
- info = this.instrumentation;
- if (info == null) {
- final int functionIndex = codeEntry.functionIndex();
- final DebugFunction debugFunction = debugFunction();
- if (debugFunction == null) {
- return this;
+ if (this.instrumentation == null) {
+ WasmContext context = WasmContext.get(this);
+ if (module.hasDebugInfo() && materializedTags.contains(StandardTags.StatementTag.class)) {
+ Lock lock = getLock();
+ lock.lock();
+ try {
+ if (this.instrumentation == null) {
+ final int functionIndex = codeEntry.functionIndex();
+ final DebugFunction debugFunction = debugFunction();
+ if (debugFunction == null) {
+ return this;
+ }
+ final SourceSection sourceSection;
+ if (context.getContextOptions().debugTestMode()) {
+ sourceSection = debugFunction.createSourceSection(context.environment());
+ } else {
+ sourceSection = debugFunction.loadSourceSection(context.environment());
+ }
+ if (sourceSection == null) {
+ return this;
+ }
+ final int functionStartOffset = module.functionSourceCodeStartOffset(functionIndex);
+ if (functionStartOffset == -1) {
+ return this;
+ }
+ final int functionEndOffset = module.functionSourceCodeEndOffset(functionIndex);
+ final DebugLineSection debugLineSection = debugFunction.lineMap().getLineIndexMap(functionStartOffset, functionEndOffset);
+ final WasmInstrumentationSupportNode support = new WasmInstrumentationSupportNode(debugLineSection, sourceSection.getSource());
+ final BinaryParser binaryParser = new BinaryParser(module, context, module.codeSection());
+ final byte[] bytecode = binaryParser.createFunctionDebugBytecode(functionIndex, debugLineSection.offsetToLineIndexMap());
+ final WasmFunctionNode functionNodeDuplicate = new WasmFunctionNode(functionNode, bytecode, support::notifyLine);
+ return new WasmInstrumentableFunctionNode(this, functionNodeDuplicate, support);
}
- this.instrumentation = info = insert(new WasmInstrumentationSupportNode(debugFunction, module, functionIndex));
- final BinaryParser binaryParser = new BinaryParser(module, context, module.codeSection());
- final byte[] bytecode = binaryParser.createFunctionDebugBytecode(functionIndex, debugFunction.lineMap().sourceLocationToLineMap());
- functionNode.updateBytecode(bytecode, 0, bytecode.length, this::notifyLine);
- // the debug info contains instrumentable nodes, so we need to notify for
- // instrumentation updates.
- notifyInserted(info);
+ } finally {
+ lock.unlock();
}
- } finally {
- lock.unlock();
}
}
return this;
@@ -198,7 +222,7 @@ public WrapperNode createWrapper(ProbeNode probe) {
@SuppressWarnings({"static-method", "unused"})
@ExportMessage
public final boolean hasScope(Frame frame) {
- return debugFunction() != null;
+ return instrumentation != null && debugFunction() != null;
}
@ExportMessage
@@ -207,7 +231,15 @@ public final Object getScope(Frame frame, @SuppressWarnings("unused") boolean no
assert debugFunction != null;
final DebugContext context = new DebugContext(instrumentation.currentSourceLocation());
final MaterializedFrame materializedFrame = frame.materialize();
- return DebugObjectDisplayValue.fromDebugFunction(debugFunction, context, materializedFrame, this, !WasmContext.get(this).getContextOptions().debugCompDirectory().isEmpty());
+ final SourceSection sourceSection;
+ if (debugFunction.hasSourceSection()) {
+ sourceSection = debugFunction.getSourceSection();
+ } else {
+ CompilerDirectives.transferToInterpreter();
+ final WasmContext wasmContext = WasmContext.get(this);
+ sourceSection = debugFunction.createSourceSection(wasmContext == null ? null : wasmContext.environment());
+ }
+ return DebugScopeDisplayValue.fromDebugFunction(debugFunction, context, materializedFrame, this, sourceSection);
}
@Override
@@ -221,24 +253,44 @@ public int loadI32FromStack(MaterializedFrame frame, int index) {
return frame.getIntStatic(localCount() + index);
}
+ @Override
+ public void storeI32IntoStack(MaterializedFrame frame, int index, int value) {
+ frame.setIntStatic(localCount() + index, value);
+ }
+
@Override
@TruffleBoundary
public long loadI64FromStack(MaterializedFrame frame, int index) {
return frame.getLongStatic(localCount() + index);
}
+ @Override
+ public void storeI64IntoStack(MaterializedFrame frame, int index, long value) {
+ frame.setLongStatic(localCount() + index, value);
+ }
+
@Override
@TruffleBoundary
public float loadF32FromStack(MaterializedFrame frame, int index) {
return frame.getFloatStatic(localCount() + index);
}
+ @Override
+ public void storeF32IntoStack(MaterializedFrame frame, int index, float value) {
+ frame.setFloatStatic(localCount() + index, value);
+ }
+
@Override
@TruffleBoundary
public double loadF64FromStack(MaterializedFrame frame, int index) {
return frame.getDoubleStatic(localCount() + index);
}
+ @Override
+ public void storeF64IntoStack(MaterializedFrame frame, int index, double value) {
+ frame.setDoubleStatic(localCount() + index, value);
+ }
+
@Override
public boolean isValidLocalIndex(MaterializedFrame frame, int index) {
return index >= 0 && index < localCount();
@@ -250,24 +302,44 @@ public int loadI32FromLocals(MaterializedFrame frame, int index) {
return frame.getIntStatic(index);
}
+ @Override
+ public void storeI32IntoLocals(MaterializedFrame frame, int index, int value) {
+ frame.setIntStatic(index, value);
+ }
+
@Override
@TruffleBoundary
public long loadI64FromLocals(MaterializedFrame frame, int index) {
return frame.getLongStatic(index);
}
+ @Override
+ public void storeI64IntoLocals(MaterializedFrame frame, int index, long value) {
+ frame.setLongStatic(index, value);
+ }
+
@Override
@TruffleBoundary
public float loadF32FromLocals(MaterializedFrame frame, int index) {
return frame.getFloatStatic(index);
}
+ @Override
+ public void storeF32IntoLocals(MaterializedFrame frame, int index, float value) {
+ frame.setFloatStatic(index, value);
+ }
+
@Override
@TruffleBoundary
public double loadF64FromLocals(MaterializedFrame frame, int index) {
return frame.getDoubleStatic(index);
}
+ @Override
+ public void storeF64IntoLocals(MaterializedFrame frame, int index, double value) {
+ frame.setDoubleStatic(index, value);
+ }
+
@Override
public boolean isValidGlobalIndex(int index) {
return index >= 0 && index < module.symbolTable().numGlobals();
@@ -281,6 +353,15 @@ public int loadI32FromGlobals(MaterializedFrame frame, int index) {
return instance.store().globals().loadAsInt(address);
}
+ @Override
+ public void storeI32IntoGlobals(MaterializedFrame frame, int index, int value) {
+ WasmInstance instance = instance(frame);
+ if (instance.module().isGlobalMutable(index)) {
+ final int address = instance.globalAddress(index);
+ instance.store().globals().storeInt(address, value);
+ }
+ }
+
@Override
@TruffleBoundary
public long loadI64FromGlobals(MaterializedFrame frame, int index) {
@@ -289,16 +370,35 @@ public long loadI64FromGlobals(MaterializedFrame frame, int index) {
return instance.store().globals().loadAsLong(address);
}
+ @Override
+ public void storeI64IntoGlobals(MaterializedFrame frame, int index, long value) {
+ WasmInstance instance = instance(frame);
+ if (instance.module().isGlobalMutable(index)) {
+ final int address = instance.globalAddress(index);
+ instance.store().globals().storeLong(address, value);
+ }
+ }
+
@Override
@TruffleBoundary
public float loadF32FromGlobals(MaterializedFrame frame, int index) {
- return Float.floatToRawIntBits(loadI32FromGlobals(frame, index));
+ return Float.intBitsToFloat(loadI32FromGlobals(frame, index));
+ }
+
+ @Override
+ public void storeF32IntoGlobals(MaterializedFrame frame, int index, float value) {
+ storeI32IntoGlobals(frame, index, Float.floatToRawIntBits(value));
}
@Override
@TruffleBoundary
public double loadF64FromGlobals(MaterializedFrame frame, int index) {
- return Double.doubleToRawLongBits(loadI64FromGlobals(frame, index));
+ return Double.longBitsToDouble(loadI64FromGlobals(frame, index));
+ }
+
+ @Override
+ public void storeF64IntoGlobals(MaterializedFrame frame, int index, double value) {
+ storeI64IntoGlobals(frame, index, Double.doubleToRawLongBits(value));
}
@Override
@@ -314,6 +414,12 @@ public byte loadI8FromMemory(MaterializedFrame frame, long address) {
return (byte) zeroMemoryLib.load_i32_8s(memory, this, address);
}
+ @Override
+ public void storeI8IntoMemory(MaterializedFrame frame, long address, byte value) {
+ final WasmMemory memory = memory0(frame);
+ zeroMemoryLib.store_i32_8(memory, this, address, value);
+ }
+
@Override
@TruffleBoundary
public short loadI16FromMemory(MaterializedFrame frame, long address) {
@@ -321,6 +427,12 @@ public short loadI16FromMemory(MaterializedFrame frame, long address) {
return (short) zeroMemoryLib.load_i32_16s(memory, this, address);
}
+ @Override
+ public void storeI16IntoMemory(MaterializedFrame frame, long address, short value) {
+ final WasmMemory memory = memory0(frame);
+ zeroMemoryLib.store_i32_16(memory, this, address, value);
+ }
+
@Override
@TruffleBoundary
public int loadI32FromMemory(MaterializedFrame frame, long address) {
@@ -328,6 +440,12 @@ public int loadI32FromMemory(MaterializedFrame frame, long address) {
return zeroMemoryLib.load_i32(memory, this, address);
}
+ @Override
+ public void storeI32IntoMemory(MaterializedFrame frame, long address, int value) {
+ final WasmMemory memory = memory0(frame);
+ zeroMemoryLib.store_i32(memory, this, address, value);
+ }
+
@Override
@TruffleBoundary
public long loadI64FromMemory(MaterializedFrame frame, long address) {
@@ -335,6 +453,12 @@ public long loadI64FromMemory(MaterializedFrame frame, long address) {
return zeroMemoryLib.load_i64(memory, this, address);
}
+ @Override
+ public void storeI64IntoMemory(MaterializedFrame frame, long address, long value) {
+ final WasmMemory memory = memory0(frame);
+ zeroMemoryLib.store_i64(memory, this, address, value);
+ }
+
@Override
@TruffleBoundary
public float loadF32FromMemory(MaterializedFrame frame, long address) {
@@ -342,6 +466,12 @@ public float loadF32FromMemory(MaterializedFrame frame, long address) {
return zeroMemoryLib.load_f32(memory, this, address);
}
+ @Override
+ public void storeF32IntoMemory(MaterializedFrame frame, long address, float value) {
+ final WasmMemory memory = memory0(frame);
+ zeroMemoryLib.store_f32(memory, this, address, value);
+ }
+
@Override
@TruffleBoundary
public double loadF64FromMemory(MaterializedFrame frame, long address) {
@@ -349,6 +479,12 @@ public double loadF64FromMemory(MaterializedFrame frame, long address) {
return zeroMemoryLib.load_f64(memory, this, address);
}
+ @Override
+ public void storeF64IntoMemory(MaterializedFrame frame, long address, double value) {
+ final WasmMemory memory = memory0(frame);
+ zeroMemoryLib.store_f64(memory, this, address, value);
+ }
+
private byte[] loadByteArrayFromMemory(MaterializedFrame frame, long address, int length) {
final WasmMemory memory = memory0(frame);
byte[] dataArray = new byte[length];
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.java
index 1f3fec75c93c..7104e3ece103 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmInstrumentationSupportNode.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
@@ -41,13 +41,9 @@
package org.graalvm.wasm.nodes;
-import java.util.SortedSet;
-
-import org.graalvm.collections.EconomicMap;
+import org.graalvm.collections.EconomicSet;
import org.graalvm.wasm.WasmConstant;
-import org.graalvm.wasm.WasmModule;
-import org.graalvm.wasm.collection.IntArrayList;
-import org.graalvm.wasm.debugging.DebugLineMap;
+import org.graalvm.wasm.debugging.DebugLineSection;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
@@ -57,7 +53,6 @@
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.Source;
-import org.graalvm.wasm.debugging.data.DebugFunction;
/**
* Represents the statements in the source file of a wasm binary. Provides some helper methods to
@@ -66,63 +61,37 @@
public final class WasmInstrumentationSupportNode extends Node {
@Children private final WasmBaseStatementNode[] statementNodes;
- private final EconomicMap lineToIndexMap;
private int sourceLocation;
@TruffleBoundary
- public WasmInstrumentationSupportNode(DebugFunction debugFunction, WasmModule module, int functionIndex) {
- final DebugLineMap sourceLineMap = debugFunction.lineMap();
- final Source source = debugFunction.sourceSection().getSource();
- final int startOffset = module.functionSourceCodeStartOffset(functionIndex);
- if (sourceLineMap != null && startOffset != -1) {
- IntArrayList functionLines = new IntArrayList();
- int endOffset = module.functionSourceCodeEndOffset(functionIndex);
-
- int startElement = sourceLineMap.getLine(startOffset);
- SortedSet lineSet = sourceLineMap.lines().tailSet(startElement);
- lineToIndexMap = EconomicMap.create(lineSet.size());
- // start of the function, calculate sub array offset and length of all lines
- for (int line : lineSet) {
- int pc = sourceLineMap.getSourceLocation(line);
- if (pc > endOffset) {
- break;
- }
- lineToIndexMap.put(line, functionLines.size());
- functionLines.add(line);
- }
-
- // create statement nodes for every source code line
- int length = functionLines.size();
- statementNodes = length == 0 ? null : new WasmBaseStatementNode[length];
- for (int i = 0; i < length; i++) {
- statementNodes[i] = new WasmStatementNode(functionLines.get(i), source);
+ public WasmInstrumentationSupportNode(DebugLineSection lineSection, Source source) {
+ if (!lineSection.isEmpty()) {
+ // the nodes in the set appear in index order
+ final EconomicSet lines = lineSection.uniqueLines();
+ statementNodes = new WasmBaseStatementNode[lines.size()];
+ int i = 0;
+ for (int line : lines) {
+ statementNodes[i++] = new WasmStatementNode(line, source);
}
} else {
statementNodes = null;
- lineToIndexMap = null;
}
}
- public void notifyLine(VirtualFrame frame, int currentLine, int nextLine, int currentSourceLocation) {
- CompilerAsserts.partialEvaluationConstant(currentLine);
- CompilerAsserts.partialEvaluationConstant(nextLine);
- final int currentLineIndex = lineIndexOrDefault(currentLine);
- final int nextLineIndex = lineIndexOrDefault(nextLine);
- if (currentLineIndex == nextLineIndex) {
+ public void notifyLine(VirtualFrame frame, int currentLineIndex, int nextLineIndex, int currentSourceLocation) {
+ CompilerAsserts.partialEvaluationConstant(currentLineIndex);
+ CompilerAsserts.partialEvaluationConstant(nextLineIndex);
+ final InstrumentableNode.WrapperNode currentLineWrapper = getWrapperAt(currentLineIndex);
+ final InstrumentableNode.WrapperNode nextLineWrapper = getWrapperAt(nextLineIndex);
+ if (currentLineWrapper == nextLineWrapper) {
return;
}
this.sourceLocation = currentSourceLocation;
- exitAt(frame, currentLineIndex);
- enterAt(frame, nextLineIndex);
+ WasmInstrumentationSupportNode.exitAt(frame, currentLineWrapper);
+ WasmInstrumentationSupportNode.enterAt(frame, nextLineWrapper);
}
- @TruffleBoundary
- private int lineIndexOrDefault(int line) {
- return lineToIndexMap.get(line, -1);
- }
-
- private void enterAt(VirtualFrame frame, int lineIndex) {
- InstrumentableNode.WrapperNode wrapperNode = getWrapperAt(lineIndex);
+ private static void enterAt(VirtualFrame frame, InstrumentableNode.WrapperNode wrapperNode) {
if (wrapperNode == null) {
return;
}
@@ -141,8 +110,7 @@ private void enterAt(VirtualFrame frame, int lineIndex) {
}
}
- private void exitAt(VirtualFrame frame, int lineIndex) {
- InstrumentableNode.WrapperNode wrapperNode = getWrapperAt(lineIndex);
+ private static void exitAt(VirtualFrame frame, InstrumentableNode.WrapperNode wrapperNode) {
if (wrapperNode == null) {
return;
}
@@ -161,9 +129,8 @@ private void exitAt(VirtualFrame frame, int lineIndex) {
}
}
- @TruffleBoundary
private InstrumentableNode.WrapperNode getWrapperAt(int lineIndex) {
- if (statementNodes == null || lineIndex < 0 || lineIndex > statementNodes.length) {
+ if (statementNodes == null || lineIndex < 0 || lineIndex >= statementNodes.length) {
return null;
}
WasmBaseStatementNode node = statementNodes[lineIndex];
diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmRootNode.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmRootNode.java
index 9af16e02df5c..ed30d0f90091 100644
--- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmRootNode.java
+++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/nodes/WasmRootNode.java
@@ -142,4 +142,9 @@ public final String toString() {
protected boolean isInstrumentable() {
return false;
}
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
}