Skip to content

Commit 84c83e6

Browse files
committed
feat: add DWARF debugging support and source mapping infrastructure
- Add new dwarf-rust module for parsing DWARF debugging information from WebAssembly modules - Implement RustParser to extract source line mapping information from DWARF sections - Add comprehensive SMAP (Source Map) parsing infrastructure to runtime module - Enhance compiler to add SMAPs to class files, and they can be used to enhance stack traces. - Add LineMapping, Smap, SmapParser, and Stratum classes for source map handling - Integrate DWARF parsing with runtime Instance and InterpreterMachine for better error reporting - Enhance TrapException with source mapping capabilities for improved debugging - Add test coverage with approval tests for Rust and TinyGo source mapping scenarios - Include sample WASM files with embedded DWARF information for testing This enhancement enables developers to get meaningful stack traces with original source line numbers when debugging WebAssembly modules compiled from high-level languages like Rust and Go. The DWARF parsing functionality bridges the gap between compiled WebAssembly bytecode and original source code, significantly improving the debugging experience. Signed-off-by: Hiram Chirino <hiram@hiramchirino.com>
1 parent 5f99f22 commit 84c83e6

37 files changed

Lines changed: 116052 additions & 15 deletions

File tree

compiler-tests/src/test/resources/com/dylibso/chicory/testing/MethodTooLargeTest.testBigFunc.approved.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ private final Lcom/dylibso/chicory/runtime/Instance; instance
33
private final Lcom/dylibso/chicory/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine
44
}
55

6+
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
7+
68
private final static Z memCopyWorkaround
9+
10+
private final static Ljava/util/HashMap; STRATA_BY_FUNC_GROUP
711
}
812

