mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
This commit is contained in:
407
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block.go
generated
vendored
Normal file
407
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block.go
generated
vendored
Normal file
@ -0,0 +1,407 @@
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
||||
)
|
||||
|
||||
// BasicBlock represents the Basic Block of an SSA function.
|
||||
// Each BasicBlock always ends with branching instructions (e.g. Branch, Return, etc.),
|
||||
// and at most two branches are allowed. If there's two branches, these two are placed together at the end of the block.
|
||||
// In other words, there's no branching instruction in the middle of the block.
|
||||
//
|
||||
// Note: we use the "block argument" variant of SSA, instead of PHI functions. See the package level doc comments.
|
||||
//
|
||||
// Note: we use "parameter/param" as a placeholder which represents a variant of PHI, and "argument/arg" as an actual
|
||||
// Value passed to that "parameter/param".
|
||||
type BasicBlock interface {
|
||||
// ID returns the unique ID of this block.
|
||||
ID() BasicBlockID
|
||||
|
||||
// Name returns the unique string ID of this block. e.g. blk0, blk1, ...
|
||||
Name() string
|
||||
|
||||
// AddParam adds the parameter to the block whose type specified by `t`.
|
||||
AddParam(b Builder, t Type) Value
|
||||
|
||||
// Params returns the number of parameters to this block.
|
||||
Params() int
|
||||
|
||||
// Param returns (Variable, Value) which corresponds to the i-th parameter of this block.
|
||||
// The returned Value is the definition of the param in this block.
|
||||
Param(i int) Value
|
||||
|
||||
// InsertInstruction inserts an instruction that implements Value into the tail of this block.
|
||||
InsertInstruction(raw *Instruction)
|
||||
|
||||
// Root returns the root instruction of this block.
|
||||
Root() *Instruction
|
||||
|
||||
// Tail returns the tail instruction of this block.
|
||||
Tail() *Instruction
|
||||
|
||||
// EntryBlock returns true if this block represents the function entry.
|
||||
EntryBlock() bool
|
||||
|
||||
// ReturnBlock returns ture if this block represents the function return.
|
||||
ReturnBlock() bool
|
||||
|
||||
// FormatHeader returns the debug string of this block, not including instruction.
|
||||
FormatHeader(b Builder) string
|
||||
|
||||
// Valid is true if this block is still valid even after optimizations.
|
||||
Valid() bool
|
||||
|
||||
// Sealed is true if this block has been sealed.
|
||||
Sealed() bool
|
||||
|
||||
// BeginPredIterator returns the first predecessor of this block.
|
||||
BeginPredIterator() BasicBlock
|
||||
|
||||
// NextPredIterator returns the next predecessor of this block.
|
||||
NextPredIterator() BasicBlock
|
||||
|
||||
// Preds returns the number of predecessors of this block.
|
||||
Preds() int
|
||||
|
||||
// Pred returns the i-th predecessor of this block.
|
||||
Pred(i int) BasicBlock
|
||||
|
||||
// Succs returns the number of successors of this block.
|
||||
Succs() int
|
||||
|
||||
// Succ returns the i-th successor of this block.
|
||||
Succ(i int) BasicBlock
|
||||
|
||||
// LoopHeader returns true if this block is a loop header.
|
||||
LoopHeader() bool
|
||||
|
||||
// LoopNestingForestChildren returns the children of this block in the loop nesting forest.
|
||||
LoopNestingForestChildren() []BasicBlock
|
||||
}
|
||||
|
||||
type (
|
||||
// basicBlock is a basic block in a SSA-transformed function.
|
||||
basicBlock struct {
|
||||
id BasicBlockID
|
||||
rootInstr, currentInstr *Instruction
|
||||
params []blockParam
|
||||
predIter int
|
||||
preds []basicBlockPredecessorInfo
|
||||
success []*basicBlock
|
||||
// singlePred is the alias to preds[0] for fast lookup, and only set after Seal is called.
|
||||
singlePred *basicBlock
|
||||
// lastDefinitions maps Variable to its last definition in this block.
|
||||
lastDefinitions map[Variable]Value
|
||||
// unknownsValues are used in builder.findValue. The usage is well-described in the paper.
|
||||
unknownValues []unknownValue
|
||||
// invalid is true if this block is made invalid during optimizations.
|
||||
invalid bool
|
||||
// sealed is true if this is sealed (all the predecessors are known).
|
||||
sealed bool
|
||||
// loopHeader is true if this block is a loop header:
|
||||
//
|
||||
// > A loop header (sometimes called the entry point of the loop) is a dominator that is the target
|
||||
// > of a loop-forming back edge. The loop header dominates all blocks in the loop body.
|
||||
// > A block may be a loop header for more than one loop. A loop may have multiple entry points,
|
||||
// > in which case it has no "loop header".
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/Control-flow_graph for more details.
|
||||
//
|
||||
// This is modified during the subPassLoopDetection pass.
|
||||
loopHeader bool
|
||||
|
||||
// loopNestingForestChildren holds the children of this block in the loop nesting forest.
|
||||
// Non-empty if and only if this block is a loop header (i.e. loopHeader=true)
|
||||
loopNestingForestChildren []BasicBlock
|
||||
|
||||
// reversePostOrder is used to sort all the blocks in the function in reverse post order.
|
||||
// This is used in builder.LayoutBlocks.
|
||||
reversePostOrder int
|
||||
|
||||
// child and sibling are the ones in the dominator tree.
|
||||
child, sibling *basicBlock
|
||||
}
|
||||
// BasicBlockID is the unique ID of a basicBlock.
|
||||
BasicBlockID uint32
|
||||
|
||||
// blockParam implements Value and represents a parameter to a basicBlock.
|
||||
blockParam struct {
|
||||
// value is the Value that corresponds to the parameter in this block,
|
||||
// and can be considered as an output of PHI instruction in traditional SSA.
|
||||
value Value
|
||||
// typ is the type of the parameter.
|
||||
typ Type
|
||||
}
|
||||
|
||||
unknownValue struct {
|
||||
// variable is the variable that this unknownValue represents.
|
||||
variable Variable
|
||||
// value is the value that this unknownValue represents.
|
||||
value Value
|
||||
}
|
||||
)
|
||||
|
||||
const basicBlockIDReturnBlock = 0xffffffff
|
||||
|
||||
// Name implements BasicBlock.Name.
|
||||
func (bb *basicBlock) Name() string {
|
||||
if bb.id == basicBlockIDReturnBlock {
|
||||
return "blk_ret"
|
||||
} else {
|
||||
return fmt.Sprintf("blk%d", bb.id)
|
||||
}
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer for debugging.
|
||||
func (bid BasicBlockID) String() string {
|
||||
if bid == basicBlockIDReturnBlock {
|
||||
return "blk_ret"
|
||||
} else {
|
||||
return fmt.Sprintf("blk%d", bid)
|
||||
}
|
||||
}
|
||||
|
||||
// ID implements BasicBlock.ID.
|
||||
func (bb *basicBlock) ID() BasicBlockID {
|
||||
return bb.id
|
||||
}
|
||||
|
||||
// basicBlockPredecessorInfo is the information of a predecessor of a basicBlock.
|
||||
// predecessor is determined by a pair of block and the branch instruction used to jump to the successor.
|
||||
type basicBlockPredecessorInfo struct {
|
||||
blk *basicBlock
|
||||
branch *Instruction
|
||||
}
|
||||
|
||||
// EntryBlock implements BasicBlock.EntryBlock.
|
||||
func (bb *basicBlock) EntryBlock() bool {
|
||||
return bb.id == 0
|
||||
}
|
||||
|
||||
// ReturnBlock implements BasicBlock.ReturnBlock.
|
||||
func (bb *basicBlock) ReturnBlock() bool {
|
||||
return bb.id == basicBlockIDReturnBlock
|
||||
}
|
||||
|
||||
// AddParam implements BasicBlock.AddParam.
|
||||
func (bb *basicBlock) AddParam(b Builder, typ Type) Value {
|
||||
paramValue := b.allocateValue(typ)
|
||||
bb.params = append(bb.params, blockParam{typ: typ, value: paramValue})
|
||||
return paramValue
|
||||
}
|
||||
|
||||
// addParamOn adds a parameter to this block whose value is already allocated.
|
||||
func (bb *basicBlock) addParamOn(typ Type, value Value) {
|
||||
bb.params = append(bb.params, blockParam{typ: typ, value: value})
|
||||
}
|
||||
|
||||
// Params implements BasicBlock.Params.
|
||||
func (bb *basicBlock) Params() int {
|
||||
return len(bb.params)
|
||||
}
|
||||
|
||||
// Param implements BasicBlock.Param.
|
||||
func (bb *basicBlock) Param(i int) Value {
|
||||
p := &bb.params[i]
|
||||
return p.value
|
||||
}
|
||||
|
||||
// Valid implements BasicBlock.Valid.
|
||||
func (bb *basicBlock) Valid() bool {
|
||||
return !bb.invalid
|
||||
}
|
||||
|
||||
// Sealed implements BasicBlock.Sealed.
|
||||
func (bb *basicBlock) Sealed() bool {
|
||||
return bb.sealed
|
||||
}
|
||||
|
||||
// InsertInstruction implements BasicBlock.InsertInstruction.
|
||||
func (bb *basicBlock) InsertInstruction(next *Instruction) {
|
||||
current := bb.currentInstr
|
||||
if current != nil {
|
||||
current.next = next
|
||||
next.prev = current
|
||||
} else {
|
||||
bb.rootInstr = next
|
||||
}
|
||||
bb.currentInstr = next
|
||||
|
||||
switch next.opcode {
|
||||
case OpcodeJump, OpcodeBrz, OpcodeBrnz:
|
||||
target := next.blk.(*basicBlock)
|
||||
target.addPred(bb, next)
|
||||
case OpcodeBrTable:
|
||||
for _, _target := range next.targets {
|
||||
target := _target.(*basicBlock)
|
||||
target.addPred(bb, next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NumPreds implements BasicBlock.NumPreds.
|
||||
func (bb *basicBlock) NumPreds() int {
|
||||
return len(bb.preds)
|
||||
}
|
||||
|
||||
// BeginPredIterator implements BasicBlock.BeginPredIterator.
|
||||
func (bb *basicBlock) BeginPredIterator() BasicBlock {
|
||||
bb.predIter = 0
|
||||
return bb.NextPredIterator()
|
||||
}
|
||||
|
||||
// NextPredIterator implements BasicBlock.NextPredIterator.
|
||||
func (bb *basicBlock) NextPredIterator() BasicBlock {
|
||||
if bb.predIter >= len(bb.preds) {
|
||||
return nil
|
||||
}
|
||||
pred := bb.preds[bb.predIter].blk
|
||||
bb.predIter++
|
||||
return pred
|
||||
}
|
||||
|
||||
// Preds implements BasicBlock.Preds.
|
||||
func (bb *basicBlock) Preds() int {
|
||||
return len(bb.preds)
|
||||
}
|
||||
|
||||
// Pred implements BasicBlock.Pred.
|
||||
func (bb *basicBlock) Pred(i int) BasicBlock {
|
||||
return bb.preds[i].blk
|
||||
}
|
||||
|
||||
// Succs implements BasicBlock.Succs.
|
||||
func (bb *basicBlock) Succs() int {
|
||||
return len(bb.success)
|
||||
}
|
||||
|
||||
// Succ implements BasicBlock.Succ.
|
||||
func (bb *basicBlock) Succ(i int) BasicBlock {
|
||||
return bb.success[i]
|
||||
}
|
||||
|
||||
// Root implements BasicBlock.Root.
|
||||
func (bb *basicBlock) Root() *Instruction {
|
||||
return bb.rootInstr
|
||||
}
|
||||
|
||||
// Tail implements BasicBlock.Tail.
|
||||
func (bb *basicBlock) Tail() *Instruction {
|
||||
return bb.currentInstr
|
||||
}
|
||||
|
||||
// reset resets the basicBlock to its initial state so that it can be reused for another function.
|
||||
func resetBasicBlock(bb *basicBlock) {
|
||||
bb.params = bb.params[:0]
|
||||
bb.rootInstr, bb.currentInstr = nil, nil
|
||||
bb.preds = bb.preds[:0]
|
||||
bb.success = bb.success[:0]
|
||||
bb.invalid, bb.sealed = false, false
|
||||
bb.singlePred = nil
|
||||
bb.unknownValues = bb.unknownValues[:0]
|
||||
bb.lastDefinitions = wazevoapi.ResetMap(bb.lastDefinitions)
|
||||
bb.reversePostOrder = -1
|
||||
bb.loopNestingForestChildren = bb.loopNestingForestChildren[:0]
|
||||
bb.loopHeader = false
|
||||
bb.sibling = nil
|
||||
bb.child = nil
|
||||
}
|
||||
|
||||
// addPred adds a predecessor to this block specified by the branch instruction.
|
||||
func (bb *basicBlock) addPred(blk BasicBlock, branch *Instruction) {
|
||||
if bb.sealed {
|
||||
panic("BUG: trying to add predecessor to a sealed block: " + bb.Name())
|
||||
}
|
||||
|
||||
pred := blk.(*basicBlock)
|
||||
for i := range bb.preds {
|
||||
existingPred := &bb.preds[i]
|
||||
if existingPred.blk == pred && existingPred.branch != branch {
|
||||
// If the target is already added, then this must come from the same BrTable,
|
||||
// otherwise such redundant branch should be eliminated by the frontend. (which should be simpler).
|
||||
panic(fmt.Sprintf("BUG: redundant non BrTable jumps in %s whose targes are the same", bb.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
bb.preds = append(bb.preds, basicBlockPredecessorInfo{
|
||||
blk: pred,
|
||||
branch: branch,
|
||||
})
|
||||
|
||||
pred.success = append(pred.success, bb)
|
||||
}
|
||||
|
||||
// FormatHeader implements BasicBlock.FormatHeader.
|
||||
func (bb *basicBlock) FormatHeader(b Builder) string {
|
||||
ps := make([]string, len(bb.params))
|
||||
for i, p := range bb.params {
|
||||
ps[i] = p.value.formatWithType(b)
|
||||
}
|
||||
|
||||
if len(bb.preds) > 0 {
|
||||
preds := make([]string, 0, len(bb.preds))
|
||||
for _, pred := range bb.preds {
|
||||
if pred.blk.invalid {
|
||||
continue
|
||||
}
|
||||
preds = append(preds, fmt.Sprintf("blk%d", pred.blk.id))
|
||||
|
||||
}
|
||||
return fmt.Sprintf("blk%d: (%s) <-- (%s)",
|
||||
bb.id, strings.Join(ps, ","), strings.Join(preds, ","))
|
||||
} else {
|
||||
return fmt.Sprintf("blk%d: (%s)", bb.id, strings.Join(ps, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
// validates validates the basicBlock for debugging purpose.
|
||||
func (bb *basicBlock) validate(b *builder) {
|
||||
if bb.invalid {
|
||||
panic("BUG: trying to validate an invalid block: " + bb.Name())
|
||||
}
|
||||
if len(bb.preds) > 0 {
|
||||
for _, pred := range bb.preds {
|
||||
if pred.branch.opcode != OpcodeBrTable {
|
||||
if target := pred.branch.blk; target != bb {
|
||||
panic(fmt.Sprintf("BUG: '%s' is not branch to %s, but to %s",
|
||||
pred.branch.Format(b), bb.Name(), target.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
var exp int
|
||||
if bb.ReturnBlock() {
|
||||
exp = len(b.currentSignature.Results)
|
||||
} else {
|
||||
exp = len(bb.params)
|
||||
}
|
||||
|
||||
if len(pred.branch.vs.View()) != exp {
|
||||
panic(fmt.Sprintf(
|
||||
"BUG: len(argument at %s) != len(params at %s): %d != %d: %s",
|
||||
pred.blk.Name(), bb.Name(),
|
||||
len(pred.branch.vs.View()), len(bb.params), pred.branch.Format(b),
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer for debugging purpose only.
|
||||
func (bb *basicBlock) String() string {
|
||||
return strconv.Itoa(int(bb.id))
|
||||
}
|
||||
|
||||
// LoopNestingForestChildren implements BasicBlock.LoopNestingForestChildren.
|
||||
func (bb *basicBlock) LoopNestingForestChildren() []BasicBlock {
|
||||
return bb.loopNestingForestChildren
|
||||
}
|
||||
|
||||
// LoopHeader implements BasicBlock.LoopHeader.
|
||||
func (bb *basicBlock) LoopHeader() bool {
|
||||
return bb.loopHeader
|
||||
}
|
34
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block_sort.go
generated
vendored
Normal file
34
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block_sort.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
//go:build go1.21
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
func sortBlocks(blocks []*basicBlock) {
|
||||
slices.SortFunc(blocks, func(i, j *basicBlock) int {
|
||||
jIsReturn := j.ReturnBlock()
|
||||
iIsReturn := i.ReturnBlock()
|
||||
if iIsReturn && jIsReturn {
|
||||
return 0
|
||||
}
|
||||
if jIsReturn {
|
||||
return 1
|
||||
}
|
||||
if iIsReturn {
|
||||
return -1
|
||||
}
|
||||
iRoot, jRoot := i.rootInstr, j.rootInstr
|
||||
if iRoot == nil && jRoot == nil { // For testing.
|
||||
return 0
|
||||
}
|
||||
if jRoot == nil {
|
||||
return 1
|
||||
}
|
||||
if iRoot == nil {
|
||||
return -1
|
||||
}
|
||||
return i.rootInstr.id - j.rootInstr.id
|
||||
})
|
||||
}
|
24
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block_sort_old.go
generated
vendored
Normal file
24
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block_sort_old.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
//go:build !go1.21
|
||||
|
||||
// TODO: delete after the floor Go version is 1.21
|
||||
|
||||
package ssa
|
||||
|
||||
import "sort"
|
||||
|
||||
func sortBlocks(blocks []*basicBlock) {
|
||||
sort.SliceStable(blocks, func(i, j int) bool {
|
||||
iBlk, jBlk := blocks[i], blocks[j]
|
||||
if jBlk.ReturnBlock() {
|
||||
return true
|
||||
}
|
||||
if iBlk.ReturnBlock() {
|
||||
return false
|
||||
}
|
||||
iRoot, jRoot := iBlk.rootInstr, jBlk.rootInstr
|
||||
if iRoot == nil || jRoot == nil { // For testing.
|
||||
return true
|
||||
}
|
||||
return iBlk.rootInstr.id < jBlk.rootInstr.id
|
||||
})
|
||||
}
|
731
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/builder.go
generated
vendored
Normal file
731
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/builder.go
generated
vendored
Normal file
@ -0,0 +1,731 @@
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
||||
)
|
||||
|
||||
// Builder is used to builds SSA consisting of Basic Blocks per function.
|
||||
type Builder interface {
|
||||
// Init must be called to reuse this builder for the next function.
|
||||
Init(typ *Signature)
|
||||
|
||||
// Signature returns the Signature of the currently-compiled function.
|
||||
Signature() *Signature
|
||||
|
||||
// BlockIDMax returns the maximum value of BasicBlocksID existing in the currently-compiled function.
|
||||
BlockIDMax() BasicBlockID
|
||||
|
||||
// AllocateBasicBlock creates a basic block in SSA function.
|
||||
AllocateBasicBlock() BasicBlock
|
||||
|
||||
// CurrentBlock returns the currently handled BasicBlock which is set by the latest call to SetCurrentBlock.
|
||||
CurrentBlock() BasicBlock
|
||||
|
||||
// EntryBlock returns the entry BasicBlock of the currently-compiled function.
|
||||
EntryBlock() BasicBlock
|
||||
|
||||
// SetCurrentBlock sets the instruction insertion target to the BasicBlock `b`.
|
||||
SetCurrentBlock(b BasicBlock)
|
||||
|
||||
// DeclareVariable declares a Variable of the given Type.
|
||||
DeclareVariable(Type) Variable
|
||||
|
||||
// DefineVariable defines a variable in the `block` with value.
|
||||
// The defining instruction will be inserted into the `block`.
|
||||
DefineVariable(variable Variable, value Value, block BasicBlock)
|
||||
|
||||
// DefineVariableInCurrentBB is the same as DefineVariable except the definition is
|
||||
// inserted into the current BasicBlock. Alias to DefineVariable(x, y, CurrentBlock()).
|
||||
DefineVariableInCurrentBB(variable Variable, value Value)
|
||||
|
||||
// AllocateInstruction returns a new Instruction.
|
||||
AllocateInstruction() *Instruction
|
||||
|
||||
// InsertInstruction executes BasicBlock.InsertInstruction for the currently handled basic block.
|
||||
InsertInstruction(raw *Instruction)
|
||||
|
||||
// allocateValue allocates an unused Value.
|
||||
allocateValue(typ Type) Value
|
||||
|
||||
// MustFindValue searches the latest definition of the given Variable and returns the result.
|
||||
MustFindValue(variable Variable) Value
|
||||
|
||||
// MustFindValueInBlk is the same as MustFindValue except it searches the latest definition from the given BasicBlock.
|
||||
MustFindValueInBlk(variable Variable, blk BasicBlock) Value
|
||||
|
||||
// FindValueInLinearPath tries to find the latest definition of the given Variable in the linear path to the current BasicBlock.
|
||||
// If it cannot find the definition, or it's not sealed yet, it returns ValueInvalid.
|
||||
FindValueInLinearPath(variable Variable) Value
|
||||
|
||||
// Seal declares that we've known all the predecessors to this block and were added via AddPred.
|
||||
// After calling this, AddPred will be forbidden.
|
||||
Seal(blk BasicBlock)
|
||||
|
||||
// AnnotateValue is for debugging purpose.
|
||||
AnnotateValue(value Value, annotation string)
|
||||
|
||||
// DeclareSignature appends the *Signature to be referenced by various instructions (e.g. OpcodeCall).
|
||||
DeclareSignature(signature *Signature)
|
||||
|
||||
// Signatures returns the slice of declared Signatures.
|
||||
Signatures() []*Signature
|
||||
|
||||
// ResolveSignature returns the Signature which corresponds to SignatureID.
|
||||
ResolveSignature(id SignatureID) *Signature
|
||||
|
||||
// RunPasses runs various passes on the constructed SSA function.
|
||||
RunPasses()
|
||||
|
||||
// Format returns the debugging string of the SSA function.
|
||||
Format() string
|
||||
|
||||
// BlockIteratorBegin initializes the state to iterate over all the valid BasicBlock(s) compiled.
|
||||
// Combined with BlockIteratorNext, we can use this like:
|
||||
//
|
||||
// for blk := builder.BlockIteratorBegin(); blk != nil; blk = builder.BlockIteratorNext() {
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// The returned blocks are ordered in the order of AllocateBasicBlock being called.
|
||||
BlockIteratorBegin() BasicBlock
|
||||
|
||||
// BlockIteratorNext advances the state for iteration initialized by BlockIteratorBegin.
|
||||
// Returns nil if there's no unseen BasicBlock.
|
||||
BlockIteratorNext() BasicBlock
|
||||
|
||||
// ValueRefCounts returns the map of ValueID to its reference count.
|
||||
// The returned slice must not be modified.
|
||||
ValueRefCounts() []int
|
||||
|
||||
// BlockIteratorReversePostOrderBegin is almost the same as BlockIteratorBegin except it returns the BasicBlock in the reverse post-order.
|
||||
// This is available after RunPasses is run.
|
||||
BlockIteratorReversePostOrderBegin() BasicBlock
|
||||
|
||||
// BlockIteratorReversePostOrderNext is almost the same as BlockIteratorPostOrderNext except it returns the BasicBlock in the reverse post-order.
|
||||
// This is available after RunPasses is run.
|
||||
BlockIteratorReversePostOrderNext() BasicBlock
|
||||
|
||||
// ReturnBlock returns the BasicBlock which is used to return from the function.
|
||||
ReturnBlock() BasicBlock
|
||||
|
||||
// InsertUndefined inserts an undefined instruction at the current position.
|
||||
InsertUndefined()
|
||||
|
||||
// SetCurrentSourceOffset sets the current source offset. The incoming instruction will be annotated with this offset.
|
||||
SetCurrentSourceOffset(line SourceOffset)
|
||||
|
||||
// LoopNestingForestRoots returns the roots of the loop nesting forest.
|
||||
LoopNestingForestRoots() []BasicBlock
|
||||
|
||||
// LowestCommonAncestor returns the lowest common ancestor in the dominator tree of the given BasicBlock(s).
|
||||
LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock
|
||||
|
||||
// Idom returns the immediate dominator of the given BasicBlock.
|
||||
Idom(blk BasicBlock) BasicBlock
|
||||
|
||||
VarLengthPool() *wazevoapi.VarLengthPool[Value]
|
||||
}
|
||||
|
||||
// NewBuilder returns a new Builder implementation.
|
||||
func NewBuilder() Builder {
|
||||
return &builder{
|
||||
instructionsPool: wazevoapi.NewPool[Instruction](resetInstruction),
|
||||
basicBlocksPool: wazevoapi.NewPool[basicBlock](resetBasicBlock),
|
||||
varLengthPool: wazevoapi.NewVarLengthPool[Value](),
|
||||
valueAnnotations: make(map[ValueID]string),
|
||||
signatures: make(map[SignatureID]*Signature),
|
||||
blkVisited: make(map[*basicBlock]int),
|
||||
valueIDAliases: make(map[ValueID]Value),
|
||||
redundantParameterIndexToValue: make(map[int]Value),
|
||||
returnBlk: &basicBlock{id: basicBlockIDReturnBlock},
|
||||
}
|
||||
}
|
||||
|
||||
// builder implements Builder interface.
|
||||
type builder struct {
|
||||
basicBlocksPool wazevoapi.Pool[basicBlock]
|
||||
instructionsPool wazevoapi.Pool[Instruction]
|
||||
varLengthPool wazevoapi.VarLengthPool[Value]
|
||||
signatures map[SignatureID]*Signature
|
||||
currentSignature *Signature
|
||||
|
||||
// reversePostOrderedBasicBlocks are the BasicBlock(s) ordered in the reverse post-order after passCalculateImmediateDominators.
|
||||
reversePostOrderedBasicBlocks []*basicBlock
|
||||
currentBB *basicBlock
|
||||
returnBlk *basicBlock
|
||||
|
||||
// variables track the types for Variable with the index regarded Variable.
|
||||
variables []Type
|
||||
// nextValueID is used by builder.AllocateValue.
|
||||
nextValueID ValueID
|
||||
// nextVariable is used by builder.AllocateVariable.
|
||||
nextVariable Variable
|
||||
|
||||
valueIDAliases map[ValueID]Value
|
||||
valueAnnotations map[ValueID]string
|
||||
|
||||
// valueRefCounts is used to lower the SSA in backend, and will be calculated
|
||||
// by the last SSA-level optimization pass.
|
||||
valueRefCounts []int
|
||||
|
||||
// dominators stores the immediate dominator of each BasicBlock.
|
||||
// The index is blockID of the BasicBlock.
|
||||
dominators []*basicBlock
|
||||
sparseTree dominatorSparseTree
|
||||
|
||||
// loopNestingForestRoots are the roots of the loop nesting forest.
|
||||
loopNestingForestRoots []BasicBlock
|
||||
|
||||
// The followings are used for optimization passes/deterministic compilation.
|
||||
instStack []*Instruction
|
||||
blkVisited map[*basicBlock]int
|
||||
valueIDToInstruction []*Instruction
|
||||
blkStack []*basicBlock
|
||||
blkStack2 []*basicBlock
|
||||
ints []int
|
||||
redundantParameterIndexToValue map[int]Value
|
||||
|
||||
// blockIterCur is used to implement blockIteratorBegin and blockIteratorNext.
|
||||
blockIterCur int
|
||||
|
||||
// donePreBlockLayoutPasses is true if all the passes before LayoutBlocks are called.
|
||||
donePreBlockLayoutPasses bool
|
||||
// doneBlockLayout is true if LayoutBlocks is called.
|
||||
doneBlockLayout bool
|
||||
// donePostBlockLayoutPasses is true if all the passes after LayoutBlocks are called.
|
||||
donePostBlockLayoutPasses bool
|
||||
|
||||
currentSourceOffset SourceOffset
|
||||
}
|
||||
|
||||
func (b *builder) VarLengthPool() *wazevoapi.VarLengthPool[Value] {
|
||||
return &b.varLengthPool
|
||||
}
|
||||
|
||||
// ReturnBlock implements Builder.ReturnBlock.
|
||||
func (b *builder) ReturnBlock() BasicBlock {
|
||||
return b.returnBlk
|
||||
}
|
||||
|
||||
// Init implements Builder.Reset.
|
||||
func (b *builder) Init(s *Signature) {
|
||||
b.nextVariable = 0
|
||||
b.currentSignature = s
|
||||
resetBasicBlock(b.returnBlk)
|
||||
b.instructionsPool.Reset()
|
||||
b.basicBlocksPool.Reset()
|
||||
b.varLengthPool.Reset()
|
||||
b.donePreBlockLayoutPasses = false
|
||||
b.doneBlockLayout = false
|
||||
b.donePostBlockLayoutPasses = false
|
||||
for _, sig := range b.signatures {
|
||||
sig.used = false
|
||||
}
|
||||
|
||||
b.ints = b.ints[:0]
|
||||
b.blkStack = b.blkStack[:0]
|
||||
b.blkStack2 = b.blkStack2[:0]
|
||||
b.dominators = b.dominators[:0]
|
||||
b.loopNestingForestRoots = b.loopNestingForestRoots[:0]
|
||||
|
||||
for i := 0; i < b.basicBlocksPool.Allocated(); i++ {
|
||||
blk := b.basicBlocksPool.View(i)
|
||||
delete(b.blkVisited, blk)
|
||||
}
|
||||
b.basicBlocksPool.Reset()
|
||||
|
||||
for v := ValueID(0); v < b.nextValueID; v++ {
|
||||
delete(b.valueAnnotations, v)
|
||||
delete(b.valueIDAliases, v)
|
||||
b.valueRefCounts[v] = 0
|
||||
b.valueIDToInstruction[v] = nil
|
||||
}
|
||||
b.nextValueID = 0
|
||||
b.reversePostOrderedBasicBlocks = b.reversePostOrderedBasicBlocks[:0]
|
||||
b.doneBlockLayout = false
|
||||
for i := range b.valueRefCounts {
|
||||
b.valueRefCounts[i] = 0
|
||||
}
|
||||
|
||||
b.currentSourceOffset = sourceOffsetUnknown
|
||||
}
|
||||
|
||||
// Signature implements Builder.Signature.
|
||||
func (b *builder) Signature() *Signature {
|
||||
return b.currentSignature
|
||||
}
|
||||
|
||||
// AnnotateValue implements Builder.AnnotateValue.
|
||||
func (b *builder) AnnotateValue(value Value, a string) {
|
||||
b.valueAnnotations[value.ID()] = a
|
||||
}
|
||||
|
||||
// AllocateInstruction implements Builder.AllocateInstruction.
|
||||
func (b *builder) AllocateInstruction() *Instruction {
|
||||
instr := b.instructionsPool.Allocate()
|
||||
instr.id = b.instructionsPool.Allocated()
|
||||
return instr
|
||||
}
|
||||
|
||||
// DeclareSignature implements Builder.AnnotateValue.
|
||||
func (b *builder) DeclareSignature(s *Signature) {
|
||||
b.signatures[s.ID] = s
|
||||
s.used = false
|
||||
}
|
||||
|
||||
// Signatures implements Builder.Signatures.
|
||||
func (b *builder) Signatures() (ret []*Signature) {
|
||||
for _, sig := range b.signatures {
|
||||
ret = append(ret, sig)
|
||||
}
|
||||
sort.Slice(ret, func(i, j int) bool {
|
||||
return ret[i].ID < ret[j].ID
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// SetCurrentSourceOffset implements Builder.SetCurrentSourceOffset.
|
||||
func (b *builder) SetCurrentSourceOffset(l SourceOffset) {
|
||||
b.currentSourceOffset = l
|
||||
}
|
||||
|
||||
func (b *builder) usedSignatures() (ret []*Signature) {
|
||||
for _, sig := range b.signatures {
|
||||
if sig.used {
|
||||
ret = append(ret, sig)
|
||||
}
|
||||
}
|
||||
sort.Slice(ret, func(i, j int) bool {
|
||||
return ret[i].ID < ret[j].ID
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ResolveSignature implements Builder.ResolveSignature.
|
||||
func (b *builder) ResolveSignature(id SignatureID) *Signature {
|
||||
return b.signatures[id]
|
||||
}
|
||||
|
||||
// AllocateBasicBlock implements Builder.AllocateBasicBlock.
|
||||
func (b *builder) AllocateBasicBlock() BasicBlock {
|
||||
return b.allocateBasicBlock()
|
||||
}
|
||||
|
||||
// allocateBasicBlock allocates a new basicBlock.
|
||||
func (b *builder) allocateBasicBlock() *basicBlock {
|
||||
id := BasicBlockID(b.basicBlocksPool.Allocated())
|
||||
blk := b.basicBlocksPool.Allocate()
|
||||
blk.id = id
|
||||
return blk
|
||||
}
|
||||
|
||||
// Idom implements Builder.Idom.
|
||||
func (b *builder) Idom(blk BasicBlock) BasicBlock {
|
||||
return b.dominators[blk.ID()]
|
||||
}
|
||||
|
||||
// InsertInstruction implements Builder.InsertInstruction.
|
||||
func (b *builder) InsertInstruction(instr *Instruction) {
|
||||
b.currentBB.InsertInstruction(instr)
|
||||
|
||||
if l := b.currentSourceOffset; l.Valid() {
|
||||
// Emit the source offset info only when the instruction has side effect because
|
||||
// these are the only instructions that are accessed by stack unwinding.
|
||||
// This reduces the significant amount of the offset info in the binary.
|
||||
if instr.sideEffect() != sideEffectNone {
|
||||
instr.annotateSourceOffset(l)
|
||||
}
|
||||
}
|
||||
|
||||
resultTypesFn := instructionReturnTypes[instr.opcode]
|
||||
if resultTypesFn == nil {
|
||||
panic("TODO: " + instr.Format(b))
|
||||
}
|
||||
|
||||
t1, ts := resultTypesFn(b, instr)
|
||||
if t1.invalid() {
|
||||
return
|
||||
}
|
||||
|
||||
r1 := b.allocateValue(t1)
|
||||
instr.rValue = r1
|
||||
|
||||
tsl := len(ts)
|
||||
if tsl == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
rValues := b.varLengthPool.Allocate(tsl)
|
||||
for i := 0; i < tsl; i++ {
|
||||
rValues = rValues.Append(&b.varLengthPool, b.allocateValue(ts[i]))
|
||||
}
|
||||
instr.rValues = rValues
|
||||
}
|
||||
|
||||
// DefineVariable implements Builder.DefineVariable.
|
||||
func (b *builder) DefineVariable(variable Variable, value Value, block BasicBlock) {
|
||||
if b.variables[variable].invalid() {
|
||||
panic("BUG: trying to define variable " + variable.String() + " but is not declared yet")
|
||||
}
|
||||
|
||||
if b.variables[variable] != value.Type() {
|
||||
panic(fmt.Sprintf("BUG: inconsistent type for variable %d: expected %s but got %s", variable, b.variables[variable], value.Type()))
|
||||
}
|
||||
bb := block.(*basicBlock)
|
||||
bb.lastDefinitions[variable] = value
|
||||
}
|
||||
|
||||
// DefineVariableInCurrentBB implements Builder.DefineVariableInCurrentBB.
|
||||
func (b *builder) DefineVariableInCurrentBB(variable Variable, value Value) {
|
||||
b.DefineVariable(variable, value, b.currentBB)
|
||||
}
|
||||
|
||||
// SetCurrentBlock implements Builder.SetCurrentBlock.
|
||||
func (b *builder) SetCurrentBlock(bb BasicBlock) {
|
||||
b.currentBB = bb.(*basicBlock)
|
||||
}
|
||||
|
||||
// CurrentBlock implements Builder.CurrentBlock.
|
||||
func (b *builder) CurrentBlock() BasicBlock {
|
||||
return b.currentBB
|
||||
}
|
||||
|
||||
// EntryBlock implements Builder.EntryBlock.
|
||||
func (b *builder) EntryBlock() BasicBlock {
|
||||
return b.entryBlk()
|
||||
}
|
||||
|
||||
// DeclareVariable implements Builder.DeclareVariable.
|
||||
func (b *builder) DeclareVariable(typ Type) Variable {
|
||||
v := b.allocateVariable()
|
||||
iv := int(v)
|
||||
if l := len(b.variables); l <= iv {
|
||||
b.variables = append(b.variables, make([]Type, 2*(l+1))...)
|
||||
}
|
||||
b.variables[v] = typ
|
||||
return v
|
||||
}
|
||||
|
||||
// allocateVariable allocates a new variable.
|
||||
func (b *builder) allocateVariable() (ret Variable) {
|
||||
ret = b.nextVariable
|
||||
b.nextVariable++
|
||||
return
|
||||
}
|
||||
|
||||
// allocateValue implements Builder.AllocateValue.
|
||||
func (b *builder) allocateValue(typ Type) (v Value) {
|
||||
v = Value(b.nextValueID)
|
||||
v = v.setType(typ)
|
||||
b.nextValueID++
|
||||
return
|
||||
}
|
||||
|
||||
// FindValueInLinearPath implements Builder.FindValueInLinearPath.
|
||||
func (b *builder) FindValueInLinearPath(variable Variable) Value {
|
||||
return b.findValueInLinearPath(variable, b.currentBB)
|
||||
}
|
||||
|
||||
func (b *builder) findValueInLinearPath(variable Variable, blk *basicBlock) Value {
|
||||
if val, ok := blk.lastDefinitions[variable]; ok {
|
||||
return val
|
||||
} else if !blk.sealed {
|
||||
return ValueInvalid
|
||||
}
|
||||
|
||||
if pred := blk.singlePred; pred != nil {
|
||||
// If this block is sealed and have only one predecessor,
|
||||
// we can use the value in that block without ambiguity on definition.
|
||||
return b.findValueInLinearPath(variable, pred)
|
||||
}
|
||||
if len(blk.preds) == 1 {
|
||||
panic("BUG")
|
||||
}
|
||||
return ValueInvalid
|
||||
}
|
||||
|
||||
func (b *builder) MustFindValueInBlk(variable Variable, blk BasicBlock) Value {
|
||||
typ := b.definedVariableType(variable)
|
||||
return b.findValue(typ, variable, blk.(*basicBlock))
|
||||
}
|
||||
|
||||
// MustFindValue implements Builder.MustFindValue.
|
||||
func (b *builder) MustFindValue(variable Variable) Value {
|
||||
typ := b.definedVariableType(variable)
|
||||
return b.findValue(typ, variable, b.currentBB)
|
||||
}
|
||||
|
||||
// findValue recursively tries to find the latest definition of a `variable`. The algorithm is described in
|
||||
// the section 2 of the paper https://link.springer.com/content/pdf/10.1007/978-3-642-37051-9_6.pdf.
|
||||
//
|
||||
// TODO: reimplement this in iterative, not recursive, to avoid stack overflow.
|
||||
func (b *builder) findValue(typ Type, variable Variable, blk *basicBlock) Value {
|
||||
if val, ok := blk.lastDefinitions[variable]; ok {
|
||||
// The value is already defined in this block!
|
||||
return val
|
||||
} else if !blk.sealed { // Incomplete CFG as in the paper.
|
||||
// If this is not sealed, that means it might have additional unknown predecessor later on.
|
||||
// So we temporarily define the placeholder value here (not add as a parameter yet!),
|
||||
// and record it as unknown.
|
||||
// The unknown values are resolved when we call seal this block via BasicBlock.Seal().
|
||||
value := b.allocateValue(typ)
|
||||
if wazevoapi.SSALoggingEnabled {
|
||||
fmt.Printf("adding unknown value placeholder for %s at %d\n", variable, blk.id)
|
||||
}
|
||||
blk.lastDefinitions[variable] = value
|
||||
blk.unknownValues = append(blk.unknownValues, unknownValue{
|
||||
variable: variable,
|
||||
value: value,
|
||||
})
|
||||
return value
|
||||
}
|
||||
|
||||
if pred := blk.singlePred; pred != nil {
|
||||
// If this block is sealed and have only one predecessor,
|
||||
// we can use the value in that block without ambiguity on definition.
|
||||
return b.findValue(typ, variable, pred)
|
||||
} else if len(blk.preds) == 0 {
|
||||
panic("BUG: value is not defined for " + variable.String())
|
||||
}
|
||||
|
||||
// If this block has multiple predecessors, we have to gather the definitions,
|
||||
// and treat them as an argument to this block.
|
||||
//
|
||||
// The first thing is to define a new parameter to this block which may or may not be redundant, but
|
||||
// later we eliminate trivial params in an optimization pass. This must be done before finding the
|
||||
// definitions in the predecessors so that we can break the cycle.
|
||||
paramValue := blk.AddParam(b, typ)
|
||||
b.DefineVariable(variable, paramValue, blk)
|
||||
|
||||
// After the new param is added, we have to manipulate the original branching instructions
|
||||
// in predecessors so that they would pass the definition of `variable` as the argument to
|
||||
// the newly added PHI.
|
||||
for i := range blk.preds {
|
||||
pred := &blk.preds[i]
|
||||
value := b.findValue(typ, variable, pred.blk)
|
||||
pred.branch.addArgumentBranchInst(b, value)
|
||||
}
|
||||
return paramValue
|
||||
}
|
||||
|
||||
// Seal implements Builder.Seal.
|
||||
func (b *builder) Seal(raw BasicBlock) {
|
||||
blk := raw.(*basicBlock)
|
||||
if len(blk.preds) == 1 {
|
||||
blk.singlePred = blk.preds[0].blk
|
||||
}
|
||||
blk.sealed = true
|
||||
|
||||
for _, v := range blk.unknownValues {
|
||||
variable, phiValue := v.variable, v.value
|
||||
typ := b.definedVariableType(variable)
|
||||
blk.addParamOn(typ, phiValue)
|
||||
for i := range blk.preds {
|
||||
pred := &blk.preds[i]
|
||||
predValue := b.findValue(typ, variable, pred.blk)
|
||||
if !predValue.Valid() {
|
||||
panic("BUG: value is not defined anywhere in the predecessors in the CFG")
|
||||
}
|
||||
pred.branch.addArgumentBranchInst(b, predValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// definedVariableType returns the type of the given variable. If the variable is not defined yet, it panics.
|
||||
func (b *builder) definedVariableType(variable Variable) Type {
|
||||
typ := b.variables[variable]
|
||||
if typ.invalid() {
|
||||
panic(fmt.Sprintf("%s is not defined yet", variable))
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// Format implements Builder.Format.
|
||||
func (b *builder) Format() string {
|
||||
str := strings.Builder{}
|
||||
usedSigs := b.usedSignatures()
|
||||
if len(usedSigs) > 0 {
|
||||
str.WriteByte('\n')
|
||||
str.WriteString("signatures:\n")
|
||||
for _, sig := range usedSigs {
|
||||
str.WriteByte('\t')
|
||||
str.WriteString(sig.String())
|
||||
str.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
var iterBegin, iterNext func() *basicBlock
|
||||
if b.doneBlockLayout {
|
||||
iterBegin, iterNext = b.blockIteratorReversePostOrderBegin, b.blockIteratorReversePostOrderNext
|
||||
} else {
|
||||
iterBegin, iterNext = b.blockIteratorBegin, b.blockIteratorNext
|
||||
}
|
||||
for bb := iterBegin(); bb != nil; bb = iterNext() {
|
||||
str.WriteByte('\n')
|
||||
str.WriteString(bb.FormatHeader(b))
|
||||
str.WriteByte('\n')
|
||||
|
||||
for cur := bb.Root(); cur != nil; cur = cur.Next() {
|
||||
str.WriteByte('\t')
|
||||
str.WriteString(cur.Format(b))
|
||||
str.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
// BlockIteratorNext implements Builder.BlockIteratorNext.
|
||||
func (b *builder) BlockIteratorNext() BasicBlock {
|
||||
if blk := b.blockIteratorNext(); blk == nil {
|
||||
return nil // BasicBlock((*basicBlock)(nil)) != BasicBlock(nil)
|
||||
} else {
|
||||
return blk
|
||||
}
|
||||
}
|
||||
|
||||
// BlockIteratorNext implements Builder.BlockIteratorNext.
|
||||
func (b *builder) blockIteratorNext() *basicBlock {
|
||||
index := b.blockIterCur
|
||||
for {
|
||||
if index == b.basicBlocksPool.Allocated() {
|
||||
return nil
|
||||
}
|
||||
ret := b.basicBlocksPool.View(index)
|
||||
index++
|
||||
if !ret.invalid {
|
||||
b.blockIterCur = index
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BlockIteratorBegin implements Builder.BlockIteratorBegin.
|
||||
func (b *builder) BlockIteratorBegin() BasicBlock {
|
||||
return b.blockIteratorBegin()
|
||||
}
|
||||
|
||||
// BlockIteratorBegin implements Builder.BlockIteratorBegin.
|
||||
func (b *builder) blockIteratorBegin() *basicBlock {
|
||||
b.blockIterCur = 0
|
||||
return b.blockIteratorNext()
|
||||
}
|
||||
|
||||
// BlockIteratorReversePostOrderBegin implements Builder.BlockIteratorReversePostOrderBegin.
|
||||
func (b *builder) BlockIteratorReversePostOrderBegin() BasicBlock {
|
||||
return b.blockIteratorReversePostOrderBegin()
|
||||
}
|
||||
|
||||
// BlockIteratorBegin implements Builder.BlockIteratorBegin.
|
||||
func (b *builder) blockIteratorReversePostOrderBegin() *basicBlock {
|
||||
b.blockIterCur = 0
|
||||
return b.blockIteratorReversePostOrderNext()
|
||||
}
|
||||
|
||||
// BlockIteratorReversePostOrderNext implements Builder.BlockIteratorReversePostOrderNext.
|
||||
func (b *builder) BlockIteratorReversePostOrderNext() BasicBlock {
|
||||
if blk := b.blockIteratorReversePostOrderNext(); blk == nil {
|
||||
return nil // BasicBlock((*basicBlock)(nil)) != BasicBlock(nil)
|
||||
} else {
|
||||
return blk
|
||||
}
|
||||
}
|
||||
|
||||
// BlockIteratorNext implements Builder.BlockIteratorNext.
|
||||
func (b *builder) blockIteratorReversePostOrderNext() *basicBlock {
|
||||
if b.blockIterCur >= len(b.reversePostOrderedBasicBlocks) {
|
||||
return nil
|
||||
} else {
|
||||
ret := b.reversePostOrderedBasicBlocks[b.blockIterCur]
|
||||
b.blockIterCur++
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
// ValueRefCounts implements Builder.ValueRefCounts.
|
||||
func (b *builder) ValueRefCounts() []int {
|
||||
return b.valueRefCounts
|
||||
}
|
||||
|
||||
// alias records the alias of the given values. The alias(es) will be
|
||||
// eliminated in the optimization pass via resolveArgumentAlias.
|
||||
func (b *builder) alias(dst, src Value) {
|
||||
b.valueIDAliases[dst.ID()] = src
|
||||
}
|
||||
|
||||
// resolveArgumentAlias resolves the alias of the arguments of the given instruction.
|
||||
func (b *builder) resolveArgumentAlias(instr *Instruction) {
|
||||
if instr.v.Valid() {
|
||||
instr.v = b.resolveAlias(instr.v)
|
||||
}
|
||||
|
||||
if instr.v2.Valid() {
|
||||
instr.v2 = b.resolveAlias(instr.v2)
|
||||
}
|
||||
|
||||
if instr.v3.Valid() {
|
||||
instr.v3 = b.resolveAlias(instr.v3)
|
||||
}
|
||||
|
||||
view := instr.vs.View()
|
||||
for i, v := range view {
|
||||
view[i] = b.resolveAlias(v)
|
||||
}
|
||||
}
|
||||
|
||||
// resolveAlias resolves the alias of the given value.
|
||||
func (b *builder) resolveAlias(v Value) Value {
|
||||
// Some aliases are chained, so we need to resolve them recursively.
|
||||
for {
|
||||
if src, ok := b.valueIDAliases[v.ID()]; ok {
|
||||
v = src
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// entryBlk returns the entry block of the function.
|
||||
func (b *builder) entryBlk() *basicBlock {
|
||||
return b.basicBlocksPool.View(0)
|
||||
}
|
||||
|
||||
// isDominatedBy returns true if the given block `n` is dominated by the given block `d`.
|
||||
// Before calling this, the builder must pass by passCalculateImmediateDominators.
|
||||
func (b *builder) isDominatedBy(n *basicBlock, d *basicBlock) bool {
|
||||
if len(b.dominators) == 0 {
|
||||
panic("BUG: passCalculateImmediateDominators must be called before calling isDominatedBy")
|
||||
}
|
||||
ent := b.entryBlk()
|
||||
doms := b.dominators
|
||||
for n != d && n != ent {
|
||||
n = doms[n.id]
|
||||
}
|
||||
return n == d
|
||||
}
|
||||
|
||||
// BlockIDMax implements Builder.BlockIDMax.
|
||||
func (b *builder) BlockIDMax() BasicBlockID {
|
||||
return BasicBlockID(b.basicBlocksPool.Allocated())
|
||||
}
|
||||
|
||||
// InsertUndefined implements Builder.InsertUndefined.
|
||||
func (b *builder) InsertUndefined() {
|
||||
instr := b.AllocateInstruction()
|
||||
instr.opcode = OpcodeUndefined
|
||||
b.InsertInstruction(instr)
|
||||
}
|
||||
|
||||
// LoopNestingForestRoots implements Builder.LoopNestingForestRoots.
|
||||
func (b *builder) LoopNestingForestRoots() []BasicBlock {
|
||||
return b.loopNestingForestRoots
|
||||
}
|
||||
|
||||
// LowestCommonAncestor implements Builder.LowestCommonAncestor.
|
||||
func (b *builder) LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock {
|
||||
return b.sparseTree.findLCA(blk1.ID(), blk2.ID())
|
||||
}
|
107
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/cmp.go
generated
vendored
Normal file
107
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/cmp.go
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
package ssa
|
||||
|
||||
// IntegerCmpCond represents a condition for integer comparison.
|
||||
type IntegerCmpCond byte
|
||||
|
||||
const (
|
||||
// IntegerCmpCondInvalid represents an invalid condition.
|
||||
IntegerCmpCondInvalid IntegerCmpCond = iota
|
||||
// IntegerCmpCondEqual represents "==".
|
||||
IntegerCmpCondEqual
|
||||
// IntegerCmpCondNotEqual represents "!=".
|
||||
IntegerCmpCondNotEqual
|
||||
// IntegerCmpCondSignedLessThan represents Signed "<".
|
||||
IntegerCmpCondSignedLessThan
|
||||
// IntegerCmpCondSignedGreaterThanOrEqual represents Signed ">=".
|
||||
IntegerCmpCondSignedGreaterThanOrEqual
|
||||
// IntegerCmpCondSignedGreaterThan represents Signed ">".
|
||||
IntegerCmpCondSignedGreaterThan
|
||||
// IntegerCmpCondSignedLessThanOrEqual represents Signed "<=".
|
||||
IntegerCmpCondSignedLessThanOrEqual
|
||||
// IntegerCmpCondUnsignedLessThan represents Unsigned "<".
|
||||
IntegerCmpCondUnsignedLessThan
|
||||
// IntegerCmpCondUnsignedGreaterThanOrEqual represents Unsigned ">=".
|
||||
IntegerCmpCondUnsignedGreaterThanOrEqual
|
||||
// IntegerCmpCondUnsignedGreaterThan represents Unsigned ">".
|
||||
IntegerCmpCondUnsignedGreaterThan
|
||||
// IntegerCmpCondUnsignedLessThanOrEqual represents Unsigned "<=".
|
||||
IntegerCmpCondUnsignedLessThanOrEqual
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (i IntegerCmpCond) String() string {
|
||||
switch i {
|
||||
case IntegerCmpCondEqual:
|
||||
return "eq"
|
||||
case IntegerCmpCondNotEqual:
|
||||
return "neq"
|
||||
case IntegerCmpCondSignedLessThan:
|
||||
return "lt_s"
|
||||
case IntegerCmpCondSignedGreaterThanOrEqual:
|
||||
return "ge_s"
|
||||
case IntegerCmpCondSignedGreaterThan:
|
||||
return "gt_s"
|
||||
case IntegerCmpCondSignedLessThanOrEqual:
|
||||
return "le_s"
|
||||
case IntegerCmpCondUnsignedLessThan:
|
||||
return "lt_u"
|
||||
case IntegerCmpCondUnsignedGreaterThanOrEqual:
|
||||
return "ge_u"
|
||||
case IntegerCmpCondUnsignedGreaterThan:
|
||||
return "gt_u"
|
||||
case IntegerCmpCondUnsignedLessThanOrEqual:
|
||||
return "le_u"
|
||||
default:
|
||||
panic("invalid integer comparison condition")
|
||||
}
|
||||
}
|
||||
|
||||
// Signed returns true if the condition is signed integer comparison.
|
||||
func (i IntegerCmpCond) Signed() bool {
|
||||
switch i {
|
||||
case IntegerCmpCondSignedLessThan, IntegerCmpCondSignedGreaterThanOrEqual,
|
||||
IntegerCmpCondSignedGreaterThan, IntegerCmpCondSignedLessThanOrEqual:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type FloatCmpCond byte
|
||||
|
||||
const (
|
||||
// FloatCmpCondInvalid represents an invalid condition.
|
||||
FloatCmpCondInvalid FloatCmpCond = iota
|
||||
// FloatCmpCondEqual represents "==".
|
||||
FloatCmpCondEqual
|
||||
// FloatCmpCondNotEqual represents "!=".
|
||||
FloatCmpCondNotEqual
|
||||
// FloatCmpCondLessThan represents "<".
|
||||
FloatCmpCondLessThan
|
||||
// FloatCmpCondLessThanOrEqual represents "<=".
|
||||
FloatCmpCondLessThanOrEqual
|
||||
// FloatCmpCondGreaterThan represents ">".
|
||||
FloatCmpCondGreaterThan
|
||||
// FloatCmpCondGreaterThanOrEqual represents ">=".
|
||||
FloatCmpCondGreaterThanOrEqual
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (f FloatCmpCond) String() string {
|
||||
switch f {
|
||||
case FloatCmpCondEqual:
|
||||
return "eq"
|
||||
case FloatCmpCondNotEqual:
|
||||
return "neq"
|
||||
case FloatCmpCondLessThan:
|
||||
return "lt"
|
||||
case FloatCmpCondLessThanOrEqual:
|
||||
return "le"
|
||||
case FloatCmpCondGreaterThan:
|
||||
return "gt"
|
||||
case FloatCmpCondGreaterThanOrEqual:
|
||||
return "ge"
|
||||
default:
|
||||
panic("invalid float comparison condition")
|
||||
}
|
||||
}
|
12
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/funcref.go
generated
vendored
Normal file
12
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/funcref.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
package ssa
|
||||
|
||||
import "fmt"
|
||||
|
||||
// FuncRef is a unique identifier for a function of the frontend,
|
||||
// and is used to reference the function in function call.
|
||||
type FuncRef uint32
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (r FuncRef) String() string {
|
||||
return fmt.Sprintf("f%d", r)
|
||||
}
|
2967
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/instructions.go
generated
vendored
Normal file
2967
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/instructions.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
417
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass.go
generated
vendored
Normal file
417
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass.go
generated
vendored
Normal file
@ -0,0 +1,417 @@
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
||||
)
|
||||
|
||||
// RunPasses implements Builder.RunPasses.
|
||||
//
|
||||
// The order here matters; some pass depends on the previous ones.
|
||||
//
|
||||
// Note that passes suffixed with "Opt" are the optimization passes, meaning that they edit the instructions and blocks
|
||||
// while the other passes are not, like passEstimateBranchProbabilities does not edit them, but only calculates the additional information.
|
||||
func (b *builder) RunPasses() {
|
||||
b.runPreBlockLayoutPasses()
|
||||
b.runBlockLayoutPass()
|
||||
b.runPostBlockLayoutPasses()
|
||||
b.runFinalizingPasses()
|
||||
}
|
||||
|
||||
func (b *builder) runPreBlockLayoutPasses() {
|
||||
passSortSuccessors(b)
|
||||
passDeadBlockEliminationOpt(b)
|
||||
passRedundantPhiEliminationOpt(b)
|
||||
// The result of passCalculateImmediateDominators will be used by various passes below.
|
||||
passCalculateImmediateDominators(b)
|
||||
passNopInstElimination(b)
|
||||
|
||||
// TODO: implement either conversion of irreducible CFG into reducible one, or irreducible CFG detection where we panic.
|
||||
// WebAssembly program shouldn't result in irreducible CFG, but we should handle it properly in just in case.
|
||||
// See FixIrreducible pass in LLVM: https://llvm.org/doxygen/FixIrreducible_8cpp_source.html
|
||||
|
||||
// TODO: implement more optimization passes like:
|
||||
// block coalescing.
|
||||
// Copy-propagation.
|
||||
// Constant folding.
|
||||
// Common subexpression elimination.
|
||||
// Arithmetic simplifications.
|
||||
// and more!
|
||||
|
||||
// passDeadCodeEliminationOpt could be more accurate if we do this after other optimizations.
|
||||
passDeadCodeEliminationOpt(b)
|
||||
b.donePreBlockLayoutPasses = true
|
||||
}
|
||||
|
||||
func (b *builder) runBlockLayoutPass() {
|
||||
if !b.donePreBlockLayoutPasses {
|
||||
panic("runBlockLayoutPass must be called after all pre passes are done")
|
||||
}
|
||||
passLayoutBlocks(b)
|
||||
b.doneBlockLayout = true
|
||||
}
|
||||
|
||||
// runPostBlockLayoutPasses runs the post block layout passes. After this point, CFG is somewhat stable,
|
||||
// but still can be modified before finalizing passes. At this point, critical edges are split by passLayoutBlocks.
|
||||
func (b *builder) runPostBlockLayoutPasses() {
|
||||
if !b.doneBlockLayout {
|
||||
panic("runPostBlockLayoutPasses must be called after block layout pass is done")
|
||||
}
|
||||
// TODO: Do more. e.g. tail duplication, loop unrolling, etc.
|
||||
|
||||
b.donePostBlockLayoutPasses = true
|
||||
}
|
||||
|
||||
// runFinalizingPasses runs the finalizing passes. After this point, CFG should not be modified.
|
||||
func (b *builder) runFinalizingPasses() {
|
||||
if !b.donePostBlockLayoutPasses {
|
||||
panic("runFinalizingPasses must be called after post block layout passes are done")
|
||||
}
|
||||
// Critical edges are split, so we fix the loop nesting forest.
|
||||
passBuildLoopNestingForest(b)
|
||||
passBuildDominatorTree(b)
|
||||
// Now that we know the final placement of the blocks, we can explicitly mark the fallthrough jumps.
|
||||
b.markFallthroughJumps()
|
||||
}
|
||||
|
||||
// passDeadBlockEliminationOpt searches the unreachable blocks, and sets the basicBlock.invalid flag true if so.
|
||||
func passDeadBlockEliminationOpt(b *builder) {
|
||||
entryBlk := b.entryBlk()
|
||||
b.clearBlkVisited()
|
||||
b.blkStack = append(b.blkStack, entryBlk)
|
||||
for len(b.blkStack) > 0 {
|
||||
reachableBlk := b.blkStack[len(b.blkStack)-1]
|
||||
b.blkStack = b.blkStack[:len(b.blkStack)-1]
|
||||
b.blkVisited[reachableBlk] = 0 // the value won't be used in this pass.
|
||||
|
||||
if !reachableBlk.sealed && !reachableBlk.ReturnBlock() {
|
||||
panic(fmt.Sprintf("%s is not sealed", reachableBlk))
|
||||
}
|
||||
|
||||
if wazevoapi.SSAValidationEnabled {
|
||||
reachableBlk.validate(b)
|
||||
}
|
||||
|
||||
for _, succ := range reachableBlk.success {
|
||||
if _, ok := b.blkVisited[succ]; ok {
|
||||
continue
|
||||
}
|
||||
b.blkStack = append(b.blkStack, succ)
|
||||
}
|
||||
}
|
||||
|
||||
for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() {
|
||||
if _, ok := b.blkVisited[blk]; !ok {
|
||||
blk.invalid = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// passRedundantPhiEliminationOpt eliminates the redundant PHIs (in our terminology, parameters of a block).
|
||||
func passRedundantPhiEliminationOpt(b *builder) {
|
||||
redundantParameterIndexes := b.ints[:0] // reuse the slice from previous iterations.
|
||||
|
||||
// TODO: this might be costly for large programs, but at least, as far as I did the experiment, it's almost the
|
||||
// same as the single iteration version in terms of the overall compilation time. That *might be* mostly thanks to the fact
|
||||
// that removing many PHIs results in the reduction of the total instructions, not because of this indefinite iteration is
|
||||
// relatively small. For example, sqlite speedtest binary results in the large number of redundant PHIs,
|
||||
// the maximum number of iteration was 22, which seems to be acceptable but not that small either since the
|
||||
// complexity here is O(BlockNum * Iterations) at the worst case where BlockNum might be the order of thousands.
|
||||
for {
|
||||
changed := false
|
||||
_ = b.blockIteratorBegin() // skip entry block!
|
||||
// Below, we intentionally use the named iteration variable name, as this comes with inevitable nested for loops!
|
||||
for blk := b.blockIteratorNext(); blk != nil; blk = b.blockIteratorNext() {
|
||||
paramNum := len(blk.params)
|
||||
|
||||
for paramIndex := 0; paramIndex < paramNum; paramIndex++ {
|
||||
phiValue := blk.params[paramIndex].value
|
||||
redundant := true
|
||||
|
||||
nonSelfReferencingValue := ValueInvalid
|
||||
for predIndex := range blk.preds {
|
||||
br := blk.preds[predIndex].branch
|
||||
// Resolve the alias in the arguments so that we could use the previous iteration's result.
|
||||
b.resolveArgumentAlias(br)
|
||||
pred := br.vs.View()[paramIndex]
|
||||
if pred == phiValue {
|
||||
// This is self-referencing: PHI from the same PHI.
|
||||
continue
|
||||
}
|
||||
|
||||
if !nonSelfReferencingValue.Valid() {
|
||||
nonSelfReferencingValue = pred
|
||||
continue
|
||||
}
|
||||
|
||||
if nonSelfReferencingValue != pred {
|
||||
redundant = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !nonSelfReferencingValue.Valid() {
|
||||
// This shouldn't happen, and must be a bug in builder.go.
|
||||
panic("BUG: params added but only self-referencing")
|
||||
}
|
||||
|
||||
if redundant {
|
||||
b.redundantParameterIndexToValue[paramIndex] = nonSelfReferencingValue
|
||||
redundantParameterIndexes = append(redundantParameterIndexes, paramIndex)
|
||||
}
|
||||
}
|
||||
|
||||
if len(b.redundantParameterIndexToValue) == 0 {
|
||||
continue
|
||||
}
|
||||
changed = true
|
||||
|
||||
// Remove the redundant PHIs from the argument list of branching instructions.
|
||||
for predIndex := range blk.preds {
|
||||
var cur int
|
||||
predBlk := blk.preds[predIndex]
|
||||
branchInst := predBlk.branch
|
||||
view := branchInst.vs.View()
|
||||
for argIndex, value := range view {
|
||||
if _, ok := b.redundantParameterIndexToValue[argIndex]; !ok {
|
||||
view[cur] = value
|
||||
cur++
|
||||
}
|
||||
}
|
||||
branchInst.vs.Cut(cur)
|
||||
}
|
||||
|
||||
// Still need to have the definition of the value of the PHI (previously as the parameter).
|
||||
for _, redundantParamIndex := range redundantParameterIndexes {
|
||||
phiValue := blk.params[redundantParamIndex].value
|
||||
onlyValue := b.redundantParameterIndexToValue[redundantParamIndex]
|
||||
// Create an alias in this block from the only phi argument to the phi value.
|
||||
b.alias(phiValue, onlyValue)
|
||||
}
|
||||
|
||||
// Finally, Remove the param from the blk.
|
||||
var cur int
|
||||
for paramIndex := 0; paramIndex < paramNum; paramIndex++ {
|
||||
param := blk.params[paramIndex]
|
||||
if _, ok := b.redundantParameterIndexToValue[paramIndex]; !ok {
|
||||
blk.params[cur] = param
|
||||
cur++
|
||||
}
|
||||
}
|
||||
blk.params = blk.params[:cur]
|
||||
|
||||
// Clears the map for the next iteration.
|
||||
for _, paramIndex := range redundantParameterIndexes {
|
||||
delete(b.redundantParameterIndexToValue, paramIndex)
|
||||
}
|
||||
redundantParameterIndexes = redundantParameterIndexes[:0]
|
||||
}
|
||||
|
||||
if !changed {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse the slice for the future passes.
|
||||
b.ints = redundantParameterIndexes
|
||||
}
|
||||
|
||||
// passDeadCodeEliminationOpt traverses all the instructions, and calculates the reference count of each Value, and
|
||||
// eliminates all the unnecessary instructions whose ref count is zero.
|
||||
// The results are stored at builder.valueRefCounts. This also assigns a InstructionGroupID to each Instruction
|
||||
// during the process. This is the last SSA-level optimization pass and after this,
|
||||
// the SSA function is ready to be used by backends.
|
||||
//
|
||||
// TODO: the algorithm here might not be efficient. Get back to this later.
|
||||
func passDeadCodeEliminationOpt(b *builder) {
|
||||
nvid := int(b.nextValueID)
|
||||
if nvid >= len(b.valueRefCounts) {
|
||||
b.valueRefCounts = append(b.valueRefCounts, make([]int, b.nextValueID)...)
|
||||
}
|
||||
if nvid >= len(b.valueIDToInstruction) {
|
||||
b.valueIDToInstruction = append(b.valueIDToInstruction, make([]*Instruction, b.nextValueID)...)
|
||||
}
|
||||
|
||||
// First, we gather all the instructions with side effects.
|
||||
liveInstructions := b.instStack[:0]
|
||||
// During the process, we will assign InstructionGroupID to each instruction, which is not
|
||||
// relevant to dead code elimination, but we need in the backend.
|
||||
var gid InstructionGroupID
|
||||
for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() {
|
||||
for cur := blk.rootInstr; cur != nil; cur = cur.next {
|
||||
cur.gid = gid
|
||||
switch cur.sideEffect() {
|
||||
case sideEffectTraps:
|
||||
// The trappable should always be alive.
|
||||
liveInstructions = append(liveInstructions, cur)
|
||||
case sideEffectStrict:
|
||||
liveInstructions = append(liveInstructions, cur)
|
||||
// The strict side effect should create different instruction groups.
|
||||
gid++
|
||||
}
|
||||
|
||||
r1, rs := cur.Returns()
|
||||
if r1.Valid() {
|
||||
b.valueIDToInstruction[r1.ID()] = cur
|
||||
}
|
||||
for _, r := range rs {
|
||||
b.valueIDToInstruction[r.ID()] = cur
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all the instructions referenced by live instructions transitively.
|
||||
for len(liveInstructions) > 0 {
|
||||
tail := len(liveInstructions) - 1
|
||||
live := liveInstructions[tail]
|
||||
liveInstructions = liveInstructions[:tail]
|
||||
if live.live {
|
||||
// If it's already marked alive, this is referenced multiple times,
|
||||
// so we can skip it.
|
||||
continue
|
||||
}
|
||||
live.live = true
|
||||
|
||||
// Before we walk, we need to resolve the alias first.
|
||||
b.resolveArgumentAlias(live)
|
||||
|
||||
v1, v2, v3, vs := live.Args()
|
||||
if v1.Valid() {
|
||||
producingInst := b.valueIDToInstruction[v1.ID()]
|
||||
if producingInst != nil {
|
||||
liveInstructions = append(liveInstructions, producingInst)
|
||||
}
|
||||
}
|
||||
|
||||
if v2.Valid() {
|
||||
producingInst := b.valueIDToInstruction[v2.ID()]
|
||||
if producingInst != nil {
|
||||
liveInstructions = append(liveInstructions, producingInst)
|
||||
}
|
||||
}
|
||||
|
||||
if v3.Valid() {
|
||||
producingInst := b.valueIDToInstruction[v3.ID()]
|
||||
if producingInst != nil {
|
||||
liveInstructions = append(liveInstructions, producingInst)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range vs {
|
||||
producingInst := b.valueIDToInstruction[v.ID()]
|
||||
if producingInst != nil {
|
||||
liveInstructions = append(liveInstructions, producingInst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that all the live instructions are flagged as live=true, we eliminate all dead instructions.
|
||||
for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() {
|
||||
for cur := blk.rootInstr; cur != nil; cur = cur.next {
|
||||
if !cur.live {
|
||||
// Remove the instruction from the list.
|
||||
if prev := cur.prev; prev != nil {
|
||||
prev.next = cur.next
|
||||
} else {
|
||||
blk.rootInstr = cur.next
|
||||
}
|
||||
if next := cur.next; next != nil {
|
||||
next.prev = cur.prev
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If the value alive, we can be sure that arguments are used definitely.
|
||||
// Hence, we can increment the value reference counts.
|
||||
v1, v2, v3, vs := cur.Args()
|
||||
if v1.Valid() {
|
||||
b.incRefCount(v1.ID(), cur)
|
||||
}
|
||||
if v2.Valid() {
|
||||
b.incRefCount(v2.ID(), cur)
|
||||
}
|
||||
if v3.Valid() {
|
||||
b.incRefCount(v3.ID(), cur)
|
||||
}
|
||||
for _, v := range vs {
|
||||
b.incRefCount(v.ID(), cur)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.instStack = liveInstructions // we reuse the stack for the next iteration.
|
||||
}
|
||||
|
||||
func (b *builder) incRefCount(id ValueID, from *Instruction) {
|
||||
if wazevoapi.SSALoggingEnabled {
|
||||
fmt.Printf("v%d referenced from %v\n", id, from.Format(b))
|
||||
}
|
||||
b.valueRefCounts[id]++
|
||||
}
|
||||
|
||||
// clearBlkVisited clears the b.blkVisited map so that we can reuse it for multiple places.
|
||||
func (b *builder) clearBlkVisited() {
|
||||
b.blkStack2 = b.blkStack2[:0]
|
||||
for key := range b.blkVisited {
|
||||
b.blkStack2 = append(b.blkStack2, key)
|
||||
}
|
||||
for _, blk := range b.blkStack2 {
|
||||
delete(b.blkVisited, blk)
|
||||
}
|
||||
b.blkStack2 = b.blkStack2[:0]
|
||||
}
|
||||
|
||||
// passNopInstElimination eliminates the instructions which is essentially a no-op.
|
||||
func passNopInstElimination(b *builder) {
|
||||
if int(b.nextValueID) >= len(b.valueIDToInstruction) {
|
||||
b.valueIDToInstruction = append(b.valueIDToInstruction, make([]*Instruction, b.nextValueID)...)
|
||||
}
|
||||
|
||||
for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() {
|
||||
for cur := blk.rootInstr; cur != nil; cur = cur.next {
|
||||
r1, rs := cur.Returns()
|
||||
if r1.Valid() {
|
||||
b.valueIDToInstruction[r1.ID()] = cur
|
||||
}
|
||||
for _, r := range rs {
|
||||
b.valueIDToInstruction[r.ID()] = cur
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() {
|
||||
for cur := blk.rootInstr; cur != nil; cur = cur.next {
|
||||
switch cur.Opcode() {
|
||||
// TODO: add more logics here.
|
||||
case OpcodeIshl, OpcodeSshr, OpcodeUshr:
|
||||
x, amount := cur.Arg2()
|
||||
definingInst := b.valueIDToInstruction[amount.ID()]
|
||||
if definingInst == nil {
|
||||
// If there's no defining instruction, that means the amount is coming from the parameter.
|
||||
continue
|
||||
}
|
||||
if definingInst.Constant() {
|
||||
v := definingInst.ConstantVal()
|
||||
|
||||
if x.Type().Bits() == 64 {
|
||||
v = v % 64
|
||||
} else {
|
||||
v = v % 32
|
||||
}
|
||||
if v == 0 {
|
||||
b.alias(cur.Return(), x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// passSortSuccessors sorts the successors of each block in the natural program order.
|
||||
func passSortSuccessors(b *builder) {
|
||||
for i := 0; i < b.basicBlocksPool.Allocated(); i++ {
|
||||
blk := b.basicBlocksPool.View(i)
|
||||
sortBlocks(blk.success)
|
||||
}
|
||||
}
|
335
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_blk_layouts.go
generated
vendored
Normal file
335
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_blk_layouts.go
generated
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
||||
)
|
||||
|
||||
// passLayoutBlocks implements Builder.LayoutBlocks. This re-organizes builder.reversePostOrderedBasicBlocks.
|
||||
//
|
||||
// TODO: there are tons of room for improvement here. e.g. LLVM has BlockPlacementPass using BlockFrequencyInfo,
|
||||
// BranchProbabilityInfo, and LoopInfo to do a much better job. Also, if we have the profiling instrumentation
|
||||
// like ball-larus algorithm, then we could do profile-guided optimization. Basically all of them are trying
|
||||
// to maximize the fall-through opportunities which is most efficient.
|
||||
//
|
||||
// Here, fallthrough happens when a block ends with jump instruction whose target is the right next block in the
|
||||
// builder.reversePostOrderedBasicBlocks.
|
||||
//
|
||||
// Currently, we just place blocks using the DFS reverse post-order of the dominator tree with the heuristics:
|
||||
// 1. a split edge trampoline towards a loop header will be placed as a fallthrough.
|
||||
// 2. we invert the brz and brnz if it makes the fallthrough more likely.
|
||||
//
|
||||
// This heuristic is done in maybeInvertBranches function.
|
||||
func passLayoutBlocks(b *builder) {
|
||||
b.clearBlkVisited()
|
||||
|
||||
// We might end up splitting critical edges which adds more basic blocks,
|
||||
// so we store the currently existing basic blocks in nonSplitBlocks temporarily.
|
||||
// That way we can iterate over the original basic blocks while appending new ones into reversePostOrderedBasicBlocks.
|
||||
nonSplitBlocks := b.blkStack[:0]
|
||||
for i, blk := range b.reversePostOrderedBasicBlocks {
|
||||
if !blk.Valid() {
|
||||
continue
|
||||
}
|
||||
nonSplitBlocks = append(nonSplitBlocks, blk)
|
||||
if i != len(b.reversePostOrderedBasicBlocks)-1 {
|
||||
_ = maybeInvertBranches(blk, b.reversePostOrderedBasicBlocks[i+1])
|
||||
}
|
||||
}
|
||||
|
||||
var trampolines []*basicBlock
|
||||
|
||||
// Reset the order slice since we update on the fly by splitting critical edges.
|
||||
b.reversePostOrderedBasicBlocks = b.reversePostOrderedBasicBlocks[:0]
|
||||
uninsertedTrampolines := b.blkStack2[:0]
|
||||
for _, blk := range nonSplitBlocks {
|
||||
for i := range blk.preds {
|
||||
pred := blk.preds[i].blk
|
||||
if _, ok := b.blkVisited[pred]; ok || !pred.Valid() {
|
||||
continue
|
||||
} else if pred.reversePostOrder < blk.reversePostOrder {
|
||||
// This means the edge is critical, and this pred is the trampoline and yet to be inserted.
|
||||
// Split edge trampolines must come before the destination in reverse post-order.
|
||||
b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, pred)
|
||||
b.blkVisited[pred] = 0 // mark as inserted, the value is not used.
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we've already added all the potential trampoline blocks incoming to this block,
|
||||
// we can add this block itself.
|
||||
b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, blk)
|
||||
b.blkVisited[blk] = 0 // mark as inserted, the value is not used.
|
||||
|
||||
if len(blk.success) < 2 {
|
||||
// There won't be critical edge originating from this block.
|
||||
continue
|
||||
} else if blk.currentInstr.opcode == OpcodeBrTable {
|
||||
// We don't split critical edges here, because at the construction site of BrTable, we already split the edges.
|
||||
continue
|
||||
}
|
||||
|
||||
for sidx, succ := range blk.success {
|
||||
if !succ.ReturnBlock() && // If the successor is a return block, we need to split the edge any way because we need "epilogue" to be inserted.
|
||||
// Plus if there's no multiple incoming edges to this successor, (pred, succ) is not critical.
|
||||
len(succ.preds) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we are sure this is a critical edge. To modify the CFG, we need to find the predecessor info
|
||||
// from the successor.
|
||||
var predInfo *basicBlockPredecessorInfo
|
||||
for i := range succ.preds { // This linear search should not be a problem since the number of predecessors should almost always small.
|
||||
pred := &succ.preds[i]
|
||||
if pred.blk == blk {
|
||||
predInfo = pred
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if predInfo == nil {
|
||||
// This must be a bug in somewhere around branch manipulation.
|
||||
panic("BUG: predecessor info not found while the successor exists in successors list")
|
||||
}
|
||||
|
||||
if wazevoapi.SSALoggingEnabled {
|
||||
fmt.Printf("trying to split edge from %d->%d at %s\n",
|
||||
blk.ID(), succ.ID(), predInfo.branch.Format(b))
|
||||
}
|
||||
|
||||
trampoline := b.splitCriticalEdge(blk, succ, predInfo)
|
||||
// Update the successors slice because the target is no longer the original `succ`.
|
||||
blk.success[sidx] = trampoline
|
||||
|
||||
if wazevoapi.SSAValidationEnabled {
|
||||
trampolines = append(trampolines, trampoline)
|
||||
}
|
||||
|
||||
if wazevoapi.SSALoggingEnabled {
|
||||
fmt.Printf("edge split from %d->%d at %s as %d->%d->%d \n",
|
||||
blk.ID(), succ.ID(), predInfo.branch.Format(b),
|
||||
blk.ID(), trampoline.ID(), succ.ID())
|
||||
}
|
||||
|
||||
fallthroughBranch := blk.currentInstr
|
||||
if fallthroughBranch.opcode == OpcodeJump && fallthroughBranch.blk == trampoline {
|
||||
// This can be lowered as fallthrough at the end of the block.
|
||||
b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, trampoline)
|
||||
b.blkVisited[trampoline] = 0 // mark as inserted, the value is not used.
|
||||
} else {
|
||||
uninsertedTrampolines = append(uninsertedTrampolines, trampoline)
|
||||
}
|
||||
}
|
||||
|
||||
for _, trampoline := range uninsertedTrampolines {
|
||||
if trampoline.success[0].reversePostOrder <= trampoline.reversePostOrder { // "<=", not "<" because the target might be itself.
|
||||
// This means the critical edge was backward, so we insert after the current block immediately.
|
||||
b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, trampoline)
|
||||
b.blkVisited[trampoline] = 0 // mark as inserted, the value is not used.
|
||||
} // If the target is forward, we can wait to insert until the target is inserted.
|
||||
}
|
||||
uninsertedTrampolines = uninsertedTrampolines[:0] // Reuse the stack for the next block.
|
||||
}
|
||||
|
||||
if wazevoapi.SSALoggingEnabled {
|
||||
var bs []string
|
||||
for _, blk := range b.reversePostOrderedBasicBlocks {
|
||||
bs = append(bs, blk.Name())
|
||||
}
|
||||
fmt.Println("ordered blocks: ", strings.Join(bs, ", "))
|
||||
}
|
||||
|
||||
if wazevoapi.SSAValidationEnabled {
|
||||
for _, trampoline := range trampolines {
|
||||
if _, ok := b.blkVisited[trampoline]; !ok {
|
||||
panic("BUG: trampoline block not inserted: " + trampoline.FormatHeader(b))
|
||||
}
|
||||
trampoline.validate(b)
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse the stack for the next iteration.
|
||||
b.blkStack2 = uninsertedTrampolines[:0]
|
||||
}
|
||||
|
||||
// markFallthroughJumps finds the fallthrough jumps and marks them as such.
|
||||
func (b *builder) markFallthroughJumps() {
|
||||
l := len(b.reversePostOrderedBasicBlocks) - 1
|
||||
for i, blk := range b.reversePostOrderedBasicBlocks {
|
||||
if i < l {
|
||||
cur := blk.currentInstr
|
||||
if cur.opcode == OpcodeJump && cur.blk == b.reversePostOrderedBasicBlocks[i+1] {
|
||||
cur.AsFallthroughJump()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// maybeInvertBranches inverts the branch instructions if it is likely possible to the fallthrough more likely with simple heuristics.
|
||||
// nextInRPO is the next block in the reverse post-order.
|
||||
//
|
||||
// Returns true if the branch is inverted for testing purpose.
|
||||
func maybeInvertBranches(now *basicBlock, nextInRPO *basicBlock) bool {
|
||||
fallthroughBranch := now.currentInstr
|
||||
if fallthroughBranch.opcode == OpcodeBrTable {
|
||||
return false
|
||||
}
|
||||
|
||||
condBranch := fallthroughBranch.prev
|
||||
if condBranch == nil || (condBranch.opcode != OpcodeBrnz && condBranch.opcode != OpcodeBrz) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(fallthroughBranch.vs.View()) != 0 || len(condBranch.vs.View()) != 0 {
|
||||
// If either one of them has arguments, we don't invert the branches.
|
||||
return false
|
||||
}
|
||||
|
||||
// So this block has two branches (a conditional branch followed by an unconditional branch) at the end.
|
||||
// We can invert the condition of the branch if it makes the fallthrough more likely.
|
||||
|
||||
fallthroughTarget, condTarget := fallthroughBranch.blk.(*basicBlock), condBranch.blk.(*basicBlock)
|
||||
|
||||
if fallthroughTarget.loopHeader {
|
||||
// First, if the tail's target is loopHeader, we don't need to do anything here,
|
||||
// because the edge is likely to be critical edge for complex loops (e.g. loop with branches inside it).
|
||||
// That means, we will split the edge in the end of LayoutBlocks function, and insert the trampoline block
|
||||
// right after this block, which will be fallthrough in any way.
|
||||
return false
|
||||
} else if condTarget.loopHeader {
|
||||
// On the other hand, if the condBranch's target is loopHeader, we invert the condition of the branch
|
||||
// so that we could get the fallthrough to the trampoline block.
|
||||
goto invert
|
||||
}
|
||||
|
||||
if fallthroughTarget == nextInRPO {
|
||||
// Also, if the tail's target is the next block in the reverse post-order, we don't need to do anything here,
|
||||
// because if this is not critical edge, we would end up placing these two blocks adjacent to each other.
|
||||
// Even if it is the critical edge, we place the trampoline block right after this block, which will be fallthrough in any way.
|
||||
return false
|
||||
} else if condTarget == nextInRPO {
|
||||
// If the condBranch's target is the next block in the reverse post-order, we invert the condition of the branch
|
||||
// so that we could get the fallthrough to the block.
|
||||
goto invert
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
invert:
|
||||
for i := range fallthroughTarget.preds {
|
||||
pred := &fallthroughTarget.preds[i]
|
||||
if pred.branch == fallthroughBranch {
|
||||
pred.branch = condBranch
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := range condTarget.preds {
|
||||
pred := &condTarget.preds[i]
|
||||
if pred.branch == condBranch {
|
||||
pred.branch = fallthroughBranch
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
condBranch.InvertBrx()
|
||||
condBranch.blk = fallthroughTarget
|
||||
fallthroughBranch.blk = condTarget
|
||||
if wazevoapi.SSALoggingEnabled {
|
||||
fmt.Printf("inverting branches at %d->%d and %d->%d\n",
|
||||
now.ID(), fallthroughTarget.ID(), now.ID(), condTarget.ID())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// splitCriticalEdge splits the critical edge between the given predecessor (`pred`) and successor (owning `predInfo`).
|
||||
//
|
||||
// - `pred` is the source of the critical edge,
|
||||
// - `succ` is the destination of the critical edge,
|
||||
// - `predInfo` is the predecessor info in the succ.preds slice which represents the critical edge.
|
||||
//
|
||||
// Why splitting critical edges is important? See following links:
|
||||
//
|
||||
// - https://en.wikipedia.org/wiki/Control-flow_graph
|
||||
// - https://nickdesaulniers.github.io/blog/2023/01/27/critical-edge-splitting/
|
||||
//
|
||||
// The returned basic block is the trampoline block which is inserted to split the critical edge.
|
||||
func (b *builder) splitCriticalEdge(pred, succ *basicBlock, predInfo *basicBlockPredecessorInfo) *basicBlock {
|
||||
// In the following, we convert the following CFG:
|
||||
//
|
||||
// pred --(originalBranch)--> succ
|
||||
//
|
||||
// to the following CFG:
|
||||
//
|
||||
// pred --(newBranch)--> trampoline --(originalBranch)-> succ
|
||||
//
|
||||
// where trampoline is a new basic block which is created to split the critical edge.
|
||||
|
||||
trampoline := b.allocateBasicBlock()
|
||||
if int(trampoline.id) >= len(b.dominators) {
|
||||
b.dominators = append(b.dominators, make([]*basicBlock, trampoline.id+1)...)
|
||||
}
|
||||
b.dominators[trampoline.id] = pred
|
||||
|
||||
originalBranch := predInfo.branch
|
||||
|
||||
// Replace originalBranch with the newBranch.
|
||||
newBranch := b.AllocateInstruction()
|
||||
newBranch.opcode = originalBranch.opcode
|
||||
newBranch.blk = trampoline
|
||||
switch originalBranch.opcode {
|
||||
case OpcodeJump:
|
||||
case OpcodeBrz, OpcodeBrnz:
|
||||
originalBranch.opcode = OpcodeJump // Trampoline consists of one unconditional branch.
|
||||
newBranch.v = originalBranch.v
|
||||
originalBranch.v = ValueInvalid
|
||||
default:
|
||||
panic("BUG: critical edge shouldn't be originated from br_table")
|
||||
}
|
||||
swapInstruction(pred, originalBranch, newBranch)
|
||||
|
||||
// Replace the original branch with the new branch.
|
||||
trampoline.rootInstr = originalBranch
|
||||
trampoline.currentInstr = originalBranch
|
||||
trampoline.success = append(trampoline.success, succ) // Do not use []*basicBlock{pred} because we might have already allocated the slice.
|
||||
trampoline.preds = append(trampoline.preds, // same as ^.
|
||||
basicBlockPredecessorInfo{blk: pred, branch: newBranch})
|
||||
b.Seal(trampoline)
|
||||
|
||||
// Update the original branch to point to the trampoline.
|
||||
predInfo.blk = trampoline
|
||||
predInfo.branch = originalBranch
|
||||
|
||||
if wazevoapi.SSAValidationEnabled {
|
||||
trampoline.validate(b)
|
||||
}
|
||||
|
||||
if len(trampoline.params) > 0 {
|
||||
panic("trampoline should not have params")
|
||||
}
|
||||
|
||||
// Assign the same order as the original block so that this will be placed before the actual destination.
|
||||
trampoline.reversePostOrder = pred.reversePostOrder
|
||||
return trampoline
|
||||
}
|
||||
|
||||
// swapInstruction replaces `old` in the block `blk` with `New`.
|
||||
func swapInstruction(blk *basicBlock, old, New *Instruction) {
|
||||
if blk.rootInstr == old {
|
||||
blk.rootInstr = New
|
||||
next := old.next
|
||||
New.next = next
|
||||
next.prev = New
|
||||
} else {
|
||||
if blk.currentInstr == old {
|
||||
blk.currentInstr = New
|
||||
}
|
||||
prev := old.prev
|
||||
prev.next, New.prev = New, prev
|
||||
if next := old.next; next != nil {
|
||||
New.next, next.prev = next, New
|
||||
}
|
||||
}
|
||||
old.prev, old.next = nil, nil
|
||||
}
|
312
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_cfg.go
generated
vendored
Normal file
312
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_cfg.go
generated
vendored
Normal file
@ -0,0 +1,312 @@
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
||||
)
|
||||
|
||||
// passCalculateImmediateDominators calculates immediate dominators for each basic block.
|
||||
// The result is stored in b.dominators. This make it possible for the following passes to
|
||||
// use builder.isDominatedBy to check if a block is dominated by another block.
|
||||
//
|
||||
// At the last of pass, this function also does the loop detection and sets the basicBlock.loop flag.
|
||||
func passCalculateImmediateDominators(b *builder) {
|
||||
reversePostOrder := b.reversePostOrderedBasicBlocks[:0]
|
||||
exploreStack := b.blkStack[:0]
|
||||
b.clearBlkVisited()
|
||||
|
||||
entryBlk := b.entryBlk()
|
||||
|
||||
// Store the reverse postorder from the entrypoint into reversePostOrder slice.
|
||||
// This calculation of reverse postorder is not described in the paper,
|
||||
// so we use heuristic to calculate it so that we could potentially handle arbitrary
|
||||
// complex CFGs under the assumption that success is sorted in program's natural order.
|
||||
// That means blk.success[i] always appears before blk.success[i+1] in the source program,
|
||||
// which is a reasonable assumption as long as SSA Builder is properly used.
|
||||
//
|
||||
// First we push blocks in postorder iteratively visit successors of the entry block.
|
||||
exploreStack = append(exploreStack, entryBlk)
|
||||
const visitStateUnseen, visitStateSeen, visitStateDone = 0, 1, 2
|
||||
b.blkVisited[entryBlk] = visitStateSeen
|
||||
for len(exploreStack) > 0 {
|
||||
tail := len(exploreStack) - 1
|
||||
blk := exploreStack[tail]
|
||||
exploreStack = exploreStack[:tail]
|
||||
switch b.blkVisited[blk] {
|
||||
case visitStateUnseen:
|
||||
// This is likely a bug in the frontend.
|
||||
panic("BUG: unsupported CFG")
|
||||
case visitStateSeen:
|
||||
// This is the first time to pop this block, and we have to see the successors first.
|
||||
// So push this block again to the stack.
|
||||
exploreStack = append(exploreStack, blk)
|
||||
// And push the successors to the stack if necessary.
|
||||
for _, succ := range blk.success {
|
||||
if succ.ReturnBlock() || succ.invalid {
|
||||
continue
|
||||
}
|
||||
if b.blkVisited[succ] == visitStateUnseen {
|
||||
b.blkVisited[succ] = visitStateSeen
|
||||
exploreStack = append(exploreStack, succ)
|
||||
}
|
||||
}
|
||||
// Finally, we could pop this block once we pop all of its successors.
|
||||
b.blkVisited[blk] = visitStateDone
|
||||
case visitStateDone:
|
||||
// Note: at this point we push blk in postorder despite its name.
|
||||
reversePostOrder = append(reversePostOrder, blk)
|
||||
}
|
||||
}
|
||||
// At this point, reversePostOrder has postorder actually, so we reverse it.
|
||||
for i := len(reversePostOrder)/2 - 1; i >= 0; i-- {
|
||||
j := len(reversePostOrder) - 1 - i
|
||||
reversePostOrder[i], reversePostOrder[j] = reversePostOrder[j], reversePostOrder[i]
|
||||
}
|
||||
|
||||
for i, blk := range reversePostOrder {
|
||||
blk.reversePostOrder = i
|
||||
}
|
||||
|
||||
// Reuse the dominators slice if possible from the previous computation of function.
|
||||
b.dominators = b.dominators[:cap(b.dominators)]
|
||||
if len(b.dominators) < b.basicBlocksPool.Allocated() {
|
||||
// Generously reserve space in the slice because the slice will be reused future allocation.
|
||||
b.dominators = append(b.dominators, make([]*basicBlock, b.basicBlocksPool.Allocated())...)
|
||||
}
|
||||
calculateDominators(reversePostOrder, b.dominators)
|
||||
|
||||
// Reuse the slices for the future use.
|
||||
b.blkStack = exploreStack
|
||||
|
||||
// For the following passes.
|
||||
b.reversePostOrderedBasicBlocks = reversePostOrder
|
||||
|
||||
// Ready to detect loops!
|
||||
subPassLoopDetection(b)
|
||||
}
|
||||
|
||||
// calculateDominators calculates the immediate dominator of each node in the CFG, and store the result in `doms`.
|
||||
// The algorithm is based on the one described in the paper "A Simple, Fast Dominance Algorithm"
|
||||
// https://www.cs.rice.edu/~keith/EMBED/dom.pdf which is a faster/simple alternative to the well known Lengauer-Tarjan algorithm.
|
||||
//
|
||||
// The following code almost matches the pseudocode in the paper with one exception (see the code comment below).
|
||||
//
|
||||
// The result slice `doms` must be pre-allocated with the size larger than the size of dfsBlocks.
|
||||
func calculateDominators(reversePostOrderedBlks []*basicBlock, doms []*basicBlock) {
|
||||
entry, reversePostOrderedBlks := reversePostOrderedBlks[0], reversePostOrderedBlks[1: /* skips entry point */]
|
||||
for _, blk := range reversePostOrderedBlks {
|
||||
doms[blk.id] = nil
|
||||
}
|
||||
doms[entry.id] = entry
|
||||
|
||||
changed := true
|
||||
for changed {
|
||||
changed = false
|
||||
for _, blk := range reversePostOrderedBlks {
|
||||
var u *basicBlock
|
||||
for i := range blk.preds {
|
||||
pred := blk.preds[i].blk
|
||||
// Skip if this pred is not reachable yet. Note that this is not described in the paper,
|
||||
// but it is necessary to handle nested loops etc.
|
||||
if doms[pred.id] == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
u = pred
|
||||
continue
|
||||
} else {
|
||||
u = intersect(doms, u, pred)
|
||||
}
|
||||
}
|
||||
if doms[blk.id] != u {
|
||||
doms[blk.id] = u
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// intersect returns the common dominator of blk1 and blk2.
|
||||
//
|
||||
// This is the `intersect` function in the paper.
|
||||
func intersect(doms []*basicBlock, blk1 *basicBlock, blk2 *basicBlock) *basicBlock {
|
||||
finger1, finger2 := blk1, blk2
|
||||
for finger1 != finger2 {
|
||||
// Move the 'finger1' upwards to its immediate dominator.
|
||||
for finger1.reversePostOrder > finger2.reversePostOrder {
|
||||
finger1 = doms[finger1.id]
|
||||
}
|
||||
// Move the 'finger2' upwards to its immediate dominator.
|
||||
for finger2.reversePostOrder > finger1.reversePostOrder {
|
||||
finger2 = doms[finger2.id]
|
||||
}
|
||||
}
|
||||
return finger1
|
||||
}
|
||||
|
||||
// subPassLoopDetection detects loops in the function using the immediate dominators.
|
||||
//
|
||||
// This is run at the last of passCalculateImmediateDominators.
|
||||
func subPassLoopDetection(b *builder) {
|
||||
for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() {
|
||||
for i := range blk.preds {
|
||||
pred := blk.preds[i].blk
|
||||
if pred.invalid {
|
||||
continue
|
||||
}
|
||||
if b.isDominatedBy(pred, blk) {
|
||||
blk.loopHeader = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildLoopNestingForest builds the loop nesting forest for the function.
|
||||
// This must be called after branch splitting since it relies on the CFG.
|
||||
func passBuildLoopNestingForest(b *builder) {
|
||||
ent := b.entryBlk()
|
||||
doms := b.dominators
|
||||
for _, blk := range b.reversePostOrderedBasicBlocks {
|
||||
n := doms[blk.id]
|
||||
for !n.loopHeader && n != ent {
|
||||
n = doms[n.id]
|
||||
}
|
||||
|
||||
if n == ent && blk.loopHeader {
|
||||
b.loopNestingForestRoots = append(b.loopNestingForestRoots, blk)
|
||||
} else if n == ent {
|
||||
} else if n.loopHeader {
|
||||
n.loopNestingForestChildren = append(n.loopNestingForestChildren, blk)
|
||||
}
|
||||
}
|
||||
|
||||
if wazevoapi.SSALoggingEnabled {
|
||||
for _, root := range b.loopNestingForestRoots {
|
||||
printLoopNestingForest(root.(*basicBlock), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printLoopNestingForest(root *basicBlock, depth int) {
|
||||
fmt.Println(strings.Repeat("\t", depth), "loop nesting forest root:", root.ID())
|
||||
for _, child := range root.loopNestingForestChildren {
|
||||
fmt.Println(strings.Repeat("\t", depth+1), "child:", child.ID())
|
||||
if child.LoopHeader() {
|
||||
printLoopNestingForest(child.(*basicBlock), depth+2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type dominatorSparseTree struct {
|
||||
time int
|
||||
euler []*basicBlock
|
||||
first, depth []int
|
||||
table [][]int
|
||||
}
|
||||
|
||||
// passBuildDominatorTree builds the dominator tree for the function, and constructs builder.sparseTree.
|
||||
func passBuildDominatorTree(b *builder) {
|
||||
// First we materialize the children of each node in the dominator tree.
|
||||
idoms := b.dominators
|
||||
for _, blk := range b.reversePostOrderedBasicBlocks {
|
||||
parent := idoms[blk.id]
|
||||
if parent == nil {
|
||||
panic("BUG")
|
||||
} else if parent == blk {
|
||||
// This is the entry block.
|
||||
continue
|
||||
}
|
||||
if prev := parent.child; prev == nil {
|
||||
parent.child = blk
|
||||
} else {
|
||||
parent.child = blk
|
||||
blk.sibling = prev
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the state from the previous computation.
|
||||
n := b.basicBlocksPool.Allocated()
|
||||
st := &b.sparseTree
|
||||
st.euler = append(st.euler[:0], make([]*basicBlock, 2*n-1)...)
|
||||
st.first = append(st.first[:0], make([]int, n)...)
|
||||
for i := range st.first {
|
||||
st.first[i] = -1
|
||||
}
|
||||
st.depth = append(st.depth[:0], make([]int, 2*n-1)...)
|
||||
st.time = 0
|
||||
|
||||
// Start building the sparse tree.
|
||||
st.eulerTour(b.entryBlk(), 0)
|
||||
st.buildSparseTable()
|
||||
}
|
||||
|
||||
func (dt *dominatorSparseTree) eulerTour(node *basicBlock, height int) {
|
||||
if wazevoapi.SSALoggingEnabled {
|
||||
fmt.Println(strings.Repeat("\t", height), "euler tour:", node.ID())
|
||||
}
|
||||
dt.euler[dt.time] = node
|
||||
dt.depth[dt.time] = height
|
||||
if dt.first[node.id] == -1 {
|
||||
dt.first[node.id] = dt.time
|
||||
}
|
||||
dt.time++
|
||||
|
||||
for child := node.child; child != nil; child = child.sibling {
|
||||
dt.eulerTour(child, height+1)
|
||||
dt.euler[dt.time] = node // add the current node again after visiting a child
|
||||
dt.depth[dt.time] = height
|
||||
dt.time++
|
||||
}
|
||||
}
|
||||
|
||||
// buildSparseTable builds a sparse table for RMQ queries.
|
||||
func (dt *dominatorSparseTree) buildSparseTable() {
|
||||
n := len(dt.depth)
|
||||
k := int(math.Log2(float64(n))) + 1
|
||||
table := dt.table
|
||||
|
||||
if n >= len(table) {
|
||||
table = append(table, make([][]int, n+1)...)
|
||||
}
|
||||
for i := range table {
|
||||
if len(table[i]) < k {
|
||||
table[i] = append(table[i], make([]int, k)...)
|
||||
}
|
||||
table[i][0] = i
|
||||
}
|
||||
|
||||
for j := 1; 1<<j <= n; j++ {
|
||||
for i := 0; i+(1<<j)-1 < n; i++ {
|
||||
if dt.depth[table[i][j-1]] < dt.depth[table[i+(1<<(j-1))][j-1]] {
|
||||
table[i][j] = table[i][j-1]
|
||||
} else {
|
||||
table[i][j] = table[i+(1<<(j-1))][j-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
dt.table = table
|
||||
}
|
||||
|
||||
// rmq performs a range minimum query on the sparse table.
|
||||
func (dt *dominatorSparseTree) rmq(l, r int) int {
|
||||
table := dt.table
|
||||
depth := dt.depth
|
||||
j := int(math.Log2(float64(r - l + 1)))
|
||||
if depth[table[l][j]] <= depth[table[r-(1<<j)+1][j]] {
|
||||
return table[l][j]
|
||||
}
|
||||
return table[r-(1<<j)+1][j]
|
||||
}
|
||||
|
||||
// findLCA finds the LCA using the Euler tour and RMQ.
|
||||
func (dt *dominatorSparseTree) findLCA(u, v BasicBlockID) *basicBlock {
|
||||
first := dt.first
|
||||
if first[u] > first[v] {
|
||||
u, v = v, u
|
||||
}
|
||||
return dt.euler[dt.rmq(first[u], first[v])]
|
||||
}
|
49
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/signature.go
generated
vendored
Normal file
49
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/signature.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Signature is a function prototype.
|
||||
type Signature struct {
|
||||
// ID is a unique identifier for this signature used to lookup.
|
||||
ID SignatureID
|
||||
// Params and Results are the types of the parameters and results of the function.
|
||||
Params, Results []Type
|
||||
|
||||
// used is true if this is used by the currently-compiled function.
|
||||
// Debugging only.
|
||||
used bool
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (s *Signature) String() string {
|
||||
str := strings.Builder{}
|
||||
str.WriteString(s.ID.String())
|
||||
str.WriteString(": ")
|
||||
if len(s.Params) > 0 {
|
||||
for _, typ := range s.Params {
|
||||
str.WriteString(typ.String())
|
||||
}
|
||||
} else {
|
||||
str.WriteByte('v')
|
||||
}
|
||||
str.WriteByte('_')
|
||||
if len(s.Results) > 0 {
|
||||
for _, typ := range s.Results {
|
||||
str.WriteString(typ.String())
|
||||
}
|
||||
} else {
|
||||
str.WriteByte('v')
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
// SignatureID is an unique identifier used to lookup.
|
||||
type SignatureID int
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (s SignatureID) String() string {
|
||||
return fmt.Sprintf("sig%d", s)
|
||||
}
|
14
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/ssa.go
generated
vendored
Normal file
14
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/ssa.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// Package ssa is used to construct SSA function. By nature this is free of Wasm specific thing
|
||||
// and ISA.
|
||||
//
|
||||
// We use the "block argument" variant of SSA: https://en.wikipedia.org/wiki/Static_single-assignment_form#Block_arguments
|
||||
// which is equivalent to the traditional PHI function based one, but more convenient during optimizations.
|
||||
// However, in this package's source code comment, we might use PHI whenever it seems necessary in order to be aligned with
|
||||
// existing literatures, e.g. SSA level optimization algorithms are often described using PHI nodes.
|
||||
//
|
||||
// The rationale doc for the choice of "block argument" by MLIR of LLVM is worth a read:
|
||||
// https://mlir.llvm.org/docs/Rationale/Rationale/#block-arguments-vs-phi-nodes
|
||||
//
|
||||
// The algorithm to resolve variable definitions used here is based on the paper
|
||||
// "Simple and Efficient Construction of Static Single Assignment Form": https://link.springer.com/content/pdf/10.1007/978-3-642-37051-9_6.pdf.
|
||||
package ssa
|
112
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/type.go
generated
vendored
Normal file
112
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/type.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
package ssa
|
||||
|
||||
type Type byte
|
||||
|
||||
const (
|
||||
typeInvalid Type = iota
|
||||
|
||||
// TODO: add 8, 16 bit types when it's needed for optimizations.
|
||||
|
||||
// TypeI32 represents an integer type with 32 bits.
|
||||
TypeI32
|
||||
|
||||
// TypeI64 represents an integer type with 64 bits.
|
||||
TypeI64
|
||||
|
||||
// TypeF32 represents 32-bit floats in the IEEE 754.
|
||||
TypeF32
|
||||
|
||||
// TypeF64 represents 64-bit floats in the IEEE 754.
|
||||
TypeF64
|
||||
|
||||
// TypeV128 represents 128-bit SIMD vectors.
|
||||
TypeV128
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (t Type) String() (ret string) {
|
||||
switch t {
|
||||
case typeInvalid:
|
||||
return "invalid"
|
||||
case TypeI32:
|
||||
return "i32"
|
||||
case TypeI64:
|
||||
return "i64"
|
||||
case TypeF32:
|
||||
return "f32"
|
||||
case TypeF64:
|
||||
return "f64"
|
||||
case TypeV128:
|
||||
return "v128"
|
||||
default:
|
||||
panic(int(t))
|
||||
}
|
||||
}
|
||||
|
||||
// IsInt returns true if the type is an integer type.
|
||||
func (t Type) IsInt() bool {
|
||||
return t == TypeI32 || t == TypeI64
|
||||
}
|
||||
|
||||
// IsFloat returns true if the type is a floating point type.
|
||||
func (t Type) IsFloat() bool {
|
||||
return t == TypeF32 || t == TypeF64
|
||||
}
|
||||
|
||||
// Bits returns the number of bits required to represent the type.
|
||||
func (t Type) Bits() byte {
|
||||
switch t {
|
||||
case TypeI32, TypeF32:
|
||||
return 32
|
||||
case TypeI64, TypeF64:
|
||||
return 64
|
||||
case TypeV128:
|
||||
return 128
|
||||
default:
|
||||
panic(int(t))
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the number of bytes required to represent the type.
|
||||
func (t Type) Size() byte {
|
||||
return t.Bits() / 8
|
||||
}
|
||||
|
||||
func (t Type) invalid() bool {
|
||||
return t == typeInvalid
|
||||
}
|
||||
|
||||
// VecLane represents a lane in a SIMD vector.
|
||||
type VecLane byte
|
||||
|
||||
const (
|
||||
VecLaneInvalid VecLane = 1 + iota
|
||||
VecLaneI8x16
|
||||
VecLaneI16x8
|
||||
VecLaneI32x4
|
||||
VecLaneI64x2
|
||||
VecLaneF32x4
|
||||
VecLaneF64x2
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (vl VecLane) String() (ret string) {
|
||||
switch vl {
|
||||
case VecLaneInvalid:
|
||||
return "invalid"
|
||||
case VecLaneI8x16:
|
||||
return "i8x16"
|
||||
case VecLaneI16x8:
|
||||
return "i16x8"
|
||||
case VecLaneI32x4:
|
||||
return "i32x4"
|
||||
case VecLaneI64x2:
|
||||
return "i64x2"
|
||||
case VecLaneF32x4:
|
||||
return "f32x4"
|
||||
case VecLaneF64x2:
|
||||
return "f64x2"
|
||||
default:
|
||||
panic(int(vl))
|
||||
}
|
||||
}
|
87
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/vs.go
generated
vendored
Normal file
87
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/vs.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
||||
)
|
||||
|
||||
// Variable is a unique identifier for a source program's variable and will correspond to
|
||||
// multiple ssa Value(s).
|
||||
//
|
||||
// For example, `Local 1` is a Variable in WebAssembly, and Value(s) will be created for it
|
||||
// whenever it executes `local.set 1`.
|
||||
//
|
||||
// Variable is useful to track the SSA Values of a variable in the source program, and
|
||||
// can be used to find the corresponding latest SSA Value via Builder.FindValue.
|
||||
type Variable uint32
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (v Variable) String() string {
|
||||
return fmt.Sprintf("var%d", v)
|
||||
}
|
||||
|
||||
// Value represents an SSA value with a type information. The relationship with Variable is 1: N (including 0),
|
||||
// that means there might be multiple Variable(s) for a Value.
|
||||
//
|
||||
// Higher 32-bit is used to store Type for this value.
|
||||
type Value uint64
|
||||
|
||||
// ValueID is the lower 32bit of Value, which is the pure identifier of Value without type info.
|
||||
type ValueID uint32
|
||||
|
||||
const (
|
||||
valueIDInvalid ValueID = math.MaxUint32
|
||||
ValueInvalid Value = Value(valueIDInvalid)
|
||||
)
|
||||
|
||||
// Format creates a debug string for this Value using the data stored in Builder.
|
||||
func (v Value) Format(b Builder) string {
|
||||
if annotation, ok := b.(*builder).valueAnnotations[v.ID()]; ok {
|
||||
return annotation
|
||||
}
|
||||
return fmt.Sprintf("v%d", v.ID())
|
||||
}
|
||||
|
||||
func (v Value) formatWithType(b Builder) (ret string) {
|
||||
if annotation, ok := b.(*builder).valueAnnotations[v.ID()]; ok {
|
||||
ret = annotation + ":" + v.Type().String()
|
||||
} else {
|
||||
ret = fmt.Sprintf("v%d:%s", v.ID(), v.Type())
|
||||
}
|
||||
|
||||
if wazevoapi.SSALoggingEnabled { // This is useful to check live value analysis bugs.
|
||||
if bd := b.(*builder); bd.donePostBlockLayoutPasses {
|
||||
id := v.ID()
|
||||
ret += fmt.Sprintf("(ref=%d)", bd.valueRefCounts[id])
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Valid returns true if this value is valid.
|
||||
func (v Value) Valid() bool {
|
||||
return v.ID() != valueIDInvalid
|
||||
}
|
||||
|
||||
// Type returns the Type of this value.
|
||||
func (v Value) Type() Type {
|
||||
return Type(v >> 32)
|
||||
}
|
||||
|
||||
// ID returns the valueID of this value.
|
||||
func (v Value) ID() ValueID {
|
||||
return ValueID(v)
|
||||
}
|
||||
|
||||
// setType sets a type to this Value and returns the updated Value.
|
||||
func (v Value) setType(typ Type) Value {
|
||||
return v | Value(typ)<<32
|
||||
}
|
||||
|
||||
// Values is a slice of Value. Use this instead of []Value to reuse the underlying memory.
|
||||
type Values = wazevoapi.VarLength[Value]
|
||||
|
||||
// ValuesNil is a nil Values.
|
||||
var ValuesNil = wazevoapi.NewNilVarLength[Value]()
|
Reference in New Issue
Block a user