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> 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> 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> 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 tag) { return tag == StandardTags.RootBodyTag.class || tag == StandardTags.RootTag.class; @@ -151,7 +158,13 @@ public boolean hasTag(Class 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; + } }