package wazevo
import (
"context"
"encoding/hex"
"errors"
"fmt"
"runtime"
"slices"
"sort"
"sync"
"sync/atomic"
"unsafe"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
"github.com/tetratelabs/wazero/internal/engine/wazevo/frontend"
"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
"github.com/tetratelabs/wazero/internal/filecache"
"github.com/tetratelabs/wazero/internal/platform"
"github.com/tetratelabs/wazero/internal/version"
"github.com/tetratelabs/wazero/internal/wasm"
)
type (
compiledModuleWithCount struct {
*compiledModule
refCount int
}
engine struct {
wazeroVersion string
fileCache filecache .Cache
compiledModules map [wasm .ModuleID ]*compiledModuleWithCount
sortedCompiledModules []*compiledModule
mux sync .RWMutex
sharedFunctions *sharedFunctions
setFinalizer func (obj interface {}, finalizer interface {})
machine backend .Machine
be backend .Compiler
}
sharedFunctions struct {
executable []byte
memoryGrowAddress *byte
checkModuleExitCodeAddress *byte
stackGrowAddress *byte
tableGrowAddress *byte
refFuncAddress *byte
memoryWait32Address *byte
memoryWait64Address *byte
memoryNotifyAddress *byte
listenerTrampolines listenerTrampolines
}
listenerTrampolines = map [*wasm .FunctionType ]struct {
executable []byte
before *byte
after *byte
}
compiledModule struct {
*executables
functionOffsets []int
parent *engine
module *wasm .Module
ensureTermination bool
listeners []experimental .FunctionListener
listenerBeforeTrampolines []*byte
listenerAfterTrampolines []*byte
offsets wazevoapi .ModuleContextOffsetData
sharedFunctions *sharedFunctions
sourceMap sourceMap
}
executables struct {
executable []byte
entryPreambles []byte
entryPreamblesPtrs []*byte
}
)
type sourceMap struct {
executableOffsets []uintptr
wasmBinaryOffsets []uint64
}
var _ wasm .Engine = (*engine )(nil )
func NewEngine (ctx context .Context , _ api .CoreFeatures , fc filecache .Cache ) wasm .Engine {
machine := newMachine ()
be := backend .NewCompiler (ctx , machine , ssa .NewBuilder ())
e := &engine {
compiledModules : make (map [wasm .ModuleID ]*compiledModuleWithCount ),
setFinalizer : runtime .SetFinalizer ,
machine : machine ,
be : be ,
fileCache : fc ,
wazeroVersion : version .GetWazeroVersion (),
}
e .compileSharedFunctions ()
return e
}
func (e *engine ) CompileModule (ctx context .Context , module *wasm .Module , listeners []experimental .FunctionListener , ensureTermination bool ) (err error ) {
if wazevoapi .PerfMapEnabled {
wazevoapi .PerfMap .Lock ()
defer wazevoapi .PerfMap .Unlock ()
}
if _ , ok , err := e .getCompiledModule (module , listeners , ensureTermination ); ok {
return nil
} else if err != nil {
return err
}
if wazevoapi .DeterministicCompilationVerifierEnabled {
ctx = wazevoapi .NewDeterministicCompilationVerifierContext (ctx , len (module .CodeSection ))
}
cm , err := e .compileModule (ctx , module , listeners , ensureTermination )
if err != nil {
return err
}
if cm , err = e .addCompiledModule (module , cm ); err != nil {
return err
}
if wazevoapi .DeterministicCompilationVerifierEnabled {
for i := 0 ; i < wazevoapi .DeterministicCompilationVerifyingIter ; i ++ {
_ , err := e .compileModule (ctx , module , listeners , ensureTermination )
if err != nil {
return err
}
}
}
if len (listeners ) > 0 {
cm .listeners = listeners
cm .listenerBeforeTrampolines = make ([]*byte , len (module .TypeSection ))
cm .listenerAfterTrampolines = make ([]*byte , len (module .TypeSection ))
for i := range module .TypeSection {
typ := &module .TypeSection [i ]
before , after := e .getListenerTrampolineForType (typ )
cm .listenerBeforeTrampolines [i ] = before
cm .listenerAfterTrampolines [i ] = after
}
}
return nil
}
func (exec *executables ) compileEntryPreambles (m *wasm .Module , machine backend .Machine , be backend .Compiler ) {
if len (m .TypeSection ) == 0 {
return
}
var preambles []byte
sizes := make ([]int , len (m .TypeSection ))
for i := range sizes {
typ := &m .TypeSection [i ]
sig := frontend .SignatureForWasmFunctionType (typ )
be .Init ()
buf := machine .CompileEntryPreamble (&sig )
preambles = append (preambles , buf ...)
align := 15 & -len (preambles )
preambles = append (preambles , make ([]byte , align )...)
sizes [i ] = len (buf ) + align
}
exec .entryPreambles = mmapExecutable (preambles )
exec .entryPreamblesPtrs = make ([]*byte , len (sizes ))
offset := 0
for i , size := range sizes {
ptr := &exec .entryPreambles [offset ]
exec .entryPreamblesPtrs [i ] = ptr
offset += size
if wazevoapi .PerfMapEnabled {
typ := &m .TypeSection [i ]
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (ptr )),
uint64 (size ), fmt .Sprintf ("entry_preamble::type=%s" , typ .String ()))
}
}
}
func (e *engine ) compileModule (ctx context .Context , module *wasm .Module , listeners []experimental .FunctionListener , ensureTermination bool ) (*compiledModule , error ) {
if module .IsHostModule {
return e .compileHostModule (ctx , module , listeners )
}
withListener := len (listeners ) > 0
cm := &compiledModule {
offsets : wazevoapi .NewModuleContextOffsetData (module , withListener ), parent : e , module : module ,
ensureTermination : ensureTermination ,
executables : &executables {},
}
importedFns , localFns := int (module .ImportFunctionCount ), len (module .FunctionSection )
if localFns == 0 {
return cm , nil
}
machine := newMachine ()
relocator , err := newEngineRelocator (machine , importedFns , localFns )
if err != nil {
return nil , err
}
needSourceInfo := module .DWARFLines != nil
ssaBuilder := ssa .NewBuilder ()
be := backend .NewCompiler (ctx , machine , ssaBuilder )
cm .executables .compileEntryPreambles (module , machine , be )
cm .functionOffsets = make ([]int , localFns )
var indexes []int
if wazevoapi .DeterministicCompilationVerifierEnabled {
indexes = wazevoapi .DeterministicCompilationVerifierRandomizeIndexes (ctx )
}
if workers := experimental .GetCompilationWorkers (ctx ); workers <= 1 {
fe := frontend .NewFrontendCompiler (module , ssaBuilder , &cm .offsets , ensureTermination , withListener , needSourceInfo )
for i := range module .CodeSection {
if wazevoapi .DeterministicCompilationVerifierEnabled {
i = indexes [i ]
}
fidx := wasm .Index (i + importedFns )
fctx := functionContext (ctx , module , i , fidx )
needListener := len (listeners ) > i && listeners [i ] != nil
body , relsPerFunc , err := e .compileLocalWasmFunction (fctx , module , wasm .Index (i ), fe , ssaBuilder , be , needListener )
if err != nil {
return nil , fmt .Errorf ("compile function %d/%d: %v" , i , len (module .CodeSection )-1 , err )
}
relocator .appendFunction (fctx , module , cm , i , fidx , body , relsPerFunc , be .SourceOffsetInfo ())
}
} else {
type compiledFunc struct {
fctx context .Context
fnum int
fidx wasm .Index
body []byte
relsPerFunc []backend .RelocationInfo
offsPerFunc []backend .SourceOffsetInfo
}
compiledFuncs := make ([]compiledFunc , len (module .CodeSection ))
ctx , cancel := context .WithCancelCause (ctx )
defer cancel (nil )
var count atomic .Uint32
var wg sync .WaitGroup
wg .Add (workers )
for range workers {
go func () {
defer wg .Done ()
machine := newMachine ()
ssaBuilder := ssa .NewBuilder ()
be := backend .NewCompiler (ctx , machine , ssaBuilder )
fe := frontend .NewFrontendCompiler (module , ssaBuilder , &cm .offsets , ensureTermination , withListener , needSourceInfo )
for {
if err := ctx .Err (); err != nil {
return
}
i := int (count .Add (1 )) - 1
if i >= len (module .CodeSection ) {
return
}
if wazevoapi .DeterministicCompilationVerifierEnabled {
i = indexes [i ]
}
fidx := wasm .Index (i + importedFns )
fctx := functionContext (ctx , module , i , fidx )
needListener := len (listeners ) > i && listeners [i ] != nil
body , relsPerFunc , err := e .compileLocalWasmFunction (fctx , module , wasm .Index (i ), fe , ssaBuilder , be , needListener )
if err != nil {
cancel (fmt .Errorf ("compile function %d/%d: %v" , i , len (module .CodeSection )-1 , err ))
return
}
compiledFuncs [i ] = compiledFunc {
fctx , i , fidx , body ,
slices .Clone (relsPerFunc ),
slices .Clone (be .SourceOffsetInfo ()),
}
}
}()
}
wg .Wait ()
if err := context .Cause (ctx ); err != nil {
return nil , err
}
for i := range compiledFuncs {
fn := &compiledFuncs [i ]
relocator .appendFunction (fn .fctx , module , cm , fn .fnum , fn .fidx , fn .body , fn .relsPerFunc , fn .offsPerFunc )
}
}
executable , err := platform .MmapCodeSegment (relocator .totalSize )
if err != nil {
panic (err )
}
cm .executable = executable
for i , b := range relocator .bodies {
offset := cm .functionOffsets [i ]
copy (executable [offset :], b )
}
if wazevoapi .PerfMapEnabled {
wazevoapi .PerfMap .Flush (uintptr (unsafe .Pointer (&executable [0 ])), cm .functionOffsets )
}
if needSourceInfo {
for i := range cm .sourceMap .executableOffsets {
cm .sourceMap .executableOffsets [i ] += uintptr (unsafe .Pointer (&cm .executable [0 ]))
}
}
relocator .resolveRelocations (machine , executable , importedFns )
if err = platform .MprotectCodeSegment (executable ); err != nil {
return nil , err
}
cm .sharedFunctions = e .sharedFunctions
e .setFinalizer (cm .executables , executablesFinalizer )
return cm , nil
}
func functionContext(ctx context .Context , module *wasm .Module , fnum int , fidx wasm .Index ) context .Context {
if wazevoapi .NeedFunctionNameInContext {
def := module .FunctionDefinition (fidx )
name := def .DebugName ()
if len (def .ExportNames ()) > 0 {
name = def .ExportNames ()[0 ]
}
ctx = wazevoapi .SetCurrentFunctionName (ctx , fnum , fmt .Sprintf ("[%d/%d]%s" , fnum , len (module .CodeSection )-1 , name ))
}
return ctx
}
type engineRelocator struct {
bodies [][]byte
refToBinaryOffset []int
rels []backend .RelocationInfo
totalSize int
trampolineInterval int
callTrampolineIslandSize int
callTrampolineIslandOffsets []int
}
func newEngineRelocator(
machine backend .Machine ,
importedFns , localFns int ,
) (r engineRelocator , err error ) {
r .trampolineInterval , r .callTrampolineIslandSize , err = machine .CallTrampolineIslandInfo (localFns )
r .refToBinaryOffset = make ([]int , importedFns +localFns )
r .bodies = make ([][]byte , 0 , localFns )
return
}
func (r *engineRelocator ) resolveRelocations (machine backend .Machine , executable []byte , importedFns int ) {
if len (r .rels ) > 0 {
machine .ResolveRelocations (r .refToBinaryOffset , importedFns , executable , r .rels , r .callTrampolineIslandOffsets )
}
}
func (r *engineRelocator ) appendFunction (
ctx context .Context ,
module *wasm .Module ,
cm *compiledModule ,
fnum int , fidx wasm .Index ,
body []byte ,
relsPerFunc []backend .RelocationInfo ,
offsPerFunc []backend .SourceOffsetInfo ,
) {
r .totalSize = (r .totalSize + 15 ) &^ 15
cm .functionOffsets [fnum ] = r .totalSize
needSourceInfo := module .DWARFLines != nil
if needSourceInfo {
cm .sourceMap .executableOffsets = append (cm .sourceMap .executableOffsets , uintptr (r .totalSize ))
cm .sourceMap .wasmBinaryOffsets = append (cm .sourceMap .wasmBinaryOffsets , module .CodeSection [fnum ].BodyOffsetInCodeSection )
for _ , info := range offsPerFunc {
cm .sourceMap .executableOffsets = append (cm .sourceMap .executableOffsets , uintptr (r .totalSize )+uintptr (info .ExecutableOffset ))
cm .sourceMap .wasmBinaryOffsets = append (cm .sourceMap .wasmBinaryOffsets , uint64 (info .SourceOffset ))
}
}
fref := frontend .FunctionIndexToFuncRef (fidx )
r .refToBinaryOffset [fref ] = r .totalSize
r .rels = slices .Grow (r .rels , len (relsPerFunc ))
for _ , rel := range relsPerFunc {
rel .Offset += int64 (r .totalSize )
r .rels = append (r .rels , rel )
}
r .totalSize += len (body )
r .bodies = append (r .bodies , body )
if wazevoapi .PrintMachineCodeHexPerFunction {
fmt .Printf ("[[[machine code for %s]]]\n%s\n\n" , wazevoapi .GetCurrentFunctionName (ctx ), hex .EncodeToString (body ))
}
if r .callTrampolineIslandSize > 0 {
if r .totalSize /r .trampolineInterval > len (r .callTrampolineIslandOffsets ) {
r .callTrampolineIslandOffsets = append (r .callTrampolineIslandOffsets , r .totalSize )
r .totalSize += r .callTrampolineIslandSize
}
}
}
func (e *engine ) compileLocalWasmFunction (
ctx context .Context ,
module *wasm .Module ,
localFunctionIndex wasm .Index ,
fe *frontend .Compiler ,
ssaBuilder ssa .Builder ,
be backend .Compiler ,
needListener bool ,
) (body []byte , rels []backend .RelocationInfo , err error ) {
typIndex := module .FunctionSection [localFunctionIndex ]
typ := &module .TypeSection [typIndex ]
codeSeg := &module .CodeSection [localFunctionIndex ]
fe .Init (localFunctionIndex , typIndex , typ , codeSeg .LocalTypes , codeSeg .Body , needListener , codeSeg .BodyOffsetInCodeSection )
be .Init ()
fe .LowerToSSA ()
if wazevoapi .PrintSSA && wazevoapi .PrintEnabledIndex (ctx ) {
fmt .Printf ("[[[SSA for %s]]]%s\n" , wazevoapi .GetCurrentFunctionName (ctx ), ssaBuilder .Format ())
}
if wazevoapi .DeterministicCompilationVerifierEnabled {
wazevoapi .VerifyOrSetDeterministicCompilationContextValue (ctx , "SSA" , ssaBuilder .Format ())
}
ssaBuilder .RunPasses ()
if wazevoapi .PrintOptimizedSSA && wazevoapi .PrintEnabledIndex (ctx ) {
fmt .Printf ("[[[Optimized SSA for %s]]]%s\n" , wazevoapi .GetCurrentFunctionName (ctx ), ssaBuilder .Format ())
}
if wazevoapi .DeterministicCompilationVerifierEnabled {
wazevoapi .VerifyOrSetDeterministicCompilationContextValue (ctx , "Optimized SSA" , ssaBuilder .Format ())
}
original , rels , err := be .Compile (ctx )
if err != nil {
return nil , nil , fmt .Errorf ("ssa->machine code: %v" , err )
}
return slices .Clone (original ), rels , nil
}
func (e *engine ) compileHostModule (ctx context .Context , module *wasm .Module , listeners []experimental .FunctionListener ) (*compiledModule , error ) {
machine := newMachine ()
be := backend .NewCompiler (ctx , machine , ssa .NewBuilder ())
num := len (module .CodeSection )
cm := &compiledModule {module : module , listeners : listeners , executables : &executables {}}
cm .functionOffsets = make ([]int , num )
totalSize := 0
bodies := make ([][]byte , num )
var sig ssa .Signature
for i := range module .CodeSection {
totalSize = (totalSize + 15 ) &^ 15
cm .functionOffsets [i ] = totalSize
typIndex := module .FunctionSection [i ]
typ := &module .TypeSection [typIndex ]
const hostFunctionNumMaximum = 1 << 16
if i >= hostFunctionNumMaximum {
return nil , fmt .Errorf ("too many host functions (maximum %d)" , hostFunctionNumMaximum )
}
sig .ID = ssa .SignatureID (typIndex )
sig .Params = append (sig .Params [:0 ],
ssa .TypeI64 ,
ssa .TypeI64 ,
)
for _ , t := range typ .Params {
sig .Params = append (sig .Params , frontend .WasmTypeToSSAType (t ))
}
sig .Results = sig .Results [:0 ]
for _ , t := range typ .Results {
sig .Results = append (sig .Results , frontend .WasmTypeToSSAType (t ))
}
c := &module .CodeSection [i ]
if c .GoFunc == nil {
panic ("BUG: GoFunc must be set for host module" )
}
withListener := len (listeners ) > 0 && listeners [i ] != nil
var exitCode wazevoapi .ExitCode
fn := c .GoFunc
switch fn .(type ) {
case api .GoModuleFunction :
exitCode = wazevoapi .ExitCodeCallGoModuleFunctionWithIndex (i , withListener )
case api .GoFunction :
exitCode = wazevoapi .ExitCodeCallGoFunctionWithIndex (i , withListener )
}
be .Init ()
machine .CompileGoFunctionTrampoline (exitCode , &sig , true )
if err := be .Finalize (ctx ); err != nil {
return nil , err
}
body := be .Buf ()
if wazevoapi .PerfMapEnabled {
name := module .FunctionDefinition (wasm .Index (i )).DebugName ()
wazevoapi .PerfMap .AddModuleEntry (i ,
int64 (totalSize ),
uint64 (len (body )),
fmt .Sprintf ("trampoline:%s" , name ))
}
bodies [i ] = slices .Clone (body )
totalSize += len (body )
}
if totalSize == 0 {
return cm , nil
}
executable , err := platform .MmapCodeSegment (totalSize )
if err != nil {
panic (err )
}
cm .executable = executable
for i , b := range bodies {
offset := cm .functionOffsets [i ]
copy (executable [offset :], b )
}
if wazevoapi .PerfMapEnabled {
wazevoapi .PerfMap .Flush (uintptr (unsafe .Pointer (&executable [0 ])), cm .functionOffsets )
}
if err = platform .MprotectCodeSegment (executable ); err != nil {
return nil , err
}
e .setFinalizer (cm .executables , executablesFinalizer )
return cm , nil
}
func (e *engine ) Close () (err error ) {
e .mux .Lock ()
defer e .mux .Unlock ()
e .sortedCompiledModules = nil
e .compiledModules = nil
e .sharedFunctions = nil
return nil
}
func (e *engine ) CompiledModuleCount () uint32 {
e .mux .RLock ()
defer e .mux .RUnlock ()
return uint32 (len (e .compiledModules ))
}
func (e *engine ) DeleteCompiledModule (m *wasm .Module ) {
e .mux .Lock ()
defer e .mux .Unlock ()
cm , ok := e .compiledModules [m .ID ]
if !ok {
return
}
cm .refCount --
if cm .refCount > 0 {
return
}
if len (cm .executable ) > 0 {
e .deleteCompiledModuleFromSortedList (cm .compiledModule )
}
delete (e .compiledModules , m .ID )
}
func (e *engine ) addCompiledModuleToSortedList (cm *compiledModule ) {
ptr := uintptr (unsafe .Pointer (&cm .executable [0 ]))
index := sort .Search (len (e .sortedCompiledModules ), func (i int ) bool {
return uintptr (unsafe .Pointer (&e .sortedCompiledModules [i ].executable [0 ])) >= ptr
})
e .sortedCompiledModules = append (e .sortedCompiledModules , nil )
copy (e .sortedCompiledModules [index +1 :], e .sortedCompiledModules [index :])
e .sortedCompiledModules [index ] = cm
}
func (e *engine ) deleteCompiledModuleFromSortedList (cm *compiledModule ) {
ptr := uintptr (unsafe .Pointer (&cm .executable [0 ]))
index := sort .Search (len (e .sortedCompiledModules ), func (i int ) bool {
return uintptr (unsafe .Pointer (&e .sortedCompiledModules [i ].executable [0 ])) >= ptr
})
if index >= len (e .sortedCompiledModules ) {
return
}
copy (e .sortedCompiledModules [index :], e .sortedCompiledModules [index +1 :])
e .sortedCompiledModules = e .sortedCompiledModules [:len (e .sortedCompiledModules )-1 ]
}
func (e *engine ) compiledModuleOfAddr (addr uintptr ) *compiledModule {
e .mux .RLock ()
defer e .mux .RUnlock ()
index := sort .Search (len (e .sortedCompiledModules ), func (i int ) bool {
return uintptr (unsafe .Pointer (&e .sortedCompiledModules [i ].executable [0 ])) > addr
})
index -= 1
if index < 0 {
return nil
}
candidate := e .sortedCompiledModules [index ]
if checkAddrInBytes (addr , candidate .executable ) {
return candidate
}
return nil
}
func checkAddrInBytes(addr uintptr , b []byte ) bool {
return uintptr (unsafe .Pointer (&b [0 ])) <= addr && addr <= uintptr (unsafe .Pointer (&b [len (b )-1 ]))
}
func (e *engine ) NewModuleEngine (m *wasm .Module , mi *wasm .ModuleInstance ) (wasm .ModuleEngine , error ) {
me := &moduleEngine {}
me .importedFunctions = make ([]importedFunction , m .ImportFunctionCount )
compiled , ok := e .getCompiledModuleFromMemory (m , false )
if !ok {
return nil , errors .New ("source module must be compiled before instantiation" )
}
me .parent = compiled
me .module = mi
me .listeners = compiled .listeners
if m .IsHostModule {
me .opaque = buildHostModuleOpaque (m , compiled .listeners )
me .opaquePtr = &me .opaque [0 ]
} else {
if size := compiled .offsets .TotalSize ; size != 0 {
opaque := newAlignedOpaque (size )
me .opaque = opaque
me .opaquePtr = &opaque [0 ]
}
}
return me , nil
}
func (e *engine ) compileSharedFunctions () {
var sizes [8 ]int
var trampolines []byte
addTrampoline := func (i int , buf []byte ) {
trampolines = append (trampolines , buf ...)
align := 15 & -len (trampolines )
trampolines = append (trampolines , make ([]byte , align )...)
sizes [i ] = len (buf ) + align
}
e .be .Init ()
addTrampoline (0 ,
e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeGrowMemory , &ssa .Signature {
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI32 },
Results : []ssa .Type {ssa .TypeI32 },
}, false ))
e .be .Init ()
addTrampoline (1 ,
e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeTableGrow , &ssa .Signature {
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI32 , ssa .TypeI32 , ssa .TypeI64 },
Results : []ssa .Type {ssa .TypeI32 },
}, false ))
e .be .Init ()
addTrampoline (2 ,
e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeCheckModuleExitCode , &ssa .Signature {
Params : []ssa .Type {ssa .TypeI32 },
Results : []ssa .Type {ssa .TypeI32 },
}, false ))
e .be .Init ()
addTrampoline (3 ,
e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeRefFunc , &ssa .Signature {
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI32 },
Results : []ssa .Type {ssa .TypeI64 },
}, false ))
e .be .Init ()
addTrampoline (4 , e .machine .CompileStackGrowCallSequence ())
e .be .Init ()
addTrampoline (5 ,
e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeMemoryWait32 , &ssa .Signature {
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI64 , ssa .TypeI32 , ssa .TypeI64 },
Results : []ssa .Type {ssa .TypeI32 },
}, false ))
e .be .Init ()
addTrampoline (6 ,
e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeMemoryWait64 , &ssa .Signature {
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI64 , ssa .TypeI64 , ssa .TypeI64 },
Results : []ssa .Type {ssa .TypeI32 },
}, false ))
e .be .Init ()
addTrampoline (7 ,
e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeMemoryNotify , &ssa .Signature {
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI32 , ssa .TypeI64 },
Results : []ssa .Type {ssa .TypeI32 },
}, false ))
fns := &sharedFunctions {
executable : mmapExecutable (trampolines ),
listenerTrampolines : make (listenerTrampolines ),
}
e .setFinalizer (fns , sharedFunctionsFinalizer )
offset := 0
fns .memoryGrowAddress = &fns .executable [offset ]
offset += sizes [0 ]
fns .tableGrowAddress = &fns .executable [offset ]
offset += sizes [1 ]
fns .checkModuleExitCodeAddress = &fns .executable [offset ]
offset += sizes [2 ]
fns .refFuncAddress = &fns .executable [offset ]
offset += sizes [3 ]
fns .stackGrowAddress = &fns .executable [offset ]
offset += sizes [4 ]
fns .memoryWait32Address = &fns .executable [offset ]
offset += sizes [5 ]
fns .memoryWait64Address = &fns .executable [offset ]
offset += sizes [6 ]
fns .memoryNotifyAddress = &fns .executable [offset ]
if wazevoapi .PerfMapEnabled {
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (fns .memoryGrowAddress )), uint64 (sizes [0 ]), "memory_grow_trampoline" )
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (fns .tableGrowAddress )), uint64 (sizes [1 ]), "table_grow_trampoline" )
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (fns .checkModuleExitCodeAddress )), uint64 (sizes [2 ]), "check_module_exit_code_trampoline" )
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (fns .refFuncAddress )), uint64 (sizes [3 ]), "ref_func_trampoline" )
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (fns .stackGrowAddress )), uint64 (sizes [4 ]), "stack_grow_trampoline" )
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (fns .memoryWait32Address )), uint64 (sizes [5 ]), "memory_wait32_trampoline" )
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (fns .memoryWait64Address )), uint64 (sizes [6 ]), "memory_wait64_trampoline" )
wazevoapi .PerfMap .AddEntry (uintptr (unsafe .Pointer (fns .memoryNotifyAddress )), uint64 (sizes [7 ]), "memory_notify_trampoline" )
}
e .sharedFunctions = fns
}
func sharedFunctionsFinalizer(sf *sharedFunctions ) {
if err := platform .MunmapCodeSegment (sf .executable ); err != nil {
panic (err )
}
for _ , f := range sf .listenerTrampolines {
if err := platform .MunmapCodeSegment (f .executable ); err != nil {
panic (err )
}
}
sf .executable = nil
sf .listenerTrampolines = nil
}
func executablesFinalizer(exec *executables ) {
if len (exec .executable ) > 0 {
if err := platform .MunmapCodeSegment (exec .executable ); err != nil {
panic (err )
}
}
exec .executable = nil
if len (exec .entryPreambles ) > 0 {
if err := platform .MunmapCodeSegment (exec .entryPreambles ); err != nil {
panic (err )
}
}
exec .entryPreambles = nil
exec .entryPreamblesPtrs = nil
}
func mmapExecutable(src []byte ) []byte {
executable , err := platform .MmapCodeSegment (len (src ))
if err != nil {
panic (err )
}
copy (executable , src )
if err = platform .MprotectCodeSegment (executable ); err != nil {
panic (err )
}
return executable
}
func (cm *compiledModule ) functionIndexOf (addr uintptr ) wasm .Index {
addr -= uintptr (unsafe .Pointer (&cm .executable [0 ]))
offset := cm .functionOffsets
index := sort .Search (len (offset ), func (i int ) bool {
return offset [i ] > int (addr )
})
index --
if index < 0 {
panic ("BUG" )
}
return wasm .Index (index )
}
func (e *engine ) getListenerTrampolineForType (functionType *wasm .FunctionType ) (before , after *byte ) {
e .mux .Lock ()
defer e .mux .Unlock ()
trampoline , ok := e .sharedFunctions .listenerTrampolines [functionType ]
if !ok {
var executable []byte
beforeSig , afterSig := frontend .SignatureForListener (functionType )
e .be .Init ()
buf := e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeCallListenerBefore , beforeSig , false )
executable = append (executable , buf ...)
align := 15 & -len (executable )
executable = append (executable , make ([]byte , align )...)
offset := len (executable )
e .be .Init ()
buf = e .machine .CompileGoFunctionTrampoline (wazevoapi .ExitCodeCallListenerAfter , afterSig , false )
executable = append (executable , buf ...)
trampoline .executable = mmapExecutable (executable )
trampoline .before = &trampoline .executable [0 ]
trampoline .after = &trampoline .executable [offset ]
e .sharedFunctions .listenerTrampolines [functionType ] = trampoline
}
return trampoline .before , trampoline .after
}
func (cm *compiledModule ) getSourceOffset (pc uintptr ) uint64 {
offsets := cm .sourceMap .executableOffsets
if len (offsets ) == 0 {
return 0
}
index := sort .Search (len (offsets ), func (i int ) bool {
return offsets [i ] >= pc
})
index --
if index < 0 {
return 0
}
return cm .sourceMap .wasmBinaryOffsets [index ]
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .