github.com/tetratelabs/wazero@v1.7.1/experimental/listener.go (about) 1 package experimental 2 3 import ( 4 "context" 5 6 "github.com/tetratelabs/wazero/api" 7 "github.com/tetratelabs/wazero/internal/expctxkeys" 8 ) 9 10 // StackIterator allows iterating on each function of the call stack, starting 11 // from the top. At least one call to Next() is required to start the iteration. 12 // 13 // Note: The iterator provides a view of the call stack at the time of 14 // iteration. As a result, parameter values may be different than the ones their 15 // function was called with. 16 type StackIterator interface { 17 // Next moves the iterator to the next function in the stack. Returns 18 // false if it reached the bottom of the stack. 19 Next() bool 20 // Function describes the function called by the current frame. 21 Function() InternalFunction 22 // ProgramCounter returns the program counter associated with the 23 // function call. 24 ProgramCounter() ProgramCounter 25 } 26 27 // FunctionListenerFactoryKey is a context.Context Value key. 28 // Its associated value should be a FunctionListenerFactory. 29 // 30 // Deprecated: use WithFunctionListenerFactory to enable snapshots. 31 type FunctionListenerFactoryKey = expctxkeys.FunctionListenerFactoryKey 32 33 // WithFunctionListenerFactory registers a FunctionListenerFactory 34 // with the context. 35 func WithFunctionListenerFactory(ctx context.Context, factory FunctionListenerFactory) context.Context { 36 return context.WithValue(ctx, expctxkeys.FunctionListenerFactoryKey{}, factory) 37 } 38 39 // FunctionListenerFactory returns FunctionListeners to be notified when a 40 // function is called. 41 type FunctionListenerFactory interface { 42 // NewFunctionListener returns a FunctionListener for a defined function. 43 // If nil is returned, no listener will be notified. 44 NewFunctionListener(api.FunctionDefinition) FunctionListener 45 // ^^ A single instance can be returned to avoid instantiating a listener 46 // per function, especially as they may be thousands of functions. Shared 47 // listeners use their FunctionDefinition parameter to clarify. 48 } 49 50 // FunctionListener can be registered for any function via 51 // FunctionListenerFactory to be notified when the function is called. 52 type FunctionListener interface { 53 // Before is invoked before a function is called. 54 // 55 // There is always one corresponding call to After or Abort for each call to 56 // Before. This guarantee allows the listener to maintain an internal stack 57 // to perform correlations between the entry and exit of functions. 58 // 59 // # Params 60 // 61 // - ctx: the context of the caller function which must be the same 62 // instance or parent of the result. 63 // - mod: the calling module. 64 // - def: the function definition. 65 // - params: api.ValueType encoded parameters. 66 // - stackIterator: iterator on the call stack. At least one entry is 67 // guaranteed (the called function), whose Args() will be equal to 68 // params. The iterator will be reused between calls to Before. 69 // 70 // Note: api.Memory is meant for inspection, not modification. 71 // mod can be cast to InternalModule to read non-exported globals. 72 Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator) 73 74 // After is invoked after a function is called. 75 // 76 // # Params 77 // 78 // - ctx: the context of the caller function. 79 // - mod: the calling module. 80 // - def: the function definition. 81 // - results: api.ValueType encoded results. 82 // 83 // # Notes 84 // 85 // - api.Memory is meant for inspection, not modification. 86 // - This is not called when a host function panics, or a guest function traps. 87 // See Abort for more details. 88 After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64) 89 90 // Abort is invoked when a function does not return due to a trap or panic. 91 // 92 // # Params 93 // 94 // - ctx: the context of the caller function. 95 // - mod: the calling module. 96 // - def: the function definition. 97 // - err: the error value representing the reason why the function aborted. 98 // 99 // # Notes 100 // 101 // - api.Memory is meant for inspection, not modification. 102 Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error) 103 } 104 105 // FunctionListenerFunc is a function type implementing the FunctionListener 106 // interface, making it possible to use regular functions and methods as 107 // listeners of function invocation. 108 // 109 // The FunctionListener interface declares two methods (Before and After), 110 // but this type invokes its value only when Before is called. It is best 111 // suites for cases where the host does not need to perform correlation 112 // between the start and end of the function call. 113 type FunctionListenerFunc func(context.Context, api.Module, api.FunctionDefinition, []uint64, StackIterator) 114 115 // Before satisfies the FunctionListener interface, calls f. 116 func (f FunctionListenerFunc) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator) { 117 f(ctx, mod, def, params, stackIterator) 118 } 119 120 // After is declared to satisfy the FunctionListener interface, but it does 121 // nothing. 122 func (f FunctionListenerFunc) After(context.Context, api.Module, api.FunctionDefinition, []uint64) { 123 } 124 125 // Abort is declared to satisfy the FunctionListener interface, but it does 126 // nothing. 127 func (f FunctionListenerFunc) Abort(context.Context, api.Module, api.FunctionDefinition, error) { 128 } 129 130 // FunctionListenerFactoryFunc is a function type implementing the 131 // FunctionListenerFactory interface, making it possible to use regular 132 // functions and methods as factory of function listeners. 133 type FunctionListenerFactoryFunc func(api.FunctionDefinition) FunctionListener 134 135 // NewFunctionListener satisfies the FunctionListenerFactory interface, calls f. 136 func (f FunctionListenerFactoryFunc) NewFunctionListener(def api.FunctionDefinition) FunctionListener { 137 return f(def) 138 } 139 140 // MultiFunctionListenerFactory constructs a FunctionListenerFactory which 141 // combines the listeners created by each of the factories passed as arguments. 142 // 143 // This function is useful when multiple listeners need to be hooked to a module 144 // because the propagation mechanism based on installing a listener factory in 145 // the context.Context used when instantiating modules allows for a single 146 // listener to be installed. 147 // 148 // The stack iterator passed to the Before method is reset so that each listener 149 // can iterate the call stack independently without impacting the ability of 150 // other listeners to do so. 151 func MultiFunctionListenerFactory(factories ...FunctionListenerFactory) FunctionListenerFactory { 152 multi := make(multiFunctionListenerFactory, len(factories)) 153 copy(multi, factories) 154 return multi 155 } 156 157 type multiFunctionListenerFactory []FunctionListenerFactory 158 159 func (multi multiFunctionListenerFactory) NewFunctionListener(def api.FunctionDefinition) FunctionListener { 160 var lstns []FunctionListener 161 for _, factory := range multi { 162 if lstn := factory.NewFunctionListener(def); lstn != nil { 163 lstns = append(lstns, lstn) 164 } 165 } 166 switch len(lstns) { 167 case 0: 168 return nil 169 case 1: 170 return lstns[0] 171 default: 172 return &multiFunctionListener{lstns: lstns} 173 } 174 } 175 176 type multiFunctionListener struct { 177 lstns []FunctionListener 178 stack stackIterator 179 } 180 181 func (multi *multiFunctionListener) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, si StackIterator) { 182 multi.stack.base = si 183 for _, lstn := range multi.lstns { 184 multi.stack.index = -1 185 lstn.Before(ctx, mod, def, params, &multi.stack) 186 } 187 } 188 189 func (multi *multiFunctionListener) After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64) { 190 for _, lstn := range multi.lstns { 191 lstn.After(ctx, mod, def, results) 192 } 193 } 194 195 func (multi *multiFunctionListener) Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error) { 196 for _, lstn := range multi.lstns { 197 lstn.Abort(ctx, mod, def, err) 198 } 199 } 200 201 type stackIterator struct { 202 base StackIterator 203 index int 204 pcs []uint64 205 fns []InternalFunction 206 } 207 208 func (si *stackIterator) Next() bool { 209 if si.base != nil { 210 si.pcs = si.pcs[:0] 211 si.fns = si.fns[:0] 212 213 for si.base.Next() { 214 si.pcs = append(si.pcs, uint64(si.base.ProgramCounter())) 215 si.fns = append(si.fns, si.base.Function()) 216 } 217 218 si.base = nil 219 } 220 si.index++ 221 return si.index < len(si.pcs) 222 } 223 224 func (si *stackIterator) ProgramCounter() ProgramCounter { 225 return ProgramCounter(si.pcs[si.index]) 226 } 227 228 func (si *stackIterator) Function() InternalFunction { 229 return si.fns[si.index] 230 } 231 232 // StackFrame represents a frame on the call stack. 233 type StackFrame struct { 234 Function api.Function 235 Params []uint64 236 Results []uint64 237 PC uint64 238 SourceOffset uint64 239 } 240 241 type internalFunction struct { 242 definition api.FunctionDefinition 243 sourceOffset uint64 244 } 245 246 func (f internalFunction) Definition() api.FunctionDefinition { 247 return f.definition 248 } 249 250 func (f internalFunction) SourceOffsetForPC(pc ProgramCounter) uint64 { 251 return f.sourceOffset 252 } 253 254 // stackFrameIterator is an implementation of the experimental.stackFrameIterator 255 // interface. 256 type stackFrameIterator struct { 257 index int 258 stack []StackFrame 259 fndef []api.FunctionDefinition 260 } 261 262 func (si *stackFrameIterator) Next() bool { 263 si.index++ 264 return si.index < len(si.stack) 265 } 266 267 func (si *stackFrameIterator) Function() InternalFunction { 268 return internalFunction{ 269 definition: si.fndef[si.index], 270 sourceOffset: si.stack[si.index].SourceOffset, 271 } 272 } 273 274 func (si *stackFrameIterator) ProgramCounter() ProgramCounter { 275 return ProgramCounter(si.stack[si.index].PC) 276 } 277 278 // NewStackIterator constructs a stack iterator from a list of stack frames. 279 // The top most frame is the last one. 280 func NewStackIterator(stack ...StackFrame) StackIterator { 281 si := &stackFrameIterator{ 282 index: -1, 283 stack: make([]StackFrame, len(stack)), 284 fndef: make([]api.FunctionDefinition, len(stack)), 285 } 286 for i := range stack { 287 si.stack[i] = stack[len(stack)-(i+1)] 288 } 289 // The size of function definition is only one pointer which should allow 290 // the compiler to optimize the conversion to api.FunctionDefinition; but 291 // the presence of internal.WazeroOnlyType, despite being defined as an 292 // empty struct, forces a heap allocation that we amortize by caching the 293 // result. 294 for i, frame := range stack { 295 si.fndef[i] = frame.Function.Definition() 296 } 297 return si 298 } 299 300 // BenchmarkFunctionListener implements a benchmark for function listeners. 301 // 302 // The benchmark calls Before and After methods repeatedly using the provided 303 // module an stack frames to invoke the methods. 304 // 305 // The stack frame is a representation of the call stack that the Before method 306 // will be invoked with. The top of the stack is stored at index zero. The stack 307 // must contain at least one frame or the benchmark will fail. 308 func BenchmarkFunctionListener(n int, module api.Module, stack []StackFrame, listener FunctionListener) { 309 if len(stack) == 0 { 310 panic("cannot benchmark function listener with an empty stack") 311 } 312 313 ctx := context.Background() 314 def := stack[0].Function.Definition() 315 params := stack[0].Params 316 results := stack[0].Results 317 stackIterator := &stackIterator{base: NewStackIterator(stack...)} 318 319 for i := 0; i < n; i++ { 320 stackIterator.index = -1 321 listener.Before(ctx, module, def, params, stackIterator) 322 listener.After(ctx, module, def, results) 323 } 324 } 325 326 // TODO: the calls to Abort are not yet tested in internal/testing/enginetest, 327 // but they are validated indirectly in tests which exercise host logging, 328 // like Test_procExit in imports/wasi_snapshot_preview1. Eventually we should 329 // add dedicated tests to validate the behavior of the interpreter and compiler 330 // engines independently.