913
final class com/dylibso/chicory/$gen/CompiledMachineFuncGroup_0 {

compiler/src/main/java/com/dylibso/chicory/compiler/MachineFactoryCompiler.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ private MachineFactoryCompiler() {
2525
* </pre>
2626
* <p>
2727
* Every instance created by the builder will pay the cost of compiling the module.
28-
* </p>
2928
*
3029
* @see #compile(WasmModule) If you want to compile the module only once for multiple instances.
3130
*/
@@ -37,7 +36,6 @@ public static Machine compile(Instance instance) {
3736
* Compiles a machine factory that can used in instance builders.
3837
* The module is only compiled once and the machine factory is reused for every
3938
* instance created by the builder.
40-
* <p>
4139
* <pre>
4240
* var module = Parser.parse(is);
4341
* var builder = Instance.builder(module)
@@ -55,7 +53,6 @@ public static Function<Instance, Machine> compile(WasmModule module) {
5553
* The builder allows you to configure the compiler options used to compile the module to
5654
* byte code.
5755
* This should be used when you want to create multiple instances of the same module.
58-
* <p>
5956
* <pre>
6057
* var module = Parser.parse(is);
6158
* var builder = Instance.builder(module)

compiler/src/main/java/com/dylibso/chicory/compiler/internal/Compiler.java

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static com.dylibso.chicory.compiler.internal.ShadedRefs.CALL_INDIRECT;
3030
import static com.dylibso.chicory.compiler.internal.ShadedRefs.CALL_INDIRECT_ON_INTERPRETER;
3131
import static com.dylibso.chicory.compiler.internal.ShadedRefs.CHECK_INTERRUPTION;
32+
import static com.dylibso.chicory.compiler.internal.ShadedRefs.ENHANCE_STACK_TRACE;
3233
import static com.dylibso.chicory.compiler.internal.ShadedRefs.INSTANCE_MEMORY;
3334
import static com.dylibso.chicory.compiler.internal.ShadedRefs.INSTANCE_TABLE;
3435
import static com.dylibso.chicory.compiler.internal.ShadedRefs.TABLE_INSTANCE;
@@ -45,6 +46,7 @@
4546
import static java.lang.invoke.MethodType.methodType;
4647
import static java.util.Objects.requireNonNull;
4748
import static java.util.Objects.requireNonNullElse;
49+
import static java.util.Objects.requireNonNullElseGet;
4850
import static java.util.stream.Collectors.toSet;
4951
import static org.objectweb.asm.Type.INT_TYPE;
5052
import static org.objectweb.asm.Type.LONG_TYPE;
@@ -60,6 +62,8 @@
6062
import com.dylibso.chicory.runtime.Machine;
6163
import com.dylibso.chicory.runtime.Memory;
6264
import com.dylibso.chicory.runtime.internal.CompilerInterpreterMachine;
65+
import com.dylibso.chicory.runtime.internal.smap.Smap;
66+
import com.dylibso.chicory.runtime.internal.smap.Stratum;
6367
import com.dylibso.chicory.wasm.ChicoryException;
6468
import com.dylibso.chicory.wasm.WasmModule;
6569
import com.dylibso.chicory.wasm.types.ExternalType;
@@ -121,16 +125,39 @@ public final class Compiler {
121125
private int maxFunctionsPerClass;
122126
private final HashSet<Integer> interpretedFunctions;
123127

128+
private final DebugContext debugContext;
129+
private int funcGroupCount;
130+
131+
// Debug information
132+
static final class DebugContext {
133+
final Stratum inputStratum;
134+
final Stratum outputStratum = new Stratum("WASM");
135+
int nextOutputLineNo = 1;
136+
137+
DebugContext(Stratum debugInfo) {
138+
this.inputStratum = requireNonNull(debugInfo, "debugInfo");
139+
this.inputStratum.optimizeForLookups();
140+
}
141+
142+
void reset() {
143+
outputStratum.clear();
144+
nextOutputLineNo = 1;
145+
}
146+
}
147+
124148
private Compiler(
125149
WasmModule module,
126150
String className,
127151
int maxFunctionsPerClass,
128152
InterpreterFallback interpreterFallback,
129-
Set<Integer> interpretedFunctions) {
153+
Set<Integer> interpretedFunctions,
154+
Stratum debugInfo) {
130155
this.className = requireNonNull(className, "className");
131156
this.module = requireNonNull(module, "module");
132157
this.analyzer = new WasmAnalyzer(module);
133158
this.functionImports = module.importSection().count(ExternalType.FUNCTION);
159+
this.debugContext =
160+
new DebugContext(requireNonNullElseGet(debugInfo, () -> new Stratum("")));
134161

135162
if (interpretedFunctions == null || interpretedFunctions.isEmpty()) {
136163
this.interpretedFunctions = new HashSet<>();
@@ -162,6 +189,7 @@ public static final class Builder {
162189
private int maxFunctionsPerClass;
163190
private InterpreterFallback interpreterFallback;
164191
private Set<Integer> interpretedFunctions;
192+
private Function<WasmModule, Stratum> debugParser;
165193

166194
private Builder(WasmModule module) {
167195
this.module = module;
@@ -172,6 +200,11 @@ public Builder withClassName(String className) {
172200
return this;
173201
}
174202

203+
public Builder withDebugParser(Function<WasmModule, Stratum> debugParser) {
204+
this.debugParser = debugParser;
205+
return this;
206+
}
207+
175208
public Builder withMaxFunctionsPerClass(int maxFunctionsPerClass) {
176209
this.maxFunctionsPerClass = maxFunctionsPerClass;
177210
return this;
@@ -197,12 +230,19 @@ public Compiler build() {
197230
if (maxFunctionsPerClass <= 0) {
198231
maxFunctionsPerClass = DEFAULT_MAX_FUNCTIONS_PER_CLASS;
199232
}
233+
234+
Stratum debugInfo = null;
235+
if (debugParser != null) {
236+
debugInfo = debugParser.apply(module);
237+
}
238+
200239
return new Compiler(
201240
module,
202241
className,
203242
maxFunctionsPerClass,
204243
interpreterFallback,
205-
interpretedFunctions);
244+
interpretedFunctions,
245+
debugInfo);
206246
}
207247
}
208248

@@ -286,12 +326,14 @@ private void compileExtraClasses() {
286326
//
287327
var originalMaxFunctionsPerClass = maxFunctionsPerClass;
288328
while (true) {
329+
funcGroupCount = 0;
289330
try {
290331
maxFunctionsPerClass =
291332
loadChunkedClass(
292333
totalFunctions,
293334
maxFunctionsPerClass,
294335
(start, end, chunkSize) -> {
336+
funcGroupCount++;
295337
maxFunctionsPerClass = chunkSize;
296338
String className = classNameForFuncGroup(start);
297339
return compileExtraClass(
@@ -396,6 +438,7 @@ private String classNameForFuncGroup(int funcId) {
396438

397439
private Consumer<ClassVisitor> emitFunctionGroup(int start, int end, String internalClassName) {
398440
return (classWriter) -> {
441+
debugContext.reset();
399442
for (int i = start; i < end; i++) {
400443
FunctionBody body = null;
401444
try {
@@ -436,6 +479,25 @@ private Consumer<ClassVisitor> emitFunctionGroup(int start, int end, String inte
436479
throw handleMethodTooLarge(e, module);
437480
}
438481
}
482+
if (!debugContext.outputStratum.isEmpty()) {
483+
// add the source map entries to the class
484+
var sourceFile = internalClassName + ".java";
485+
486+
String smap =
487+
new Smap()
488+
.withOutputFileName(sourceFile)
489+
.withStratum(debugContext.outputStratum.optimizeForWrite(), true)
490+
.toString();
491+
492+
classWriter.visitSource(internalClassName, smap);
493+
494+
classWriter.visitField(
495+
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC,
496+
"SMAP",
497+
getDescriptor(String.class),
498+
null,
499+
smap);
500+
}
439501
};
440502
}
441503

@@ -471,6 +533,35 @@ private byte[] compileClass() {
471533
null);
472534
}
473535

536+
// if we have debug info
537+
if (!debugContext.inputStratum.isEmpty()) {
538+
539+
// emit a: public static final String[] SMAPS = new String[]{ FuncGroup_0.SMAP,
540+
// FuncGroup_1.SMAP, ... };
541+
classWriter.visitField(
542+
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC,
543+
"SMAPS",
544+
getDescriptor(String[].class),
545+
null,
546+
null);
547+
548+
// static initializer for SMAPS field
549+
emitFunction(
550+
classWriter,
551+
"<clinit>",
552+
methodType(void.class),
553+
true,
554+
asm -> compileStaticInitializer(asm, internalClassName));
555+
556+
// emit: enhanceStackTrace method
557+
emitFunction(
558+
classWriter,
559+
"enhanceStackTrace",
560+
methodType(StackTraceElement[].class, StackTraceElement[].class),
561+
true,
562+
asm -> compileEnhanceStackTrace(asm, internalClassName));
563+
}
564+
474565
// constructor
475566
emitFunction(
476567
classWriter,
@@ -574,6 +665,34 @@ private static void emitCallSuper(InstructionAdapter asm) {
574665
OBJECT_TYPE.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE), false);
575666
}
576667

668+
private void compileStaticInitializer(InstructionAdapter asm, String internalClassName) {
669+
670+
asm.iconst(funcGroupCount);
671+
asm.visitTypeInsn(Opcodes.ANEWARRAY, getInternalName(String.class));
672+
for (int i = 0; i < funcGroupCount; i++) {
673+
asm.dup();
674+
asm.iconst(i);
675+
asm.getstatic(
676+
internalClassName + classNameForFuncGroup(i * maxFunctionsPerClass),
677+
"SMAP",
678+
getDescriptor(String.class));
679+
asm.astore(OBJECT_TYPE);
680+
}
681+
682+
asm.putstatic(internalClassName, "SMAPS", getDescriptor(String[].class));
683+
asm.areturn(VOID_TYPE);
684+
}
685+
686+
private void compileEnhanceStackTrace(InstructionAdapter asm, String internalClassName) {
687+
// return Shaded.enhanceStackTrace(SMAPS, elements);
688+
asm.getstatic(internalClassName, "SMAPS", getDescriptor(String[].class));
689+
asm.load(0, OBJECT_TYPE); // elements parameter
690+
var classNamePrefix = internalClassName.replaceAll("/", ".") + "FuncGroup_";
691+
asm.aconst(classNamePrefix);
692+
emitInvokeStatic(asm, ENHANCE_STACK_TRACE);
693+
asm.areturn(OBJECT_TYPE);
694+
}
695+
577696
private void compileConstructor(InstructionAdapter asm, String internalClassName) {
578697
emitCallSuper(asm);
579698

@@ -1187,7 +1306,7 @@ private void compileFunction(
11871306
type,
11881307
body);
11891308

1190-
List<CompilerInstruction> instructions = analyzer.analyze(funcId);
1309+
List<CompilerInstruction> instructions = analyzer.analyze(funcId, debugContext);
11911310

11921311
int localsCount = type.params().size();
11931312
if (hasTooManyParameters(type)) {
@@ -1225,13 +1344,20 @@ private void compileFunction(
12251344

12261345
// compile the function body
12271346
for (CompilerInstruction ins : instructions) {
1347+
12281348
switch (ins.opcode()) {
12291349
case LABEL:
12301350
Label label = labels.get(ins.operand(0));
12311351
if (label != null) {
12321352
asm.mark(label);
12331353
visitedTargets.add(ins.operand(0));
12341354
}
1355+
1356+
break;
1357+
case LINE_NUMBER:
1358+
int line = (int) ins.operand(0);
1359+
label = labels.get(ins.operand(1));
1360+
asm.visitLineNumber(line, label);
12351361
break;
12361362
case GOTO:
12371363
if (visitedTargets.contains(ins.operand(0))) {

compiler/src/main/java/com/dylibso/chicory/compiler/internal/CompilerInstruction.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public long[] labelTargets() {
5656
case IFNE:
5757
case SWITCH:
5858
return operands;
59+
case LINE_NUMBER:
60+
return new long[] {operands[1]};
5961
default:
6062
return EMPTY;
6163
}

compiler/src/main/java/com/dylibso/chicory/compiler/internal/CompilerOpCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
enum CompilerOpCode {
99
LABEL,
10+
LINE_NUMBER,
1011
DROP_KEEP,
1112
TRAP,
1213
GOTO,

compiler/src/main/java/com/dylibso/chicory/compiler/internal/Shaded.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
import com.dylibso.chicory.runtime.OpcodeImpl;
1111
import com.dylibso.chicory.runtime.TrapException;
1212
import com.dylibso.chicory.runtime.WasmRuntimeException;
13+
import com.dylibso.chicory.runtime.internal.smap.SmapParser;
14+
import com.dylibso.chicory.runtime.internal.smap.Stratum;
1315
import com.dylibso.chicory.wasm.ChicoryException;
1416
import com.dylibso.chicory.wasm.InvalidException;
1517
import com.dylibso.chicory.wasm.types.FunctionType;
18+
import java.util.HashMap;
1619

1720
/**
1821
* This class will get shaded into the compiled code.
@@ -204,4 +207,47 @@ public static long readGlobal(int index, Instance instance) {
204207
public static void writeGlobal(long value, int index, Instance instance) {
205208
instance.global(index).setValue(value);
206209
}
210+
211+
private static final HashMap<Integer, Stratum> STRATA_BY_FUNC_GROUP = new HashMap<>();
212+
213+
private static Stratum getStratum(int group, String smap) {
214+
synchronized (STRATA_BY_FUNC_GROUP) {
215+
return STRATA_BY_FUNC_GROUP.computeIfAbsent(
216+
group, (x) -> SmapParser.parse(smap).getDefaultStratum().optimizeForLookups());
217+
}
218+
}
219+
220+
public static StackTraceElement[] enhanceStackTrace(
221+
String[] smaps, StackTraceElement[] elements, String funcGroupClassPrefix) {
222+
223+
for (int i = 0; i < elements.length; i++) {
224+
var element = elements[i];
225+
if (element.getClassName().startsWith(funcGroupClassPrefix)) {
226+
227+
String suffix = element.getClassName().substring(funcGroupClassPrefix.length());
228+
var group = Integer.parseInt(suffix);
229+
if (group < smaps.length) {
230+
231+
var line = element.getLineNumber();
232+
233+
var stratum = getStratum(group, smaps[group]);
234+
var lineMapping = stratum.getLineMapping(line);
235+
var path = stratum.getPath(lineMapping.lineFileID());
236+
237+
// Find what it maps to.
238+
if (lineMapping != null) {
239+
elements[i] =
240+
new StackTraceElement(
241+
element.getClassName(),
242+
element.getMethodName(), // todo: can we get the wasm
243+
// function name?
244+
path,
245+
(int) lineMapping.inputStartLine());
246+
}
247+
}
248+
}
249+
}
250+
251+
return elements;
252+
}
207253
}

compiler/src/main/java/com/dylibso/chicory/compiler/internal/ShadedRefs.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public final class ShadedRefs {
5252
static final Method THROW_TRAP_EXCEPTION;
5353
static final Method THROW_UNKNOWN_FUNCTION;
5454
static final Method AOT_INTERPRETER_MACHINE_CALL;
55+
static final Method ENHANCE_STACK_TRACE;
5556

5657
static {
5758
try {
@@ -160,6 +161,12 @@ public final class ShadedRefs {
160161

161162
AOT_INTERPRETER_MACHINE_CALL =
162163
CompilerInterpreterMachine.class.getMethod("call", int.class, long[].class);
164+
ENHANCE_STACK_TRACE =
165+
Shaded.class.getMethod(
166+
"enhanceStackTrace",
167+
String[].class,
168+
StackTraceElement[].class,
169+
String.class);
163170
} catch (NoSuchMethodException e) {
164171
throw new AssertionError(e);
165172
}

0 commit comments

Comments
 (0)