This replaces the basicBlock.params field with the reusable
VarLength[Value] type. As a result, the compilation starts
using less memory and allocations.
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
Compilation/wazero-10 2.004 ± 2% 2.001 ± 0% ~ (p=0.620 n=7)
Compilation/zig-10 4.164 ± 1% 4.174 ± 3% ~ (p=0.097 n=7)
geomean 2.888 2.890 +0.06%
│ old.txt │ new.txt │
│ B/op │ B/op vs base │
Compilation/wazero-10 297.7Mi ± 0% 297.5Mi ± 0% -0.06% (p=0.001 n=7)
Compilation/zig-10 594.0Mi ± 0% 593.9Mi ± 0% -0.01% (p=0.001 n=7)
geomean 420.5Mi 420.3Mi -0.03%
│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
Compilation/wazero-10 472.5k ± 0% 457.1k ± 0% -3.25% (p=0.001 n=7)
Compilation/zig-10 277.2k ± 0% 275.7k ± 0% -0.53% (p=0.001 n=7)
geomean 361.9k 355.0k -1.90%
```
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
381 lines
11 KiB
Go
381 lines
11 KiB
Go
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
|
|
|
|
// 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
|
|
|
|
// 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 are Values that represent parameters to a basicBlock.
|
|
// Each parameter can be considered as an output of PHI instruction in traditional SSA.
|
|
params Values
|
|
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 wazevoapi.VarLength[BasicBlock]
|
|
|
|
// reversePostOrder is used to sort all the blocks in the function in reverse post order.
|
|
// This is used in builder.LayoutBlocks.
|
|
reversePostOrder int32
|
|
|
|
// visited is used during various traversals.
|
|
visited int32
|
|
|
|
// child and sibling are the ones in the dominator tree.
|
|
child, sibling *basicBlock
|
|
}
|
|
// BasicBlockID is the unique ID of a basicBlock.
|
|
BasicBlockID uint32
|
|
|
|
unknownValue struct {
|
|
// variable is the variable that this unknownValue represents.
|
|
variable Variable
|
|
// value is the value that this unknownValue represents.
|
|
value Value
|
|
}
|
|
)
|
|
|
|
// basicBlockVarLengthNil is the default nil value for basicBlock.loopNestingForestChildren.
|
|
var basicBlockVarLengthNil = wazevoapi.NewNilVarLength[BasicBlock]()
|
|
|
|
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 = bb.params.Append(&b.(*builder).varLengthPool, paramValue)
|
|
return paramValue
|
|
}
|
|
|
|
// addParamOn adds a parameter to this block whose value is already allocated.
|
|
func (bb *basicBlock) addParamOn(b *builder, value Value) {
|
|
bb.params = bb.params.Append(&b.varLengthPool, value)
|
|
}
|
|
|
|
// Params implements BasicBlock.Params.
|
|
func (bb *basicBlock) Params() int {
|
|
return len(bb.params.View())
|
|
}
|
|
|
|
// Param implements BasicBlock.Param.
|
|
func (bb *basicBlock) Param(i int) Value {
|
|
return bb.params.View()[i]
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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 = ValuesNil
|
|
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.visited = 0
|
|
bb.loopNestingForestChildren = basicBlockVarLengthNil
|
|
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 returns the string representation of the header of the basicBlock.
|
|
func (bb *basicBlock) formatHeader(b Builder) string {
|
|
ps := make([]string, len(bb.params.View()))
|
|
for i, p := range bb.params.View() {
|
|
ps[i] = p.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.View())
|
|
}
|
|
|
|
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.View()), 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.View()
|
|
}
|
|
|
|
// LoopHeader implements BasicBlock.LoopHeader.
|
|
func (bb *basicBlock) LoopHeader() bool {
|
|
return bb.loopHeader
|
|
}
|