debug_options.go

  1package wazevoapi
  2
  3import (
  4	"context"
  5	"encoding/hex"
  6	"fmt"
  7	"math/rand"
  8	"os"
  9	"time"
 10)
 11
 12// These consts are used various places in the wazevo implementations.
 13// Instead of defining them in each file, we define them here so that we can quickly iterate on
 14// debugging without spending "where do we have debug logging?" time.
 15
 16// ----- Debug logging -----
 17// These consts must be disabled by default. Enable them only when debugging.
 18
 19const (
 20	FrontEndLoggingEnabled = false
 21	SSALoggingEnabled      = false
 22	RegAllocLoggingEnabled = false
 23)
 24
 25// ----- Output prints -----
 26// These consts must be disabled by default. Enable them only when debugging.
 27
 28const (
 29	PrintSSA                                 = false
 30	PrintOptimizedSSA                        = false
 31	PrintSSAToBackendIRLowering              = false
 32	PrintRegisterAllocated                   = false
 33	PrintFinalizedMachineCode                = false
 34	PrintMachineCodeHexPerFunction           = printMachineCodeHexPerFunctionUnmodified || PrintMachineCodeHexPerFunctionDisassemblable //nolint
 35	printMachineCodeHexPerFunctionUnmodified = false
 36	// PrintMachineCodeHexPerFunctionDisassemblable prints the machine code while modifying the actual result
 37	// to make it disassemblable. This is useful when debugging the final machine code. See the places where this is used for detail.
 38	// When this is enabled, functions must not be called.
 39	PrintMachineCodeHexPerFunctionDisassemblable = false
 40)
 41
 42// printTarget is the function index to print the machine code. This is used for debugging to print the machine code
 43// of a specific function.
 44const printTarget = -1
 45
 46// PrintEnabledIndex returns true if the current function index is the print target.
 47func PrintEnabledIndex(ctx context.Context) bool {
 48	if printTarget == -1 {
 49		return true
 50	}
 51	return GetCurrentFunctionIndex(ctx) == printTarget
 52}
 53
 54// ----- Validations -----
 55const (
 56	// SSAValidationEnabled enables the SSA validation. This is disabled by default since the operation is expensive.
 57	SSAValidationEnabled = false
 58)
 59
 60// ----- Stack Guard Check -----
 61const (
 62	// StackGuardCheckEnabled enables the stack guard check to ensure that our stack bounds check works correctly.
 63	StackGuardCheckEnabled       = false
 64	StackGuardCheckGuardPageSize = 8096
 65)
 66
 67// CheckStackGuardPage checks the given stack guard page is not corrupted.
 68func CheckStackGuardPage(s []byte) {
 69	for i := 0; i < StackGuardCheckGuardPageSize; i++ {
 70		if s[i] != 0 {
 71			panic(
 72				fmt.Sprintf("BUG: stack guard page is corrupted:\n\tguard_page=%s\n\tstack=%s",
 73					hex.EncodeToString(s[:StackGuardCheckGuardPageSize]),
 74					hex.EncodeToString(s[StackGuardCheckGuardPageSize:]),
 75				))
 76		}
 77	}
 78}
 79
 80// ----- Deterministic compilation verifier -----
 81
 82const (
 83	// DeterministicCompilationVerifierEnabled enables the deterministic compilation verifier. This is disabled by default
 84	// since the operation is expensive. But when in doubt, enable this to make sure the compilation is deterministic.
 85	DeterministicCompilationVerifierEnabled = false
 86	DeterministicCompilationVerifyingIter   = 5
 87)
 88
 89type (
 90	verifierState struct {
 91		initialCompilationDone bool
 92		maybeRandomizedIndexes []int
 93		r                      *rand.Rand
 94		values                 map[string]string
 95	}
 96	verifierStateContextKey struct{}
 97	currentFunctionNameKey  struct{}
 98	currentFunctionIndexKey struct{}
 99)
