github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/call_engine.go (about) 1 package wazevo 2 3 import ( 4 "context" 5 "encoding/binary" 6 "fmt" 7 "reflect" 8 "runtime" 9 "sync/atomic" 10 "unsafe" 11 12 "github.com/tetratelabs/wazero/api" 13 "github.com/tetratelabs/wazero/experimental" 14 "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" 15 "github.com/tetratelabs/wazero/internal/expctxkeys" 16 "github.com/tetratelabs/wazero/internal/internalapi" 17 "github.com/tetratelabs/wazero/internal/wasm" 18 "github.com/tetratelabs/wazero/internal/wasmdebug" 19 "github.com/tetratelabs/wazero/internal/wasmruntime" 20 ) 21 22 type ( 23 // callEngine implements api.Function. 24 callEngine struct { 25 internalapi.WazeroOnly 26 stack []byte 27 // stackTop is the pointer to the *aligned* top of the stack. This must be updated 28 // whenever the stack is changed. This is passed to the assembly function 29 // at the very beginning of api.Function Call/CallWithStack. 30 stackTop uintptr 31 // executable is the pointer to the executable code for this function. 32 executable *byte 33 preambleExecutable *byte 34 // parent is the *moduleEngine from which this callEngine is created. 35 parent *moduleEngine 36 // indexInModule is the index of the function in the module. 37 indexInModule wasm.Index 38 // sizeOfParamResultSlice is the size of the parameter/result slice. 39 sizeOfParamResultSlice int 40 requiredParams int 41 // execCtx holds various information to be read/written by assembly functions. 42 execCtx executionContext 43 // execCtxPtr holds the pointer to the executionContext which doesn't change after callEngine is created. 44 execCtxPtr uintptr 45 numberOfResults int 46 stackIteratorImpl stackIterator 47 } 48 49 // executionContext is the struct to be read/written by assembly functions. 50 executionContext struct { 51 // exitCode holds the wazevoapi.ExitCode describing the state of the function execution. 52 exitCode wazevoapi.ExitCode 53 // callerModuleContextPtr holds the moduleContextOpaque for Go function calls. 54 callerModuleContextPtr *byte 55 // originalFramePointer holds the original frame pointer of the caller of the assembly function. 56 originalFramePointer uintptr 57 // originalStackPointer holds the original stack pointer of the caller of the assembly function. 58 originalStackPointer uintptr 59 // goReturnAddress holds the return address to go back to the caller of the assembly function. 60 goReturnAddress uintptr 61 // stackBottomPtr holds the pointer to the bottom of the stack. 62 stackBottomPtr *byte 63 // goCallReturnAddress holds the return address to go back to the caller of the Go function. 64 goCallReturnAddress *byte 65 // stackPointerBeforeGoCall holds the stack pointer before calling a Go function. 66 stackPointerBeforeGoCall *uint64 67 // stackGrowRequiredSize holds the required size of stack grow. 68 stackGrowRequiredSize uintptr 69 // memoryGrowTrampolineAddress holds the address of memory grow trampoline function. 70 memoryGrowTrampolineAddress *byte 71 // stackGrowCallTrampolineAddress holds the address of stack grow trampoline function. 72 stackGrowCallTrampolineAddress *byte 73 // checkModuleExitCodeTrampolineAddress holds the address of check-module-exit-code function. 74 checkModuleExitCodeTrampolineAddress *byte 75 // savedRegisters is the opaque spaces for save/restore registers. 76 // We want to align 16 bytes for each register, so we use [64][2]uint64. 77 savedRegisters [64][2]uint64 78 // goFunctionCallCalleeModuleContextOpaque is the pointer to the target Go function's moduleContextOpaque. 79 goFunctionCallCalleeModuleContextOpaque uintptr 80 // tableGrowTrampolineAddress holds the address of table grow trampoline function. 81 tableGrowTrampolineAddress *byte 82 // refFuncTrampolineAddress holds the address of ref-func trampoline function. 83 refFuncTrampolineAddress *byte 84 // memmoveAddress holds the address of memmove function implemented by Go runtime. See memmove.go. 85 memmoveAddress uintptr 86 // framePointerBeforeGoCall holds the frame pointer before calling a Go function. Note: only used in amd64. 87 framePointerBeforeGoCall uintptr 88 // memoryWait32TrampolineAddress holds the address of memory_wait32 trampoline function. 89 memoryWait32TrampolineAddress *byte 90 // memoryWait32TrampolineAddress holds the address of memory_wait64 trampoline function. 91 memoryWait64TrampolineAddress *byte 92 // memoryNotifyTrampolineAddress holds the address of the memory_notify trampoline function. 93 memoryNotifyTrampolineAddress *byte 94 } 95 ) 96 97 func (c *callEngine) requiredInitialStackSize() int { 98 const initialStackSizeDefault = 10240 99 stackSize := initialStackSizeDefault 100 paramResultInBytes := c.sizeOfParamResultSlice * 8 * 2 // * 8 because uint64 is 8 bytes, and *2 because we need both separated param/result slots. 101 required := paramResultInBytes + 32 + 16 // 32 is enough to accommodate the call frame info, and 16 exists just in case when []byte is not aligned to 16 bytes. 102 if required > stackSize { 103 stackSize = required 104 } 105 return stackSize 106 } 107 108 func (c *callEngine) init() { 109 stackSize := c.requiredInitialStackSize() 110 if wazevoapi.StackGuardCheckEnabled { 111 stackSize += wazevoapi.StackGuardCheckGuardPageSize 112 } 113 c.stack = make([]byte, stackSize) 114 c.stackTop = alignedStackTop(c.stack) 115 if wazevoapi.StackGuardCheckEnabled { 116 c.execCtx.stackBottomPtr = &c.stack[wazevoapi.StackGuardCheckGuardPageSize] 117 } else { 118 c.execCtx.stackBottomPtr = &c.stack[0] 119 } 120 c.execCtxPtr = uintptr(unsafe.Pointer(&c.execCtx)) 121 } 122 123 // alignedStackTop returns 16-bytes aligned stack top of given stack. 124 // 16 bytes should be good for all platform (arm64/amd64). 125 func alignedStackTop(s []byte) uintptr { 126 stackAddr := uintptr(unsafe.Pointer(&s[len(s)-1])) 127 return stackAddr - (stackAddr & (16 - 1)) 128 } 129 130 // Definition implements api.Function. 131 func (c *callEngine) Definition() api.FunctionDefinition { 132 return c.parent.module.Source.FunctionDefinition(c.indexInModule) 133 } 134 135 // Call implements api.Function. 136 func (c *callEngine) Call(ctx context.Context, params ...uint64) ([]uint64, error) { 137 if c.requiredParams != len(params) { 138 return nil, fmt.Errorf("expected %d params, but passed %d", c.requiredParams, len(params)) 139 } 140 paramResultSlice := make([]uint64, c.sizeOfParamResultSlice) 141 copy(paramResultSlice, params) 142 if err := c.callWithStack(ctx, paramResultSlice); err != nil { 143 return nil, err 144 } 145 return paramResultSlice[:c.numberOfResults], nil 146 } 147 148 func (c *callEngine) addFrame(builder wasmdebug.ErrorBuilder, addr uintptr) (def api.FunctionDefinition, listener experimental.FunctionListener) { 149 eng := c.parent.parent.parent 150 cm := eng.compiledModuleOfAddr(addr) 151 if cm == nil { 152 // This case, the module might have been closed and deleted from the engine. 153 // We fall back to searching the imported modules that can be referenced from this callEngine. 154 155 // First, we check itself. 156 if checkAddrInBytes(addr, c.parent.parent.executable) { 157 cm = c.parent.parent 158 } else { 159 // Otherwise, search all imported modules. TODO: maybe recursive, but not sure it's useful in practice. 160 p := c.parent 161 for i := range p.importedFunctions { 162 candidate := p.importedFunctions[i].me.parent 163 if checkAddrInBytes(addr, candidate.executable) { 164 cm = candidate 165 break 166 } 167 } 168 } 169 } 170 171 if cm != nil { 172 index := cm.functionIndexOf(addr) 173 def = cm.module.FunctionDefinition(cm.module.ImportFunctionCount + index) 174 var sources []string 175 if dw := cm.module.DWARFLines; dw != nil { 176 sourceOffset := cm.getSourceOffset(addr) 177 sources = dw.Line(sourceOffset) 178 } 179 builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources) 180 if len(cm.listeners) > 0 { 181 listener = cm.listeners[index] 182 } 183 } 184 return 185 } 186 187 // CallWithStack implements api.Function. 188 func (c *callEngine) CallWithStack(ctx context.Context, paramResultStack []uint64) (err error) { 189 if c.sizeOfParamResultSlice > len(paramResultStack) { 190 return fmt.Errorf("need %d params, but stack size is %d", c.sizeOfParamResultSlice, len(paramResultStack)) 191 } 192 return c.callWithStack(ctx, paramResultStack) 193 } 194 195 // CallWithStack implements api.Function. 196 func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint64) (err error) { 197 snapshotEnabled := ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil 198 if snapshotEnabled { 199 ctx = context.WithValue(ctx, expctxkeys.SnapshotterKey{}, c) 200 } 201 202 if wazevoapi.StackGuardCheckEnabled { 203 defer func() { 204 wazevoapi.CheckStackGuardPage(c.stack) 205 }() 206 } 207 208 p := c.parent 209 ensureTermination := p.parent.ensureTermination 210 m := p.module 211 if ensureTermination { 212 select { 213 case <-ctx.Done(): 214 // If the provided context is already done, close the module and return the error. 215 m.CloseWithCtxErr(ctx) 216 return m.FailIfClosed() 217 default: 218 } 219 } 220 221 var paramResultPtr *uint64 222 if len(paramResultStack) > 0 { 223 paramResultPtr = ¶mResultStack[0] 224 } 225 defer func() { 226 r := recover() 227 if s, ok := r.(*snapshot); ok { 228 // A snapshot that wasn't handled was created by a different call engine possibly from a nested wasm invocation, 229 // let it propagate up to be handled by the caller. 230 panic(s) 231 } 232 if r != nil { 233 type listenerForAbort struct { 234 def api.FunctionDefinition 235 lsn experimental.FunctionListener 236 } 237 238 var listeners []listenerForAbort 239 builder := wasmdebug.NewErrorBuilder() 240 def, lsn := c.addFrame(builder, uintptr(unsafe.Pointer(c.execCtx.goCallReturnAddress))) 241 if lsn != nil { 242 listeners = append(listeners, listenerForAbort{def, lsn}) 243 } 244 returnAddrs := unwindStack( 245 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), 246 c.execCtx.framePointerBeforeGoCall, 247 c.stackTop, 248 nil, 249 ) 250 for _, retAddr := range returnAddrs[:len(returnAddrs)-1] { // the last return addr is the trampoline, so we skip it. 251 def, lsn = c.addFrame(builder, retAddr) 252 if lsn != nil { 253 listeners = append(listeners, listenerForAbort{def, lsn}) 254 } 255 } 256 err = builder.FromRecovered(r) 257 258 for _, lsn := range listeners { 259 lsn.lsn.Abort(ctx, m, lsn.def, err) 260 } 261 } else { 262 if err != wasmruntime.ErrRuntimeStackOverflow { // Stackoverflow case shouldn't be panic (to avoid extreme stack unwinding). 263 err = c.parent.module.FailIfClosed() 264 } 265 } 266 267 if err != nil { 268 // Ensures that we can reuse this callEngine even after an error. 269 c.execCtx.exitCode = wazevoapi.ExitCodeOK 270 } 271 }() 272 273 if ensureTermination { 274 done := m.CloseModuleOnCanceledOrTimeout(ctx) 275 defer done() 276 } 277 278 if c.stackTop&(16-1) != 0 { 279 panic("BUG: stack must be aligned to 16 bytes") 280 } 281 entrypoint(c.preambleExecutable, c.executable, c.execCtxPtr, c.parent.opaquePtr, paramResultPtr, c.stackTop) 282 for { 283 switch ec := c.execCtx.exitCode; ec & wazevoapi.ExitCodeMask { 284 case wazevoapi.ExitCodeOK: 285 return nil 286 case wazevoapi.ExitCodeGrowStack: 287 oldsp := uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)) 288 oldTop := c.stackTop 289 oldStack := c.stack 290 var newsp, newfp uintptr 291 if wazevoapi.StackGuardCheckEnabled { 292 newsp, newfp, err = c.growStackWithGuarded() 293 } else { 294 newsp, newfp, err = c.growStack() 295 } 296 if err != nil { 297 return err 298 } 299 adjustClonedStack(oldsp, oldTop, newsp, newfp, c.stackTop) 300 // Old stack must be alive until the new stack is adjusted. 301 runtime.KeepAlive(oldStack) 302 c.execCtx.exitCode = wazevoapi.ExitCodeOK 303 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, newsp, newfp) 304 case wazevoapi.ExitCodeGrowMemory: 305 mod := c.callerModuleInstance() 306 mem := mod.MemoryInstance 307 s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 308 argRes := &s[0] 309 if res, ok := mem.Grow(uint32(*argRes)); !ok { 310 *argRes = uint64(0xffffffff) // = -1 in signed 32-bit integer. 311 } else { 312 *argRes = uint64(res) 313 calleeOpaque := opaqueViewFromPtr(uintptr(unsafe.Pointer(c.execCtx.callerModuleContextPtr))) 314 if mod.Source.MemorySection != nil { // Local memory. 315 putLocalMemory(calleeOpaque, 8 /* local memory begins at 8 */, mem) 316 } else { 317 // Imported memory's owner at offset 16 of the callerModuleContextPtr. 318 opaquePtr := uintptr(binary.LittleEndian.Uint64(calleeOpaque[16:])) 319 importedMemOwner := opaqueViewFromPtr(opaquePtr) 320 putLocalMemory(importedMemOwner, 8 /* local memory begins at 8 */, mem) 321 } 322 } 323 c.execCtx.exitCode = wazevoapi.ExitCodeOK 324 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 325 case wazevoapi.ExitCodeTableGrow: 326 mod := c.callerModuleInstance() 327 s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 328 tableIndex, num, ref := uint32(s[0]), uint32(s[1]), uintptr(s[2]) 329 table := mod.Tables[tableIndex] 330 s[0] = uint64(uint32(int32(table.Grow(num, ref)))) 331 c.execCtx.exitCode = wazevoapi.ExitCodeOK 332 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 333 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 334 case wazevoapi.ExitCodeCallGoFunction: 335 index := wazevoapi.GoFunctionIndexFromExitCode(ec) 336 f := hostModuleGoFuncFromOpaque[api.GoFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque) 337 func() { 338 if snapshotEnabled { 339 defer snapshotRecoverFn(c) 340 } 341 f.Call(ctx, goCallStackView(c.execCtx.stackPointerBeforeGoCall)) 342 }() 343 // Back to the native code. 344 c.execCtx.exitCode = wazevoapi.ExitCodeOK 345 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 346 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 347 case wazevoapi.ExitCodeCallGoFunctionWithListener: 348 index := wazevoapi.GoFunctionIndexFromExitCode(ec) 349 f := hostModuleGoFuncFromOpaque[api.GoFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque) 350 listeners := hostModuleListenersSliceFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque) 351 s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 352 // Call Listener.Before. 353 callerModule := c.callerModuleInstance() 354 listener := listeners[index] 355 hostModule := hostModuleFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque) 356 def := hostModule.FunctionDefinition(wasm.Index(index)) 357 listener.Before(ctx, callerModule, def, s, c.stackIterator(true)) 358 // Call into the Go function. 359 func() { 360 if snapshotEnabled { 361 defer snapshotRecoverFn(c) 362 } 363 f.Call(ctx, s) 364 }() 365 // Call Listener.After. 366 listener.After(ctx, callerModule, def, s) 367 // Back to the native code. 368 c.execCtx.exitCode = wazevoapi.ExitCodeOK 369 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 370 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 371 case wazevoapi.ExitCodeCallGoModuleFunction: 372 index := wazevoapi.GoFunctionIndexFromExitCode(ec) 373 f := hostModuleGoFuncFromOpaque[api.GoModuleFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque) 374 mod := c.callerModuleInstance() 375 func() { 376 if snapshotEnabled { 377 defer snapshotRecoverFn(c) 378 } 379 f.Call(ctx, mod, goCallStackView(c.execCtx.stackPointerBeforeGoCall)) 380 }() 381 // Back to the native code. 382 c.execCtx.exitCode = wazevoapi.ExitCodeOK 383 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 384 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 385 case wazevoapi.ExitCodeCallGoModuleFunctionWithListener: 386 index := wazevoapi.GoFunctionIndexFromExitCode(ec) 387 f := hostModuleGoFuncFromOpaque[api.GoModuleFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque) 388 listeners := hostModuleListenersSliceFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque) 389 s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 390 // Call Listener.Before. 391 callerModule := c.callerModuleInstance() 392 listener := listeners[index] 393 hostModule := hostModuleFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque) 394 def := hostModule.FunctionDefinition(wasm.Index(index)) 395 listener.Before(ctx, callerModule, def, s, c.stackIterator(true)) 396 // Call into the Go function. 397 func() { 398 if snapshotEnabled { 399 defer snapshotRecoverFn(c) 400 } 401 f.Call(ctx, callerModule, s) 402 }() 403 // Call Listener.After. 404 listener.After(ctx, callerModule, def, s) 405 // Back to the native code. 406 c.execCtx.exitCode = wazevoapi.ExitCodeOK 407 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 408 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 409 case wazevoapi.ExitCodeCallListenerBefore: 410 stack := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 411 index := wasm.Index(stack[0]) 412 mod := c.callerModuleInstance() 413 listener := mod.Engine.(*moduleEngine).listeners[index] 414 def := mod.Source.FunctionDefinition(index + mod.Source.ImportFunctionCount) 415 listener.Before(ctx, mod, def, stack[1:], c.stackIterator(false)) 416 c.execCtx.exitCode = wazevoapi.ExitCodeOK 417 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 418 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 419 case wazevoapi.ExitCodeCallListenerAfter: 420 stack := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 421 index := wasm.Index(stack[0]) 422 mod := c.callerModuleInstance() 423 listener := mod.Engine.(*moduleEngine).listeners[index] 424 def := mod.Source.FunctionDefinition(index + mod.Source.ImportFunctionCount) 425 listener.After(ctx, mod, def, stack[1:]) 426 c.execCtx.exitCode = wazevoapi.ExitCodeOK 427 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 428 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 429 case wazevoapi.ExitCodeCheckModuleExitCode: 430 // Note: this operation must be done in Go, not native code. The reason is that 431 // native code cannot be preempted and that means it can block forever if there are not 432 // enough OS threads (which we don't have control over). 433 if err := m.FailIfClosed(); err != nil { 434 panic(err) 435 } 436 c.execCtx.exitCode = wazevoapi.ExitCodeOK 437 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 438 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 439 case wazevoapi.ExitCodeRefFunc: 440 mod := c.callerModuleInstance() 441 s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 442 funcIndex := wasm.Index(s[0]) 443 ref := mod.Engine.FunctionInstanceReference(funcIndex) 444 s[0] = uint64(ref) 445 c.execCtx.exitCode = wazevoapi.ExitCodeOK 446 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 447 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 448 case wazevoapi.ExitCodeMemoryWait32: 449 mod := c.callerModuleInstance() 450 mem := mod.MemoryInstance 451 if !mem.Shared { 452 panic(wasmruntime.ErrRuntimeExpectedSharedMemory) 453 } 454 455 s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 456 timeout, exp, addr := int64(s[0]), uint32(s[1]), uintptr(s[2]) 457 base := uintptr(unsafe.Pointer(&mem.Buffer[0])) 458 459 offset := uint32(addr - base) 460 res := mem.Wait32(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint32 { 461 addr := unsafe.Add(unsafe.Pointer(&mem.Buffer[0]), offset) 462 return atomic.LoadUint32((*uint32)(addr)) 463 }) 464 s[0] = res 465 c.execCtx.exitCode = wazevoapi.ExitCodeOK 466 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 467 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 468 case wazevoapi.ExitCodeMemoryWait64: 469 mod := c.callerModuleInstance() 470 mem := mod.MemoryInstance 471 if !mem.Shared { 472 panic(wasmruntime.ErrRuntimeExpectedSharedMemory) 473 } 474 475 s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 476 timeout, exp, addr := int64(s[0]), uint64(s[1]), uintptr(s[2]) 477 base := uintptr(unsafe.Pointer(&mem.Buffer[0])) 478 479 offset := uint32(addr - base) 480 res := mem.Wait64(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint64 { 481 addr := unsafe.Add(unsafe.Pointer(&mem.Buffer[0]), offset) 482 return atomic.LoadUint64((*uint64)(addr)) 483 }) 484 s[0] = uint64(res) 485 c.execCtx.exitCode = wazevoapi.ExitCodeOK 486 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 487 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 488 case wazevoapi.ExitCodeMemoryNotify: 489 mod := c.callerModuleInstance() 490 mem := mod.MemoryInstance 491 492 s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) 493 count, addr := uint32(s[0]), s[1] 494 offset := uint32(uintptr(addr) - uintptr(unsafe.Pointer(&mem.Buffer[0]))) 495 res := mem.Notify(offset, count) 496 s[0] = uint64(res) 497 c.execCtx.exitCode = wazevoapi.ExitCodeOK 498 afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, 499 uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) 500 case wazevoapi.ExitCodeUnreachable: 501 panic(wasmruntime.ErrRuntimeUnreachable) 502 case wazevoapi.ExitCodeMemoryOutOfBounds: 503 panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) 504 case wazevoapi.ExitCodeTableOutOfBounds: 505 panic(wasmruntime.ErrRuntimeInvalidTableAccess) 506 case wazevoapi.ExitCodeIndirectCallNullPointer: 507 panic(wasmruntime.ErrRuntimeInvalidTableAccess) 508 case wazevoapi.ExitCodeIndirectCallTypeMismatch: 509 panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch) 510 case wazevoapi.ExitCodeIntegerOverflow: 511 panic(wasmruntime.ErrRuntimeIntegerOverflow) 512 case wazevoapi.ExitCodeIntegerDivisionByZero: 513 panic(wasmruntime.ErrRuntimeIntegerDivideByZero) 514 case wazevoapi.ExitCodeInvalidConversionToInteger: 515 panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) 516 case wazevoapi.ExitCodeUnalignedAtomic: 517 panic(wasmruntime.ErrRuntimeUnalignedAtomic) 518 default: 519 panic("BUG") 520 } 521 } 522 } 523 524 func (c *callEngine) callerModuleInstance() *wasm.ModuleInstance { 525 return moduleInstanceFromOpaquePtr(c.execCtx.callerModuleContextPtr) 526 } 527 528 func opaqueViewFromPtr(ptr uintptr) []byte { 529 var opaque []byte 530 sh := (*reflect.SliceHeader)(unsafe.Pointer(&opaque)) 531 sh.Data = ptr 532 setSliceLimits(sh, 24, 24) 533 return opaque 534 } 535 536 const callStackCeiling = uintptr(50000000) // in uint64 (8 bytes) == 400000000 bytes in total == 400mb. 537 538 func (c *callEngine) growStackWithGuarded() (newSP uintptr, newFP uintptr, err error) { 539 if wazevoapi.StackGuardCheckEnabled { 540 wazevoapi.CheckStackGuardPage(c.stack) 541 } 542 newSP, newFP, err = c.growStack() 543 if err != nil { 544 return 545 } 546 if wazevoapi.StackGuardCheckEnabled { 547 c.execCtx.stackBottomPtr = &c.stack[wazevoapi.StackGuardCheckGuardPageSize] 548 } 549 return 550 } 551 552 // growStack grows the stack, and returns the new stack pointer. 553 func (c *callEngine) growStack() (newSP, newFP uintptr, err error) { 554 currentLen := uintptr(len(c.stack)) 555 if callStackCeiling < currentLen { 556 err = wasmruntime.ErrRuntimeStackOverflow 557 return 558 } 559 560 newLen := 2*currentLen + c.execCtx.stackGrowRequiredSize + 16 // Stack might be aligned to 16 bytes, so add 16 bytes just in case. 561 newSP, newFP, c.stackTop, c.stack = c.cloneStack(newLen) 562 c.execCtx.stackBottomPtr = &c.stack[0] 563 return 564 } 565 566 func (c *callEngine) cloneStack(l uintptr) (newSP, newFP, newTop uintptr, newStack []byte) { 567 newStack = make([]byte, l) 568 569 relSp := c.stackTop - uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)) 570 relFp := c.stackTop - c.execCtx.framePointerBeforeGoCall 571 572 // Copy the existing contents in the previous Go-allocated stack into the new one. 573 var prevStackAligned, newStackAligned []byte 574 { 575 sh := (*reflect.SliceHeader)(unsafe.Pointer(&prevStackAligned)) 576 sh.Data = c.stackTop - relSp 577 setSliceLimits(sh, relSp, relSp) 578 } 579 newTop = alignedStackTop(newStack) 580 { 581 newSP = newTop - relSp 582 newFP = newTop - relFp 583 sh := (*reflect.SliceHeader)(unsafe.Pointer(&newStackAligned)) 584 sh.Data = newSP 585 setSliceLimits(sh, relSp, relSp) 586 } 587 copy(newStackAligned, prevStackAligned) 588 return 589 } 590 591 func (c *callEngine) stackIterator(onHostCall bool) experimental.StackIterator { 592 c.stackIteratorImpl.reset(c, onHostCall) 593 return &c.stackIteratorImpl 594 } 595 596 // stackIterator implements experimental.StackIterator. 597 type stackIterator struct { 598 retAddrs []uintptr 599 retAddrCursor int 600 eng *engine 601 pc uint64 602 603 currentDef *wasm.FunctionDefinition 604 } 605 606 func (si *stackIterator) reset(c *callEngine, onHostCall bool) { 607 if onHostCall { 608 si.retAddrs = append(si.retAddrs[:0], uintptr(unsafe.Pointer(c.execCtx.goCallReturnAddress))) 609 } else { 610 si.retAddrs = si.retAddrs[:0] 611 } 612 si.retAddrs = unwindStack(uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall, c.stackTop, si.retAddrs) 613 si.retAddrs = si.retAddrs[:len(si.retAddrs)-1] // the last return addr is the trampoline, so we skip it. 614 si.retAddrCursor = 0 615 si.eng = c.parent.parent.parent 616 } 617 618 // Next implements the same method as documented on experimental.StackIterator. 619 func (si *stackIterator) Next() bool { 620 if si.retAddrCursor >= len(si.retAddrs) { 621 return false 622 } 623 624 addr := si.retAddrs[si.retAddrCursor] 625 cm := si.eng.compiledModuleOfAddr(addr) 626 if cm != nil { 627 index := cm.functionIndexOf(addr) 628 def := cm.module.FunctionDefinition(cm.module.ImportFunctionCount + index) 629 si.currentDef = def 630 si.retAddrCursor++ 631 si.pc = uint64(addr) 632 return true 633 } 634 return false 635 } 636 637 // ProgramCounter implements the same method as documented on experimental.StackIterator. 638 func (si *stackIterator) ProgramCounter() experimental.ProgramCounter { 639 return experimental.ProgramCounter(si.pc) 640 } 641 642 // Function implements the same method as documented on experimental.StackIterator. 643 func (si *stackIterator) Function() experimental.InternalFunction { 644 return si 645 } 646 647 // Definition implements the same method as documented on experimental.InternalFunction. 648 func (si *stackIterator) Definition() api.FunctionDefinition { 649 return si.currentDef 650 } 651 652 // SourceOffsetForPC implements the same method as documented on experimental.InternalFunction. 653 func (si *stackIterator) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 { 654 upc := uintptr(pc) 655 cm := si.eng.compiledModuleOfAddr(upc) 656 return cm.getSourceOffset(upc) 657 } 658 659 // snapshot implements experimental.Snapshot 660 type snapshot struct { 661 sp, fp, top uintptr 662 returnAddress *byte 663 stack []byte 664 savedRegisters [64][2]uint64 665 ret []uint64 666 c *callEngine 667 } 668 669 // Snapshot implements the same method as documented on experimental.Snapshotter. 670 func (c *callEngine) Snapshot() experimental.Snapshot { 671 returnAddress := c.execCtx.goCallReturnAddress 672 oldTop, oldSp := c.stackTop, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)) 673 newSP, newFP, newTop, newStack := c.cloneStack(uintptr(len(c.stack)) + 16) 674 adjustClonedStack(oldSp, oldTop, newSP, newFP, newTop) 675 return &snapshot{ 676 sp: newSP, 677 fp: newFP, 678 top: newTop, 679 savedRegisters: c.execCtx.savedRegisters, 680 returnAddress: returnAddress, 681 stack: newStack, 682 c: c, 683 } 684 } 685 686 // Restore implements the same method as documented on experimental.Snapshot. 687 func (s *snapshot) Restore(ret []uint64) { 688 s.ret = ret 689 panic(s) 690 } 691 692 func (s *snapshot) doRestore() { 693 spp := *(**uint64)(unsafe.Pointer(&s.sp)) 694 view := goCallStackView(spp) 695 copy(view, s.ret) 696 697 c := s.c 698 c.stack = s.stack 699 c.stackTop = s.top 700 ec := &c.execCtx 701 ec.stackBottomPtr = &c.stack[0] 702 ec.stackPointerBeforeGoCall = spp 703 ec.framePointerBeforeGoCall = s.fp 704 ec.goCallReturnAddress = s.returnAddress 705 ec.savedRegisters = s.savedRegisters 706 } 707 708 // Error implements the same method on error. 709 func (s *snapshot) Error() string { 710 return "unhandled snapshot restore, this generally indicates restore was called from a different " + 711 "exported function invocation than snapshot" 712 } 713 714 func snapshotRecoverFn(c *callEngine) { 715 if r := recover(); r != nil { 716 if s, ok := r.(*snapshot); ok && s.c == c { 717 s.doRestore() 718 } else { 719 panic(r) 720 } 721 } 722 }