diff --git a/bom/pom.xml b/bom/pom.xml
index c98a8d3b9..aad4e3126 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -59,6 +59,11 @@
runtime
${project.version}
+
+ com.dylibso.chicory
+ rustc-demangle
+ ${project.version}
+
com.dylibso.chicory
simd
diff --git a/compiler-tests/src/test/resources/com/dylibso/chicory/testing/MethodTooLargeTest.testBigFunc.approved.txt b/compiler-tests/src/test/resources/com/dylibso/chicory/testing/MethodTooLargeTest.testBigFunc.approved.txt
index 9d155aa8a..caaf849b5 100644
--- a/compiler-tests/src/test/resources/com/dylibso/chicory/testing/MethodTooLargeTest.testBigFunc.approved.txt
+++ b/compiler-tests/src/test/resources/com/dylibso/chicory/testing/MethodTooLargeTest.testBigFunc.approved.txt
@@ -3,6 +3,8 @@ private final Lcom/dylibso/chicory/runtime/Instance; instance
private final Lcom/dylibso/chicory/runtime/internal/CompilerInterpreterMachine; compilerInterpreterMachine
}
+ public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
+
private final static Z memCopyWorkaround
}
diff --git a/compiler/src/main/java/com/dylibso/chicory/compiler/internal/Emitters.java b/compiler/src/main/java/com/dylibso/chicory/compiler/internal/Emitters.java
index fc12e592c..0138239f2 100644
--- a/compiler/src/main/java/com/dylibso/chicory/compiler/internal/Emitters.java
+++ b/compiler/src/main/java/com/dylibso/chicory/compiler/internal/Emitters.java
@@ -69,6 +69,8 @@ public static Builder builder() {
}
public static void TRAP(Context ctx, CompilerInstruction ins, InstructionAdapter asm) {
+ asm.load(ctx.instanceSlot(), OBJECT_TYPE);
+ asm.visitLdcInsn(ctx.internalClassName());
emitInvokeStatic(asm, ShadedRefs.THROW_TRAP_EXCEPTION);
asm.athrow();
}
diff --git a/compiler/src/main/java/com/dylibso/chicory/compiler/internal/Shaded.java b/compiler/src/main/java/com/dylibso/chicory/compiler/internal/Shaded.java
index 4b02b2061..9085ae96e 100644
--- a/compiler/src/main/java/com/dylibso/chicory/compiler/internal/Shaded.java
+++ b/compiler/src/main/java/com/dylibso/chicory/compiler/internal/Shaded.java
@@ -13,6 +13,7 @@
import com.dylibso.chicory.wasm.ChicoryException;
import com.dylibso.chicory.wasm.InvalidException;
import com.dylibso.chicory.wasm.types.FunctionType;
+import java.util.Arrays;
/**
* This class will get shaded into the compiled code.
@@ -183,8 +184,34 @@ public static RuntimeException throwOutOfBoundsMemoryAccess() {
throw new WasmRuntimeException("out of bounds memory access");
}
- public static RuntimeException throwTrapException() {
- throw new TrapException("Trapped on unreachable instruction");
+ public static RuntimeException throwTrapException(Instance instance, String internalClassName) {
+ StackTraceElement[] trace = new Throwable().getStackTrace();
+ var originalClassName = internalClassName.replace('/', '.');
+
+ StackTraceElement[] filtered =
+ Arrays.stream(trace)
+ .filter(
+ stackTraceElement ->
+ stackTraceElement
+ .getClassName()
+ .startsWith(originalClassName)
+ && stackTraceElement
+ .getMethodName()
+ .startsWith("func_"))
+ .flatMap(
+ stackTraceElement -> {
+ var funcId =
+ Integer.parseInt(
+ stackTraceElement
+ .getMethodName()
+ .replace("func_", ""));
+
+ return instance.computeStackFrame(funcId).stream();
+ })
+ .toArray(StackTraceElement[]::new);
+
+ throw new TrapException(
+ "Trapped on unreachable instruction in: " + originalClassName, filtered);
}
public static RuntimeException throwUnknownFunction(int index) {
diff --git a/compiler/src/main/java/com/dylibso/chicory/compiler/internal/ShadedRefs.java b/compiler/src/main/java/com/dylibso/chicory/compiler/internal/ShadedRefs.java
index 8973b7e6e..9f18d3e5c 100644
--- a/compiler/src/main/java/com/dylibso/chicory/compiler/internal/ShadedRefs.java
+++ b/compiler/src/main/java/com/dylibso/chicory/compiler/internal/ShadedRefs.java
@@ -155,7 +155,8 @@ public final class ShadedRefs {
Shaded.class.getMethod("throwIndirectCallTypeMismatch");
THROW_OUT_OF_BOUNDS_MEMORY_ACCESS =
Shaded.class.getMethod("throwOutOfBoundsMemoryAccess");
- THROW_TRAP_EXCEPTION = Shaded.class.getMethod("throwTrapException");
+ THROW_TRAP_EXCEPTION =
+ Shaded.class.getMethod("throwTrapException", Instance.class, String.class);
THROW_UNKNOWN_FUNCTION = Shaded.class.getMethod("throwUnknownFunction", int.class);
AOT_INTERPRETER_MACHINE_CALL =
diff --git a/compiler/src/test/resources/com/dylibso/chicory/approvals/ApprovalTest.verifyI32Renamed.approved.txt b/compiler/src/test/resources/com/dylibso/chicory/approvals/ApprovalTest.verifyI32Renamed.approved.txt
index d23140ddd..abd04097b 100644
--- a/compiler/src/test/resources/com/dylibso/chicory/approvals/ApprovalTest.verifyI32Renamed.approved.txt
+++ b/compiler/src/test/resources/com/dylibso/chicory/approvals/ApprovalTest.verifyI32Renamed.approved.txt
@@ -69,6 +69,8 @@ public final class FOO implements com/dylibso/chicory/runtime/Machine {
final class FOOShaded {
+ public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
+
private final static Z memCopyWorkaround
private ()V
@@ -473,13 +475,63 @@ final class FOOShaded {
INVOKESPECIAL com/dylibso/chicory/runtime/WasmRuntimeException. (Ljava/lang/String;)V
ATHROW
- public static throwTrapException()Ljava/lang/RuntimeException;
+ public static throwTrapException(Lcom/dylibso/chicory/runtime/Instance;Ljava/lang/String;)Ljava/lang/RuntimeException;
L0
+ NEW java/lang/Throwable
+ DUP
+ INVOKESPECIAL java/lang/Throwable. ()V
+ INVOKEVIRTUAL java/lang/Throwable.getStackTrace ()[Ljava/lang/StackTraceElement;
+ ASTORE 2
+ L1
+ ALOAD 1
+ BIPUSH 47
+ BIPUSH 46
+ INVOKEVIRTUAL java/lang/String.replace (CC)Ljava/lang/String;
+ ASTORE 3
+ L2
+ ALOAD 2
+ L3
+ INVOKESTATIC java/util/Arrays.stream ([Ljava/lang/Object;)Ljava/util/stream/Stream;
+ ALOAD 3
+ INVOKEDYNAMIC test(Ljava/lang/String;)Ljava/util/function/Predicate; [
+ java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+ (Ljava/lang/Object;)Z,
+ FOOShaded.lambda$throwTrapException$0(Ljava/lang/String;Ljava/lang/StackTraceElement;)Z,
+ (Ljava/lang/StackTraceElement;)Z
+ ]
+ L4
+ INVOKEINTERFACE java/util/stream/Stream.filter (Ljava/util/function/Predicate;)Ljava/util/stream/Stream; (itf)
+ ALOAD 0
+ INVOKEDYNAMIC apply(Lcom/dylibso/chicory/runtime/Instance;)Ljava/util/function/Function; [
+ java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+ (Ljava/lang/Object;)Ljava/lang/Object;,
+ FOOShaded.lambda$throwTrapException$1(Lcom/dylibso/chicory/runtime/Instance;Ljava/lang/StackTraceElement;)Ljava/util/stream/Stream;,
+ (Ljava/lang/StackTraceElement;)Ljava/util/stream/Stream;
+ ]
+ L5
+ INVOKEINTERFACE java/util/stream/Stream.flatMap (Ljava/util/function/Function;)Ljava/util/stream/Stream; (itf)
+ INVOKEDYNAMIC apply()Ljava/util/function/IntFunction; [
+ java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+ (I)Ljava/lang/Object;,
+ FOOShaded.lambda$throwTrapException$2(I)[Ljava/lang/StackTraceElement;,
+ (I)[Ljava/lang/StackTraceElement;
+ ]
+ L6
+ INVOKEINTERFACE java/util/stream/Stream.toArray (Ljava/util/function/IntFunction;)[Ljava/lang/Object; (itf)
+ CHECKCAST [Ljava/lang/StackTraceElement;
+ ASTORE 4
+ L7
NEW com/dylibso/chicory/runtime/TrapException
DUP
- LDC "Trapped on unreachable instruction"
- INVOKESPECIAL com/dylibso/chicory/runtime/TrapException. (Ljava/lang/String;)V
+ ALOAD 3
+ INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;)Ljava/lang/String; [
+ java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
+ "Trapped on unreachable instruction in: \u0001"
+ ]
+ ALOAD 4
+ INVOKESPECIAL com/dylibso/chicory/runtime/TrapException. (Ljava/lang/String;[Ljava/lang/StackTraceElement;)V
ATHROW
+ L8
public static throwUnknownFunction(I)Ljava/lang/RuntimeException;
L0
@@ -532,6 +584,57 @@ final class FOOShaded {
RETURN
L2
+ private static synthetic lambda$throwTrapException$2(I)[Ljava/lang/StackTraceElement;
+ L0
+ ILOAD 0
+ ANEWARRAY java/lang/StackTraceElement
+ ARETURN
+ L1
+
+ private static synthetic lambda$throwTrapException$1(Lcom/dylibso/chicory/runtime/Instance;Ljava/lang/StackTraceElement;)Ljava/util/stream/Stream;
+ L0
+ ALOAD 1
+ L1
+ INVOKEVIRTUAL java/lang/StackTraceElement.getMethodName ()Ljava/lang/String;
+ LDC "func_"
+ LDC ""
+ L2
+ INVOKEVIRTUAL java/lang/String.replace (Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;
+ L3
+ INVOKESTATIC java/lang/Integer.parseInt (Ljava/lang/String;)I
+ ISTORE 2
+ L4
+ ALOAD 0
+ ILOAD 2
+ INVOKEVIRTUAL com/dylibso/chicory/runtime/Instance.computeStackFrame (I)Ljava/util/List;
+ INVOKEINTERFACE java/util/List.stream ()Ljava/util/stream/Stream; (itf)
+ ARETURN
+ L5
+
+ private static synthetic lambda$throwTrapException$0(Ljava/lang/String;Ljava/lang/StackTraceElement;)Z
+ L0
+ ALOAD 1
+ L1
+ INVOKEVIRTUAL java/lang/StackTraceElement.getClassName ()Ljava/lang/String;
+ ALOAD 0
+ L2
+ INVOKEVIRTUAL java/lang/String.startsWith (Ljava/lang/String;)Z
+ IFEQ L3
+ ALOAD 1
+ L4
+ INVOKEVIRTUAL java/lang/StackTraceElement.getMethodName ()Ljava/lang/String;
+ LDC "func_"
+ L5
+ INVOKEVIRTUAL java/lang/String.startsWith (Ljava/lang/String;)Z
+ IFEQ L3
+ ICONST_1
+ GOTO L6
+ L3
+ ICONST_0
+ L6
+ IRETURN
+ L7
+
static ()V
L0
LDC "chicory.memCopyWorkaround"
diff --git a/compiler/src/test/resources/com/dylibso/chicory/approvals/ApprovalTest.verifyTrap.approved.txt b/compiler/src/test/resources/com/dylibso/chicory/approvals/ApprovalTest.verifyTrap.approved.txt
index 9eeb822f4..dcbd76a85 100644
--- a/compiler/src/test/resources/com/dylibso/chicory/approvals/ApprovalTest.verifyTrap.approved.txt
+++ b/compiler/src/test/resources/com/dylibso/chicory/approvals/ApprovalTest.verifyTrap.approved.txt
@@ -78,7 +78,9 @@ public final class com/dylibso/chicory/$gen/CompiledMachine implements com/dylib
final class com/dylibso/chicory/$gen/CompiledMachineFuncGroup_0 {
public static func_0(Lcom/dylibso/chicory/runtime/Memory;Lcom/dylibso/chicory/runtime/Instance;)V
- INVOKESTATIC com/dylibso/chicory/$gen/CompiledMachineShaded.throwTrapException ()Ljava/lang/RuntimeException;
+ ALOAD 1
+ LDC "com/dylibso/chicory/$gen/CompiledMachine"
+ INVOKESTATIC com/dylibso/chicory/$gen/CompiledMachineShaded.throwTrapException (Lcom/dylibso/chicory/runtime/Instance;Ljava/lang/String;)Ljava/lang/RuntimeException;
ATHROW
L0
ATHROW
diff --git a/machine-tests/pom.xml b/machine-tests/pom.xml
index 3825c1129..324831dcd 100644
--- a/machine-tests/pom.xml
+++ b/machine-tests/pom.xml
@@ -26,6 +26,11 @@
compiler
test
+
+ com.dylibso.chicory
+ rustc-demangle
+ test
+
com.dylibso.chicory
wasi
@@ -84,6 +89,16 @@
${project.basedir}/../wabt/src/main/resources/wat2wasm
+
+ count_vowels
+
+ compile
+
+
+ com.dylibso.chicory.testing.CountVowels
+ ${project.basedir}/../wasm-corpus/src/main/resources/compiled/count_vowels.rs.wasm
+
+
diff --git a/machine-tests/src/test/java/com/dylibso/chicory/testing/MachinesTest.java b/machine-tests/src/test/java/com/dylibso/chicory/testing/MachinesTest.java
index fc1d09b89..edac2452d 100644
--- a/machine-tests/src/test/java/com/dylibso/chicory/testing/MachinesTest.java
+++ b/machine-tests/src/test/java/com/dylibso/chicory/testing/MachinesTest.java
@@ -8,6 +8,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.dylibso.chicory.compiler.MachineFactoryCompiler;
+import com.dylibso.chicory.demangle.Demangler;
import com.dylibso.chicory.runtime.ImportTable;
import com.dylibso.chicory.runtime.ImportValues;
import com.dylibso.chicory.runtime.Instance;
@@ -32,11 +33,14 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
import org.junit.jupiter.api.Test;
public final class MachinesTest {
@@ -225,7 +229,7 @@ public void shouldUseMachineCallOnlyForExport() throws Exception {
}
@Test
- public void shouldCallIndirectInterpreterToAot() {
+ public void shouldCallIndirectInterpreterToCompiler() {
var store = new Store();
var table =
new TableInstance(
@@ -242,6 +246,7 @@ public void shouldCallIndirectInterpreterToAot() {
Instance.builder(loadModule("compiled/call_indirect-import.wat.wasm"))
.withImportValues(store.toImportValues())
.withMachineFactory(MachineFactoryCompiler::compile)
+ .withExceptionConverter(demanglerExceptionConverter("wasm-compiled-module"))
.build();
assertEquals(42, instance.export("call-self").apply()[0]);
@@ -249,11 +254,11 @@ public void shouldCallIndirectInterpreterToAot() {
var ex = assertThrows(TrapException.class, instance.export("call-other-fail")::apply);
var className = ex.getStackTrace()[0].getClassName();
- assertTrue(className.contains("CompiledMachine"), className);
+ assertTrue(className.contains("wasm-compiled-module"), className);
}
@Test
- public void shouldCallIndirectAotToInterpreter() {
+ public void shouldCallIndirectCompilerToInterpreter() {
var store = new Store();
var table =
new TableInstance(
@@ -270,6 +275,7 @@ public void shouldCallIndirectAotToInterpreter() {
Instance.builder(loadModule("compiled/call_indirect-import.wat.wasm"))
.withImportValues(store.toImportValues())
.withMachineFactory(InterpreterMachine::new)
+ .withExceptionConverter(demanglerExceptionConverter("wasm-interpreted-module"))
.build();
assertEquals(42, instance.export("call-self").apply()[0]);
@@ -277,6 +283,88 @@ public void shouldCallIndirectAotToInterpreter() {
var ex = assertThrows(TrapException.class, instance.export("call-other-fail")::apply);
var className = ex.getStackTrace()[0].getClassName();
- assertTrue(className.contains("InterpreterMachine"), className);
+ assertTrue(className.contains("wasm-interpreted-module"), className);
+ }
+
+ private String readStackTrace(Throwable t) {
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ return sw.toString();
+ }
+
+ private static List demanglerExceptionConverter(
+ Instance instance, int funcIdx) {
+ return demanglerExceptionConverter("wasm-module").apply(instance, funcIdx);
+ }
+
+ private static BiFunction>
+ demanglerExceptionConverter(String fallbackModuleName) {
+ return (instance, funcIdx) -> {
+ var moduleName = instance.module().moduleName().orElse(fallbackModuleName);
+ var funcName =
+ instance.module()
+ .functionName(funcIdx)
+ .map(str -> Demangler.demangle(str))
+ .orElse("function[" + funcIdx + "]");
+ return List.of(new StackTraceElement(moduleName, funcName, null, -1));
+ };
+ }
+
+ private static BiFunction>
+ demanglerExceptionConverter(WasmModule debugModule) {
+ return (instance, funcIdx) -> {
+ var moduleName = debugModule.moduleName().orElse("wasm-module");
+ var funcName =
+ debugModule
+ .functionName(funcIdx)
+ .map(str -> Demangler.demangle(str))
+ .orElse("function[" + funcIdx + "]");
+ return List.of(new StackTraceElement(moduleName, funcName, null, -1));
+ };
+ }
+
+ @Test
+ public void shouldEmitUnderstandableStackTracesWithInterpreter() throws Exception {
+ var instance =
+ Instance.builder(loadModule("compiled/count_vowels.rs.wasm"))
+ .withExceptionConverter(MachinesTest::demanglerExceptionConverter)
+ .build();
+ var countVowels = instance.export("count_vowels");
+ var exception = assertThrows(TrapException.class, () -> countVowels.apply(0, -1));
+ var exceptionTxt = readStackTrace(exception);
+
+ assertTrue(exceptionTxt.contains("count_vowels.wasm.count_vowels"));
+ assertTrue(exceptionTxt.contains("count_vowels.wasm.std::panicking::rust_panic_with_hook"));
+ }
+
+ @Test
+ public void shouldEmitUnderstandableStackTracesRuntimeCompiled() throws Exception {
+ var instance =
+ Instance.builder(loadModule("compiled/count_vowels.rs.wasm"))
+ .withMachineFactory(MachineFactoryCompiler::compile)
+ .withExceptionConverter(MachinesTest::demanglerExceptionConverter)
+ .build();
+ var countVowels = instance.export("count_vowels");
+ var exception = assertThrows(TrapException.class, () -> countVowels.apply(0, -1));
+ var exceptionTxt = readStackTrace(exception);
+
+ assertTrue(exceptionTxt.contains("count_vowels.wasm.count_vowels"));
+ assertTrue(exceptionTxt.contains("count_vowels.wasm.std::panicking::rust_panic_with_hook"));
+ }
+
+ @Test
+ public void shouldEmitUnderstandableStackTracesBuildTimeCompiled() throws Exception {
+ var debugModule = loadModule("compiled/count_vowels.rs.wasm");
+ var instance =
+ Instance.builder(CountVowels.load())
+ .withMachineFactory(CountVowels::create)
+ .withExceptionConverter(demanglerExceptionConverter(debugModule))
+ .build();
+ var countVowels = instance.export("count_vowels");
+ var exception = assertThrows(TrapException.class, () -> countVowels.apply(0, -1));
+ var exceptionTxt = readStackTrace(exception);
+
+ assertTrue(exceptionTxt.contains("count_vowels.wasm.count_vowels"));
+ assertTrue(exceptionTxt.contains("count_vowels.wasm.std::panicking::rust_panic_with_hook"));
}
}
diff --git a/pom.xml b/pom.xml
index 7b3cbb668..442451db1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -154,6 +154,11 @@
runtime
${project.version}
+
+ com.dylibso.chicory
+ rustc-demangle
+ ${project.version}
+
com.dylibso.chicory
simd
@@ -882,6 +887,7 @@
machine-tests
runtime
runtime-tests
+ rustc-demangle
test-gen-lib
test-gen-plugin
wabt
@@ -912,6 +918,7 @@
compiler-maven-plugin
log
runtime
+ rustc-demangle
wabt
wasi
wasm
diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java b/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java
index 693ec5324..866be4110 100644
--- a/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java
+++ b/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java
@@ -44,6 +44,8 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -67,6 +69,9 @@ public class Instance {
private final ExecutionListener listener;
private final Exports fluentExports;
+ private final Optional>>
+ exceptionConverter;
+
private final Map exnRefs;
Instance(
@@ -85,8 +90,10 @@ public class Instance {
Function machineFactory,
boolean initialize,
boolean start,
- ExecutionListener listener) {
+ ExecutionListener listener,
+ Optional>> exceptionConverter) {
this.module = module;
+ this.exceptionConverter = exceptionConverter;
this.globalInitializers = globalInitializers.clone();
this.globals = new GlobalInstance[globalInitializers.length];
this.memory = memory;
@@ -334,6 +341,14 @@ public WasmException exn(int idx) {
return exnRefs.get(idx);
}
+ public List computeStackFrame(int funcIdx) {
+ if (exceptionConverter.isPresent()) {
+ return exceptionConverter.get().apply(this, funcIdx);
+ } else {
+ return List.of();
+ }
+ }
+
public Machine getMachine() {
return machine;
}
@@ -358,6 +373,7 @@ public static final class Builder {
private ExecutionListener listener;
private ImportValues importValues;
private Function machineFactory;
+ private BiFunction> exceptionConverter;
private Builder(WasmModule module) {
this.module = Objects.requireNonNull(module);
@@ -401,6 +417,26 @@ public Builder withMachineFactory(Function machineFactory) {
return this;
}
+ public Builder withExceptionConverter(
+ BiFunction> exceptionConverter) {
+ this.exceptionConverter = exceptionConverter;
+ return this;
+ }
+
+ public Builder withDefaultExceptionConverter() {
+ this.exceptionConverter =
+ (inst, funcIdx) -> {
+ var funcName =
+ inst.module()
+ .functionName(funcIdx)
+ .orElse("function[" + funcIdx + "]");
+ var moduleName = inst.module().moduleName().orElse("wasm-module");
+
+ return List.of(new StackTraceElement(moduleName, funcName, null, -1));
+ };
+ return this;
+ }
+
private boolean checkExternalFunctionSignature(FunctionImport imprt, ImportFunction f) {
try {
validateExternalFunctionSignature(imprt, f);
@@ -884,7 +920,8 @@ public Instance build() {
machineFactory,
initialize,
start,
- listener);
+ listener,
+ Optional.ofNullable(exceptionConverter));
}
}
}
diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/InterpreterMachine.java b/runtime/src/main/java/com/dylibso/chicory/runtime/InterpreterMachine.java
index 575a842cf..23a4d0f71 100644
--- a/runtime/src/main/java/com/dylibso/chicory/runtime/InterpreterMachine.java
+++ b/runtime/src/main/java/com/dylibso/chicory/runtime/InterpreterMachine.java
@@ -13,6 +13,7 @@
import com.dylibso.chicory.wasm.types.ValType;
import com.dylibso.chicory.wasm.types.Value;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
@@ -168,7 +169,23 @@ protected void eval(MStack stack, Instance instance, Deque callStack
instance.onExecution(instruction, stack);
switch (opcode) {
case UNREACHABLE:
- throw new TrapException("Trapped on unreachable instruction");
+ {
+ List elements = new ArrayList<>();
+ int last = -1;
+ while (frame != null) {
+ while (frame != null && frame.ctrlStackSize() > 0) {
+ if (frame.funcId() != last) {
+ elements.addAll(instance.computeStackFrame(frame.funcId()));
+ last = frame.funcId();
+ }
+ frame = callStack.isEmpty() ? null : callStack.pop();
+ }
+ }
+
+ throw new TrapException(
+ "Trapped on unreachable instruction in InterpreterMachine",
+ elements.toArray(StackTraceElement[]::new));
+ }
case NOP:
break;
case LOOP:
diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/TrapException.java b/runtime/src/main/java/com/dylibso/chicory/runtime/TrapException.java
index a7155687b..9571707b0 100644
--- a/runtime/src/main/java/com/dylibso/chicory/runtime/TrapException.java
+++ b/runtime/src/main/java/com/dylibso/chicory/runtime/TrapException.java
@@ -6,4 +6,9 @@ public class TrapException extends ChicoryException {
public TrapException(String msg) {
super(msg);
}
+
+ public TrapException(String msg, StackTraceElement[] elements) {
+ super(msg);
+ setStackTrace(elements);
+ }
}
diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/WasmModuleTest.java b/runtime/src/test/java/com/dylibso/chicory/runtime/WasmModuleTest.java
index 68bbe87b6..6dd387f17 100644
--- a/runtime/src/test/java/com/dylibso/chicory/runtime/WasmModuleTest.java
+++ b/runtime/src/test/java/com/dylibso/chicory/runtime/WasmModuleTest.java
@@ -20,6 +20,8 @@
import com.dylibso.chicory.wasm.types.TagType;
import com.dylibso.chicory.wasm.types.ValType;
import com.dylibso.chicory.wasm.types.Value;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -565,4 +567,26 @@ public void timeoutExecution() throws Exception {
var future = service.submit(() -> function.apply());
assertThrows(TimeoutException.class, () -> future.get(100, TimeUnit.MILLISECONDS));
}
+
+ private String readStackTrace(Throwable t) {
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ return sw.toString();
+ }
+
+ @Test
+ public void shouldEmitUnderstandableStackTraces() throws Exception {
+ var instance =
+ Instance.builder(loadModule("compiled/count_vowels.rs.wasm"))
+ .withDefaultExceptionConverter()
+ .build();
+ var countVowels = instance.export("count_vowels");
+ var exception = assertThrows(TrapException.class, () -> countVowels.apply(0, -1));
+ var exceptionTxt = readStackTrace(exception);
+
+ System.out.println();
+
+ assertTrue(exceptionTxt.contains("count_vowels.wasm.count_vowels"));
+ assertTrue(exceptionTxt.contains("rust_panic"));
+ }
}
diff --git a/rustc-demangle/pom.xml b/rustc-demangle/pom.xml
new file mode 100644
index 000000000..4f0cdb737
--- /dev/null
+++ b/rustc-demangle/pom.xml
@@ -0,0 +1,79 @@
+
+
+ 4.0.0
+
+
+ com.dylibso.chicory
+ chicory
+ 999-SNAPSHOT
+ ../pom.xml
+
+ rustc-demangle
+ jar
+ Chicory - RustC Demangle
+ Chicory - Rustc demangle
+
+
+
+ com.dylibso.chicory
+ annotations
+
+
+ com.dylibso.chicory
+ runtime
+
+
+ com.dylibso.chicory
+ wasm
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+
+
+
+
+ com.dylibso.chicory
+ chicory-compiler-maven-plugin
+
+
+ compile-rustc-demangle
+
+ compile
+
+
+ com.dylibso.chicory.demangle.RustCDemangle
+ src/main/resources/rustc_demangle.wasm
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ com.dylibso.chicory
+ annotations-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/rustc-demangle/src/main/java/com/dylibso/chicory/demangle/Demangler.java b/rustc-demangle/src/main/java/com/dylibso/chicory/demangle/Demangler.java
new file mode 100644
index 000000000..6ab9cfa50
--- /dev/null
+++ b/rustc-demangle/src/main/java/com/dylibso/chicory/demangle/Demangler.java
@@ -0,0 +1,63 @@
+package com.dylibso.chicory.demangle;
+
+import com.dylibso.chicory.annotations.WasmModuleInterface;
+import com.dylibso.chicory.runtime.ByteArrayMemory;
+import com.dylibso.chicory.runtime.Instance;
+
+@WasmModuleInterface("rustc_demangle.wasm")
+public final class Demangler {
+
+ private static final Instance INSTANCE =
+ Instance.builder(RustCDemangle.load())
+ .withMemoryFactory(ByteArrayMemory::new)
+ .withMachineFactory(RustCDemangle::create)
+ .build();
+ private static final Demangler_ModuleExports exports = new Demangler_ModuleExports(INSTANCE);
+
+ private Demangler() {}
+
+ // Demangles symbol given in `mangled` argument into `out` buffer
+ //
+ // Returns 0 if `mangled` is not Rust symbol or if `out` buffer is too small
+ // Returns 1 otherwise
+ // int rustc_demangle(const char *mangled, char *out, size_t out_size);
+ private static String rustcDemangle(String mangled) {
+ int mangledPtr = 0;
+ int mangledSize = mangled.length() + 1;
+ int outPtr = 0;
+ int outSize = 0;
+
+ try {
+ mangledPtr = exports.alloc(mangledSize);
+ exports.memory().writeCString(mangledPtr, mangled);
+
+ outSize = exports.rustcDemangleLen(mangledPtr);
+ // Not a Rust symbol
+ if (outSize == 0) {
+ return mangled;
+ } else {
+ outPtr = exports.alloc(outSize);
+ var result = exports.rustcDemangle(mangledPtr, outPtr, outSize);
+ if (result == 0) {
+ throw new RuntimeException(
+ "Failed to demangle '"
+ + mangled
+ + "' is not Rust symbol or the out buffer is too small");
+ } else {
+ return exports.memory().readCString(outPtr);
+ }
+ }
+ } finally {
+ if (mangledPtr != 0) {
+ exports.dealloc(mangledPtr, mangledSize);
+ }
+ if (outPtr != 0 && outSize != 0) {
+ exports.dealloc(outPtr, outSize);
+ }
+ }
+ }
+
+ public static String demangle(String str) {
+ return rustcDemangle(str);
+ }
+}
diff --git a/rustc-demangle/src/main/resources/rustc_demangle.wasm b/rustc-demangle/src/main/resources/rustc_demangle.wasm
new file mode 100755
index 000000000..8689dccf5
Binary files /dev/null and b/rustc-demangle/src/main/resources/rustc_demangle.wasm differ
diff --git a/rustc-demangle/src/test/java/com/dylibso/chicory/demangle/DemanglerTest.java b/rustc-demangle/src/test/java/com/dylibso/chicory/demangle/DemanglerTest.java
new file mode 100644
index 000000000..26d3ece8b
--- /dev/null
+++ b/rustc-demangle/src/test/java/com/dylibso/chicory/demangle/DemanglerTest.java
@@ -0,0 +1,17 @@
+package com.dylibso.chicory.demangle;
+
+import static com.dylibso.chicory.demangle.Demangler.demangle;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class DemanglerTest {
+
+ @Test
+ public void basicDemanglingExamples() {
+ // https://docs.rs/rustc-demangle/latest/rustc_demangle/#examples
+ assertEquals("test", demangle("_ZN4testE"));
+ assertEquals("foo::bar", demangle("_ZN3foo3barE"));
+ assertEquals("foo", demangle("foo"));
+ }
+}
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/WasmModule.java b/wasm/src/main/java/com/dylibso/chicory/wasm/WasmModule.java
index 601ed8fe9..1024c3823 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/WasmModule.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/WasmModule.java
@@ -143,6 +143,31 @@ public List ignoredSections() {
return ignoredSections;
}
+ // Helper functions
+ public Optional functionName(int idx) {
+ if (idx < importSection.importCount()) {
+ return Optional.of(importSection.getImport(idx).name());
+ } else {
+ var customSection = customSections.get("name");
+ if (customSection != null && customSection instanceof NameCustomSection) {
+ var nameCustomSection = (NameCustomSection) customSection;
+ var originalName = nameCustomSection.nameOfFunction(idx);
+ return Optional.ofNullable(originalName);
+ }
+ return Optional.empty();
+ }
+ }
+
+ public Optional moduleName() {
+ var customSection = customSections.get("name");
+ if (customSection != null && customSection instanceof NameCustomSection) {
+ var nameCustomSection = (NameCustomSection) customSection;
+ return nameCustomSection.moduleName();
+ } else {
+ return Optional.empty();
+ }
+ }
+
public static Builder builder() {
return new Builder();
}