github.com/tetratelabs/wazero@v1.7.1/internal/engine/wazevo/engine.go (about) 1 package wazevo 2 3 import ( 4 "context" 5 "encoding/hex" 6 "errors" 7 "fmt" 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/engine/wazevo/backend" 16 "github.com/tetratelabs/wazero/internal/engine/wazevo/frontend" 17 "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" 18 "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" 19 "github.com/tetratelabs/wazero/internal/filecache" 20 "github.com/tetratelabs/wazero/internal/platform" 21 "github.com/tetratelabs/wazero/internal/version" 22 "github.com/tetratelabs/wazero/internal/wasm" 23 ) 24 25 type ( 26 // engine implements wasm.Engine. 27 engine struct { 28 wazeroVersion string 29 fileCache filecache.Cache 30 compiledModules map[wasm.ModuleID]*compiledModule 31 // sortedCompiledModules is a list of compiled modules sorted by the initial address of the executable. 32 sortedCompiledModules []*compiledModule 33 mux sync.RWMutex 34 // rels is a list of relocations to be resolved. This is reused for each compilation to avoid allocation. 35 rels []backend.RelocationInfo 36 // refToBinaryOffset is reused for each compilation to avoid allocation. 37 refToBinaryOffset map[ssa.FuncRef]int 38 // sharedFunctions is compiled functions shared by all modules. 39 sharedFunctions *sharedFunctions 40 // setFinalizer defaults to runtime.SetFinalizer, but overridable for tests. 41 setFinalizer func(obj interface{}, finalizer interface{}) 42 43 // The followings are reused for compiling shared functions. 44 machine backend.Machine 45 be backend.Compiler 46 } 47 48 sharedFunctions struct { 49 // memoryGrowExecutable is a compiled trampoline executable for memory.grow builtin function. 50 memoryGrowExecutable []byte 51 // checkModuleExitCode is a compiled trampoline executable for checking module instance exit code. This 52 // is used when ensureTermination is true. 53 checkModuleExitCode []byte 54 // stackGrowExecutable is a compiled executable for growing stack builtin function. 55 stackGrowExecutable []byte 56 // tableGrowExecutable is a compiled trampoline executable for table.grow builtin function. 57 tableGrowExecutable []byte 58 // refFuncExecutable is a compiled trampoline executable for ref.func builtin function. 59 refFuncExecutable []byte 60 // memoryWait32Executable is a compiled trampoline executable for memory.wait32 builtin function 61 memoryWait32Executable []byte 62 // memoryWait64Executable is a compiled trampoline executable for memory.wait64 builtin function 63 memoryWait64Executable []byte 64 // memoryNotifyExecutable is a compiled trampoline executable for memory.notify builtin function 65 memoryNotifyExecutable []byte 66 listenerBeforeTrampolines map[*wasm.FunctionType][]byte 67 listenerAfterTrampolines map[*wasm.FunctionType][]byte 68 } 69 70 // compiledModule is a compiled variant of a wasm.Module and ready to be used for instantiation. 71 compiledModule struct { 72 *executables 73 // functionOffsets maps a local function index to the offset in the executable. 74 functionOffsets []int 75 parent *engine 76 module *wasm.Module 77 ensureTermination bool 78 listeners []experimental.FunctionListener 79 listenerBeforeTrampolines []*byte 80 listenerAfterTrampolines []*byte 81 82 // The followings are only available for non host modules. 83 84 offsets wazevoapi.ModuleContextOffsetData 85 sharedFunctions *sharedFunctions 86 sourceMap sourceMap 87 } 88 89 executables struct { 90 executable []byte 91 entryPreambles [][]byte 92 } 93 ) 94 95 // sourceMap is a mapping from the offset of the executable to the offset of the original wasm binary. 96 type sourceMap struct { 97 // executableOffsets is a sorted list of offsets of the executable. This is index-correlated with wasmBinaryOffsets, 98 // in other words executableOffsets[i] is the offset of the executable which corresponds to the offset of a Wasm 99 // binary pointed by wasmBinaryOffsets[i]. 100 executableOffsets []uintptr 101 // wasmBinaryOffsets is the counterpart of executableOffsets. 102 wasmBinaryOffsets []uint64 103 } 104 105 var _ wasm.Engine = (*engine)(nil) 106 107 // NewEngine returns the implementation of wasm.Engine. 108 func NewEngine(ctx context.Context, _ api.CoreFeatures, fc filecache.Cache) wasm.Engine { 109 machine := newMachine() 110 be := backend.NewCompiler(ctx, machine, ssa.NewBuilder()) 111 e := &engine{ 112 compiledModules: make(map[wasm.ModuleID]*compiledModule), refToBinaryOffset: make(map[ssa.FuncRef]int), 113 setFinalizer: runtime.SetFinalizer, 114 machine: machine, 115 be: be, 116 fileCache: fc, 117 wazeroVersion: version.GetWazeroVersion(), 118 } 119 e.compileSharedFunctions() 120 return e 121 } 122 123 // CompileModule implements wasm.Engine. 124 func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (err error) { 125 if wazevoapi.PerfMapEnabled { 126 wazevoapi.PerfMap.Lock() 127 defer wazevoapi.PerfMap.Unlock() 128 } 129 130 if _, ok, err := e.getCompiledModule(module, listeners, ensureTermination); ok { // cache hit! 131 return nil 132 } else if err != nil { 133 return err 134 } 135 136 if wazevoapi.DeterministicCompilationVerifierEnabled { 137 ctx = wazevoapi.NewDeterministicCompilationVerifierContext(ctx, len(module.CodeSection)) 138 } 139 cm, err := e.compileModule(ctx, module, listeners, ensureTermination) 140 if err != nil { 141 return err 142 } 143 if err = e.addCompiledModule(module, cm); err != nil { 144 return err 145 } 146 147 if wazevoapi.DeterministicCompilationVerifierEnabled { 148 for i := 0; i < wazevoapi.DeterministicCompilationVerifyingIter; i++ { 149 _, err := e.compileModule(ctx, module, listeners, ensureTermination) 150 if err != nil { 151 return err 152 } 153 } 154 } 155 156 if len(listeners) > 0 { 157 cm.listeners = listeners 158 cm.listenerBeforeTrampolines = make([]*byte, len(module.TypeSection)) 159 cm.listenerAfterTrampolines = make([]*byte, len(module.TypeSection)) 160 for i := range module.TypeSection { 161 typ := &module.TypeSection[i] 162 before, after := e.getListenerTrampolineForType(typ) 163 cm.listenerBeforeTrampolines[i] = before 164 cm.listenerAfterTrampolines[i] = after 165 } 166 } 167 return nil 168 } 169 170 func (exec *executables) compileEntryPreambles(m *wasm.Module, machine backend.Machine, be backend.Compiler) { 171 exec.entryPreambles = make([][]byte, len(m.TypeSection)) 172 for i := range m.TypeSection { 173 typ := &m.TypeSection[i] 174 sig := frontend.SignatureForWasmFunctionType(typ) 175 be.Init() 176 buf := machine.CompileEntryPreamble(&sig) 177 executable := mmapExecutable(buf) 178 exec.entryPreambles[i] = executable 179 180 if wazevoapi.PerfMapEnabled { 181 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&executable[0])), 182 uint64(len(executable)), fmt.Sprintf("entry_preamble::type=%s", typ.String())) 183 } 184 } 185 } 186 187 func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (*compiledModule, error) { 188 withListener := len(listeners) > 0 189 e.rels = e.rels[:0] 190 cm := &compiledModule{ 191 offsets: wazevoapi.NewModuleContextOffsetData(module, withListener), parent: e, module: module, 192 ensureTermination: ensureTermination, 193 executables: &executables{}, 194 } 195 196 if module.IsHostModule { 197 return e.compileHostModule(ctx, module, listeners) 198 } 199 200 importedFns, localFns := int(module.ImportFunctionCount), len(module.FunctionSection) 201 if localFns == 0 { 202 return cm, nil 203 } 204 205 if wazevoapi.DeterministicCompilationVerifierEnabled { 206 // The compilation must be deterministic regardless of the order of functions being compiled. 207 wazevoapi.DeterministicCompilationVerifierRandomizeIndexes(ctx) 208 } 209 210 needSourceInfo := module.DWARFLines != nil 211 212 // Creates new compiler instances which are reused for each function. 213 ssaBuilder := ssa.NewBuilder() 214 fe := frontend.NewFrontendCompiler(module, ssaBuilder, &cm.offsets, ensureTermination, withListener, needSourceInfo) 215 machine := newMachine() 216 be := backend.NewCompiler(ctx, machine, ssaBuilder) 217 218 cm.executables.compileEntryPreambles(module, machine, be) 219 220 totalSize := 0 // Total binary size of the executable. 221 cm.functionOffsets = make([]int, localFns) 222 bodies := make([][]byte, localFns) 223 224 // Trampoline relocation related variables. 225 trampolineInterval, callTrampolineIslandSize, err := machine.CallTrampolineIslandInfo(localFns) 226 if err != nil { 227 return nil, err 228 } 229 needCallTrampoline := callTrampolineIslandSize > 0 230 var callTrampolineIslandOffsets []int // Holds the offsets of trampoline islands. 231 232 for i := range module.CodeSection { 233 if wazevoapi.DeterministicCompilationVerifierEnabled { 234 i = wazevoapi.DeterministicCompilationVerifierGetRandomizedLocalFunctionIndex(ctx, i) 235 } 236 237 fidx := wasm.Index(i + importedFns) 238 239 if wazevoapi.NeedFunctionNameInContext { 240 def := module.FunctionDefinition(fidx) 241 name := def.DebugName() 242 if len(def.ExportNames()) > 0 { 243 name = def.ExportNames()[0] 244 } 245 ctx = wazevoapi.SetCurrentFunctionName(ctx, i, fmt.Sprintf("[%d/%d]%s", i, len(module.CodeSection)-1, name)) 246 } 247 248 needListener := len(listeners) > 0 && listeners[i] != nil 249 body, rels, err := e.compileLocalWasmFunction(ctx, module, wasm.Index(i), fe, ssaBuilder, be, needListener) 250 if err != nil { 251 return nil, fmt.Errorf("compile function %d/%d: %v", i, len(module.CodeSection)-1, err) 252 } 253 254 // Align 16-bytes boundary. 255 totalSize = (totalSize + 15) &^ 15 256 cm.functionOffsets[i] = totalSize 257 258 if needSourceInfo { 259 // At the beginning of the function, we add the offset of the function body so that 260 // we can resolve the source location of the call site of before listener call. 261 cm.sourceMap.executableOffsets = append(cm.sourceMap.executableOffsets, uintptr(totalSize)) 262 cm.sourceMap.wasmBinaryOffsets = append(cm.sourceMap.wasmBinaryOffsets, module.CodeSection[i].BodyOffsetInCodeSection) 263 264 for _, info := range be.SourceOffsetInfo() { 265 cm.sourceMap.executableOffsets = append(cm.sourceMap.executableOffsets, uintptr(totalSize)+uintptr(info.ExecutableOffset)) 266 cm.sourceMap.wasmBinaryOffsets = append(cm.sourceMap.wasmBinaryOffsets, uint64(info.SourceOffset)) 267 } 268 } 269 270 fref := frontend.FunctionIndexToFuncRef(fidx) 271 e.refToBinaryOffset[fref] = totalSize 272 273 // At this point, relocation offsets are relative to the start of the function body, 274 // so we adjust it to the start of the executable. 275 for _, r := range rels { 276 r.Offset += int64(totalSize) 277 e.rels = append(e.rels, r) 278 } 279 280 bodies[i] = body 281 totalSize += len(body) 282 if wazevoapi.PrintMachineCodeHexPerFunction { 283 fmt.Printf("[[[machine code for %s]]]\n%s\n\n", wazevoapi.GetCurrentFunctionName(ctx), hex.EncodeToString(body)) 284 } 285 286 if needCallTrampoline { 287 // If the total size exceeds the trampoline interval, we need to add a trampoline island. 288 if totalSize/trampolineInterval > len(callTrampolineIslandOffsets) { 289 callTrampolineIslandOffsets = append(callTrampolineIslandOffsets, totalSize) 290 totalSize += callTrampolineIslandSize 291 } 292 } 293 } 294 295 // Allocate executable memory and then copy the generated machine code. 296 executable, err := platform.MmapCodeSegment(totalSize) 297 if err != nil { 298 panic(err) 299 } 300 cm.executable = executable 301 302 for i, b := range bodies { 303 offset := cm.functionOffsets[i] 304 copy(executable[offset:], b) 305 } 306 307 if wazevoapi.PerfMapEnabled { 308 wazevoapi.PerfMap.Flush(uintptr(unsafe.Pointer(&executable[0])), cm.functionOffsets) 309 } 310 311 if needSourceInfo { 312 for i := range cm.sourceMap.executableOffsets { 313 cm.sourceMap.executableOffsets[i] += uintptr(unsafe.Pointer(&cm.executable[0])) 314 } 315 } 316 317 // Resolve relocations for local function calls. 318 if len(e.rels) > 0 { 319 machine.ResolveRelocations(e.refToBinaryOffset, executable, e.rels, callTrampolineIslandOffsets) 320 } 321 322 if runtime.GOARCH == "arm64" { 323 // On arm64, we cannot give all of rwx at the same time, so we change it to exec. 324 if err = platform.MprotectRX(executable); err != nil { 325 return nil, err 326 } 327 } 328 cm.sharedFunctions = e.sharedFunctions 329 e.setFinalizer(cm.executables, executablesFinalizer) 330 return cm, nil 331 } 332 333 func (e *engine) compileLocalWasmFunction( 334 ctx context.Context, 335 module *wasm.Module, 336 localFunctionIndex wasm.Index, 337 fe *frontend.Compiler, 338 ssaBuilder ssa.Builder, 339 be backend.Compiler, 340 needListener bool, 341 ) (body []byte, rels []backend.RelocationInfo, err error) { 342 typIndex := module.FunctionSection[localFunctionIndex] 343 typ := &module.TypeSection[typIndex] 344 codeSeg := &module.CodeSection[localFunctionIndex] 345 346 // Initializes both frontend and backend compilers. 347 fe.Init(localFunctionIndex, typIndex, typ, codeSeg.LocalTypes, codeSeg.Body, needListener, codeSeg.BodyOffsetInCodeSection) 348 be.Init() 349 350 // Lower Wasm to SSA. 351 fe.LowerToSSA() 352 if wazevoapi.PrintSSA && wazevoapi.PrintEnabledIndex(ctx) { 353 fmt.Printf("[[[SSA for %s]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), ssaBuilder.Format()) 354 } 355 356 if wazevoapi.DeterministicCompilationVerifierEnabled { 357 wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "SSA", ssaBuilder.Format()) 358 } 359 360 // Run SSA-level optimization passes. 361 ssaBuilder.RunPasses() 362 363 if wazevoapi.PrintOptimizedSSA && wazevoapi.PrintEnabledIndex(ctx) { 364 fmt.Printf("[[[Optimized SSA for %s]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), ssaBuilder.Format()) 365 } 366 367 if wazevoapi.DeterministicCompilationVerifierEnabled { 368 wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "Optimized SSA", ssaBuilder.Format()) 369 } 370 371 // Now our ssaBuilder contains the necessary information to further lower them to 372 // machine code. 373 original, rels, err := be.Compile(ctx) 374 if err != nil { 375 return nil, nil, fmt.Errorf("ssa->machine code: %v", err) 376 } 377 378 // TODO: optimize as zero copy. 379 copied := make([]byte, len(original)) 380 copy(copied, original) 381 return copied, rels, nil 382 } 383 384 func (e *engine) compileHostModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener) (*compiledModule, error) { 385 machine := newMachine() 386 be := backend.NewCompiler(ctx, machine, ssa.NewBuilder()) 387 388 num := len(module.CodeSection) 389 cm := &compiledModule{module: module, listeners: listeners, executables: &executables{}} 390 cm.functionOffsets = make([]int, num) 391 totalSize := 0 // Total binary size of the executable. 392 bodies := make([][]byte, num) 393 var sig ssa.Signature 394 for i := range module.CodeSection { 395 totalSize = (totalSize + 15) &^ 15 396 cm.functionOffsets[i] = totalSize 397 398 typIndex := module.FunctionSection[i] 399 typ := &module.TypeSection[typIndex] 400 401 // We can relax until the index fits together in ExitCode as we do in wazevoapi.ExitCodeCallGoModuleFunctionWithIndex. 402 // However, 1 << 16 should be large enough for a real use case. 403 const hostFunctionNumMaximum = 1 << 16 404 if i >= hostFunctionNumMaximum { 405 return nil, fmt.Errorf("too many host functions (maximum %d)", hostFunctionNumMaximum) 406 } 407 408 sig.ID = ssa.SignatureID(typIndex) // This is important since we reuse the `machine` which caches the ABI based on the SignatureID. 409 sig.Params = append(sig.Params[:0], 410 ssa.TypeI64, // First argument must be exec context. 411 ssa.TypeI64, // The second argument is the moduleContextOpaque of this host module. 412 ) 413 for _, t := range typ.Params { 414 sig.Params = append(sig.Params, frontend.WasmTypeToSSAType(t)) 415 } 416 417 sig.Results = sig.Results[:0] 418 for _, t := range typ.Results { 419 sig.Results = append(sig.Results, frontend.WasmTypeToSSAType(t)) 420 } 421 422 c := &module.CodeSection[i] 423 if c.GoFunc == nil { 424 panic("BUG: GoFunc must be set for host module") 425 } 426 427 withListener := len(listeners) > 0 && listeners[i] != nil 428 var exitCode wazevoapi.ExitCode 429 fn := c.GoFunc 430 switch fn.(type) { 431 case api.GoModuleFunction: 432 exitCode = wazevoapi.ExitCodeCallGoModuleFunctionWithIndex(i, withListener) 433 case api.GoFunction: 434 exitCode = wazevoapi.ExitCodeCallGoFunctionWithIndex(i, withListener) 435 } 436 437 be.Init() 438 machine.CompileGoFunctionTrampoline(exitCode, &sig, true) 439 if err := be.Finalize(ctx); err != nil { 440 return nil, err 441 } 442 body := be.Buf() 443 444 if wazevoapi.PerfMapEnabled { 445 name := module.FunctionDefinition(wasm.Index(i)).DebugName() 446 wazevoapi.PerfMap.AddModuleEntry(i, 447 int64(totalSize), 448 uint64(len(body)), 449 fmt.Sprintf("trampoline:%s", name)) 450 } 451 452 // TODO: optimize as zero copy. 453 copied := make([]byte, len(body)) 454 copy(copied, body) 455 bodies[i] = copied 456 totalSize += len(body) 457 } 458 459 if totalSize == 0 { 460 // Empty module. 461 return cm, nil 462 } 463 464 // Allocate executable memory and then copy the generated machine code. 465 executable, err := platform.MmapCodeSegment(totalSize) 466 if err != nil { 467 panic(err) 468 } 469 cm.executable = executable 470 471 for i, b := range bodies { 472 offset := cm.functionOffsets[i] 473 copy(executable[offset:], b) 474 } 475 476 if wazevoapi.PerfMapEnabled { 477 wazevoapi.PerfMap.Flush(uintptr(unsafe.Pointer(&executable[0])), cm.functionOffsets) 478 } 479 480 if runtime.GOARCH == "arm64" { 481 // On arm64, we cannot give all of rwx at the same time, so we change it to exec. 482 if err = platform.MprotectRX(executable); err != nil { 483 return nil, err 484 } 485 } 486 e.setFinalizer(cm.executables, executablesFinalizer) 487 return cm, nil 488 } 489 490 // Close implements wasm.Engine. 491 func (e *engine) Close() (err error) { 492 e.mux.Lock() 493 defer e.mux.Unlock() 494 e.sortedCompiledModules = nil 495 e.compiledModules = nil 496 e.sharedFunctions = nil 497 return nil 498 } 499 500 // CompiledModuleCount implements wasm.Engine. 501 func (e *engine) CompiledModuleCount() uint32 { 502 e.mux.RLock() 503 defer e.mux.RUnlock() 504 return uint32(len(e.compiledModules)) 505 } 506 507 // DeleteCompiledModule implements wasm.Engine. 508 func (e *engine) DeleteCompiledModule(m *wasm.Module) { 509 e.mux.Lock() 510 defer e.mux.Unlock() 511 cm, ok := e.compiledModules[m.ID] 512 if ok { 513 if len(cm.executable) > 0 { 514 e.deleteCompiledModuleFromSortedList(cm) 515 } 516 delete(e.compiledModules, m.ID) 517 } 518 } 519 520 func (e *engine) addCompiledModuleToSortedList(cm *compiledModule) { 521 ptr := uintptr(unsafe.Pointer(&cm.executable[0])) 522 523 index := sort.Search(len(e.sortedCompiledModules), func(i int) bool { 524 return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) >= ptr 525 }) 526 e.sortedCompiledModules = append(e.sortedCompiledModules, nil) 527 copy(e.sortedCompiledModules[index+1:], e.sortedCompiledModules[index:]) 528 e.sortedCompiledModules[index] = cm 529 } 530 531 func (e *engine) deleteCompiledModuleFromSortedList(cm *compiledModule) { 532 ptr := uintptr(unsafe.Pointer(&cm.executable[0])) 533 534 index := sort.Search(len(e.sortedCompiledModules), func(i int) bool { 535 return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) >= ptr 536 }) 537 if index >= len(e.sortedCompiledModules) { 538 return 539 } 540 copy(e.sortedCompiledModules[index:], e.sortedCompiledModules[index+1:]) 541 e.sortedCompiledModules = e.sortedCompiledModules[:len(e.sortedCompiledModules)-1] 542 } 543 544 func (e *engine) compiledModuleOfAddr(addr uintptr) *compiledModule { 545 e.mux.RLock() 546 defer e.mux.RUnlock() 547 548 index := sort.Search(len(e.sortedCompiledModules), func(i int) bool { 549 return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) > addr 550 }) 551 index -= 1 552 if index < 0 { 553 return nil 554 } 555 candidate := e.sortedCompiledModules[index] 556 if checkAddrInBytes(addr, candidate.executable) { 557 // If a module is already deleted, the found module may have been wrong. 558 return candidate 559 } 560 return nil 561 } 562 563 func checkAddrInBytes(addr uintptr, b []byte) bool { 564 return uintptr(unsafe.Pointer(&b[0])) <= addr && addr <= uintptr(unsafe.Pointer(&b[len(b)-1])) 565 } 566 567 // NewModuleEngine implements wasm.Engine. 568 func (e *engine) NewModuleEngine(m *wasm.Module, mi *wasm.ModuleInstance) (wasm.ModuleEngine, error) { 569 me := &moduleEngine{} 570 571 // Note: imported functions are resolved in moduleEngine.ResolveImportedFunction. 572 me.importedFunctions = make([]importedFunction, m.ImportFunctionCount) 573 574 compiled, ok := e.compiledModules[m.ID] 575 if !ok { 576 return nil, errors.New("source module must be compiled before instantiation") 577 } 578 me.parent = compiled 579 me.module = mi 580 me.listeners = compiled.listeners 581 582 if m.IsHostModule { 583 me.opaque = buildHostModuleOpaque(m, compiled.listeners) 584 me.opaquePtr = &me.opaque[0] 585 } else { 586 if size := compiled.offsets.TotalSize; size != 0 { 587 opaque := newAlignedOpaque(size) 588 me.opaque = opaque 589 me.opaquePtr = &opaque[0] 590 } 591 } 592 return me, nil 593 } 594 595 func (e *engine) compileSharedFunctions() { 596 e.sharedFunctions = &sharedFunctions{ 597 listenerBeforeTrampolines: make(map[*wasm.FunctionType][]byte), 598 listenerAfterTrampolines: make(map[*wasm.FunctionType][]byte), 599 } 600 601 e.be.Init() 602 { 603 src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeGrowMemory, &ssa.Signature{ 604 Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32}, 605 Results: []ssa.Type{ssa.TypeI32}, 606 }, false) 607 e.sharedFunctions.memoryGrowExecutable = mmapExecutable(src) 608 if wazevoapi.PerfMapEnabled { 609 exe := e.sharedFunctions.memoryGrowExecutable 610 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "memory_grow_trampoline") 611 } 612 } 613 614 e.be.Init() 615 { 616 src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeTableGrow, &ssa.Signature{ 617 Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* table index */, ssa.TypeI32 /* num */, ssa.TypeI64 /* ref */}, 618 Results: []ssa.Type{ssa.TypeI32}, 619 }, false) 620 e.sharedFunctions.tableGrowExecutable = mmapExecutable(src) 621 if wazevoapi.PerfMapEnabled { 622 exe := e.sharedFunctions.tableGrowExecutable 623 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "table_grow_trampoline") 624 } 625 } 626 627 e.be.Init() 628 { 629 src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCheckModuleExitCode, &ssa.Signature{ 630 Params: []ssa.Type{ssa.TypeI32 /* exec context */}, 631 Results: []ssa.Type{ssa.TypeI32}, 632 }, false) 633 e.sharedFunctions.checkModuleExitCode = mmapExecutable(src) 634 if wazevoapi.PerfMapEnabled { 635 exe := e.sharedFunctions.checkModuleExitCode 636 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "check_module_exit_code_trampoline") 637 } 638 } 639 640 e.be.Init() 641 { 642 src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeRefFunc, &ssa.Signature{ 643 Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* function index */}, 644 Results: []ssa.Type{ssa.TypeI64}, // returns the function reference. 645 }, false) 646 e.sharedFunctions.refFuncExecutable = mmapExecutable(src) 647 if wazevoapi.PerfMapEnabled { 648 exe := e.sharedFunctions.refFuncExecutable 649 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "ref_func_trampoline") 650 } 651 } 652 653 e.be.Init() 654 { 655 src := e.machine.CompileStackGrowCallSequence() 656 e.sharedFunctions.stackGrowExecutable = mmapExecutable(src) 657 if wazevoapi.PerfMapEnabled { 658 exe := e.sharedFunctions.stackGrowExecutable 659 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "stack_grow_trampoline") 660 } 661 } 662 663 e.be.Init() 664 { 665 src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryWait32, &ssa.Signature{ 666 // exec context, timeout, expected, addr 667 Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, 668 // Returns the status. 669 Results: []ssa.Type{ssa.TypeI32}, 670 }, false) 671 e.sharedFunctions.memoryWait32Executable = mmapExecutable(src) 672 if wazevoapi.PerfMapEnabled { 673 exe := e.sharedFunctions.memoryWait32Executable 674 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "memory_wait32_trampoline") 675 } 676 } 677 678 e.be.Init() 679 { 680 src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryWait64, &ssa.Signature{ 681 // exec context, timeout, expected, addr 682 Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64, ssa.TypeI64}, 683 // Returns the status. 684 Results: []ssa.Type{ssa.TypeI32}, 685 }, false) 686 e.sharedFunctions.memoryWait64Executable = mmapExecutable(src) 687 if wazevoapi.PerfMapEnabled { 688 exe := e.sharedFunctions.memoryWait64Executable 689 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "memory_wait64_trampoline") 690 } 691 } 692 693 e.be.Init() 694 { 695 src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryNotify, &ssa.Signature{ 696 // exec context, count, addr 697 Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, 698 // Returns the number notified. 699 Results: []ssa.Type{ssa.TypeI32}, 700 }, false) 701 e.sharedFunctions.memoryNotifyExecutable = mmapExecutable(src) 702 if wazevoapi.PerfMapEnabled { 703 exe := e.sharedFunctions.memoryNotifyExecutable 704 wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(&exe[0])), uint64(len(exe)), "memory_notify_trampoline") 705 } 706 } 707 708 e.setFinalizer(e.sharedFunctions, sharedFunctionsFinalizer) 709 } 710 711 func sharedFunctionsFinalizer(sf *sharedFunctions) { 712 if err := platform.MunmapCodeSegment(sf.memoryGrowExecutable); err != nil { 713 panic(err) 714 } 715 if err := platform.MunmapCodeSegment(sf.checkModuleExitCode); err != nil { 716 panic(err) 717 } 718 if err := platform.MunmapCodeSegment(sf.stackGrowExecutable); err != nil { 719 panic(err) 720 } 721 if err := platform.MunmapCodeSegment(sf.tableGrowExecutable); err != nil { 722 panic(err) 723 } 724 if err := platform.MunmapCodeSegment(sf.refFuncExecutable); err != nil { 725 panic(err) 726 } 727 if err := platform.MunmapCodeSegment(sf.memoryWait32Executable); err != nil { 728 panic(err) 729 } 730 if err := platform.MunmapCodeSegment(sf.memoryWait64Executable); err != nil { 731 panic(err) 732 } 733 if err := platform.MunmapCodeSegment(sf.memoryNotifyExecutable); err != nil { 734 panic(err) 735 } 736 for _, f := range sf.listenerBeforeTrampolines { 737 if err := platform.MunmapCodeSegment(f); err != nil { 738 panic(err) 739 } 740 } 741 for _, f := range sf.listenerAfterTrampolines { 742 if err := platform.MunmapCodeSegment(f); err != nil { 743 panic(err) 744 } 745 } 746 747 sf.memoryGrowExecutable = nil 748 sf.checkModuleExitCode = nil 749 sf.stackGrowExecutable = nil 750 sf.tableGrowExecutable = nil 751 sf.refFuncExecutable = nil 752 sf.memoryWait32Executable = nil 753 sf.memoryWait64Executable = nil 754 sf.memoryNotifyExecutable = nil 755 sf.listenerBeforeTrampolines = nil 756 sf.listenerAfterTrampolines = nil 757 } 758 759 func executablesFinalizer(exec *executables) { 760 if len(exec.executable) > 0 { 761 if err := platform.MunmapCodeSegment(exec.executable); err != nil { 762 panic(err) 763 } 764 } 765 exec.executable = nil 766 767 for _, f := range exec.entryPreambles { 768 if err := platform.MunmapCodeSegment(f); err != nil { 769 panic(err) 770 } 771 } 772 exec.entryPreambles = nil 773 } 774 775 func mmapExecutable(src []byte) []byte { 776 executable, err := platform.MmapCodeSegment(len(src)) 777 if err != nil { 778 panic(err) 779 } 780 781 copy(executable, src) 782 783 if runtime.GOARCH == "arm64" { 784 // On arm64, we cannot give all of rwx at the same time, so we change it to exec. 785 if err = platform.MprotectRX(executable); err != nil { 786 panic(err) 787 } 788 } 789 return executable 790 } 791 792 func (cm *compiledModule) functionIndexOf(addr uintptr) wasm.Index { 793 addr -= uintptr(unsafe.Pointer(&cm.executable[0])) 794 offset := cm.functionOffsets 795 index := sort.Search(len(offset), func(i int) bool { 796 return offset[i] > int(addr) 797 }) 798 index-- 799 if index < 0 { 800 panic("BUG") 801 } 802 return wasm.Index(index) 803 } 804 805 func (e *engine) getListenerTrampolineForType(functionType *wasm.FunctionType) (before, after *byte) { 806 e.mux.Lock() 807 defer e.mux.Unlock() 808 809 beforeBuf, ok := e.sharedFunctions.listenerBeforeTrampolines[functionType] 810 afterBuf := e.sharedFunctions.listenerAfterTrampolines[functionType] 811 if ok { 812 return &beforeBuf[0], &afterBuf[0] 813 } 814 815 beforeSig, afterSig := frontend.SignatureForListener(functionType) 816 817 e.be.Init() 818 buf := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCallListenerBefore, beforeSig, false) 819 beforeBuf = mmapExecutable(buf) 820 821 e.be.Init() 822 buf = e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCallListenerAfter, afterSig, false) 823 afterBuf = mmapExecutable(buf) 824 825 e.sharedFunctions.listenerBeforeTrampolines[functionType] = beforeBuf 826 e.sharedFunctions.listenerAfterTrampolines[functionType] = afterBuf 827 return &beforeBuf[0], &afterBuf[0] 828 } 829 830 func (cm *compiledModule) getSourceOffset(pc uintptr) uint64 { 831 offsets := cm.sourceMap.executableOffsets 832 if len(offsets) == 0 { 833 return 0 834 } 835 836 index := sort.Search(len(offsets), func(i int) bool { 837 return offsets[i] >= pc 838 }) 839 840 index-- 841 if index < 0 { 842 return 0 843 } 844 return cm.sourceMap.wasmBinaryOffsets[index] 845 }