package experimentalimport ()// StackIterator allows iterating on each function of the call stack, starting// from the top. At least one call to Next() is required to start the iteration.//// Note: The iterator provides a view of the call stack at the time of// iteration. As a result, parameter values may be different than the ones their// function was called with.typeStackIteratorinterface {// Next moves the iterator to the next function in the stack. Returns // false if it reached the bottom of the stack.Next() bool// Function describes the function called by the current frame.Function() InternalFunction// ProgramCounter returns the program counter associated with the // function call.ProgramCounter() ProgramCounter}// WithFunctionListenerFactory registers a FunctionListenerFactory// with the context.func ( context.Context, FunctionListenerFactory) context.Context {returncontext.WithValue(, expctxkeys.FunctionListenerFactoryKey{}, )}// FunctionListenerFactory returns FunctionListeners to be notified when a// function is called.typeFunctionListenerFactoryinterface {// NewFunctionListener returns a FunctionListener for a defined function. // If nil is returned, no listener will be notified.NewFunctionListener(api.FunctionDefinition) FunctionListener// ^^ A single instance can be returned to avoid instantiating a listener // per function, especially as they may be thousands of functions. Shared // listeners use their FunctionDefinition parameter to clarify.}// FunctionListener can be registered for any function via// FunctionListenerFactory to be notified when the function is called.typeFunctionListenerinterface {// Before is invoked before a function is called. // // There is always one corresponding call to After or Abort for each call to // Before. This guarantee allows the listener to maintain an internal stack // to perform correlations between the entry and exit of functions. // // # Params // // - ctx: the context of the caller function which must be the same // instance or parent of the result. // - mod: the calling module. // - def: the function definition. // - params: api.ValueType encoded parameters. // - stackIterator: iterator on the call stack. At least one entry is // guaranteed (the called function), whose Args() will be equal to // params. The iterator will be reused between calls to Before. // // Note: api.Memory is meant for inspection, not modification. // mod can be cast to InternalModule to read non-exported globals.Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator)// After is invoked after a function is called. // // # Params // // - ctx: the context of the caller function. // - mod: the calling module. // - def: the function definition. // - results: api.ValueType encoded results. // // # Notes // // - api.Memory is meant for inspection, not modification. // - This is not called when a host function panics, or a guest function traps. // See Abort for more details.After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64)// Abort is invoked when a function does not return due to a trap or panic. // // # Params // // - ctx: the context of the caller function. // - mod: the calling module. // - def: the function definition. // - err: the error value representing the reason why the function aborted. // // # Notes // // - api.Memory is meant for inspection, not modification.Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error)}// FunctionListenerFunc is a function type implementing the FunctionListener// interface, making it possible to use regular functions and methods as// listeners of function invocation.//// The FunctionListener interface declares two methods (Before and After),// but this type invokes its value only when Before is called. It is best// suites for cases where the host does not need to perform correlation// between the start and end of the function call.typeFunctionListenerFuncfunc(context.Context, api.Module, api.FunctionDefinition, []uint64, StackIterator)// Before satisfies the FunctionListener interface, calls f.func ( FunctionListenerFunc) ( context.Context, api.Module, api.FunctionDefinition, []uint64, StackIterator) { (, , , , )}// After is declared to satisfy the FunctionListener interface, but it does// nothing.func ( FunctionListenerFunc) (context.Context, api.Module, api.FunctionDefinition, []uint64) {}// Abort is declared to satisfy the FunctionListener interface, but it does// nothing.func ( FunctionListenerFunc) (context.Context, api.Module, api.FunctionDefinition, error) {}// FunctionListenerFactoryFunc is a function type implementing the// FunctionListenerFactory interface, making it possible to use regular// functions and methods as factory of function listeners.typeFunctionListenerFactoryFuncfunc(api.FunctionDefinition) FunctionListener// NewFunctionListener satisfies the FunctionListenerFactory interface, calls f.func ( FunctionListenerFactoryFunc) ( api.FunctionDefinition) FunctionListener {return ()}// MultiFunctionListenerFactory constructs a FunctionListenerFactory which// combines the listeners created by each of the factories passed as arguments.//// This function is useful when multiple listeners need to be hooked to a module// because the propagation mechanism based on installing a listener factory in// the context.Context used when instantiating modules allows for a single// listener to be installed.//// The stack iterator passed to the Before method is reset so that each listener// can iterate the call stack independently without impacting the ability of// other listeners to do so.func ( ...FunctionListenerFactory) FunctionListenerFactory { := make(multiFunctionListenerFactory, len())copy(, )return}type multiFunctionListenerFactory []FunctionListenerFactoryfunc ( multiFunctionListenerFactory) ( api.FunctionDefinition) FunctionListener {var []FunctionListenerfor , := range {if := .NewFunctionListener(); != nil { = append(, ) } }switchlen() {case0:returnnilcase1:return [0]default:return &multiFunctionListener{lstns: } }}type multiFunctionListener struct { lstns []FunctionListener stack stackIterator}func ( *multiFunctionListener) ( context.Context, api.Module, api.FunctionDefinition, []uint64, StackIterator) { .stack.base = for , := range .lstns { .stack.index = -1 .Before(, , , , &.stack) }}func ( *multiFunctionListener) ( context.Context, api.Module, api.FunctionDefinition, []uint64) {for , := range .lstns { .After(, , , ) }}func ( *multiFunctionListener) ( context.Context, api.Module, api.FunctionDefinition, error) {for , := range .lstns { .Abort(, , , ) }}type stackIterator struct { base StackIterator index int pcs []uint64 fns []InternalFunction}func ( *stackIterator) () bool {if .base != nil { .pcs = .pcs[:0] .fns = .fns[:0]for .base.Next() { .pcs = append(.pcs, uint64(.base.ProgramCounter())) .fns = append(.fns, .base.Function()) } .base = nil } .index++return .index < len(.pcs)}func ( *stackIterator) () ProgramCounter {returnProgramCounter(.pcs[.index])}func ( *stackIterator) () InternalFunction {return .fns[.index]}// StackFrame represents a frame on the call stack.typeStackFramestruct { Function api.Function Params []uint64 Results []uint64 PC uint64 SourceOffset uint64}type internalFunction struct { definition api.FunctionDefinition sourceOffset uint64}func ( internalFunction) () api.FunctionDefinition {return .definition}func ( internalFunction) ( ProgramCounter) uint64 {return .sourceOffset}// stackFrameIterator is an implementation of the experimental.stackFrameIterator// interface.type stackFrameIterator struct { index int stack []StackFrame fndef []api.FunctionDefinition}func ( *stackFrameIterator) () bool { .index++return .index < len(.stack)}func ( *stackFrameIterator) () InternalFunction {returninternalFunction{definition: .fndef[.index],sourceOffset: .stack[.index].SourceOffset, }}func ( *stackFrameIterator) () ProgramCounter {returnProgramCounter(.stack[.index].PC)}// NewStackIterator constructs a stack iterator from a list of stack frames.// The top most frame is the last one.func ( ...StackFrame) StackIterator { := &stackFrameIterator{index: -1,stack: make([]StackFrame, len()),fndef: make([]api.FunctionDefinition, len()), }for := range { .stack[] = [len()-(+1)] }// The size of function definition is only one pointer which should allow // the compiler to optimize the conversion to api.FunctionDefinition; but // the presence of internal.WazeroOnlyType, despite being defined as an // empty struct, forces a heap allocation that we amortize by caching the // result.for , := range { .fndef[] = .Function.Definition() }return}// BenchmarkFunctionListener implements a benchmark for function listeners.//// The benchmark calls Before and After methods repeatedly using the provided// module an stack frames to invoke the methods.//// The stack frame is a representation of the call stack that the Before method// will be invoked with. The top of the stack is stored at index zero. The stack// must contain at least one frame or the benchmark will fail.func ( int, api.Module, []StackFrame, FunctionListener) {iflen() == 0 {panic("cannot benchmark function listener with an empty stack") } := context.Background() := [0].Function.Definition() := [0].Params := [0].Results := &stackIterator{base: NewStackIterator(...)}for := 0; < ; ++ { .index = -1 .Before(, , , , ) .After(, , , ) }}// TODO: the calls to Abort are not yet tested in internal/testing/enginetest,// but they are validated indirectly in tests which exercise host logging,// like Test_procExit in imports/wasi_snapshot_preview1. Eventually we should// add dedicated tests to validate the behavior of the interpreter and compiler// engines independently.
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.