github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/wazevo/wazevoapi/debug_options.go (about)

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