diff options
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go')
-rw-r--r-- | vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go | 567 |
1 files changed, 0 insertions, 567 deletions
diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go deleted file mode 100644 index eebdba034..000000000 --- a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go +++ /dev/null @@ -1,567 +0,0 @@ -// Package frontend implements the translation of WebAssembly to SSA IR using the ssa package. -package frontend - -import ( - "bytes" - "math" - - "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" - "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" - "github.com/tetratelabs/wazero/internal/wasm" -) - -// Compiler is in charge of lowering Wasm to SSA IR, and does the optimization -// on top of it in architecture-independent way. -type Compiler struct { - // Per-module data that is used across all functions. - - m *wasm.Module - offset *wazevoapi.ModuleContextOffsetData - // ssaBuilder is a ssa.Builder used by this frontend. - ssaBuilder ssa.Builder - signatures map[*wasm.FunctionType]*ssa.Signature - listenerSignatures map[*wasm.FunctionType][2]*ssa.Signature - memoryGrowSig ssa.Signature - memoryWait32Sig ssa.Signature - memoryWait64Sig ssa.Signature - memoryNotifySig ssa.Signature - checkModuleExitCodeSig ssa.Signature - tableGrowSig ssa.Signature - refFuncSig ssa.Signature - memmoveSig ssa.Signature - ensureTermination bool - - // Followings are reset by per function. - - // wasmLocalToVariable maps the index (considered as wasm.Index of locals) - // to the corresponding ssa.Variable. - wasmLocalToVariable [] /* local index to */ ssa.Variable - wasmLocalFunctionIndex wasm.Index - wasmFunctionTypeIndex wasm.Index - wasmFunctionTyp *wasm.FunctionType - wasmFunctionLocalTypes []wasm.ValueType - wasmFunctionBody []byte - wasmFunctionBodyOffsetInCodeSection uint64 - memoryBaseVariable, memoryLenVariable ssa.Variable - needMemory bool - memoryShared bool - globalVariables []ssa.Variable - globalVariablesTypes []ssa.Type - mutableGlobalVariablesIndexes []wasm.Index // index to ^. - needListener bool - needSourceOffsetInfo bool - // br is reused during lowering. - br *bytes.Reader - loweringState loweringState - - knownSafeBounds [] /* ssa.ValueID to */ knownSafeBound - knownSafeBoundsSet []ssa.ValueID - - knownSafeBoundsAtTheEndOfBlocks [] /* ssa.BlockID to */ knownSafeBoundsAtTheEndOfBlock - varLengthKnownSafeBoundWithIDPool wazevoapi.VarLengthPool[knownSafeBoundWithID] - - execCtxPtrValue, moduleCtxPtrValue ssa.Value - - // Following are reused for the known safe bounds analysis. - - pointers []int - bounds [][]knownSafeBoundWithID -} - -type ( - // knownSafeBound represents a known safe bound for a value. - knownSafeBound struct { - // bound is a constant upper bound for the value. - bound uint64 - // absoluteAddr is the absolute address of the value. - absoluteAddr ssa.Value - } - // knownSafeBoundWithID is a knownSafeBound with the ID of the value. - knownSafeBoundWithID struct { - knownSafeBound - id ssa.ValueID - } - knownSafeBoundsAtTheEndOfBlock = wazevoapi.VarLength[knownSafeBoundWithID] -) - -var knownSafeBoundsAtTheEndOfBlockNil = wazevoapi.NewNilVarLength[knownSafeBoundWithID]() - -// NewFrontendCompiler returns a frontend Compiler. -func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoapi.ModuleContextOffsetData, ensureTermination bool, listenerOn bool, sourceInfo bool) *Compiler { - c := &Compiler{ - m: m, - ssaBuilder: ssaBuilder, - br: bytes.NewReader(nil), - offset: offset, - ensureTermination: ensureTermination, - needSourceOffsetInfo: sourceInfo, - varLengthKnownSafeBoundWithIDPool: wazevoapi.NewVarLengthPool[knownSafeBoundWithID](), - } - c.declareSignatures(listenerOn) - return c -} - -func (c *Compiler) declareSignatures(listenerOn bool) { - m := c.m - c.signatures = make(map[*wasm.FunctionType]*ssa.Signature, len(m.TypeSection)+2) - if listenerOn { - c.listenerSignatures = make(map[*wasm.FunctionType][2]*ssa.Signature, len(m.TypeSection)) - } - for i := range m.TypeSection { - wasmSig := &m.TypeSection[i] - sig := SignatureForWasmFunctionType(wasmSig) - sig.ID = ssa.SignatureID(i) - c.signatures[wasmSig] = &sig - c.ssaBuilder.DeclareSignature(&sig) - - if listenerOn { - beforeSig, afterSig := SignatureForListener(wasmSig) - beforeSig.ID = ssa.SignatureID(i) + ssa.SignatureID(len(m.TypeSection)) - afterSig.ID = ssa.SignatureID(i) + ssa.SignatureID(len(m.TypeSection))*2 - c.listenerSignatures[wasmSig] = [2]*ssa.Signature{beforeSig, afterSig} - c.ssaBuilder.DeclareSignature(beforeSig) - c.ssaBuilder.DeclareSignature(afterSig) - } - } - - begin := ssa.SignatureID(len(m.TypeSection)) - if listenerOn { - begin *= 3 - } - c.memoryGrowSig = ssa.Signature{ - ID: begin, - // Takes execution context and the page size to grow. - Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32}, - // Returns the previous page size. - Results: []ssa.Type{ssa.TypeI32}, - } - c.ssaBuilder.DeclareSignature(&c.memoryGrowSig) - - c.checkModuleExitCodeSig = ssa.Signature{ - ID: c.memoryGrowSig.ID + 1, - // Only takes execution context. - Params: []ssa.Type{ssa.TypeI64}, - } - c.ssaBuilder.DeclareSignature(&c.checkModuleExitCodeSig) - - c.tableGrowSig = ssa.Signature{ - ID: c.checkModuleExitCodeSig.ID + 1, - Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* table index */, ssa.TypeI32 /* num */, ssa.TypeI64 /* ref */}, - // Returns the previous size. - Results: []ssa.Type{ssa.TypeI32}, - } - c.ssaBuilder.DeclareSignature(&c.tableGrowSig) - - c.refFuncSig = ssa.Signature{ - ID: c.tableGrowSig.ID + 1, - Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* func index */}, - // Returns the function reference. - Results: []ssa.Type{ssa.TypeI64}, - } - c.ssaBuilder.DeclareSignature(&c.refFuncSig) - - c.memmoveSig = ssa.Signature{ - ID: c.refFuncSig.ID + 1, - // dst, src, and the byte count. - Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64}, - } - - c.ssaBuilder.DeclareSignature(&c.memmoveSig) - - c.memoryWait32Sig = ssa.Signature{ - ID: c.memmoveSig.ID + 1, - // exec context, timeout, expected, addr - Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, - // Returns the status. - Results: []ssa.Type{ssa.TypeI32}, - } - c.ssaBuilder.DeclareSignature(&c.memoryWait32Sig) - - c.memoryWait64Sig = ssa.Signature{ - ID: c.memoryWait32Sig.ID + 1, - // exec context, timeout, expected, addr - Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64, ssa.TypeI64}, - // Returns the status. - Results: []ssa.Type{ssa.TypeI32}, - } - c.ssaBuilder.DeclareSignature(&c.memoryWait64Sig) - - c.memoryNotifySig = ssa.Signature{ - ID: c.memoryWait64Sig.ID + 1, - // exec context, count, addr - Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, - // Returns the number notified. - Results: []ssa.Type{ssa.TypeI32}, - } - c.ssaBuilder.DeclareSignature(&c.memoryNotifySig) -} - -// SignatureForWasmFunctionType returns the ssa.Signature for the given wasm.FunctionType. -func SignatureForWasmFunctionType(typ *wasm.FunctionType) ssa.Signature { - sig := ssa.Signature{ - // +2 to pass moduleContextPtr and executionContextPtr. See the inline comment LowerToSSA. - Params: make([]ssa.Type, len(typ.Params)+2), - Results: make([]ssa.Type, len(typ.Results)), - } - sig.Params[0] = executionContextPtrTyp - sig.Params[1] = moduleContextPtrTyp - for j, typ := range typ.Params { - sig.Params[j+2] = WasmTypeToSSAType(typ) - } - for j, typ := range typ.Results { - sig.Results[j] = WasmTypeToSSAType(typ) - } - return sig -} - -// Init initializes the state of frontendCompiler and make it ready for a next function. -func (c *Compiler) Init(idx, typIndex wasm.Index, typ *wasm.FunctionType, localTypes []wasm.ValueType, body []byte, needListener bool, bodyOffsetInCodeSection uint64) { - c.ssaBuilder.Init(c.signatures[typ]) - c.loweringState.reset() - - c.wasmFunctionTypeIndex = typIndex - c.wasmLocalFunctionIndex = idx - c.wasmFunctionTyp = typ - c.wasmFunctionLocalTypes = localTypes - c.wasmFunctionBody = body - c.wasmFunctionBodyOffsetInCodeSection = bodyOffsetInCodeSection - c.needListener = needListener - c.clearSafeBounds() - c.varLengthKnownSafeBoundWithIDPool.Reset() - c.knownSafeBoundsAtTheEndOfBlocks = c.knownSafeBoundsAtTheEndOfBlocks[:0] -} - -// Note: this assumes 64-bit platform (I believe we won't have 32-bit backend ;)). -const executionContextPtrTyp, moduleContextPtrTyp = ssa.TypeI64, ssa.TypeI64 - -// LowerToSSA lowers the current function to SSA function which will be held by ssaBuilder. -// After calling this, the caller will be able to access the SSA info in *Compiler.ssaBuilder. -// -// Note that this only does the naive lowering, and do not do any optimization, instead the caller is expected to do so. -func (c *Compiler) LowerToSSA() { - builder := c.ssaBuilder - - // Set up the entry block. - entryBlock := builder.AllocateBasicBlock() - builder.SetCurrentBlock(entryBlock) - - // Functions always take two parameters in addition to Wasm-level parameters: - // - // 1. executionContextPtr: pointer to the *executionContext in wazevo package. - // This will be used to exit the execution in the face of trap, plus used for host function calls. - // - // 2. moduleContextPtr: pointer to the *moduleContextOpaque in wazevo package. - // This will be used to access memory, etc. Also, this will be used during host function calls. - // - // Note: it's clear that sometimes a function won't need them. For example, - // if the function doesn't trap and doesn't make function call, then - // we might be able to eliminate the parameter. However, if that function - // can be called via call_indirect, then we cannot eliminate because the - // signature won't match with the expected one. - // TODO: maybe there's some way to do this optimization without glitches, but so far I have no clue about the feasibility. - // - // Note: In Wasmtime or many other runtimes, moduleContextPtr is called "vmContext". Also note that `moduleContextPtr` - // is wazero-specific since other runtimes can naturally use the OS-level signal to do this job thanks to the fact that - // they can use native stack vs wazero cannot use Go-routine stack and have to use Go-runtime allocated []byte as a stack. - c.execCtxPtrValue = entryBlock.AddParam(builder, executionContextPtrTyp) - c.moduleCtxPtrValue = entryBlock.AddParam(builder, moduleContextPtrTyp) - builder.AnnotateValue(c.execCtxPtrValue, "exec_ctx") - builder.AnnotateValue(c.moduleCtxPtrValue, "module_ctx") - - for i, typ := range c.wasmFunctionTyp.Params { - st := WasmTypeToSSAType(typ) - variable := builder.DeclareVariable(st) - value := entryBlock.AddParam(builder, st) - builder.DefineVariable(variable, value, entryBlock) - c.setWasmLocalVariable(wasm.Index(i), variable) - } - c.declareWasmLocals() - c.declareNecessaryVariables() - - c.lowerBody(entryBlock) -} - -// localVariable returns the SSA variable for the given Wasm local index. -func (c *Compiler) localVariable(index wasm.Index) ssa.Variable { - return c.wasmLocalToVariable[index] -} - -func (c *Compiler) setWasmLocalVariable(index wasm.Index, variable ssa.Variable) { - idx := int(index) - if idx >= len(c.wasmLocalToVariable) { - c.wasmLocalToVariable = append(c.wasmLocalToVariable, make([]ssa.Variable, idx+1-len(c.wasmLocalToVariable))...) - } - c.wasmLocalToVariable[idx] = variable -} - -// declareWasmLocals declares the SSA variables for the Wasm locals. -func (c *Compiler) declareWasmLocals() { - localCount := wasm.Index(len(c.wasmFunctionTyp.Params)) - for i, typ := range c.wasmFunctionLocalTypes { - st := WasmTypeToSSAType(typ) - variable := c.ssaBuilder.DeclareVariable(st) - c.setWasmLocalVariable(wasm.Index(i)+localCount, variable) - c.ssaBuilder.InsertZeroValue(st) - } -} - -func (c *Compiler) declareNecessaryVariables() { - if c.needMemory = c.m.MemorySection != nil; c.needMemory { - c.memoryShared = c.m.MemorySection.IsShared - } else if c.needMemory = c.m.ImportMemoryCount > 0; c.needMemory { - for _, imp := range c.m.ImportSection { - if imp.Type == wasm.ExternTypeMemory { - c.memoryShared = imp.DescMem.IsShared - break - } - } - } - - if c.needMemory { - c.memoryBaseVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64) - c.memoryLenVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64) - } - - c.globalVariables = c.globalVariables[:0] - c.mutableGlobalVariablesIndexes = c.mutableGlobalVariablesIndexes[:0] - c.globalVariablesTypes = c.globalVariablesTypes[:0] - for _, imp := range c.m.ImportSection { - if imp.Type == wasm.ExternTypeGlobal { - desc := imp.DescGlobal - c.declareWasmGlobal(desc.ValType, desc.Mutable) - } - } - for _, g := range c.m.GlobalSection { - desc := g.Type - c.declareWasmGlobal(desc.ValType, desc.Mutable) - } - - // TODO: add tables. -} - -func (c *Compiler) declareWasmGlobal(typ wasm.ValueType, mutable bool) { - var st ssa.Type - switch typ { - case wasm.ValueTypeI32: - st = ssa.TypeI32 - case wasm.ValueTypeI64, - // Both externref and funcref are represented as I64 since we only support 64-bit platforms. - wasm.ValueTypeExternref, wasm.ValueTypeFuncref: - st = ssa.TypeI64 - case wasm.ValueTypeF32: - st = ssa.TypeF32 - case wasm.ValueTypeF64: - st = ssa.TypeF64 - case wasm.ValueTypeV128: - st = ssa.TypeV128 - default: - panic("TODO: " + wasm.ValueTypeName(typ)) - } - v := c.ssaBuilder.DeclareVariable(st) - index := wasm.Index(len(c.globalVariables)) - c.globalVariables = append(c.globalVariables, v) - c.globalVariablesTypes = append(c.globalVariablesTypes, st) - if mutable { - c.mutableGlobalVariablesIndexes = append(c.mutableGlobalVariablesIndexes, index) - } -} - -// WasmTypeToSSAType converts wasm.ValueType to ssa.Type. -func WasmTypeToSSAType(vt wasm.ValueType) ssa.Type { - switch vt { - case wasm.ValueTypeI32: - return ssa.TypeI32 - case wasm.ValueTypeI64, - // Both externref and funcref are represented as I64 since we only support 64-bit platforms. - wasm.ValueTypeExternref, wasm.ValueTypeFuncref: - return ssa.TypeI64 - case wasm.ValueTypeF32: - return ssa.TypeF32 - case wasm.ValueTypeF64: - return ssa.TypeF64 - case wasm.ValueTypeV128: - return ssa.TypeV128 - default: - panic("TODO: " + wasm.ValueTypeName(vt)) - } -} - -// addBlockParamsFromWasmTypes adds the block parameters to the given block. -func (c *Compiler) addBlockParamsFromWasmTypes(tps []wasm.ValueType, blk ssa.BasicBlock) { - for _, typ := range tps { - st := WasmTypeToSSAType(typ) - blk.AddParam(c.ssaBuilder, st) - } -} - -// formatBuilder outputs the constructed SSA function as a string with a source information. -func (c *Compiler) formatBuilder() string { - return c.ssaBuilder.Format() -} - -// SignatureForListener returns the signatures for the listener functions. -func SignatureForListener(wasmSig *wasm.FunctionType) (*ssa.Signature, *ssa.Signature) { - beforeSig := &ssa.Signature{} - beforeSig.Params = make([]ssa.Type, len(wasmSig.Params)+2) - beforeSig.Params[0] = ssa.TypeI64 // Execution context. - beforeSig.Params[1] = ssa.TypeI32 // Function index. - for i, p := range wasmSig.Params { - beforeSig.Params[i+2] = WasmTypeToSSAType(p) - } - afterSig := &ssa.Signature{} - afterSig.Params = make([]ssa.Type, len(wasmSig.Results)+2) - afterSig.Params[0] = ssa.TypeI64 // Execution context. - afterSig.Params[1] = ssa.TypeI32 // Function index. - for i, p := range wasmSig.Results { - afterSig.Params[i+2] = WasmTypeToSSAType(p) - } - return beforeSig, afterSig -} - -// isBoundSafe returns true if the given value is known to be safe to access up to the given bound. -func (c *Compiler) getKnownSafeBound(v ssa.ValueID) *knownSafeBound { - if int(v) >= len(c.knownSafeBounds) { - return nil - } - return &c.knownSafeBounds[v] -} - -// recordKnownSafeBound records the given safe bound for the given value. -func (c *Compiler) recordKnownSafeBound(v ssa.ValueID, safeBound uint64, absoluteAddr ssa.Value) { - if int(v) >= len(c.knownSafeBounds) { - c.knownSafeBounds = append(c.knownSafeBounds, make([]knownSafeBound, v+1)...) - } - - if exiting := c.knownSafeBounds[v]; exiting.bound == 0 { - c.knownSafeBounds[v] = knownSafeBound{ - bound: safeBound, - absoluteAddr: absoluteAddr, - } - c.knownSafeBoundsSet = append(c.knownSafeBoundsSet, v) - } else if safeBound > exiting.bound { - c.knownSafeBounds[v].bound = safeBound - } -} - -// clearSafeBounds clears the known safe bounds. -func (c *Compiler) clearSafeBounds() { - for _, v := range c.knownSafeBoundsSet { - ptr := &c.knownSafeBounds[v] - ptr.bound = 0 - ptr.absoluteAddr = ssa.ValueInvalid - } - c.knownSafeBoundsSet = c.knownSafeBoundsSet[:0] -} - -// resetAbsoluteAddressInSafeBounds resets the absolute addresses recorded in the known safe bounds. -func (c *Compiler) resetAbsoluteAddressInSafeBounds() { - for _, v := range c.knownSafeBoundsSet { - ptr := &c.knownSafeBounds[v] - ptr.absoluteAddr = ssa.ValueInvalid - } -} - -func (k *knownSafeBound) valid() bool { - return k != nil && k.bound > 0 -} - -func (c *Compiler) allocateVarLengthValues(_cap int, vs ...ssa.Value) ssa.Values { - builder := c.ssaBuilder - pool := builder.VarLengthPool() - args := pool.Allocate(_cap) - args = args.Append(builder.VarLengthPool(), vs...) - return args -} - -func (c *Compiler) finalizeKnownSafeBoundsAtTheEndOfBlock(bID ssa.BasicBlockID) { - _bID := int(bID) - if l := len(c.knownSafeBoundsAtTheEndOfBlocks); _bID >= l { - c.knownSafeBoundsAtTheEndOfBlocks = append(c.knownSafeBoundsAtTheEndOfBlocks, - make([]knownSafeBoundsAtTheEndOfBlock, _bID+1-len(c.knownSafeBoundsAtTheEndOfBlocks))...) - for i := l; i < len(c.knownSafeBoundsAtTheEndOfBlocks); i++ { - c.knownSafeBoundsAtTheEndOfBlocks[i] = knownSafeBoundsAtTheEndOfBlockNil - } - } - p := &c.varLengthKnownSafeBoundWithIDPool - size := len(c.knownSafeBoundsSet) - allocated := c.varLengthKnownSafeBoundWithIDPool.Allocate(size) - // Sort the known safe bounds by the value ID so that we can use the intersection algorithm in initializeCurrentBlockKnownBounds. - sortSSAValueIDs(c.knownSafeBoundsSet) - for _, vID := range c.knownSafeBoundsSet { - kb := c.knownSafeBounds[vID] - allocated = allocated.Append(p, knownSafeBoundWithID{ - knownSafeBound: kb, - id: vID, - }) - } - c.knownSafeBoundsAtTheEndOfBlocks[bID] = allocated - c.clearSafeBounds() -} - -func (c *Compiler) initializeCurrentBlockKnownBounds() { - currentBlk := c.ssaBuilder.CurrentBlock() - switch preds := currentBlk.Preds(); preds { - case 0: - case 1: - pred := currentBlk.Pred(0).ID() - for _, kb := range c.getKnownSafeBoundsAtTheEndOfBlocks(pred).View() { - // Unless the block is sealed, we cannot assume the absolute address is valid: - // later we might add another predecessor that has no visibility of that value. - addr := ssa.ValueInvalid - if currentBlk.Sealed() { - addr = kb.absoluteAddr - } - c.recordKnownSafeBound(kb.id, kb.bound, addr) - } - default: - c.pointers = c.pointers[:0] - c.bounds = c.bounds[:0] - for i := 0; i < preds; i++ { - c.bounds = append(c.bounds, c.getKnownSafeBoundsAtTheEndOfBlocks(currentBlk.Pred(i).ID()).View()) - c.pointers = append(c.pointers, 0) - } - - // If there are multiple predecessors, we need to find the intersection of the known safe bounds. - - outer: - for { - smallestID := ssa.ValueID(math.MaxUint32) - for i, ptr := range c.pointers { - if ptr >= len(c.bounds[i]) { - break outer - } - cb := &c.bounds[i][ptr] - if id := cb.id; id < smallestID { - smallestID = cb.id - } - } - - // Check if current elements are the same across all lists. - same := true - minBound := uint64(math.MaxUint64) - for i := 0; i < preds; i++ { - cb := &c.bounds[i][c.pointers[i]] - if cb.id != smallestID { - same = false - } else { - if cb.bound < minBound { - minBound = cb.bound - } - c.pointers[i]++ - } - } - - if same { // All elements are the same. - // Absolute address cannot be used in the intersection since the value might be only defined in one of the predecessors. - c.recordKnownSafeBound(smallestID, minBound, ssa.ValueInvalid) - } - } - } -} - -func (c *Compiler) getKnownSafeBoundsAtTheEndOfBlocks(id ssa.BasicBlockID) knownSafeBoundsAtTheEndOfBlock { - if int(id) >= len(c.knownSafeBoundsAtTheEndOfBlocks) { - return knownSafeBoundsAtTheEndOfBlockNil - } - return c.knownSafeBoundsAtTheEndOfBlocks[id] -} |