Repository: tetratelabs/wazero
Pull request #1672 (2023-08-29 23:38:32)

wazevo: adds disassemblable machine code option for debugging
#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index 59ea3b33..92c00fa4 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -557,6 +557,8 @@ func encodeConditionalSelect(kind instructionKind, rd, rn, rm uint32, c condFlag
 	return ret
 }
 
+const dummyInstruction uint32 = 0x14000000 // "b 0"
+
 // encodeLoadFpuConst32 encodes the following three instructions:
 //
 //	ldr s8, #8  ;; literal load of data.f32
@@ -568,7 +570,12 @@ func encodeLoadFpuConst32(c backend.Compiler, rd uint32, rawF32 uint64) {
 		0b111<<26 | (0x8/4)<<5 | rd,
 	)
 	c.Emit4Bytes(encodeUnconditionalBranch(false, 8)) // b 8
-	c.Emit4Bytes(uint32(rawF32))                      // data.f32 xxxxxxx
+	if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable {
+		// Inlined data.f32 cannot be disassembled, so we add a dummy instruction here.
+		c.Emit4Bytes(dummyInstruction)
+	} else {
+		c.Emit4Bytes(uint32(rawF32)) // data.f32 xxxxxxx
+	}
 }
 
 // encodeLoadFpuConst64 encodes the following three instructions:
@@ -582,9 +589,15 @@ func encodeLoadFpuConst64(c backend.Compiler, rd uint32, rawF64 uint64) {
 		0b1<<30 | 0b111<<26 | (0x8/4)<<5 | rd,
 	)
 	c.Emit4Bytes(encodeUnconditionalBranch(false, 12)) // b 12
-	// data.f64 xxxxxxx
-	c.Emit4Bytes(uint32(rawF64))
-	c.Emit4Bytes(uint32(rawF64 >> 32))
+	if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable {
+		// Inlined data.f64 cannot be disassembled, so we add dummy instructions here.
+		c.Emit4Bytes(dummyInstruction)
+		c.Emit4Bytes(dummyInstruction)
+	} else {
+		// data.f64 xxxxxxx
+		c.Emit4Bytes(uint32(rawF64))
+		c.Emit4Bytes(uint32(rawF64 >> 32))
+	}
 }
 
 // encodeAluRRRR encodes as Data-processing (3 source) in
@@ -1244,7 +1257,12 @@ func encodeBrTableSequence(c backend.Compiler, index regalloc.VReg, targets []ui
 
 	// Offsets are resolved in ResolveRelativeAddress phase.
 	for _, offset := range targets {
-		c.Emit4Bytes(offset)
+		if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable {
+			// Inlined offset tables cannot be disassembled properly, so pad dummy instructions to make the debugging easier.
+			c.Emit4Bytes(dummyInstruction)
+		} else {
+			c.Emit4Bytes(offset)
+		}
 	}
 }
 
diff --git a/internal/engine/wazevo/engine.go b/internal/engine/wazevo/engine.go
index 49f560dc..8a052c07 100644
--- a/internal/engine/wazevo/engine.go
+++ b/internal/engine/wazevo/engine.go
@@ -204,7 +204,8 @@ func (e *engine) CompileModule(_ context.Context, module *wasm.Module, _ []exper
 		bodies[i] = copied
 		totalSize += len(body)
 		if wazevoapi.PrintMachineCodeHexPerFunction {
-			fmt.Println(hex.EncodeToString(body))
+			fmt.Printf("[[[machine code SSA for %d/%d %s]]]\n%s\n",
+				i, len(module.CodeSection)-1, exportedFnIndex[fidx], hex.EncodeToString(body))
 		}
 	}
 
diff --git a/internal/engine/wazevo/module_engine.go b/internal/engine/wazevo/module_engine.go
index 20a75e89..249d20cb 100644
--- a/internal/engine/wazevo/module_engine.go
+++ b/internal/engine/wazevo/module_engine.go
@@ -107,6 +107,10 @@ func (m *moduleEngine) setupOpaque() {
 
 // NewFunction implements wasm.ModuleEngine.
 func (m *moduleEngine) NewFunction(index wasm.Index) api.Function {
+	if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable {
+		panic("When PrintMachineCodeHexPerFunctionDisassemblable enabled, functions must not be called")
+	}
+
 	localIndex := index
 	if importedFnCount := m.module.Source.ImportFunctionCount; index < importedFnCount {
 		imported := &m.importedFunctions[index]
diff --git a/internal/engine/wazevo/wazevoapi/debug_consts.go b/internal/engine/wazevo/wazevoapi/debug_consts.go
index 61ad7fc2..fa807974 100644
--- a/internal/engine/wazevo/wazevoapi/debug_consts.go
+++ b/internal/engine/wazevo/wazevoapi/debug_consts.go
@@ -17,13 +17,18 @@ const (
 // These consts must be disabled by default. Enable them only when debugging.
 
 const (
-	PrintSSA                       = false
-	PrintOptimizedSSA              = false
-	PrintBlockLaidOutSSA           = false
-	PrintSSAToBackendIRLowering    = false
-	PrintRegisterAllocated         = false
-	PrintFinalizedMachineCode      = false
-	PrintMachineCodeHexPerFunction = false
+	PrintSSA                                 = false
+	PrintOptimizedSSA                        = false
+	PrintBlockLaidOutSSA                     = false
+	PrintSSAToBackendIRLowering              = false
+	PrintRegisterAllocated                   = false
+	PrintFinalizedMachineCode                = false
+	PrintMachineCodeHexPerFunction           = printMachineCodeHexPerFunctionUnmodified || PrintMachineCodeHexPerFunctionDisassemblable //nolint
+	printMachineCodeHexPerFunctionUnmodified = false
+	// PrintMachineCodeHexPerFunctionDisassemblable prints the machine code while modifying the actual result
+	// to make it disassemblable. This is useful when debugging the final machine code. See the places where this is used for detail.
+	// When this is enabled, functions must not be called.
+	PrintMachineCodeHexPerFunctionDisassemblable = false
 )
 
 // ----- Validations -----


Test patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
index d250ffb3..30158e7e 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -13,6 +13,10 @@ import (
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
+func Test_dummy(t *testing.T) {
+	require.Equal(t, dummyInstruction, encodeUnconditionalBranch(false, 0))
+}
+
 func TestInstruction_encode(t *testing.T) {
 	dummyLabel := label(1)
 	for _, tc := range []struct {