100
101// NewDeterministicCompilationVerifierContext creates a new context with the deterministic compilation verifier used per wasm.Module.
102func NewDeterministicCompilationVerifierContext(ctx context.Context, localFunctions int) context.Context {
103	maybeRandomizedIndexes := make([]int, localFunctions)
104	for i := range maybeRandomizedIndexes {
105		maybeRandomizedIndexes[i] = i
106	}
107	r := rand.New(rand.NewSource(time.Now().UnixNano()))
108	return context.WithValue(ctx, verifierStateContextKey{}, &verifierState{
109		r: r, maybeRandomizedIndexes: maybeRandomizedIndexes, values: map[string]string{},
110	})
111}
112
113// DeterministicCompilationVerifierRandomizeIndexes randomizes the indexes for the deterministic compilation verifier.
114// To get the randomized index, use DeterministicCompilationVerifierGetRandomizedLocalFunctionIndex.
115func DeterministicCompilationVerifierRandomizeIndexes(ctx context.Context) {
116	state := ctx.Value(verifierStateContextKey{}).(*verifierState)
117	if !state.initialCompilationDone {
118		// If this is the first attempt, we use the index as-is order.
119		state.initialCompilationDone = true
120		return
121	}
122	r := state.r
123	r.Shuffle(len(state.maybeRandomizedIndexes), func(i, j int) {
124		state.maybeRandomizedIndexes[i], state.maybeRandomizedIndexes[j] = state.maybeRandomizedIndexes[j], state.maybeRandomizedIndexes[i]
125	})
126}
127
128// DeterministicCompilationVerifierGetRandomizedLocalFunctionIndex returns the randomized index for the given `index`
129// which is assigned by DeterministicCompilationVerifierRandomizeIndexes.
130func DeterministicCompilationVerifierGetRandomizedLocalFunctionIndex(ctx context.Context, index int) int {
131	state := ctx.Value(verifierStateContextKey{}).(*verifierState)
132	ret := state.maybeRandomizedIndexes[index]
133	return ret
134}
135
136// VerifyOrSetDeterministicCompilationContextValue verifies that the `newValue` is the same as the previous value for the given `scope`
137// and the current function name. If the previous value doesn't exist, it sets the value to the given `newValue`.
138//
139// If the verification fails, this prints the diff and exits the process.
140func VerifyOrSetDeterministicCompilationContextValue(ctx context.Context, scope string, newValue string) {
141	fn := ctx.Value(currentFunctionNameKey{}).(string)
142	key := fn + ": " + scope
143	verifierCtx := ctx.Value(verifierStateContextKey{}).(*verifierState)
144	oldValue, ok := verifierCtx.values[key]
145	if !ok {
146		verifierCtx.values[key] = newValue
147		return
148	}
149	if oldValue != newValue {
150		fmt.Printf(
151			`BUG: Deterministic compilation failed for function%s at scope="%s".
152
153This is mostly due to (but might not be limited to):
154	* Resetting ssa.Builder, backend.Compiler or frontend.Compiler, etc doens't work as expected, and the compilation has been affected by the previous iterations.
155	* Using a map with non-deterministic iteration order.
156
157---------- [old] ----------
158%s
159
160---------- [new] ----------
161%s
162`,
163			fn, scope, oldValue, newValue,
164		)
165		os.Exit(1)
166	}
167}
168
169// nolint
170const NeedFunctionNameInContext = PrintSSA ||
171	PrintOptimizedSSA ||
172	PrintSSAToBackendIRLowering ||
173	PrintRegisterAllocated ||
174	PrintFinalizedMachineCode ||
175	PrintMachineCodeHexPerFunction ||
176	DeterministicCompilationVerifierEnabled ||
177	PerfMapEnabled
178
179// SetCurrentFunctionName sets the current function name to the given `functionName`.
180func SetCurrentFunctionName(ctx context.Context, index int, functionName string) context.Context {
181	ctx = context.WithValue(ctx, currentFunctionNameKey{}, functionName)
182	ctx = context.WithValue(ctx, currentFunctionIndexKey{}, index)
183	return ctx
184}
185
186// GetCurrentFunctionName returns the current function name.
187func GetCurrentFunctionName(ctx context.Context) string {
188	ret, _ := ctx.Value(currentFunctionNameKey{}).(string)
189	return ret
190}
191
192// GetCurrentFunctionIndex returns the current function index.
193func GetCurrentFunctionIndex(ctx context.Context) int {
194	ret, _ := ctx.Value(currentFunctionIndexKey{}).(int)
195	return ret
196}