wa-lang.org/wazero@v1.0.2/internal/wasm/call_context.go (about) 1 package wasm 2 3 import ( 4 "context" 5 "fmt" 6 "sync/atomic" 7 8 "wa-lang.org/wazero/api" 9 internalsys "wa-lang.org/wazero/internal/sys" 10 "wa-lang.org/wazero/sys" 11 ) 12 13 // compile time check to ensure CallContext implements api.Module 14 var _ api.Module = &CallContext{} 15 16 func NewCallContext(ns *Namespace, instance *ModuleInstance, sys *internalsys.Context) *CallContext { 17 zero := uint64(0) 18 return &CallContext{memory: instance.Memory, module: instance, ns: ns, Sys: sys, closed: &zero} 19 } 20 21 // CallContext is a function call context bound to a module. This is important as one module's functions can call 22 // imported functions, but all need to effect the same memory. 23 // 24 // Note: This does not include the context.Context because doing so risks caching the wrong context which can break 25 // functionality like trace propagation. 26 // Note: this also implements api.Module in order to simplify usage as a host function parameter. 27 type CallContext struct { 28 // TODO: We've never found a great name for this. It is only used for function calls, hence CallContext, but it 29 // moves on a different axis than, for example, the context.Context. context.Context is the same root for the whole 30 // call stack, where the CallContext can change depending on where memory is defined and who defines the calling 31 // function. When we rename this again, we should try to capture as many key points possible on the docs. 32 33 module *ModuleInstance 34 // memory is returned by Memory and overridden WithMemory 35 memory api.Memory 36 ns *Namespace 37 38 // Sys is exposed for use in special imports such as WASI, assemblyscript 39 // and gojs. 40 // 41 // # Notes 42 // 43 // - This is a part of CallContext so that scope and Close is coherent. 44 // - This is not exposed outside this repository (as a host function 45 // parameter) because we haven't thought through capabilities based 46 // security implications. 47 Sys *internalsys.Context 48 49 // closed is the pointer used both to guard moduleEngine.CloseWithExitCode and to store the exit code. 50 // 51 // The update value is 1 + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed. 52 // 53 // Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations. 54 // See /RATIONALE.md 55 closed *uint64 56 57 // CodeCloser is non-nil when the code should be closed after this module. 58 CodeCloser api.Closer 59 } 60 61 // FailIfClosed returns a sys.ExitError if CloseWithExitCode was called. 62 func (m *CallContext) FailIfClosed() error { 63 if closed := atomic.LoadUint64(m.closed); closed != 0 { 64 return sys.NewExitError(m.module.Name, uint32(closed>>32)) // Unpack the high order bits as the exit code. 65 } 66 return nil 67 } 68 69 // Name implements the same method as documented on api.Module 70 func (m *CallContext) Name() string { 71 return m.module.Name 72 } 73 74 // WithMemory allows overriding memory without re-allocation when the result would be the same. 75 func (m *CallContext) WithMemory(memory *MemoryInstance) *CallContext { 76 if memory != nil && memory != m.memory { // only re-allocate if it will change the effective memory 77 return &CallContext{module: m.module, memory: memory, Sys: m.Sys, closed: m.closed} 78 } 79 return m 80 } 81 82 // String implements the same method as documented on api.Module 83 func (m *CallContext) String() string { 84 return fmt.Sprintf("Module[%s]", m.Name()) 85 } 86 87 // Close implements the same method as documented on api.Module. 88 func (m *CallContext) Close(ctx context.Context) (err error) { 89 return m.CloseWithExitCode(ctx, 0) 90 } 91 92 // CloseWithExitCode implements the same method as documented on api.Module. 93 func (m *CallContext) CloseWithExitCode(ctx context.Context, exitCode uint32) error { 94 closed, err := m.close(ctx, exitCode) 95 if !closed { 96 return nil 97 } 98 m.ns.deleteModule(m.Name()) 99 if m.CodeCloser == nil { 100 return err 101 } 102 if e := m.CodeCloser.Close(ctx); e != nil && err == nil { 103 err = e 104 } 105 return err 106 } 107 108 // close marks this CallContext as closed and releases underlying system resources. 109 // 110 // Note: The caller is responsible for removing the module from the Namespace. 111 func (m *CallContext) close(ctx context.Context, exitCode uint32) (c bool, err error) { 112 closed := uint64(1) + uint64(exitCode)<<32 // Store exitCode as high-order bits. 113 if !atomic.CompareAndSwapUint64(m.closed, 0, closed) { 114 return false, nil 115 } 116 c = true 117 if sysCtx := m.Sys; sysCtx != nil { // nil if from HostModuleBuilder 118 err = sysCtx.FS(ctx).Close(ctx) 119 } 120 return 121 } 122 123 // Memory implements the same method as documented on api.Module. 124 func (m *CallContext) Memory() api.Memory { 125 return m.module.Memory 126 } 127 128 // ExportedMemory implements the same method as documented on api.Module. 129 func (m *CallContext) ExportedMemory(name string) api.Memory { 130 exp, err := m.module.getExport(name, ExternTypeMemory) 131 if err != nil { 132 return nil 133 } 134 return exp.Memory 135 } 136 137 // ExportedFunction implements the same method as documented on api.Module. 138 func (m *CallContext) ExportedFunction(name string) api.Function { 139 exp, err := m.module.getExport(name, ExternTypeFunc) 140 if err != nil { 141 return nil 142 } 143 144 return m.function(exp.Function) 145 } 146 147 // Module is exposed for emscripten. 148 func (m *CallContext) Module() *ModuleInstance { 149 return m.module 150 } 151 152 func (m *CallContext) Function(funcIdx Index) api.Function { 153 if uint32(len(m.module.Functions)) < funcIdx { 154 return nil 155 } 156 return m.function(m.module.Functions[funcIdx]) 157 } 158 159 func (m *CallContext) function(f *FunctionInstance) api.Function { 160 ce, err := f.Module.Engine.NewCallEngine(m, f) 161 if err != nil { 162 return nil 163 } 164 165 if f.Module == m.module { 166 return &function{fi: f, ce: ce} 167 } else { 168 return &importedFn{importingModule: m, importedFn: f, ce: ce} 169 } 170 } 171 172 // function implements api.Function. This couples FunctionInstance with CallEngine so that 173 // it can be used to make function calls originating from the FunctionInstance. 174 type function struct { 175 fi *FunctionInstance 176 ce CallEngine 177 } 178 179 // Definition implements the same method as documented on api.FunctionDefinition. 180 func (f *function) Definition() api.FunctionDefinition { 181 return f.fi.Definition 182 } 183 184 // Call implements the same method as documented on api.Function. 185 func (f *function) Call(ctx context.Context, params ...uint64) (ret []uint64, err error) { 186 return f.ce.Call(ctx, f.fi.Module.CallCtx, params) 187 } 188 189 // importedFn implements api.Function and ensures the call context of an imported function is the importing module. 190 type importedFn struct { 191 ce CallEngine 192 importingModule *CallContext 193 importedFn *FunctionInstance 194 } 195 196 // Definition implements the same method as documented on api.Function. 197 func (f *importedFn) Definition() api.FunctionDefinition { 198 return f.importedFn.Definition 199 } 200 201 // Call implements the same method as documented on api.Function. 202 func (f *importedFn) Call(ctx context.Context, params ...uint64) (ret []uint64, err error) { 203 if f.importedFn.IsHostFunction { 204 return nil, fmt.Errorf("directly calling host function is not supported") 205 } 206 mod := f.importingModule 207 return f.ce.Call(ctx, mod, params) 208 } 209 210 // GlobalVal is an internal hack to get the lower 64 bits of a global. 211 func (m *CallContext) GlobalVal(idx Index) uint64 { 212 return m.module.Globals[idx].Val 213 } 214 215 // ExportedGlobal implements the same method as documented on api.Module. 216 func (m *CallContext) ExportedGlobal(name string) api.Global { 217 exp, err := m.module.getExport(name, ExternTypeGlobal) 218 if err != nil { 219 return nil 220 } 221 if exp.Global.Type.Mutable { 222 return &mutableGlobal{exp.Global} 223 } 224 valType := exp.Global.Type.ValType 225 switch valType { 226 case ValueTypeI32: 227 return globalI32(exp.Global.Val) 228 case ValueTypeI64: 229 return globalI64(exp.Global.Val) 230 case ValueTypeF32: 231 return globalF32(exp.Global.Val) 232 case ValueTypeF64: 233 return globalF64(exp.Global.Val) 234 default: 235 panic(fmt.Errorf("BUG: unknown value type %X", valType)) 236 } 237 }