Repository: tetratelabs/wazero
Pull request #1734 (2023-09-25 01:41:11)

wazevo: adds support for DWARF based stack trace
#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/compiler.go b/internal/engine/wazevo/backend/compiler.go
index 8852478c..11312cfc 100644
--- a/internal/engine/wazevo/backend/compiler.go
+++ b/internal/engine/wazevo/backend/compiler.go
@@ -104,11 +104,17 @@ type Compiler interface {
 	// AddRelocationInfo appends the relocation information for the function reference at the current buffer offset.
 	AddRelocationInfo(funcRef ssa.FuncRef)
 
+	// AddSourceOffsetInfo appends the source offset information for the given offset.
+	AddSourceOffsetInfo(executableOffset int64, sourceOffset ssa.SourceOffset)
+
+	// SourceOffsetInfo returns the source offset information for the current buffer offset.
+	SourceOffsetInfo() []SourceOffsetInfo
+
 	// Emit4Bytes appends 4 bytes to the buffer. Used during the code emission.
 	Emit4Bytes(b uint32)
 }
 
-// RelocationInfo is represents the relocation information for a call instruction.
+// RelocationInfo represents the relocation information for a call instruction.
 type RelocationInfo struct {
 	// Offset represents the offset from the beginning of the machine code of either a function or the entire module.
 	Offset int64
@@ -145,6 +151,15 @@ type compiler struct {
 	ssaTypeOfVRegID map[regalloc.VRegID]ssa.Type
 	buf             []byte
 	relocations     []RelocationInfo
+	sourceOffsets   []SourceOffsetInfo
+}
+
+// SourceOffsetInfo is a data to associate the source offset with the executable offset.
+type SourceOffsetInfo struct {
+	// SourceOffset is the source offset in the original source code.
+	SourceOffset ssa.SourceOffset
+	// ExecutableOffset is the offset in the compiled executable.
+	ExecutableOffset int64
 }
 
 // Compile implements Compiler.Compile.
@@ -300,6 +315,7 @@ func (c *compiler) Init() {
 	c.resetVRegSet()
 	c.regAlloc.Reset()
 	c.buf = c.buf[:0]
+	c.sourceOffsets = c.sourceOffsets[:0]
 	c.relocations = c.relocations[:0]
 }
 
@@ -376,6 +392,19 @@ func (c *compiler) ResolveSignature(id ssa.SignatureID) *ssa.Signature {
 	return c.ssaBuilder.ResolveSignature(id)
 }
 
+// AddSourceOffsetInfo implements Compiler.AddSourceOffsetInfo.
+func (c *compiler) AddSourceOffsetInfo(executableOffset int64, sourceOffset ssa.SourceOffset) {
+	c.sourceOffsets = append(c.sourceOffsets, SourceOffsetInfo{
+		SourceOffset:     sourceOffset,
+		ExecutableOffset: executableOffset,
+	})
+}
+
+// SourceOffsetInfo implements Compiler.SourceOffsetInfo.
+func (c *compiler) SourceOffsetInfo() []SourceOffsetInfo {
+	return c.sourceOffsets
+}
+
 // AddRelocationInfo implements Compiler.AddRelocationInfo.
 func (c *compiler) AddRelocationInfo(funcRef ssa.FuncRef) {
 	c.relocations = append(c.relocations, RelocationInfo{
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index 2273d1ea..3ec7be8f 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -45,72 +45,73 @@ const (
 )
 
 var defKinds = [numInstructionKinds]defKind{
-	adr:             defKindRD,
-	aluRRR:          defKindRD,
-	aluRRRR:         defKindRD,
-	aluRRImm12:      defKindRD,
-	aluRRBitmaskImm: defKindRD,
-	aluRRRShift:     defKindRD,
-	aluRRImmShift:   defKindRD,
-	aluRRRExtend:    defKindRD,
-	bitRR:           defKindRD,
-	movZ:            defKindRD,
-	movK:            defKindRD,
-	movN:            defKindRD,
-	mov32:           defKindRD,
-	mov64:           defKindRD,
-	fpuMov64:        defKindRD,
-	fpuMov128:       defKindRD,
-	fpuRR:           defKindRD,
-	fpuRRR:          defKindRD,
-	nop0:            defKindNone,
-	call:            defKindCall,
-	callInd:         defKindCall,
-	ret:             defKindNone,
-	store8:          defKindNone,
-	store16:         defKindNone,
-	store32:         defKindNone,
-	store64:         defKindNone,
-	exitSequence:    defKindNone,
-	condBr:          defKindNone,
-	br:              defKindNone,
-	brTableSequence: defKindNone,
-	cSet:            defKindRD,
-	extend:          defKindRD,
-	fpuCmp:          defKindNone,
-	uLoad8:          defKindRD,
-	uLoad16:         defKindRD,
-	uLoad32:         defKindRD,
-	sLoad8:          defKindRD,
-	sLoad16:         defKindRD,
-	sLoad32:         defKindRD,
-	uLoad64:         defKindRD,
-	fpuLoad32:       defKindRD,
-	fpuLoad64:       defKindRD,
-	fpuLoad128:      defKindRD,
-	loadFpuConst32:  defKindRD,
-	loadFpuConst64:  defKindRD,
-	loadFpuConst128: defKindRD,
-	fpuStore32:      defKindNone,
-	fpuStore64:      defKindNone,
-	fpuStore128:     defKindNone,
-	udf:             defKindNone,
-	cSel:            defKindRD,
-	fpuCSel:         defKindRD,
-	movToVec:        defKindRD,
-	movFromVec:      defKindRD,
-	vecDup:          defKindRD,
-	vecExtract:      defKindRD,
-	vecMisc:         defKindRD,
-	vecLanes:        defKindRD,
-	vecShiftImm:     defKindRD,
-	vecPermute:      defKindRD,
-	vecRRR:          defKindRD,
-	fpuToInt:        defKindRD,
-	intToFpu:        defKindRD,
-	cCmpImm:         defKindNone,
-	movToFPSR:       defKindNone,
-	movFromFPSR:     defKindRD,
+	adr:                  defKindRD,
+	aluRRR:               defKindRD,
+	aluRRRR:              defKindRD,
+	aluRRImm12:           defKindRD,
+	aluRRBitmaskImm:      defKindRD,
+	aluRRRShift:          defKindRD,
+	aluRRImmShift:        defKindRD,
+	aluRRRExtend:         defKindRD,
+	bitRR:                defKindRD,
+	movZ:                 defKindRD,
+	movK:                 defKindRD,
+	movN:                 defKindRD,
+	mov32:                defKindRD,
+	mov64:                defKindRD,
+	fpuMov64:             defKindRD,
+	fpuMov128:            defKindRD,
+	fpuRR:                defKindRD,
+	fpuRRR:               defKindRD,
+	nop0:                 defKindNone,
+	call:                 defKindCall,
+	callInd:              defKindCall,
+	ret:                  defKindNone,
+	store8:               defKindNone,
+	store16:              defKindNone,
+	store32:              defKindNone,
+	store64:              defKindNone,
+	exitSequence:         defKindNone,
+	condBr:               defKindNone,
+	br:                   defKindNone,
+	brTableSequence:      defKindNone,
+	cSet:                 defKindRD,
+	extend:               defKindRD,
+	fpuCmp:               defKindNone,
+	uLoad8:               defKindRD,
+	uLoad16:              defKindRD,
+	uLoad32:              defKindRD,
+	sLoad8:               defKindRD,
+	sLoad16:              defKindRD,
+	sLoad32:              defKindRD,
+	uLoad64:              defKindRD,
+	fpuLoad32:            defKindRD,
+	fpuLoad64:            defKindRD,
+	fpuLoad128:           defKindRD,
+	loadFpuConst32:       defKindRD,
+	loadFpuConst64:       defKindRD,
+	loadFpuConst128:      defKindRD,
+	fpuStore32:           defKindNone,
+	fpuStore64:           defKindNone,
+	fpuStore128:          defKindNone,
+	udf:                  defKindNone,
+	cSel:                 defKindRD,
+	fpuCSel:              defKindRD,
+	movToVec:             defKindRD,
+	movFromVec:           defKindRD,
+	vecDup:               defKindRD,
+	vecExtract:           defKindRD,
+	vecMisc:              defKindRD,
+	vecLanes:             defKindRD,
+	vecShiftImm:          defKindRD,
+	vecPermute:           defKindRD,
+	vecRRR:               defKindRD,
+	fpuToInt:             defKindRD,
+	intToFpu:             defKindRD,
+	cCmpImm:              defKindNone,
+	movToFPSR:            defKindNone,
+	movFromFPSR:          defKindRD,
+	emitSourceOffsetInfo: defKindNone,
 }
 
 // defs returns the list of regalloc.VReg that are defined by the instruction.
@@ -156,72 +157,73 @@ const (
 )
 
 var useKinds = [numInstructionKinds]useKind{
-	udf:             useKindNone,
-	aluRRR:          useKindRNRM,
-	aluRRRR:         useKindRNRMRA,
-	aluRRImm12:      useKindRN,
-	aluRRBitmaskImm: useKindRN,
-	aluRRRShift:     useKindRNRM,
-	aluRRImmShift:   useKindRN,
-	aluRRRExtend:    useKindRNRM,
-	bitRR:           useKindRN,
-	movZ:            useKindNone,
-	movK:            useKindNone,
-	movN:            useKindNone,
-	mov32:           useKindRN,
-	mov64:           useKindRN,
-	fpuMov64:        useKindRN,
-	fpuMov128:       useKindRN,
-	fpuRR:           useKindRN,
-	fpuRRR:          useKindRNRM,
-	nop0:            useKindNone,
-	call:            useKindCall,
-	callInd:         useKindCallInd,
-	ret:             useKindRet,
-	store8:          useKindRNAMode,
-	store16:         useKindRNAMode,
-	store32:         useKindRNAMode,
-	store64:         useKindRNAMode,
-	exitSequence:    useKindRN,
-	condBr:          useKindCond,
-	br:              useKindNone,
-	brTableSequence: useKindRN,
-	cSet:            useKindNone,
-	extend:          useKindRN,
-	fpuCmp:          useKindRNRM,
-	uLoad8:          useKindAMode,
-	uLoad16:         useKindAMode,
-	uLoad32:         useKindAMode,
-	sLoad8:          useKindAMode,
-	sLoad16:         useKindAMode,
-	sLoad32:         useKindAMode,
-	uLoad64:         useKindAMode,
-	fpuLoad32:       useKindAMode,
-	fpuLoad64:       useKindAMode,
-	fpuLoad128:      useKindAMode,
-	fpuStore32:      useKindRNAMode,
-	fpuStore64:      useKindRNAMode,
-	fpuStore128:     useKindRNAMode,
-	loadFpuConst32:  useKindNone,
-	loadFpuConst64:  useKindNone,
-	loadFpuConst128: useKindNone,
-	cSel:            useKindRNRM,
-	fpuCSel:         useKindRNRM,
-	movToVec:        useKindRN,
-	movFromVec:      useKindRN,
-	vecDup:          useKindRN,
-	vecExtract:      useKindRNRM,
-	cCmpImm:         useKindRN,
-	vecMisc:         useKindRN,
-	vecLanes:        useKindRN,
-	vecShiftImm:     useKindRN,
-	vecRRR:          useKindRNRM,
-	vecPermute:      useKindRNRM,
-	fpuToInt:        useKindRN,
-	intToFpu:        useKindRN,
-	movToFPSR:       useKindRN,
-	movFromFPSR:     useKindNone,
-	adr:             useKindNone,
+	udf:                  useKindNone,
+	aluRRR:               useKindRNRM,
+	aluRRRR:              useKindRNRMRA,
+	aluRRImm12:           useKindRN,
+	aluRRBitmaskImm:      useKindRN,
+	aluRRRShift:          useKindRNRM,
+	aluRRImmShift:        useKindRN,
+	aluRRRExtend:         useKindRNRM,
+	bitRR:                useKindRN,
+	movZ:                 useKindNone,
+	movK:                 useKindNone,
+	movN:                 useKindNone,
+	mov32:                useKindRN,
+	mov64:                useKindRN,
+	fpuMov64:             useKindRN,
+	fpuMov128:            useKindRN,
+	fpuRR:                useKindRN,
+	fpuRRR:               useKindRNRM,
+	nop0:                 useKindNone,
+	call:                 useKindCall,
+	callInd:              useKindCallInd,
+	ret:                  useKindRet,
+	store8:               useKindRNAMode,
+	store16:              useKindRNAMode,
+	store32:              useKindRNAMode,
+	store64:              useKindRNAMode,
+	exitSequence:         useKindRN,
+	condBr:               useKindCond,
+	br:                   useKindNone,
+	brTableSequence:      useKindRN,
+	cSet:                 useKindNone,
+	extend:               useKindRN,
+	fpuCmp:               useKindRNRM,
+	uLoad8:               useKindAMode,
+	uLoad16:              useKindAMode,
+	uLoad32:              useKindAMode,
+	sLoad8:               useKindAMode,
+	sLoad16:              useKindAMode,
+	sLoad32:              useKindAMode,
+	uLoad64:              useKindAMode,
+	fpuLoad32:            useKindAMode,
+	fpuLoad64:            useKindAMode,
+	fpuLoad128:           useKindAMode,
+	fpuStore32:           useKindRNAMode,
+	fpuStore64:           useKindRNAMode,
+	fpuStore128:          useKindRNAMode,
+	loadFpuConst32:       useKindNone,
+	loadFpuConst64:       useKindNone,
+	loadFpuConst128:      useKindNone,
+	cSel:                 useKindRNRM,
+	fpuCSel:              useKindRNRM,
+	movToVec:             useKindRN,
+	movFromVec:           useKindRN,
+	vecDup:               useKindRN,
+	vecExtract:           useKindRNRM,
+	cCmpImm:              useKindRN,
+	vecMisc:              useKindRN,
+	vecLanes:             useKindRN,
+	vecShiftImm:          useKindRN,
+	vecRRR:               useKindRNRM,
+	vecPermute:           useKindRNRM,
+	fpuToInt:             useKindRN,
+	intToFpu:             useKindRN,
+	movToFPSR:            useKindRN,
+	movFromFPSR:          useKindNone,
+	adr:                  useKindNone,
+	emitSourceOffsetInfo: useKindNone,
 }
 
 // uses returns the list of regalloc.VReg that are used by the instruction.
@@ -1262,6 +1264,8 @@ func (i *instruction) String() (str string) {
 		str = fmt.Sprintf("exit_sequence %s", formatVRegSized(i.rn.nr(), 64))
 	case udf:
 		str = "udf"
+	case emitSourceOffsetInfo:
+		str = fmt.Sprintf("source_offset_info %d", ssa.SourceOffset(i.u1))
 	default:
 		panic(i.kind)
 	}
@@ -1434,10 +1438,24 @@ const (
 	// UDF is the undefined instruction. For debugging only.
 	udf
 
+	// emitSourceOffsetInfo is a dummy instruction to emit source offset info.
+	// The existence of this instruction does not affect the execution.
+	emitSourceOffsetInfo
+
 	// ------------------- do not define below this line -------------------
 	numInstructionKinds
 )
 
+func (i *instruction) asEmitSourceOffsetInfo(l ssa.SourceOffset) *instruction {
+	i.kind = emitSourceOffsetInfo
+	i.u1 = uint64(l)
+	return i
+}
+
+func (i *instruction) sourceOffsetInfo() ssa.SourceOffset {
+	return ssa.SourceOffset(i.u1)
+}
+
 func (i *instruction) asUDF() *instruction {
 	i.kind = udf
 	return i
@@ -1971,6 +1989,8 @@ func (i *instruction) size() int64 {
 		return exitSequenceSize // 5 instructions as in encodeExitSequence.
 	case nop0:
 		return 0
+	case emitSourceOffsetInfo:
+		return 0
 	case loadFpuConst32:
 		if i.u1 == 0 {
 			return 4 // zero loading can be encoded as a single instruction.
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index 02a6f80b..711dd5dd 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -21,7 +21,7 @@ func (m *machine) encode(root *instruction) {
 
 func (i *instruction) encode(c backend.Compiler) {
 	switch kind := i.kind; kind {
-	case nop0:
+	case nop0, emitSourceOffsetInfo:
 	case exitSequence:
 		encodeExitSequence(c, i.rn.reg())
 	case ret:
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index ce7b3b28..335097fe 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -126,6 +126,11 @@ func (m *machine) LowerConditionalBranch(b *ssa.Instruction) {
 
 // LowerInstr implements backend.Machine.
 func (m *machine) LowerInstr(instr *ssa.Instruction) {
+	if l := instr.SourceOffset(); l.Valid() {
+		info := m.allocateInstr().asEmitSourceOffsetInfo(l)
+		m.insert(info)
+	}
+
 	switch op := instr.Opcode(); op {
 	case ssa.OpcodeBrz, ssa.OpcodeBrnz, ssa.OpcodeJump, ssa.OpcodeBrTable:
 		panic("BUG: branching instructions are handled by LowerBranches")
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine.go b/internal/engine/wazevo/backend/isa/arm64/machine.go
index 86c2e394..65bae30b 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine.go
@@ -370,8 +370,8 @@ func (m *machine) ResolveRelativeAddresses() {
 			}
 			divided := diff >> 2
 			if divided < minSignedInt26 || divided > maxSignedInt26 {
-				// Though, this means the currently compiled single function is extremely large.
-				panic("TODO: implement branch relocation for large unconditional branch larger than 26-bit range")
+				// This means the currently compiled single function is extremely large.
+				panic("BUG: implement branch relocation for large unconditional branch larger than 26-bit range")
 			}
 			cur.brOffsetResolved(diff)
 		case condBr:
@@ -385,7 +385,8 @@ func (m *machine) ResolveRelativeAddresses() {
 				divided := diff >> 2
 				if divided < minSignedInt19 || divided > maxSignedInt19 {
 					// This case we can insert "trampoline block" in the middle and jump to it.
-					// After that, we need to re-calculate the offset of labels after the trampoline block.
+					// After that, we need to re-calculate the offset of labels after the trampoline block by
+					// recursively calling this function.
 					panic("TODO: implement branch relocation for large conditional branch larger than 19-bit range")
 				}
 				cur.condBrOffsetResolve(diff)
@@ -398,6 +399,8 @@ func (m *machine) ResolveRelativeAddresses() {
 				cur.targets[i] = uint32(diff)
 			}
 			cur.brTableSequenceOffsetsResolved()
+		case emitSourceOffsetInfo:
+			m.compiler.AddSourceOffsetInfo(currentOffset, cur.sourceOffsetInfo())
 		}
 		currentOffset += cur.size()
 	}
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index 1c097a54..401433e4 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -134,8 +134,12 @@ func (c *callEngine) addFrame(builder wasmdebug.ErrorBuilder, addr uintptr) (def
 	if cm != nil {
 		index := cm.functionIndexOf(addr)
 		def = cm.module.FunctionDefinition(cm.module.ImportFunctionCount + index)
-		// TODO: DWARF.
-		builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), nil)
+		var sources []string
+		if dw := cm.module.DWARFLines; dw != nil {
+			sourceOffset := cm.getSourceOffset(addr)
+			sources = dw.Line(sourceOffset)
+		}
+		builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources)
 		if len(cm.listeners) > 0 {
 			listener = cm.listeners[index]
 		}
@@ -485,7 +489,8 @@ func (si *stackIterator) Definition() api.FunctionDefinition {
 }
 
 // SourceOffsetForPC implements the same method as documented on experimental.InternalFunction.
-func (si *stackIterator) SourceOffsetForPC(experimental.ProgramCounter) uint64 {
-	// TODO: DWARF.
-	return 0
+func (si *stackIterator) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 {
+	upc := uintptr(pc)
+	cm := si.eng.compiledModuleOfAddr(upc)
+	return cm.getSourceOffset(upc)
 }
diff --git a/internal/engine/wazevo/engine.go b/internal/engine/wazevo/engine.go
index d73b3118..de6ea721 100644
--- a/internal/engine/wazevo/engine.go
+++ b/internal/engine/wazevo/engine.go
@@ -72,9 +72,20 @@ type (
 
 		offsets         wazevoapi.ModuleContextOffsetData
 		sharedFunctions *sharedFunctions
+		sourceMap       sourceMap
 	}
 )
 
+// sourceMap is a mapping from the offset of the executable to the offset of the original wasm binary.
+type sourceMap struct {
+	// executableOffsets is a sorted list of offsets of the executable. This is index-correlated with wasmBinaryOffsets,
+	// in other words executableOffsets[i] is the offset of the executable which corresponds to the offset of a Wasm
+	// binary pointed by wasmBinaryOffsets[i].
+	executableOffsets []uintptr
+	// wasmBinaryOffsets is the counterpart of executableOffsets.
+	wasmBinaryOffsets []uint64
+}
+
 var _ wasm.Engine = (*engine)(nil)
 
 // NewEngine returns the implementation of wasm.Engine.
@@ -152,9 +163,11 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene
 		wazevoapi.DeterministicCompilationVerifierRandomizeIndexes(ctx)
 	}
 
+	needSourceInfo := module.DWARFLines != nil
+
 	// Creates new compiler instances which are reused for each function.
 	ssaBuilder := ssa.NewBuilder()
-	fe := frontend.NewFrontendCompiler(module, ssaBuilder, &cm.offsets, ensureTermination, withListener)
+	fe := frontend.NewFrontendCompiler(module, ssaBuilder, &cm.offsets, ensureTermination, withListener, needSourceInfo)
 	machine := newMachine()
 	be := backend.NewCompiler(ctx, machine, ssaBuilder)
 
@@ -187,6 +200,18 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene
 		totalSize = (totalSize + 15) &^ 15
 		cm.functionOffsets[i] = totalSize
 
+		if needSourceInfo {
+			// At the beginning of the function, we add the offset of the function body so that
+			// we can resolve the source location of the call site of before listener call.
+			cm.sourceMap.executableOffsets = append(cm.sourceMap.executableOffsets, uintptr(totalSize))
+			cm.sourceMap.wasmBinaryOffsets = append(cm.sourceMap.wasmBinaryOffsets, module.CodeSection[i].BodyOffsetInCodeSection)
+
+			for _, info := range be.SourceOffsetInfo() {
+				cm.sourceMap.executableOffsets = append(cm.sourceMap.executableOffsets, uintptr(totalSize)+uintptr(info.ExecutableOffset))
+				cm.sourceMap.wasmBinaryOffsets = append(cm.sourceMap.wasmBinaryOffsets, uint64(info.SourceOffset))
+			}
+		}
+
 		fref := frontend.FunctionIndexToFuncRef(fidx)
 		e.refToBinaryOffset[fref] = totalSize
 
@@ -216,6 +241,12 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene
 		copy(executable[offset:], b)
 	}
 
+	if needSourceInfo {
+		for i := range cm.sourceMap.executableOffsets {
+			cm.sourceMap.executableOffsets[i] += uintptr(unsafe.Pointer(&cm.executable[0]))
+		}
+	}
+
 	// Resolve relocations for local function calls.
 	machine.ResolveRelocations(e.refToBinaryOffset, executable, e.rels)
 
@@ -245,7 +276,7 @@ func (e *engine) compileLocalWasmFunction(
 	codeSeg := &module.CodeSection[localFunctionIndex]
 
 	// Initializes both frontend and backend compilers.
-	fe.Init(localFunctionIndex, typIndex, typ, codeSeg.LocalTypes, codeSeg.Body, needListener)
+	fe.Init(localFunctionIndex, typIndex, typ, codeSeg.LocalTypes, codeSeg.Body, needListener, codeSeg.BodyOffsetInCodeSection)
 	be.Init()
 
 	// Lower Wasm to SSA.
@@ -593,7 +624,7 @@ func (cm *compiledModule) functionIndexOf(addr uintptr) wasm.Index {
 	index := sort.Search(len(offset), func(i int) bool {
 		return offset[i] > int(addr)
 	})
-	index -= 1
+	index--
 	if index < 0 {
 		panic("BUG")
 	}
@@ -641,3 +672,20 @@ func (e *engine) getListenerTrampolineForType(functionType *wasm.FunctionType) (
 	e.sharedFunctions.listenerAfterTrampolines[functionType] = afterBuf
 	return &beforeBuf[0], &afterBuf[0]
 }
+
+func (cm *compiledModule) getSourceOffset(pc uintptr) uint64 {
+	offsets := cm.sourceMap.executableOffsets
+	if len(offsets) == 0 {
+		return 0
+	}
+
+	index := sort.Search(len(offsets), func(i int) bool {
+		return offsets[i] >= pc
+	})
+
+	index--
+	if index < 0 {
+		return 0
+	}
+	return cm.sourceMap.wasmBinaryOffsets[index]
+}
diff --git a/internal/engine/wazevo/frontend/frontend.go b/internal/engine/wazevo/frontend/frontend.go
index 4b3f43eb..0ce80220 100644
--- a/internal/engine/wazevo/frontend/frontend.go
+++ b/internal/engine/wazevo/frontend/frontend.go
@@ -35,12 +35,14 @@ type Compiler struct {
 	wasmFunctionTyp                       *wasm.FunctionType
 	wasmFunctionLocalTypes                []wasm.ValueType
 	wasmFunctionBody                      []byte
+	wasmFunctionBodyOffsetInCodeSection   uint64
 	memoryBaseVariable, memoryLenVariable ssa.Variable
 	needMemory                            bool
 	globalVariables                       []ssa.Variable
 	globalVariablesTypes                  []ssa.Type
 	mutableGlobalVariablesIndexes         []wasm.Index // index to ^.
 	needListener                          bool
+	needSourceOffsetInfo                  bool
 	// br is reused during lowering.
 	br            *bytes.Reader
 	loweringState loweringState
@@ -49,14 +51,15 @@ type Compiler struct {
 }
 
 // NewFrontendCompiler returns a frontend Compiler.
-func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoapi.ModuleContextOffsetData, ensureTermination bool, listenerOn bool) *Compiler {
+func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoapi.ModuleContextOffsetData, ensureTermination bool, listenerOn bool, sourceInfo bool) *Compiler {
 	c := &Compiler{
-		m:                   m,
-		ssaBuilder:          ssaBuilder,
-		br:                  bytes.NewReader(nil),
-		wasmLocalToVariable: make(map[wasm.Index]ssa.Variable),
-		offset:              offset,
-		ensureTermination:   ensureTermination,
+		m:                    m,
+		ssaBuilder:           ssaBuilder,
+		br:                   bytes.NewReader(nil),
+		wasmLocalToVariable:  make(map[wasm.Index]ssa.Variable),
+		offset:               offset,
+		ensureTermination:    ensureTermination,
+		needSourceOffsetInfo: sourceInfo,
 	}
 	c.declareSignatures(listenerOn)
 	return c
@@ -125,7 +128,7 @@ func SignatureForWasmFunctionType(typ *wasm.FunctionType) ssa.Signature {
 }
 
 // Init initializes the state of frontendCompiler and make it ready for a next function.
-func (c *Compiler) Init(idx, typIndex wasm.Index, typ *wasm.FunctionType, localTypes []wasm.ValueType, body []byte, needListener bool) {
+func (c *Compiler) Init(idx, typIndex wasm.Index, typ *wasm.FunctionType, localTypes []wasm.ValueType, body []byte, needListener bool, bodyOffsetInCodeSection uint64) {
 	c.ssaBuilder.Init(c.signatures[typ])
 	c.loweringState.reset()
 
@@ -134,6 +137,7 @@ func (c *Compiler) Init(idx, typIndex wasm.Index, typ *wasm.FunctionType, localT
 	c.wasmFunctionTyp = typ
 	c.wasmFunctionLocalTypes = localTypes
 	c.wasmFunctionBody = body
+	c.wasmFunctionBodyOffsetInCodeSection = bodyOffsetInCodeSection
 	c.needListener = needListener
 }
 
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index f29cf7e2..f7c19e11 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -178,6 +178,12 @@ func (c *Compiler) state() *loweringState {
 func (c *Compiler) lowerCurrentOpcode() {
 	op := c.wasmFunctionBody[c.loweringState.pc]
 
+	if c.needSourceOffsetInfo {
+		c.ssaBuilder.SetCurrentSourceOffset(
+			ssa.SourceOffset(c.loweringState.pc) + ssa.SourceOffset(c.wasmFunctionBodyOffsetInCodeSection),
+		)
+	}
+
 	builder := c.ssaBuilder
 	state := c.state()
 	switch op {
diff --git a/internal/engine/wazevo/ssa/builder.go b/internal/engine/wazevo/ssa/builder.go
index 96e404ad..8abe0a0b 100644
--- a/internal/engine/wazevo/ssa/builder.go
+++ b/internal/engine/wazevo/ssa/builder.go
@@ -118,6 +118,9 @@ type Builder interface {
 
 	// InsertUndefined inserts an undefined instruction at the current position.
 	InsertUndefined()
+
+	// SetCurrentSourceOffset sets the current source offset. The incoming instruction will be annotated with this offset.
+	SetCurrentSourceOffset(line SourceOffset)
 }
 
 // NewBuilder returns a new Builder implementation.
@@ -181,6 +184,8 @@ type builder struct {
 	donePasses bool
 	// doneBlockLayout is true if LayoutBlocks is called.
 	doneBlockLayout bool
+
+	currentSourceOffset SourceOffset
 }
 
 // ReturnBlock implements Builder.ReturnBlock.
@@ -228,6 +233,8 @@ func (b *builder) Init(s *Signature) {
 	for i := range b.valueRefCounts {
 		b.valueRefCounts[i] = 0
 	}
+
+	b.currentSourceOffset = sourceOffsetUnknown
 }
 
 // Signature implements Builder.Signature.
@@ -264,6 +271,11 @@ func (b *builder) Signatures() (ret []*Signature) {
 	return
 }
 
+// SetCurrentSourceOffset implements Builder.SetCurrentSourceOffset.
+func (b *builder) SetCurrentSourceOffset(l SourceOffset) {
+	b.currentSourceOffset = l
+}
+
 func (b *builder) usedSignatures() (ret []*Signature) {
 	for _, sig := range b.signatures {
 		if sig.used {
@@ -300,6 +312,15 @@ func (b *builder) allocateBasicBlock() *basicBlock {
 func (b *builder) InsertInstruction(instr *Instruction) {
 	b.currentBB.InsertInstruction(instr)
 
+	if l := b.currentSourceOffset; l.Valid() {
+		// Emit the source offset info only when the instruction has side effect because
+		// these are the only instructions that are accessed by stack unwinding.
+		// This reduces the significant amount of the offset info in the binary.
+		if instr.sideEffect() != sideEffectNone {
+			instr.annotateSourceOffset(l)
+		}
+	}
+
 	resultTypesFn := instructionReturnTypes[instr.opcode]
 	if resultTypesFn == nil {
 		panic("TODO: " + instr.Format(b))
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 2ea2cedb..e5d30fac 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -27,10 +27,30 @@ type Instruction struct {
 	targets    []BasicBlock
 	prev, next *Instruction
 
-	rValue  Value
-	rValues []Value
-	gid     InstructionGroupID
-	live    bool
+	rValue       Value
+	rValues      []Value
+	gid          InstructionGroupID
+	sourceOffset SourceOffset
+	live         bool
+}
+
+// SourceOffset represents the offset of the source of an instruction.
+type SourceOffset int64
+
+const sourceOffsetUnknown = -1
+
+// Valid returns true if this source offset is valid.
+func (l SourceOffset) Valid() bool {
+	return l != sourceOffsetUnknown
+}
+
+func (i *Instruction) annotateSourceOffset(line SourceOffset) {
+	i.sourceOffset = line
+}
+
+// SourceOffset returns the source offset of this instruction.
+func (i *Instruction) SourceOffset() SourceOffset {
+	return i.sourceOffset
 }
 
 // Opcode returns the opcode of this instruction.
@@ -52,6 +72,7 @@ func (i *Instruction) reset() {
 	i.rValue = ValueInvalid
 	i.typ = typeInvalid
 	i.vs = nil
+	i.sourceOffset = sourceOffsetUnknown
 }
 
 // InstructionGroupID is assigned to each instruction and represents a group of instructions


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index 2de4f81c..84fbdf9a 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -2053,7 +2053,7 @@ L9 (SSA Block: blk6):
 		t.Run(tc.name, func(t *testing.T) {
 			ssab := ssa.NewBuilder()
 			offset := wazevoapi.NewModuleContextOffsetData(tc.m, false)
-			fc := frontend.NewFrontendCompiler(tc.m, ssab, &offset, false, false)
+			fc := frontend.NewFrontendCompiler(tc.m, ssab, &offset, false, false, false)
 			machine := newMachine()
 			machine.DisableStackCheck()
 			be := backend.NewCompiler(context.Background(), machine, ssab)
@@ -2061,7 +2061,7 @@ L9 (SSA Block: blk6):
 			// Lowers the Wasm to SSA.
 			typeIndex := tc.m.FunctionSection[tc.targetIndex]
 			code := &tc.m.CodeSection[tc.targetIndex]
-			fc.Init(tc.targetIndex, typeIndex, &tc.m.TypeSection[typeIndex], code.LocalTypes, code.Body, false)
+			fc.Init(tc.targetIndex, typeIndex, &tc.m.TypeSection[typeIndex], code.LocalTypes, code.Body, false, 0)
 			fc.LowerToSSA()
 			if verbose {
 				fmt.Println("============ SSA before passes ============")
diff --git a/internal/engine/wazevo/backend/isa/arm64/util_test.go b/internal/engine/wazevo/backend/isa/arm64/util_test.go
index 98657429..577e7dd4 100644
--- a/internal/engine/wazevo/backend/isa/arm64/util_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/util_test.go
@@ -62,6 +62,10 @@ type mockCompiler struct {
 	buf         []byte
 }
 
+func (m *mockCompiler) SourceOffsetInfo() []backend.SourceOffsetInfo { return nil }
+
+func (m *mockCompiler) AddSourceOffsetInfo(int64, ssa.SourceOffset) {}
+
 func (m *mockCompiler) AddRelocationInfo(funcRef ssa.FuncRef) {
 	m.relocs = append(m.relocs, backend.RelocationInfo{FuncRef: funcRef, Offset: int64(len(m.buf))})
 }
diff --git a/internal/engine/wazevo/frontend/frontend_test.go b/internal/engine/wazevo/frontend/frontend_test.go
index e90854c5..73c2c731 100644
--- a/internal/engine/wazevo/frontend/frontend_test.go
+++ b/internal/engine/wazevo/frontend/frontend_test.go
@@ -1792,10 +1792,10 @@ blk4: () <-- (blk2,blk3)
 			b := ssa.NewBuilder()
 
 			offset := wazevoapi.NewModuleContextOffsetData(tc.m, tc.needListener)
-			fc := NewFrontendCompiler(tc.m, b, &offset, tc.ensureTermination, tc.needListener)
+			fc := NewFrontendCompiler(tc.m, b, &offset, tc.ensureTermination, tc.needListener, false)
 			typeIndex := tc.m.FunctionSection[tc.targetIndex]
 			code := &tc.m.CodeSection[tc.targetIndex]
-			fc.Init(tc.targetIndex, typeIndex, &tc.m.TypeSection[typeIndex], code.LocalTypes, code.Body, tc.needListener)
+			fc.Init(tc.targetIndex, typeIndex, &tc.m.TypeSection[typeIndex], code.LocalTypes, code.Body, tc.needListener, 0)
 
 			fc.LowerToSSA()
 
diff --git a/internal/integration_test/engine/adhoc_test.go b/internal/integration_test/engine/adhoc_test.go
index cf2589e4..ff743c0c 100644
--- a/internal/integration_test/engine/adhoc_test.go
+++ b/internal/integration_test/engine/adhoc_test.go
@@ -61,7 +61,7 @@ var tests = map[string]testCase{
 	"two indirection to host":                                          {f: testTwoIndirection},
 	"before listener globals":                                          {f: testBeforeListenerGlobals},
 	"before listener stack iterator":                                   {f: testBeforeListenerStackIterator},
-	"before listener stack iterator offsets":                           {f: testListenerStackIteratorOffset, wazevoSkip: true},
+	"before listener stack iterator offsets":                           {f: testListenerStackIteratorOffset},
 	"many params many results / doubler":                               {f: testManyParamsResultsDoubler},
 	"many params many results / doubler / listener":                    {f: testManyParamsResultsDoublerListener},
 	"many params many results / call_many_consts":                      {f: testManyParamsResultsCallManyConsts},
diff --git a/internal/integration_test/engine/dwarf_test.go b/internal/integration_test/engine/dwarf_test.go
index da067d53..a097582d 100644
--- a/internal/integration_test/engine/dwarf_test.go
+++ b/internal/integration_test/engine/dwarf_test.go
@@ -3,21 +3,23 @@ package adhoc
 import (
 	"bufio"
 	_ "embed"
+	"runtime"
 	"strings"
 	"testing"
 
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo"
 	"github.com/tetratelabs/wazero/internal/platform"
 	"github.com/tetratelabs/wazero/internal/testing/dwarftestdata"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
 var dwarfTests = map[string]testCase{
-	"tinygo": {f: testTinyGoDWARF, wazevoSkip: true},
-	"zig":    {f: testZigDWARF, wazevoSkip: true},
-	"cc":     {f: testCCDWARF, wazevoSkip: true},
-	"rust":   {f: testRustDWARF, wazevoSkip: true},
+	"tinygo": {f: testTinyGoDWARF, wazevoSkip: true}, // TODO: Skip wazevo because of some v2 instructions.
+	"zig":    {f: testZigDWARF},
+	"cc":     {f: testCCDWARF},
+	"rust":   {f: testRustDWARF},
 }
 
 func TestEngineCompiler_DWARF(t *testing.T) {
@@ -32,7 +34,12 @@ func TestEngineInterpreter_DWARF(t *testing.T) {
 }
 
 func TestEngineWazevo_DWARF(t *testing.T) {
-	t.Skip("TODO")
+	if runtime.GOARCH != "arm64" {
+		t.Skip()
+	}
+	config := wazero.NewRuntimeConfigInterpreter()
+	wazevo.ConfigureWazevo(config)
+	runAllTests(t, dwarfTests, config, true)
 }
 
 func testTinyGoDWARF(t *testing.T, r wazero.Runtime) {