summaryrefslogtreecommitdiff
path: root/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go
diff options
context:
space:
mode:
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.go594
1 files changed, 594 insertions, 0 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
new file mode 100644
index 000000000..873a35a55
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go
@@ -0,0 +1,594 @@
+// 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(entryBlock)
+ 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(entry ssa.BasicBlock) {
+ 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)
+
+ zeroInst := c.ssaBuilder.AllocateInstruction()
+ switch st {
+ case ssa.TypeI32:
+ zeroInst.AsIconst32(0)
+ case ssa.TypeI64:
+ zeroInst.AsIconst64(0)
+ case ssa.TypeF32:
+ zeroInst.AsF32const(0)
+ case ssa.TypeF64:
+ zeroInst.AsF64const(0)
+ case ssa.TypeV128:
+ zeroInst.AsVconst(0, 0)
+ default:
+ panic("TODO: " + wasm.ValueTypeName(typ))
+ }
+
+ c.ssaBuilder.InsertInstruction(zeroInst)
+ value := zeroInst.Return()
+ c.ssaBuilder.DefineVariable(variable, value, entry)
+ }
+}
+
+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
+ break
+ } else {
+ if cb.bound < minBound {
+ minBound = cb.bound
+ }
+ }
+ }
+
+ 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)
+ }
+
+ // Move pointer(s) for the smallest ID forward (if same, move all).
+ for i := 0; i < preds; i++ {
+ cb := &c.bounds[i][c.pointers[i]]
+ if cb.id == smallestID {
+ c.pointers[i]++
+ }
+ }
+ }
+ }
+}
+
+func (c *Compiler) getKnownSafeBoundsAtTheEndOfBlocks(id ssa.BasicBlockID) knownSafeBoundsAtTheEndOfBlock {
+ if int(id) >= len(c.knownSafeBoundsAtTheEndOfBlocks) {
+ return knownSafeBoundsAtTheEndOfBlockNil
+ }
+ return c.knownSafeBoundsAtTheEndOfBlocks[id]
+}