github.com/tetratelabs/wazero@v1.2.1/internal/engine/compiler/engine.go (about) 1 package compiler 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "reflect" 8 "runtime" 9 "sort" 10 "sync" 11 "unsafe" 12 13 "github.com/tetratelabs/wazero/api" 14 "github.com/tetratelabs/wazero/experimental" 15 "github.com/tetratelabs/wazero/internal/asm" 16 "github.com/tetratelabs/wazero/internal/bitpack" 17 "github.com/tetratelabs/wazero/internal/filecache" 18 "github.com/tetratelabs/wazero/internal/internalapi" 19 "github.com/tetratelabs/wazero/internal/platform" 20 "github.com/tetratelabs/wazero/internal/version" 21 "github.com/tetratelabs/wazero/internal/wasm" 22 "github.com/tetratelabs/wazero/internal/wasmdebug" 23 "github.com/tetratelabs/wazero/internal/wasmruntime" 24 "github.com/tetratelabs/wazero/internal/wazeroir" 25 ) 26 27 // NOTE: The offset of many of the struct fields defined here are referenced from 28 // assembly using the constants below such as moduleEngineFunctionsOffset. 29 // If changing a struct, update the constant and associated tests as needed. 30 type ( 31 // engine is a Compiler implementation of wasm.Engine 32 engine struct { 33 enabledFeatures api.CoreFeatures 34 codes map[wasm.ModuleID]*compiledModule // guarded by mutex. 35 fileCache filecache.Cache 36 mux sync.RWMutex 37 // setFinalizer defaults to runtime.SetFinalizer, but overridable for tests. 38 setFinalizer func(obj interface{}, finalizer interface{}) 39 wazeroVersion string 40 } 41 42 // moduleEngine implements wasm.ModuleEngine 43 moduleEngine struct { 44 // See note at top of file before modifying this struct. 45 46 // functions are the functions in a module instances. 47 // The index is module instance-scoped. We intentionally avoid using map 48 // as the underlying memory region is accessed by assembly directly by using 49 // codesElement0Address. 50 functions []function 51 } 52 53 // callEngine holds context per moduleEngine.Call, and shared across all the 54 // function calls originating from the same moduleEngine.Call execution. 55 // 56 // This implements api.Function. 57 callEngine struct { 58 internalapi.WazeroOnlyType 59 60 // See note at top of file before modifying this struct. 61 62 // These contexts are read and written by compiled code. 63 // Note: structs are embedded to reduce the costs to access fields inside them. Also, this eases field offset 64 // calculation. 65 moduleContext 66 stackContext 67 exitContext 68 archContext 69 70 // The following fields are not accessed by compiled code directly. 71 72 // stack is the go-allocated stack for holding values and call frames. 73 // Note: We never edit len or cap in compiled code, so we won't get screwed when GC comes in. 74 // 75 // At any point of execution, say currently executing function F2 which was called by F1, then 76 // the stack should look like like: 77 // 78 // [..., arg0, arg1, ..., argN, _, _, _, v1, v2, v3, .... 79 // ^ { } 80 // | F1's callFrame 81 // | 82 // stackBasePointer 83 // 84 // where 85 // - callFrame is the F1's callFrame which called F2. It contains F1's return address, F1's base pointer, and F1's *function. 86 // - stackBasePointer is the stack base pointer stored at (callEngine stackContext.stackBasePointerInBytes) 87 // - arg0, ..., argN are the function parameters, and v1, v2, v3,... are the local variables 88 // including the non-function param locals as well as the temporary variable produced by instructions (e.g i32.const). 89 // 90 // If the F2 makes a function call to F3 which takes two arguments, then the stack will become: 91 // 92 // [..., arg0, arg1, ..., argN, _, _, _, v1, v2, v3, _, _, _ 93 // { } ^ { } 94 // F1's callFrame | F2's callFrame 95 // | 96 // stackBasePointer 97 // where 98 // - F2's callFrame is pushed above the v2 and v3 (arguments for F3). 99 // - The previous stackBasePointer (pointed at arg0) was saved inside the F2's callFrame. 100 // 101 // Then, if F3 returns one result, say w1, then the result will look like: 102 // 103 // [..., arg0, arg1, ..., argN, _, _, _, v1, w1, ... 104 // ^ { } 105 // | F1's callFrame 106 // | 107 // stackBasePointer 108 // 109 // where 110 // - stackBasePointer was reverted to the position at arg0 111 // - The result from F3 was pushed above v1 112 // 113 // If the number of parameters is smaller than that of return values, then the empty slots are reserved 114 // below the callFrame to store the results on teh return. 115 // For example, if F3 takes no parameter but returns N(>0) results, then the stack 116 // after making a call against F3 will look like: 117 // 118 // [..., arg0, arg1, ..., argN, _, _, _, v1, v2, v3, res_1, _, res_N, _, _, _ 119 // { } ^ { } 120 // F1's callFrame | F2's callFrame 121 // | 122 // stackBasePointer 123 // where res_1, ..., res_N are the reserved slots below the call frame. In general, 124 // the number of reserved slots equals max(0, len(results)-len(params). 125 // 126 // This reserved slots are necessary to save the result values onto the stack while not destroying 127 // the callFrame value on function returns. 128 stack []uint64 129 130 // initialFn is the initial function for this call engine. 131 initialFn *function 132 133 // stackIterator provides a way to iterate over the stack for Listeners. 134 // It is setup and valid only during a call to a Listener hook. 135 stackIterator stackIterator 136 137 ensureTermination bool 138 } 139 140 // moduleContext holds the per-function call specific module information. 141 // This is subject to be manipulated from compiled native code whenever we make function calls. 142 moduleContext struct { 143 // See note at top of file before modifying this struct. 144 145 // fn holds the currently executed *function. 146 fn *function 147 148 // moduleInstance is the address of module instance from which we initialize 149 // the following fields. This is set whenever we enter a function or return from function calls. 150 // 151 // On the entry to the native code, this must be initialized to zero to let native code preamble know 152 // that this is the initial function call (which leads to moduleContext initialization pass). 153 moduleInstance *wasm.ModuleInstance //lint:ignore U1000 This is only used by Compiler code. 154 155 // globalElement0Address is the address of the first element in the global slice, 156 // i.e. &ModuleInstance.Globals[0] as uintptr. 157 globalElement0Address uintptr 158 // memoryElement0Address is the address of the first element in the global slice, 159 // i.e. &ModuleInstance.Memory.Buffer[0] as uintptr. 160 memoryElement0Address uintptr 161 // memorySliceLen is the length of the memory buffer, i.e. len(ModuleInstance.Memory.Buffer). 162 memorySliceLen uint64 163 // memoryInstance holds the memory instance for this module instance. 164 memoryInstance *wasm.MemoryInstance 165 // tableElement0Address is the address of the first item in the tables slice, 166 // i.e. &ModuleInstance.Tables[0] as uintptr. 167 tablesElement0Address uintptr 168 169 // functionsElement0Address is &moduleContext.functions[0] as uintptr. 170 functionsElement0Address uintptr 171 172 // typeIDsElement0Address holds the &ModuleInstance.TypeIDs[0] as uintptr. 173 typeIDsElement0Address uintptr 174 175 // dataInstancesElement0Address holds the &ModuleInstance.DataInstances[0] as uintptr. 176 dataInstancesElement0Address uintptr 177 178 // elementInstancesElement0Address holds the &ModuleInstance.ElementInstances[0] as uintptr. 179 elementInstancesElement0Address uintptr 180 } 181 182 // stackContext stores the data to access engine.stack. 183 stackContext struct { 184 // See note at top of file before modifying this struct. 185 186 // stackPointer on .stack field which is accessed by stack[stackBasePointer+stackBasePointerInBytes*8]. 187 // 188 // Note: stackPointer is not used in assembly since the native code knows exact position of 189 // each variable in the value stack from the info from compilation. 190 // Therefore, only updated when native code exit from the Compiler world and go back to the Go function. 191 stackPointer uint64 192 193 // stackBasePointerInBytes is updated whenever we make function calls. 194 // Background: Functions might be compiled as if they use the stack from the bottom. 195 // However, in reality, they have to use it from the middle of the stack depending on 196 // when these function calls are made. So instead of accessing stack via stackPointer alone, 197 // functions are compiled, so they access the stack via [stackBasePointer](fixed for entire function) + [stackPointer]. 198 // More precisely, stackBasePointer is set to [callee's stack pointer] + [callee's stack base pointer] - [caller's params]. 199 // This way, compiled functions can be independent of the timing of functions calls made against them. 200 stackBasePointerInBytes uint64 201 202 // stackElement0Address is &engine.stack[0] as uintptr. 203 // Note: this is updated when growing the stack in builtinFunctionGrowStack. 204 stackElement0Address uintptr 205 206 // stackLenInBytes is len(engine.stack[0]) * 8 (bytes). 207 // Note: this is updated when growing the stack in builtinFunctionGrowStack. 208 stackLenInBytes uint64 209 } 210 211 // exitContext will be manipulated whenever compiled native code returns into the Go function. 212 exitContext struct { 213 // See note at top of file before modifying this struct. 214 215 // Where we store the status code of Compiler execution. 216 statusCode nativeCallStatusCode 217 218 // Set when statusCode == compilerStatusCallBuiltInFunction 219 // Indicating the function call index. 220 builtinFunctionCallIndex wasm.Index 221 222 // returnAddress is the return address which the engine jumps into 223 // after executing a builtin function or host function. 224 returnAddress uintptr 225 226 // callerModuleInstance holds the caller's wasm.ModuleInstance, and is only valid if currently executing a host function. 227 callerModuleInstance *wasm.ModuleInstance 228 } 229 230 // callFrame holds the information to which the caller function can return. 231 // This is mixed in callEngine.stack with other Wasm values just like any other 232 // native program (where the stack is the system stack though), and we retrieve the struct 233 // with unsafe pointer casts. 234 callFrame struct { 235 // See note at top of file before modifying this struct. 236 237 // returnAddress is the return address to which the engine jumps when the callee function returns. 238 returnAddress uintptr 239 // returnStackBasePointerInBytes is the stack base pointer to set on stackContext.stackBasePointerInBytes 240 // when the callee function returns. 241 returnStackBasePointerInBytes uint64 242 // function is the caller *function, and is used to retrieve the stack trace. 243 // Note: should be possible to revive *function from returnAddress, but might be costly. 244 function *function 245 } 246 247 // Function corresponds to function instance in Wasm, and is created from `code`. 248 function struct { 249 // See note at top of file before modifying this struct. 250 251 // codeInitialAddress is the pre-calculated pointer pointing to the initial byte of .codeSegment slice. 252 // That mean codeInitialAddress always equals uintptr(unsafe.Pointer(&.codeSegment[0])) 253 // and we cache the value (uintptr(unsafe.Pointer(&.codeSegment[0]))) to this field, 254 // so we don't need to repeat the calculation on each function call. 255 codeInitialAddress uintptr 256 // moduleInstance holds the address of source.ModuleInstance. 257 moduleInstance *wasm.ModuleInstance 258 // typeID is the corresponding wasm.FunctionTypeID for funcType. 259 typeID wasm.FunctionTypeID 260 // funcType is the function type for this function. Created during compilation. 261 funcType *wasm.FunctionType 262 // parent holds code from which this is created. 263 parent *compiledFunction 264 } 265 266 compiledModule struct { 267 executable asm.CodeSegment 268 functions []compiledFunction 269 source *wasm.Module 270 ensureTermination bool 271 } 272 273 // compiledFunction corresponds to a function in a module (not instantiated one). This holds the machine code 274 // compiled by wazero compiler. 275 compiledFunction struct { 276 // codeSegment is holding the compiled native code as a byte slice. 277 executableOffset uintptr 278 // See the doc for codeStaticData type. 279 // stackPointerCeil is the max of the stack pointer this function can reach. Lazily applied via maybeGrowStack. 280 stackPointerCeil uint64 281 282 index wasm.Index 283 goFunc interface{} 284 listener experimental.FunctionListener 285 parent *compiledModule 286 sourceOffsetMap sourceOffsetMap 287 } 288 289 // sourceOffsetMap holds the information to retrieve the original offset in 290 // the Wasm binary from the offset in the native binary. 291 // 292 // The fields are implemented as bit-packed arrays of 64 bits integers to 293 // reduce the memory footprint. Indexing into such arrays is not as fast as 294 // indexing into a simple slice, but the source offset map is intended to be 295 // used for debugging, lookups into the arrays should not appear on code 296 // paths that are critical to the application performance. 297 // 298 // The bitpack.OffsetArray fields may be nil, use bitpack.OffsetArrayLen to 299 // determine whether they are empty prior to indexing into the arrays to 300 // avoid panics caused by accessing nil pointers. 301 sourceOffsetMap struct { 302 // See note at top of file before modifying this struct. 303 304 // irOperationOffsetsInNativeBinary is index-correlated with irOperationSourceOffsetsInWasmBinary, 305 // and maps each index (corresponding to each IR Operation) to the offset in the compiled native code. 306 irOperationOffsetsInNativeBinary bitpack.OffsetArray 307 // irOperationSourceOffsetsInWasmBinary is index-correlated with irOperationOffsetsInNativeBinary. 308 // See wazeroir.CompilationResult irOperationOffsetsInNativeBinary. 309 irOperationSourceOffsetsInWasmBinary bitpack.OffsetArray 310 } 311 312 // functionListenerInvocation captures arguments needed to perform function 313 // listener invocations when unwinding the call stack. 314 functionListenerInvocation struct { 315 experimental.FunctionListener 316 def api.FunctionDefinition 317 } 318 ) 319 320 // Native code reads/writes Go's structs with the following constants. 321 // See TestVerifyOffsetValue for how to derive these values. 322 const ( 323 // Offsets for moduleEngine.functions 324 moduleEngineFunctionsOffset = 0 325 326 // Offsets for callEngine moduleContext. 327 callEngineModuleContextFnOffset = 0 328 callEngineModuleContextModuleInstanceOffset = 8 329 callEngineModuleContextGlobalElement0AddressOffset = 16 330 callEngineModuleContextMemoryElement0AddressOffset = 24 331 callEngineModuleContextMemorySliceLenOffset = 32 332 callEngineModuleContextMemoryInstanceOffset = 40 333 callEngineModuleContextTablesElement0AddressOffset = 48 334 callEngineModuleContextFunctionsElement0AddressOffset = 56 335 callEngineModuleContextTypeIDsElement0AddressOffset = 64 336 callEngineModuleContextDataInstancesElement0AddressOffset = 72 337 callEngineModuleContextElementInstancesElement0AddressOffset = 80 338 339 // Offsets for callEngine stackContext. 340 callEngineStackContextStackPointerOffset = 88 341 callEngineStackContextStackBasePointerInBytesOffset = 96 342 callEngineStackContextStackElement0AddressOffset = 104 343 callEngineStackContextStackLenInBytesOffset = 112 344 345 // Offsets for callEngine exitContext. 346 callEngineExitContextNativeCallStatusCodeOffset = 120 347 callEngineExitContextBuiltinFunctionCallIndexOffset = 124 348 callEngineExitContextReturnAddressOffset = 128 349 callEngineExitContextCallerModuleInstanceOffset = 136 350 351 // Offsets for function. 352 functionCodeInitialAddressOffset = 0 353 functionModuleInstanceOffset = 8 354 functionTypeIDOffset = 16 355 functionSize = 40 356 357 // Offsets for wasm.ModuleInstance. 358 moduleInstanceGlobalsOffset = 32 359 moduleInstanceMemoryOffset = 56 360 moduleInstanceTablesOffset = 64 361 moduleInstanceEngineOffset = 88 362 moduleInstanceTypeIDsOffset = 104 363 moduleInstanceDataInstancesOffset = 128 364 moduleInstanceElementInstancesOffset = 152 365 366 // Offsets for wasm.TableInstance. 367 tableInstanceTableOffset = 0 368 tableInstanceTableLenOffset = 8 369 370 // Offsets for wasm.MemoryInstance. 371 memoryInstanceBufferOffset = 0 372 memoryInstanceBufferLenOffset = 8 373 374 // Offsets for wasm.GlobalInstance. 375 globalInstanceValueOffset = 8 376 377 // Offsets for Go's interface. 378 // https://research.swtch.com/interfaces 379 // https://github.com/golang/go/blob/release-branch.go1.20/src/runtime/runtime2.go#L207-L210 380 interfaceDataOffset = 8 381 382 // Consts for wasm.DataInstance. 383 dataInstanceStructSize = 24 384 385 // Consts for wasm.ElementInstance. 386 elementInstanceStructSize = 32 387 388 // pointerSizeLog2 satisfies: 1 << pointerSizeLog2 = sizeOf(uintptr) 389 pointerSizeLog2 = 3 390 391 // callFrameDataSizeInUint64 is the size of callFrame struct per 8 bytes (= size of uint64). 392 callFrameDataSizeInUint64 = 24 / 8 393 ) 394 395 // nativeCallStatusCode represents the result of `nativecall`. 396 // This is set by the native code. 397 type nativeCallStatusCode uint32 398 399 const ( 400 // nativeCallStatusCodeReturned means the nativecall reaches the end of function, and returns successfully. 401 nativeCallStatusCodeReturned nativeCallStatusCode = iota 402 // nativeCallStatusCodeCallGoHostFunction means the nativecall returns to make a host function call. 403 nativeCallStatusCodeCallGoHostFunction 404 // nativeCallStatusCodeCallBuiltInFunction means the nativecall returns to make a builtin function call. 405 nativeCallStatusCodeCallBuiltInFunction 406 // nativeCallStatusCodeUnreachable means the function invocation reaches "unreachable" instruction. 407 nativeCallStatusCodeUnreachable 408 // nativeCallStatusCodeInvalidFloatToIntConversion means an invalid conversion of integer to floats happened. 409 nativeCallStatusCodeInvalidFloatToIntConversion 410 // nativeCallStatusCodeMemoryOutOfBounds means an out-of-bounds memory access happened. 411 nativeCallStatusCodeMemoryOutOfBounds 412 // nativeCallStatusCodeInvalidTableAccess means either offset to the table was out of bounds of table, or 413 // the target element in the table was uninitialized during call_indirect instruction. 414 nativeCallStatusCodeInvalidTableAccess 415 // nativeCallStatusCodeTypeMismatchOnIndirectCall means the type check failed during call_indirect. 416 nativeCallStatusCodeTypeMismatchOnIndirectCall 417 nativeCallStatusIntegerOverflow 418 nativeCallStatusIntegerDivisionByZero 419 nativeCallStatusModuleClosed 420 ) 421 422 // causePanic causes a panic with the corresponding error to the nativeCallStatusCode. 423 func (s nativeCallStatusCode) causePanic() { 424 var err error 425 switch s { 426 case nativeCallStatusIntegerOverflow: 427 err = wasmruntime.ErrRuntimeIntegerOverflow 428 case nativeCallStatusIntegerDivisionByZero: 429 err = wasmruntime.ErrRuntimeIntegerDivideByZero 430 case nativeCallStatusCodeInvalidFloatToIntConversion: 431 err = wasmruntime.ErrRuntimeInvalidConversionToInteger 432 case nativeCallStatusCodeUnreachable: 433 err = wasmruntime.ErrRuntimeUnreachable 434 case nativeCallStatusCodeMemoryOutOfBounds: 435 err = wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess 436 case nativeCallStatusCodeInvalidTableAccess: 437 err = wasmruntime.ErrRuntimeInvalidTableAccess 438 case nativeCallStatusCodeTypeMismatchOnIndirectCall: 439 err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch 440 } 441 panic(err) 442 } 443 444 func (s nativeCallStatusCode) String() (ret string) { 445 switch s { 446 case nativeCallStatusCodeReturned: 447 ret = "returned" 448 case nativeCallStatusCodeCallGoHostFunction: 449 ret = "call_host_function" 450 case nativeCallStatusCodeCallBuiltInFunction: 451 ret = "call_builtin_function" 452 case nativeCallStatusCodeUnreachable: 453 ret = "unreachable" 454 case nativeCallStatusCodeInvalidFloatToIntConversion: 455 ret = "invalid float to int conversion" 456 case nativeCallStatusCodeMemoryOutOfBounds: 457 ret = "memory out of bounds" 458 case nativeCallStatusCodeInvalidTableAccess: 459 ret = "invalid table access" 460 case nativeCallStatusCodeTypeMismatchOnIndirectCall: 461 ret = "type mismatch on indirect call" 462 case nativeCallStatusIntegerOverflow: 463 ret = "integer overflow" 464 case nativeCallStatusIntegerDivisionByZero: 465 ret = "integer division by zero" 466 case nativeCallStatusModuleClosed: 467 ret = "module closed" 468 default: 469 panic("BUG") 470 } 471 return 472 } 473 474 // releaseCompiledModule is a runtime.SetFinalizer function that munmaps the compiledModule.executable. 475 func releaseCompiledModule(cm *compiledModule) { 476 if err := cm.executable.Unmap(); err != nil { 477 // munmap failure cannot recover, and happen asynchronously on the 478 // finalizer thread. While finalizer functions can return errors, 479 // they are ignored. 480 panic(fmt.Errorf("compiler: failed to munmap code segment: %w", err)) 481 } 482 } 483 484 // CompiledModuleCount implements the same method as documented on wasm.Engine. 485 func (e *engine) CompiledModuleCount() uint32 { 486 return uint32(len(e.codes)) 487 } 488 489 // DeleteCompiledModule implements the same method as documented on wasm.Engine. 490 func (e *engine) DeleteCompiledModule(module *wasm.Module) { 491 e.deleteCompiledModule(module) 492 } 493 494 // Close implements the same method as documented on wasm.Engine. 495 func (e *engine) Close() (err error) { 496 e.mux.Lock() 497 defer e.mux.Unlock() 498 // Releasing the references to compiled codes including the memory-mapped machine codes. 499 e.codes = nil 500 return 501 } 502 503 // CompileModule implements the same method as documented on wasm.Engine. 504 func (e *engine) CompileModule(_ context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) error { 505 if _, ok, err := e.getCompiledModule(module, listeners); ok { // cache hit! 506 return nil 507 } else if err != nil { 508 return err 509 } 510 511 irCompiler, err := wazeroir.NewCompiler(e.enabledFeatures, callFrameDataSizeInUint64, module, ensureTermination) 512 if err != nil { 513 return err 514 } 515 516 var withGoFunc bool 517 localFuncs, importedFuncs := len(module.FunctionSection), module.ImportFunctionCount 518 cm := &compiledModule{ 519 functions: make([]compiledFunction, localFuncs), 520 ensureTermination: ensureTermination, 521 source: module, 522 } 523 524 if localFuncs == 0 { 525 return e.addCompiledModule(module, cm, withGoFunc) 526 } 527 528 // As this uses mmap, we need to munmap on the compiled machine code when it's GCed. 529 e.setFinalizer(cm, releaseCompiledModule) 530 ln := len(listeners) 531 cmp := newCompiler() 532 asmNodes := new(asmNodes) 533 offsets := new(offsets) 534 535 // The executable code is allocated in memory mappings held by the 536 // CodeSegment, which gros on demand when it exhausts its capacity. 537 var executable asm.CodeSegment 538 defer func() { 539 // At the end of the function, the executable is set on the compiled 540 // module and the local variable cleared; until then, the function owns 541 // the memory mapping and is reponsible for clearing it if it returns 542 // due to an error. Note that an error at this stage is not recoverable 543 // so we panic if we fail to unmap the memory segment. 544 if err := executable.Unmap(); err != nil { 545 panic(fmt.Errorf("compiler: failed to munmap code segment: %w", err)) 546 } 547 }() 548 549 for i := range module.CodeSection { 550 typ := &module.TypeSection[module.FunctionSection[i]] 551 buf := executable.NextCodeSection() 552 funcIndex := wasm.Index(i) 553 compiledFn := &cm.functions[i] 554 compiledFn.executableOffset = executable.Size() 555 compiledFn.parent = cm 556 compiledFn.index = importedFuncs + funcIndex 557 if i < ln { 558 compiledFn.listener = listeners[i] 559 } 560 561 if codeSeg := &module.CodeSection[i]; codeSeg.GoFunc != nil { 562 cmp.Init(typ, nil, compiledFn.listener != nil) 563 withGoFunc = true 564 if err = compileGoDefinedHostFunction(buf, cmp); err != nil { 565 def := module.FunctionDefinition(compiledFn.index) 566 return fmt.Errorf("error compiling host go func[%s]: %w", def.DebugName(), err) 567 } 568 compiledFn.goFunc = codeSeg.GoFunc 569 } else { 570 ir, err := irCompiler.Next() 571 if err != nil { 572 return fmt.Errorf("failed to lower func[%d]: %v", i, err) 573 } 574 cmp.Init(typ, ir, compiledFn.listener != nil) 575 576 compiledFn.stackPointerCeil, compiledFn.sourceOffsetMap, err = compileWasmFunction(buf, cmp, ir, asmNodes, offsets) 577 if err != nil { 578 def := module.FunctionDefinition(compiledFn.index) 579 return fmt.Errorf("error compiling wasm func[%s]: %w", def.DebugName(), err) 580 } 581 } 582 } 583 584 if runtime.GOARCH == "arm64" { 585 // On arm64, we cannot give all of rwx at the same time, so we change it to exec. 586 if err := platform.MprotectRX(executable.Bytes()); err != nil { 587 return err 588 } 589 } 590 cm.executable, executable = executable, asm.CodeSegment{} 591 return e.addCompiledModule(module, cm, withGoFunc) 592 } 593 594 // NewModuleEngine implements the same method as documented on wasm.Engine. 595 func (e *engine) NewModuleEngine(module *wasm.Module, instance *wasm.ModuleInstance) (wasm.ModuleEngine, error) { 596 me := &moduleEngine{ 597 functions: make([]function, len(module.FunctionSection)+int(module.ImportFunctionCount)), 598 } 599 600 // Note: imported functions are resolved in moduleEngine.ResolveImportedFunction. 601 602 cm, ok, err := e.getCompiledModule(module, 603 // listeners arg is not needed here since NewModuleEngine is called after CompileModule which 604 // ensures the association of listener with *code. 605 nil) 606 if !ok { 607 return nil, errors.New("source module must be compiled before instantiation") 608 } else if err != nil { 609 return nil, err 610 } 611 612 for i := range cm.functions { 613 c := &cm.functions[i] 614 offset := int(module.ImportFunctionCount) + i 615 typeIndex := module.FunctionSection[i] 616 me.functions[offset] = function{ 617 codeInitialAddress: cm.executable.Addr() + c.executableOffset, 618 moduleInstance: instance, 619 typeID: instance.TypeIDs[typeIndex], 620 funcType: &module.TypeSection[typeIndex], 621 parent: c, 622 } 623 } 624 return me, nil 625 } 626 627 // ResolveImportedFunction implements wasm.ModuleEngine. 628 func (e *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) { 629 imported := importedModuleEngine.(*moduleEngine) 630 // Copies the content from the import target moduleEngine. 631 e.functions[index] = imported.functions[indexInImportedModule] 632 } 633 634 // FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine. 635 func (e *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference { 636 return uintptr(unsafe.Pointer(&e.functions[funcIndex])) 637 } 638 639 // NewFunction implements wasm.ModuleEngine. 640 func (e *moduleEngine) NewFunction(index wasm.Index) api.Function { 641 return e.newFunction(&e.functions[index]) 642 } 643 644 func (e *moduleEngine) newFunction(f *function) api.Function { 645 initStackSize := initialStackSize 646 if initialStackSize < f.parent.stackPointerCeil { 647 initStackSize = f.parent.stackPointerCeil * 2 648 } 649 return e.newCallEngine(initStackSize, f) 650 } 651 652 // LookupFunction implements the same method as documented on wasm.ModuleEngine. 653 func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (f api.Function, err error) { 654 if tableOffset >= uint32(len(t.References)) || t.Type != wasm.RefTypeFuncref { 655 err = wasmruntime.ErrRuntimeInvalidTableAccess 656 return 657 } 658 rawPtr := t.References[tableOffset] 659 if rawPtr == 0 { 660 err = wasmruntime.ErrRuntimeInvalidTableAccess 661 return 662 } 663 664 tf := functionFromUintptr(rawPtr) 665 if tf.typeID != typeId { 666 err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch 667 return 668 } 669 f = e.newFunction(tf) 670 return 671 } 672 673 // functionFromUintptr resurrects the original *function from the given uintptr 674 // which comes from either funcref table or OpcodeRefFunc instruction. 675 func functionFromUintptr(ptr uintptr) *function { 676 // Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector. 677 // 678 // For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr" 679 // subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation" 680 // https://github.com/golang/go/blob/1ce7fcf139417d618c2730010ede2afb41664211/src/runtime/checkptr.go#L69 681 var wrapped *uintptr = &ptr 682 return *(**function)(unsafe.Pointer(wrapped)) 683 } 684 685 // Definition implements the same method as documented on wasm.ModuleEngine. 686 func (ce *callEngine) Definition() api.FunctionDefinition { 687 return ce.initialFn.definition() 688 } 689 690 func (f *function) definition() api.FunctionDefinition { 691 compiled := f.parent 692 return compiled.parent.source.FunctionDefinition(compiled.index) 693 } 694 695 // Call implements the same method as documented on wasm.ModuleEngine. 696 func (ce *callEngine) Call(ctx context.Context, params ...uint64) (results []uint64, err error) { 697 ft := ce.initialFn.funcType 698 if n := ft.ParamNumInUint64; n != len(params) { 699 return nil, fmt.Errorf("expected %d params, but passed %d", n, len(params)) 700 } 701 return ce.call(ctx, params, nil) 702 } 703 704 // CallWithStack implements the same method as documented on wasm.ModuleEngine. 705 func (ce *callEngine) CallWithStack(ctx context.Context, stack []uint64) error { 706 params, results, err := wasm.SplitCallStack(ce.initialFn.funcType, stack) 707 if err != nil { 708 return err 709 } 710 _, err = ce.call(ctx, params, results) 711 return err 712 } 713 714 func (ce *callEngine) call(ctx context.Context, params, results []uint64) (_ []uint64, err error) { 715 m := ce.initialFn.moduleInstance 716 if ce.ensureTermination { 717 select { 718 case <-ctx.Done(): 719 // If the provided context is already done, close the call context 720 // and return the error. 721 m.CloseWithCtxErr(ctx) 722 return nil, m.FailIfClosed() 723 default: 724 } 725 } 726 727 // We ensure that this Call method never panics as 728 // this Call method is indirectly invoked by embedders via store.CallFunction, 729 // and we have to make sure that all the runtime errors, including the one happening inside 730 // host functions, will be captured as errors, not panics. 731 defer func() { 732 err = ce.deferredOnCall(ctx, m, recover()) 733 if err == nil { 734 // If the module closed during the call, and the call didn't err for another reason, set an ExitError. 735 err = m.FailIfClosed() 736 } 737 }() 738 739 ft := ce.initialFn.funcType 740 ce.initializeStack(ft, params) 741 742 if ce.ensureTermination { 743 done := m.CloseModuleOnCanceledOrTimeout(ctx) 744 defer done() 745 } 746 747 ce.execWasmFunction(ctx, m) 748 749 // This returns a safe copy of the results, instead of a slice view. If we 750 // returned a re-slice, the caller could accidentally or purposefully 751 // corrupt the stack of subsequent calls. 752 if results == nil && ft.ResultNumInUint64 > 0 { 753 results = make([]uint64, ft.ResultNumInUint64) 754 } 755 copy(results, ce.stack) 756 return results, nil 757 } 758 759 // initializeStack initializes callEngine.stack before entering native code. 760 // 761 // The stack must look like, if len(params) < len(results): 762 // 763 // [arg0, arg1, ..., argN, 0, 0, 0, ... 764 // { } ^ 765 // callFrame | 766 // | 767 // stackPointer 768 // 769 // else: 770 // 771 // [arg0, arg1, ..., argN, _, _, _, 0, 0, 0, ... 772 // | | { } ^ 773 // |reserved| callFrame | 774 // | | | 775 // |--------> stackPointer 776 // len(results)-len(params) 777 // 778 // where we reserve the slots below the callframe with the length len(results)-len(params). 779 // 780 // Note: callFrame { } is zeroed to indicate that the initial "caller" is this callEngine, not the Wasm function. 781 // 782 // See callEngine.stack as well. 783 func (ce *callEngine) initializeStack(tp *wasm.FunctionType, args []uint64) { 784 for _, v := range args { 785 ce.pushValue(v) 786 } 787 788 ce.stackPointer = uint64(callFrameOffset(tp)) 789 790 for i := 0; i < callFrameDataSizeInUint64; i++ { 791 ce.stack[ce.stackPointer] = 0 792 ce.stackPointer++ 793 } 794 } 795 796 // callFrameOffset returns the offset of the call frame from the stack base pointer. 797 // 798 // See the diagram in callEngine.stack. 799 func callFrameOffset(funcType *wasm.FunctionType) (ret int) { 800 ret = funcType.ResultNumInUint64 801 if ret < funcType.ParamNumInUint64 { 802 ret = funcType.ParamNumInUint64 803 } 804 return 805 } 806 807 // deferredOnCall takes the recovered value `recovered`, and wraps it 808 // with the call frame stack traces when not nil. This also resets 809 // the state of callEngine so that it can be used for the subsequent calls. 810 // 811 // This is defined for testability. 812 func (ce *callEngine) deferredOnCall(ctx context.Context, m *wasm.ModuleInstance, recovered interface{}) (err error) { 813 if recovered != nil { 814 builder := wasmdebug.NewErrorBuilder() 815 816 // Unwinds call frames from the values stack, starting from the 817 // current function `ce.fn`, and the current stack base pointer `ce.stackBasePointerInBytes`. 818 fn := ce.fn 819 pc := uint64(ce.returnAddress) 820 stackBasePointer := int(ce.stackBasePointerInBytes >> 3) 821 functionListeners := make([]functionListenerInvocation, 0, 16) 822 823 for { 824 def := fn.definition() 825 826 // sourceInfo holds the source code information corresponding to the frame. 827 // It is not empty only when the DWARF is enabled. 828 var sources []string 829 if p := fn.parent; p.parent.executable.Bytes() != nil { 830 if fn.parent.sourceOffsetMap.irOperationSourceOffsetsInWasmBinary != nil { 831 offset := fn.getSourceOffsetInWasmBinary(pc) 832 sources = p.parent.source.DWARFLines.Line(offset) 833 } 834 } 835 builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources) 836 837 if fn.parent.listener != nil { 838 functionListeners = append(functionListeners, functionListenerInvocation{ 839 FunctionListener: fn.parent.listener, 840 def: fn.definition(), 841 }) 842 } 843 844 callFrameOffset := callFrameOffset(fn.funcType) 845 if stackBasePointer != 0 { 846 frame := *(*callFrame)(unsafe.Pointer(&ce.stack[stackBasePointer+callFrameOffset])) 847 fn = frame.function 848 pc = uint64(frame.returnAddress) 849 stackBasePointer = int(frame.returnStackBasePointerInBytes >> 3) 850 } else { // base == 0 means that this was the last call frame stacked. 851 break 852 } 853 } 854 855 err = builder.FromRecovered(recovered) 856 for i := range functionListeners { 857 functionListeners[i].Abort(ctx, m, functionListeners[i].def, err) 858 } 859 } 860 861 // Allows the reuse of CallEngine. 862 ce.stackBasePointerInBytes, ce.stackPointer, ce.moduleInstance = 0, 0, nil 863 ce.moduleContext.fn = ce.initialFn 864 return 865 } 866 867 // getSourceOffsetInWasmBinary returns the corresponding offset in the original Wasm binary's code section 868 // for the given pc (which is an absolute address in the memory). 869 // If needPreviousInstr equals true, this returns the previous instruction's offset for the given pc. 870 func (f *function) getSourceOffsetInWasmBinary(pc uint64) uint64 { 871 srcMap := &f.parent.sourceOffsetMap 872 n := bitpack.OffsetArrayLen(srcMap.irOperationOffsetsInNativeBinary) + 1 873 874 // Calculate the offset in the compiled native binary. 875 pcOffsetInNativeBinary := pc - uint64(f.codeInitialAddress) 876 877 // Then, do the binary search on the list of offsets in the native binary 878 // for all the IR operations. This returns the index of the *next* IR 879 // operation of the one corresponding to the origin of this pc. 880 // See sort.Search. 881 // 882 // TODO: the underlying implementation of irOperationOffsetsInNativeBinary 883 // uses uses delta encoding an calls to the Index method might require a 884 // O(N) scan of the underlying array, turning binary search into a 885 // O(N*log(N)) operation. If this code path ends up being a bottleneck, 886 // we could add a Search method on the bitpack.OffsetArray types to delegate 887 // the lookup to the underlying data structure, allowing for the selection 888 // of a more optimized version of the algorithm. If you do so, please add a 889 // benchmark to verify the impact on compute time. 890 index := sort.Search(n, func(i int) bool { 891 if i == n-1 { 892 return true 893 } 894 return srcMap.irOperationOffsetsInNativeBinary.Index(i) >= pcOffsetInNativeBinary 895 }) 896 if index == 0 && bitpack.OffsetArrayLen(srcMap.irOperationSourceOffsetsInWasmBinary) > 0 { 897 // When pc is the beginning of the function, the next IR 898 // operation (returned by sort.Search) is the first of the 899 // offset map. 900 return srcMap.irOperationSourceOffsetsInWasmBinary.Index(0) 901 } 902 903 if index == n || index == 0 { // This case, somehow pc is not found in the source offset map. 904 return 0 905 } else { 906 return srcMap.irOperationSourceOffsetsInWasmBinary.Index(index - 1) 907 } 908 } 909 910 func NewEngine(_ context.Context, enabledFeatures api.CoreFeatures, fileCache filecache.Cache) wasm.Engine { 911 return newEngine(enabledFeatures, fileCache) 912 } 913 914 func newEngine(enabledFeatures api.CoreFeatures, fileCache filecache.Cache) *engine { 915 return &engine{ 916 enabledFeatures: enabledFeatures, 917 codes: map[wasm.ModuleID]*compiledModule{}, 918 setFinalizer: runtime.SetFinalizer, 919 fileCache: fileCache, 920 wazeroVersion: version.GetWazeroVersion(), 921 } 922 } 923 924 // Do not make this variable as constant, otherwise there would be 925 // dangerous memory access from native code. 926 // 927 // Background: Go has a mechanism called "goroutine stack-shrink" where Go 928 // runtime shrinks Goroutine's stack when it is GCing. Shrinking means that 929 // all the contents on the goroutine stack will be relocated by runtime, 930 // Therefore, the memory address of these contents change undeterministically. 931 // Not only shrinks, but also Go runtime grows the goroutine stack at any point 932 // of function call entries, which also might end up relocating contents. 933 // 934 // On the other hand, we hold pointers to the data region of value stack and 935 // call-frame stack slices and use these raw pointers from native code. 936 // Therefore, it is dangerous if these two stacks are allocated on stack 937 // as these stack's address might be changed by Goroutine which we cannot 938 // detect. 939 // 940 // By declaring these values as `var`, slices created via `make([]..., var)` 941 // will never be allocated on stack [1]. This means accessing these slices via 942 // raw pointers is safe: As of version 1.18, Go's garbage collector never relocates 943 // heap-allocated objects (aka no compaction of memory [2]). 944 // 945 // On Go upgrades, re-validate heap-allocation via `go build -gcflags='-m' ./internal/engine/compiler/...`. 946 // 947 // [1] https://github.com/golang/go/blob/68ecdc2c70544c303aa923139a5f16caf107d955/src/cmd/compile/internal/escape/utils.go#L206-L208 948 // [2] https://github.com/golang/go/blob/68ecdc2c70544c303aa923139a5f16caf107d955/src/runtime/mgc.go#L9 949 // [3] https://mayurwadekar2.medium.com/escape-analysis-in-golang-ee40a1c064c1 950 // [4] https://medium.com/@yulang.chu/go-stack-or-heap-2-slices-which-keep-in-stack-have-limitation-of-size-b3f3adfd6190 951 var initialStackSize uint64 = 512 952 953 func (e *moduleEngine) newCallEngine(stackSize uint64, fn *function) *callEngine { 954 ce := &callEngine{ 955 stack: make([]uint64, stackSize), 956 archContext: newArchContext(), 957 initialFn: fn, 958 moduleContext: moduleContext{fn: fn}, 959 ensureTermination: fn.parent.parent.ensureTermination, 960 } 961 962 stackHeader := (*reflect.SliceHeader)(unsafe.Pointer(&ce.stack)) 963 ce.stackContext = stackContext{ 964 stackElement0Address: stackHeader.Data, 965 stackLenInBytes: uint64(stackHeader.Len) << 3, 966 } 967 return ce 968 } 969 970 func (ce *callEngine) popValue() (ret uint64) { 971 ce.stackContext.stackPointer-- 972 ret = ce.stack[ce.stackTopIndex()] 973 return 974 } 975 976 func (ce *callEngine) pushValue(v uint64) { 977 ce.stack[ce.stackTopIndex()] = v 978 ce.stackContext.stackPointer++ 979 } 980 981 func (ce *callEngine) stackTopIndex() uint64 { 982 return ce.stackContext.stackPointer + (ce.stackContext.stackBasePointerInBytes >> 3) 983 } 984 985 const ( 986 builtinFunctionIndexMemoryGrow wasm.Index = iota 987 builtinFunctionIndexGrowStack 988 builtinFunctionIndexTableGrow 989 builtinFunctionIndexFunctionListenerBefore 990 builtinFunctionIndexFunctionListenerAfter 991 builtinFunctionIndexCheckExitCode 992 // builtinFunctionIndexBreakPoint is internal (only for wazero developers). Disabled by default. 993 builtinFunctionIndexBreakPoint 994 ) 995 996 func (ce *callEngine) execWasmFunction(ctx context.Context, m *wasm.ModuleInstance) { 997 codeAddr := ce.initialFn.codeInitialAddress 998 modAddr := ce.initialFn.moduleInstance 999 1000 entry: 1001 { 1002 // Call into the native code. 1003 nativecall(codeAddr, ce, modAddr) 1004 1005 // Check the status code from Compiler code. 1006 switch status := ce.exitContext.statusCode; status { 1007 case nativeCallStatusCodeReturned: 1008 case nativeCallStatusCodeCallGoHostFunction: 1009 calleeHostFunction := ce.moduleContext.fn 1010 base := int(ce.stackBasePointerInBytes >> 3) 1011 1012 // In the compiler engine, ce.stack has enough capacity for the 1013 // max of param or result length, so we don't need to grow when 1014 // there are more results than parameters. 1015 stackLen := calleeHostFunction.funcType.ParamNumInUint64 1016 if resultLen := calleeHostFunction.funcType.ResultNumInUint64; resultLen > stackLen { 1017 stackLen = resultLen 1018 } 1019 stack := ce.stack[base : base+stackLen] 1020 1021 fn := calleeHostFunction.parent.goFunc 1022 switch fn := fn.(type) { 1023 case api.GoModuleFunction: 1024 fn.Call(ctx, ce.callerModuleInstance, stack) 1025 case api.GoFunction: 1026 fn.Call(ctx, stack) 1027 } 1028 1029 codeAddr, modAddr = ce.returnAddress, ce.moduleInstance 1030 goto entry 1031 case nativeCallStatusCodeCallBuiltInFunction: 1032 caller := ce.moduleContext.fn 1033 switch ce.exitContext.builtinFunctionCallIndex { 1034 case builtinFunctionIndexMemoryGrow: 1035 ce.builtinFunctionMemoryGrow(caller.moduleInstance.MemoryInstance) 1036 case builtinFunctionIndexGrowStack: 1037 ce.builtinFunctionGrowStack(caller.parent.stackPointerCeil) 1038 case builtinFunctionIndexTableGrow: 1039 ce.builtinFunctionTableGrow(caller.moduleInstance.Tables) 1040 case builtinFunctionIndexFunctionListenerBefore: 1041 ce.builtinFunctionFunctionListenerBefore(ctx, m, caller) 1042 case builtinFunctionIndexFunctionListenerAfter: 1043 ce.builtinFunctionFunctionListenerAfter(ctx, m, caller) 1044 case builtinFunctionIndexCheckExitCode: 1045 // Note: this operation must be done in Go, not native code. The reason is that 1046 // native code cannot be preempted and that means it can block forever if there are not 1047 // enough OS threads (which we don't have control over). 1048 if err := m.FailIfClosed(); err != nil { 1049 panic(err) 1050 } 1051 } 1052 if false { 1053 if ce.exitContext.builtinFunctionCallIndex == builtinFunctionIndexBreakPoint { 1054 runtime.Breakpoint() 1055 } 1056 } 1057 1058 codeAddr, modAddr = ce.returnAddress, ce.moduleInstance 1059 goto entry 1060 default: 1061 status.causePanic() 1062 } 1063 } 1064 } 1065 1066 // callStackCeiling is the maximum WebAssembly call frame stack height. This allows wazero to raise 1067 // wasm.ErrCallStackOverflow instead of overflowing the Go runtime. 1068 // 1069 // The default value should suffice for most use cases. Those wishing to change this can via `go build -ldflags`. 1070 // 1071 // TODO: allows to configure this via context? 1072 var callStackCeiling = uint64(5000000) // in uint64 (8 bytes) == 40000000 bytes in total == 40mb. 1073 1074 func (ce *callEngine) builtinFunctionGrowStack(stackPointerCeil uint64) { 1075 oldLen := uint64(len(ce.stack)) 1076 if callStackCeiling < oldLen { 1077 panic(wasmruntime.ErrRuntimeStackOverflow) 1078 } 1079 1080 // Extends the stack's length to oldLen*2+stackPointerCeil. 1081 newLen := oldLen<<1 + (stackPointerCeil) 1082 newStack := make([]uint64, newLen) 1083 top := ce.stackTopIndex() 1084 copy(newStack[:top], ce.stack[:top]) 1085 ce.stack = newStack 1086 stackHeader := (*reflect.SliceHeader)(unsafe.Pointer(&ce.stack)) 1087 ce.stackContext.stackElement0Address = stackHeader.Data 1088 ce.stackContext.stackLenInBytes = newLen << 3 1089 } 1090 1091 func (ce *callEngine) builtinFunctionMemoryGrow(mem *wasm.MemoryInstance) { 1092 newPages := ce.popValue() 1093 1094 if res, ok := mem.Grow(uint32(newPages)); !ok { 1095 ce.pushValue(uint64(0xffffffff)) // = -1 in signed 32-bit integer. 1096 } else { 1097 ce.pushValue(uint64(res)) 1098 } 1099 1100 // Update the moduleContext fields as they become stale after the update ^^. 1101 bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&mem.Buffer)) 1102 ce.moduleContext.memorySliceLen = uint64(bufSliceHeader.Len) 1103 ce.moduleContext.memoryElement0Address = bufSliceHeader.Data 1104 } 1105 1106 func (ce *callEngine) builtinFunctionTableGrow(tables []*wasm.TableInstance) { 1107 tableIndex := uint32(ce.popValue()) 1108 table := tables[tableIndex] // verified not to be out of range by the func validation at compilation phase. 1109 num := ce.popValue() 1110 ref := ce.popValue() 1111 res := table.Grow(uint32(num), uintptr(ref)) 1112 ce.pushValue(uint64(res)) 1113 } 1114 1115 // stackIterator implements experimental.StackIterator. 1116 type stackIterator struct { 1117 stack []uint64 1118 fn *function 1119 base int 1120 pc uint64 1121 started bool 1122 } 1123 1124 func (si *stackIterator) reset(stack []uint64, fn *function, base int, pc uint64) { 1125 si.stack = stack 1126 si.fn = fn 1127 si.base = base 1128 si.pc = pc 1129 si.started = false 1130 } 1131 1132 func (si *stackIterator) clear() { 1133 si.stack = nil 1134 si.fn = nil 1135 si.base = 0 1136 si.started = false 1137 } 1138 1139 // Next implements the same method as documented on experimental.StackIterator. 1140 func (si *stackIterator) Next() bool { 1141 if !si.started { 1142 si.started = true 1143 return true 1144 } 1145 1146 if si.fn == nil || si.base == 0 { 1147 return false 1148 } 1149 1150 frame := si.base + callFrameOffset(si.fn.funcType) 1151 si.pc = si.stack[frame+0] 1152 si.base = int(si.stack[frame+1] >> 3) 1153 // *function lives in the third field of callFrame struct. This must be 1154 // aligned with the definition of callFrame struct. 1155 si.fn = *(**function)(unsafe.Pointer(&si.stack[frame+2])) 1156 return si.fn != nil 1157 } 1158 1159 // ProgramCounter implements the same method as documented on experimental.StackIterator. 1160 func (si *stackIterator) ProgramCounter() experimental.ProgramCounter { 1161 return experimental.ProgramCounter(si.pc) 1162 } 1163 1164 // Function implements the same method as documented on experimental.StackIterator. 1165 func (si *stackIterator) Function() experimental.InternalFunction { 1166 return internalFunction{si.fn} 1167 } 1168 1169 // Parameters implements the same method as documented on experimental.StackIterator. 1170 func (si *stackIterator) Parameters() []uint64 { 1171 return si.stack[si.base : si.base+si.fn.funcType.ParamNumInUint64] 1172 } 1173 1174 // internalFunction implements experimental.InternalFunction. 1175 type internalFunction struct{ *function } 1176 1177 // Definition implements the same method as documented on experimental.InternalFunction. 1178 func (f internalFunction) Definition() api.FunctionDefinition { 1179 return f.definition() 1180 } 1181 1182 // SourceOffsetForPC implements the same method as documented on experimental.InternalFunction. 1183 func (f internalFunction) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 { 1184 p := f.parent 1185 if bitpack.OffsetArrayLen(p.sourceOffsetMap.irOperationSourceOffsetsInWasmBinary) == 0 { 1186 return 0 // source not available 1187 } 1188 return f.getSourceOffsetInWasmBinary(uint64(pc)) 1189 } 1190 1191 func (ce *callEngine) builtinFunctionFunctionListenerBefore(ctx context.Context, mod api.Module, fn *function) { 1192 base := int(ce.stackBasePointerInBytes >> 3) 1193 pc := uint64(ce.returnAddress) 1194 ce.stackIterator.reset(ce.stack, fn, base, pc) 1195 1196 params := ce.stack[base : base+fn.funcType.ParamNumInUint64] 1197 fn.parent.listener.Before(ctx, mod, fn.definition(), params, &ce.stackIterator) 1198 1199 ce.stackIterator.clear() 1200 } 1201 1202 func (ce *callEngine) builtinFunctionFunctionListenerAfter(ctx context.Context, mod api.Module, fn *function) { 1203 base := int(ce.stackBasePointerInBytes >> 3) 1204 fn.parent.listener.After(ctx, mod, fn.definition(), ce.stack[base:base+fn.funcType.ResultNumInUint64]) 1205 } 1206 1207 func compileGoDefinedHostFunction(buf asm.Buffer, cmp compiler) error { 1208 if err := cmp.compileGoDefinedHostFunction(); err != nil { 1209 return err 1210 } 1211 _, err := cmp.compile(buf) 1212 return err 1213 } 1214 1215 type asmNodes struct { 1216 nodes []asm.Node 1217 } 1218 1219 type offsets struct { 1220 values []uint64 1221 } 1222 1223 func compileWasmFunction(buf asm.Buffer, cmp compiler, ir *wazeroir.CompilationResult, asmNodes *asmNodes, offsets *offsets) (spCeil uint64, sm sourceOffsetMap, err error) { 1224 if err = cmp.compilePreamble(); err != nil { 1225 err = fmt.Errorf("failed to emit preamble: %w", err) 1226 return 1227 } 1228 1229 needSourceOffsets := len(ir.IROperationSourceOffsetsInWasmBinary) > 0 1230 var irOpBegins []asm.Node 1231 if needSourceOffsets { 1232 irOpBegins = append(asmNodes.nodes[:0], make([]asm.Node, len(ir.Operations))...) 1233 defer func() { asmNodes.nodes = irOpBegins }() 1234 } 1235 1236 var skip bool 1237 for i := range ir.Operations { 1238 op := &ir.Operations[i] 1239 if needSourceOffsets { 1240 // If this compilation requires source offsets for DWARF based back trace, 1241 // we emit a NOP node at the beginning of each IR operation to get the 1242 // binary offset of the beginning of the corresponding compiled native code. 1243 irOpBegins[i] = cmp.compileNOP() 1244 } 1245 1246 // Compiler determines whether skip the entire label. 1247 // For example, if the label doesn't have any caller, 1248 // we don't need to generate native code at all as we never reach the region. 1249 if op.Kind == wazeroir.OperationKindLabel { 1250 skip = cmp.compileLabel(op) 1251 } 1252 if skip { 1253 continue 1254 } 1255 1256 if false { 1257 fmt.Printf("compiling op=%s: %s\n", op.Kind, cmp) 1258 } 1259 switch op.Kind { 1260 case wazeroir.OperationKindUnreachable: 1261 err = cmp.compileUnreachable() 1262 case wazeroir.OperationKindLabel: 1263 // label op is already handled ^^. 1264 case wazeroir.OperationKindBr: 1265 err = cmp.compileBr(op) 1266 case wazeroir.OperationKindBrIf: 1267 err = cmp.compileBrIf(op) 1268 case wazeroir.OperationKindBrTable: 1269 err = cmp.compileBrTable(op) 1270 case wazeroir.OperationKindCall: 1271 err = cmp.compileCall(op) 1272 case wazeroir.OperationKindCallIndirect: 1273 err = cmp.compileCallIndirect(op) 1274 case wazeroir.OperationKindDrop: 1275 err = cmp.compileDrop(op) 1276 case wazeroir.OperationKindSelect: 1277 err = cmp.compileSelect(op) 1278 case wazeroir.OperationKindPick: 1279 err = cmp.compilePick(op) 1280 case wazeroir.OperationKindSet: 1281 err = cmp.compileSet(op) 1282 case wazeroir.OperationKindGlobalGet: 1283 err = cmp.compileGlobalGet(op) 1284 case wazeroir.OperationKindGlobalSet: 1285 err = cmp.compileGlobalSet(op) 1286 case wazeroir.OperationKindLoad: 1287 err = cmp.compileLoad(op) 1288 case wazeroir.OperationKindLoad8: 1289 err = cmp.compileLoad8(op) 1290 case wazeroir.OperationKindLoad16: 1291 err = cmp.compileLoad16(op) 1292 case wazeroir.OperationKindLoad32: 1293 err = cmp.compileLoad32(op) 1294 case wazeroir.OperationKindStore: 1295 err = cmp.compileStore(op) 1296 case wazeroir.OperationKindStore8: 1297 err = cmp.compileStore8(op) 1298 case wazeroir.OperationKindStore16: 1299 err = cmp.compileStore16(op) 1300 case wazeroir.OperationKindStore32: 1301 err = cmp.compileStore32(op) 1302 case wazeroir.OperationKindMemorySize: 1303 err = cmp.compileMemorySize() 1304 case wazeroir.OperationKindMemoryGrow: 1305 err = cmp.compileMemoryGrow() 1306 case wazeroir.OperationKindConstI32: 1307 err = cmp.compileConstI32(op) 1308 case wazeroir.OperationKindConstI64: 1309 err = cmp.compileConstI64(op) 1310 case wazeroir.OperationKindConstF32: 1311 err = cmp.compileConstF32(op) 1312 case wazeroir.OperationKindConstF64: 1313 err = cmp.compileConstF64(op) 1314 case wazeroir.OperationKindEq: 1315 err = cmp.compileEq(op) 1316 case wazeroir.OperationKindNe: 1317 err = cmp.compileNe(op) 1318 case wazeroir.OperationKindEqz: 1319 err = cmp.compileEqz(op) 1320 case wazeroir.OperationKindLt: 1321 err = cmp.compileLt(op) 1322 case wazeroir.OperationKindGt: 1323 err = cmp.compileGt(op) 1324 case wazeroir.OperationKindLe: 1325 err = cmp.compileLe(op) 1326 case wazeroir.OperationKindGe: 1327 err = cmp.compileGe(op) 1328 case wazeroir.OperationKindAdd: 1329 err = cmp.compileAdd(op) 1330 case wazeroir.OperationKindSub: 1331 err = cmp.compileSub(op) 1332 case wazeroir.OperationKindMul: 1333 err = cmp.compileMul(op) 1334 case wazeroir.OperationKindClz: 1335 err = cmp.compileClz(op) 1336 case wazeroir.OperationKindCtz: 1337 err = cmp.compileCtz(op) 1338 case wazeroir.OperationKindPopcnt: 1339 err = cmp.compilePopcnt(op) 1340 case wazeroir.OperationKindDiv: 1341 err = cmp.compileDiv(op) 1342 case wazeroir.OperationKindRem: 1343 err = cmp.compileRem(op) 1344 case wazeroir.OperationKindAnd: 1345 err = cmp.compileAnd(op) 1346 case wazeroir.OperationKindOr: 1347 err = cmp.compileOr(op) 1348 case wazeroir.OperationKindXor: 1349 err = cmp.compileXor(op) 1350 case wazeroir.OperationKindShl: 1351 err = cmp.compileShl(op) 1352 case wazeroir.OperationKindShr: 1353 err = cmp.compileShr(op) 1354 case wazeroir.OperationKindRotl: 1355 err = cmp.compileRotl(op) 1356 case wazeroir.OperationKindRotr: 1357 err = cmp.compileRotr(op) 1358 case wazeroir.OperationKindAbs: 1359 err = cmp.compileAbs(op) 1360 case wazeroir.OperationKindNeg: 1361 err = cmp.compileNeg(op) 1362 case wazeroir.OperationKindCeil: 1363 err = cmp.compileCeil(op) 1364 case wazeroir.OperationKindFloor: 1365 err = cmp.compileFloor(op) 1366 case wazeroir.OperationKindTrunc: 1367 err = cmp.compileTrunc(op) 1368 case wazeroir.OperationKindNearest: 1369 err = cmp.compileNearest(op) 1370 case wazeroir.OperationKindSqrt: 1371 err = cmp.compileSqrt(op) 1372 case wazeroir.OperationKindMin: 1373 err = cmp.compileMin(op) 1374 case wazeroir.OperationKindMax: 1375 err = cmp.compileMax(op) 1376 case wazeroir.OperationKindCopysign: 1377 err = cmp.compileCopysign(op) 1378 case wazeroir.OperationKindI32WrapFromI64: 1379 err = cmp.compileI32WrapFromI64() 1380 case wazeroir.OperationKindITruncFromF: 1381 err = cmp.compileITruncFromF(op) 1382 case wazeroir.OperationKindFConvertFromI: 1383 err = cmp.compileFConvertFromI(op) 1384 case wazeroir.OperationKindF32DemoteFromF64: 1385 err = cmp.compileF32DemoteFromF64() 1386 case wazeroir.OperationKindF64PromoteFromF32: 1387 err = cmp.compileF64PromoteFromF32() 1388 case wazeroir.OperationKindI32ReinterpretFromF32: 1389 err = cmp.compileI32ReinterpretFromF32() 1390 case wazeroir.OperationKindI64ReinterpretFromF64: 1391 err = cmp.compileI64ReinterpretFromF64() 1392 case wazeroir.OperationKindF32ReinterpretFromI32: 1393 err = cmp.compileF32ReinterpretFromI32() 1394 case wazeroir.OperationKindF64ReinterpretFromI64: 1395 err = cmp.compileF64ReinterpretFromI64() 1396 case wazeroir.OperationKindExtend: 1397 err = cmp.compileExtend(op) 1398 case wazeroir.OperationKindSignExtend32From8: 1399 err = cmp.compileSignExtend32From8() 1400 case wazeroir.OperationKindSignExtend32From16: 1401 err = cmp.compileSignExtend32From16() 1402 case wazeroir.OperationKindSignExtend64From8: 1403 err = cmp.compileSignExtend64From8() 1404 case wazeroir.OperationKindSignExtend64From16: 1405 err = cmp.compileSignExtend64From16() 1406 case wazeroir.OperationKindSignExtend64From32: 1407 err = cmp.compileSignExtend64From32() 1408 case wazeroir.OperationKindMemoryInit: 1409 err = cmp.compileMemoryInit(op) 1410 case wazeroir.OperationKindDataDrop: 1411 err = cmp.compileDataDrop(op) 1412 case wazeroir.OperationKindMemoryCopy: 1413 err = cmp.compileMemoryCopy() 1414 case wazeroir.OperationKindMemoryFill: 1415 err = cmp.compileMemoryFill() 1416 case wazeroir.OperationKindTableInit: 1417 err = cmp.compileTableInit(op) 1418 case wazeroir.OperationKindElemDrop: 1419 err = cmp.compileElemDrop(op) 1420 case wazeroir.OperationKindTableCopy: 1421 err = cmp.compileTableCopy(op) 1422 case wazeroir.OperationKindRefFunc: 1423 err = cmp.compileRefFunc(op) 1424 case wazeroir.OperationKindTableGet: 1425 err = cmp.compileTableGet(op) 1426 case wazeroir.OperationKindTableSet: 1427 err = cmp.compileTableSet(op) 1428 case wazeroir.OperationKindTableGrow: 1429 err = cmp.compileTableGrow(op) 1430 case wazeroir.OperationKindTableSize: 1431 err = cmp.compileTableSize(op) 1432 case wazeroir.OperationKindTableFill: 1433 err = cmp.compileTableFill(op) 1434 case wazeroir.OperationKindV128Const: 1435 err = cmp.compileV128Const(op) 1436 case wazeroir.OperationKindV128Add: 1437 err = cmp.compileV128Add(op) 1438 case wazeroir.OperationKindV128Sub: 1439 err = cmp.compileV128Sub(op) 1440 case wazeroir.OperationKindV128Load: 1441 err = cmp.compileV128Load(op) 1442 case wazeroir.OperationKindV128LoadLane: 1443 err = cmp.compileV128LoadLane(op) 1444 case wazeroir.OperationKindV128Store: 1445 err = cmp.compileV128Store(op) 1446 case wazeroir.OperationKindV128StoreLane: 1447 err = cmp.compileV128StoreLane(op) 1448 case wazeroir.OperationKindV128ExtractLane: 1449 err = cmp.compileV128ExtractLane(op) 1450 case wazeroir.OperationKindV128ReplaceLane: 1451 err = cmp.compileV128ReplaceLane(op) 1452 case wazeroir.OperationKindV128Splat: 1453 err = cmp.compileV128Splat(op) 1454 case wazeroir.OperationKindV128Shuffle: 1455 err = cmp.compileV128Shuffle(op) 1456 case wazeroir.OperationKindV128Swizzle: 1457 err = cmp.compileV128Swizzle(op) 1458 case wazeroir.OperationKindV128AnyTrue: 1459 err = cmp.compileV128AnyTrue(op) 1460 case wazeroir.OperationKindV128AllTrue: 1461 err = cmp.compileV128AllTrue(op) 1462 case wazeroir.OperationKindV128BitMask: 1463 err = cmp.compileV128BitMask(op) 1464 case wazeroir.OperationKindV128And: 1465 err = cmp.compileV128And(op) 1466 case wazeroir.OperationKindV128Not: 1467 err = cmp.compileV128Not(op) 1468 case wazeroir.OperationKindV128Or: 1469 err = cmp.compileV128Or(op) 1470 case wazeroir.OperationKindV128Xor: 1471 err = cmp.compileV128Xor(op) 1472 case wazeroir.OperationKindV128Bitselect: 1473 err = cmp.compileV128Bitselect(op) 1474 case wazeroir.OperationKindV128AndNot: 1475 err = cmp.compileV128AndNot(op) 1476 case wazeroir.OperationKindV128Shl: 1477 err = cmp.compileV128Shl(op) 1478 case wazeroir.OperationKindV128Shr: 1479 err = cmp.compileV128Shr(op) 1480 case wazeroir.OperationKindV128Cmp: 1481 err = cmp.compileV128Cmp(op) 1482 case wazeroir.OperationKindV128AddSat: 1483 err = cmp.compileV128AddSat(op) 1484 case wazeroir.OperationKindV128SubSat: 1485 err = cmp.compileV128SubSat(op) 1486 case wazeroir.OperationKindV128Mul: 1487 err = cmp.compileV128Mul(op) 1488 case wazeroir.OperationKindV128Div: 1489 err = cmp.compileV128Div(op) 1490 case wazeroir.OperationKindV128Neg: 1491 err = cmp.compileV128Neg(op) 1492 case wazeroir.OperationKindV128Sqrt: 1493 err = cmp.compileV128Sqrt(op) 1494 case wazeroir.OperationKindV128Abs: 1495 err = cmp.compileV128Abs(op) 1496 case wazeroir.OperationKindV128Popcnt: 1497 err = cmp.compileV128Popcnt(op) 1498 case wazeroir.OperationKindV128Min: 1499 err = cmp.compileV128Min(op) 1500 case wazeroir.OperationKindV128Max: 1501 err = cmp.compileV128Max(op) 1502 case wazeroir.OperationKindV128AvgrU: 1503 err = cmp.compileV128AvgrU(op) 1504 case wazeroir.OperationKindV128Pmin: 1505 err = cmp.compileV128Pmin(op) 1506 case wazeroir.OperationKindV128Pmax: 1507 err = cmp.compileV128Pmax(op) 1508 case wazeroir.OperationKindV128Ceil: 1509 err = cmp.compileV128Ceil(op) 1510 case wazeroir.OperationKindV128Floor: 1511 err = cmp.compileV128Floor(op) 1512 case wazeroir.OperationKindV128Trunc: 1513 err = cmp.compileV128Trunc(op) 1514 case wazeroir.OperationKindV128Nearest: 1515 err = cmp.compileV128Nearest(op) 1516 case wazeroir.OperationKindV128Extend: 1517 err = cmp.compileV128Extend(op) 1518 case wazeroir.OperationKindV128ExtMul: 1519 err = cmp.compileV128ExtMul(op) 1520 case wazeroir.OperationKindV128Q15mulrSatS: 1521 err = cmp.compileV128Q15mulrSatS(op) 1522 case wazeroir.OperationKindV128ExtAddPairwise: 1523 err = cmp.compileV128ExtAddPairwise(op) 1524 case wazeroir.OperationKindV128FloatPromote: 1525 err = cmp.compileV128FloatPromote(op) 1526 case wazeroir.OperationKindV128FloatDemote: 1527 err = cmp.compileV128FloatDemote(op) 1528 case wazeroir.OperationKindV128FConvertFromI: 1529 err = cmp.compileV128FConvertFromI(op) 1530 case wazeroir.OperationKindV128Dot: 1531 err = cmp.compileV128Dot(op) 1532 case wazeroir.OperationKindV128Narrow: 1533 err = cmp.compileV128Narrow(op) 1534 case wazeroir.OperationKindV128ITruncSatFromF: 1535 err = cmp.compileV128ITruncSatFromF(op) 1536 case wazeroir.OperationKindBuiltinFunctionCheckExitCode: 1537 err = cmp.compileBuiltinFunctionCheckExitCode() 1538 default: 1539 err = errors.New("unsupported") 1540 } 1541 if err != nil { 1542 err = fmt.Errorf("operation %s: %w", op.Kind.String(), err) 1543 return 1544 } 1545 } 1546 1547 spCeil, err = cmp.compile(buf) 1548 if err != nil { 1549 err = fmt.Errorf("failed to compile: %w", err) 1550 return 1551 } 1552 1553 if needSourceOffsets { 1554 offsetInNativeBin := append(offsets.values[:0], make([]uint64, len(irOpBegins))...) 1555 offsets.values = offsetInNativeBin 1556 for i, nop := range irOpBegins { 1557 offsetInNativeBin[i] = nop.OffsetInBinary() 1558 } 1559 sm.irOperationOffsetsInNativeBinary = bitpack.NewOffsetArray(offsetInNativeBin) 1560 sm.irOperationSourceOffsetsInWasmBinary = bitpack.NewOffsetArray(ir.IROperationSourceOffsetsInWasmBinary) 1561 } 1562 return 1563 }