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}