package ssa
import (
"fmt"
"sort"
"strings"
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
)
type Builder interface {
Init (typ *Signature )
Signature () *Signature
BlockIDMax () BasicBlockID
AllocateBasicBlock () BasicBlock
CurrentBlock () BasicBlock
EntryBlock () BasicBlock
SetCurrentBlock (b BasicBlock )
DeclareVariable (Type ) Variable
DefineVariable (variable Variable , value Value , block BasicBlock )
DefineVariableInCurrentBB (variable Variable , value Value )
AllocateInstruction () *Instruction
InsertInstruction (raw *Instruction )
allocateValue(typ Type ) Value
MustFindValue (variable Variable ) Value
FindValueInLinearPath (variable Variable ) Value
Seal (blk BasicBlock )
AnnotateValue (value Value , annotation string )
DeclareSignature (signature *Signature )
Signatures () []*Signature
ResolveSignature (id SignatureID ) *Signature
RunPasses ()
Format () string
BlockIteratorBegin () BasicBlock
BlockIteratorNext () BasicBlock
ValuesInfo () []ValueInfo
BlockIteratorReversePostOrderBegin () BasicBlock
BlockIteratorReversePostOrderNext () BasicBlock
ReturnBlock () BasicBlock
InsertUndefined ()
SetCurrentSourceOffset (line SourceOffset )
LoopNestingForestRoots () []BasicBlock
LowestCommonAncestor (blk1, blk2 BasicBlock ) BasicBlock
Idom (blk BasicBlock ) BasicBlock
VarLengthPool () *wazevoapi .VarLengthPool [Value ]
InsertZeroValue (t Type )
BasicBlock (id BasicBlockID ) BasicBlock
InstructionOfValue (v Value ) *Instruction
}
func NewBuilder () Builder {
return &builder {
instructionsPool : wazevoapi .NewPool [Instruction ](resetInstruction ),
basicBlocksPool : wazevoapi .NewPool [basicBlock ](resetBasicBlock ),
varLengthBasicBlockPool : wazevoapi .NewVarLengthPool [BasicBlock ](),
varLengthPool : wazevoapi .NewVarLengthPool [Value ](),
valueAnnotations : make (map [ValueID ]string ),
signatures : make (map [SignatureID ]*Signature ),
returnBlk : &basicBlock {id : basicBlockIDReturnBlock },
}
}
type builder struct {
basicBlocksPool wazevoapi .Pool [basicBlock ]
instructionsPool wazevoapi .Pool [Instruction ]
varLengthPool wazevoapi .VarLengthPool [Value ]
signatures map [SignatureID ]*Signature
currentSignature *Signature
reversePostOrderedBasicBlocks []*basicBlock
currentBB *basicBlock
returnBlk *basicBlock
nextValueID ValueID
nextVariable Variable
valueAnnotations map [ValueID ]string
valuesInfo []ValueInfo
dominators []*basicBlock
sparseTree dominatorSparseTree
varLengthBasicBlockPool wazevoapi .VarLengthPool [BasicBlock ]
loopNestingForestRoots []BasicBlock
instStack []*Instruction
blkStack []*basicBlock
blkStack2 []*basicBlock
redundantParams []redundantParam
blockIterCur int
donePreBlockLayoutPasses bool
doneBlockLayout bool
donePostBlockLayoutPasses bool
currentSourceOffset SourceOffset
zeros [typeEnd ]Value
}
type ValueInfo struct {
RefCount uint32
alias Value
}
type redundantParam struct {
index int
uniqueValue Value
}
func (b *builder ) BasicBlock (id BasicBlockID ) BasicBlock {
return b .basicBlock (id )
}
func (b *builder ) basicBlock (id BasicBlockID ) *basicBlock {
if id == basicBlockIDReturnBlock {
return b .returnBlk
}
return b .basicBlocksPool .View (int (id ))
}
func (b *builder ) InsertZeroValue (t Type ) {
if b .zeros [t ].Valid () {
return
}
zeroInst := b .AllocateInstruction ()
switch t {
case TypeI32 :
zeroInst .AsIconst32 (0 )
case TypeI64 :
zeroInst .AsIconst64 (0 )
case TypeF32 :
zeroInst .AsF32const (0 )
case TypeF64 :
zeroInst .AsF64const (0 )
case TypeV128 :
zeroInst .AsVconst (0 , 0 )
default :
panic ("TODO: " + t .String ())
}
b .zeros [t ] = zeroInst .Insert (b ).Return ()
}
func (b *builder ) VarLengthPool () *wazevoapi .VarLengthPool [Value ] {
return &b .varLengthPool
}
func (b *builder ) ReturnBlock () BasicBlock {
return b .returnBlk
}
func (b *builder ) Init (s *Signature ) {
b .nextVariable = 0
b .currentSignature = s
b .zeros = [typeEnd ]Value {ValueInvalid , ValueInvalid , ValueInvalid , ValueInvalid , ValueInvalid , ValueInvalid }
resetBasicBlock (b .returnBlk )
b .instructionsPool .Reset ()
b .basicBlocksPool .Reset ()
b .varLengthPool .Reset ()
b .varLengthBasicBlockPool .Reset ()
b .donePreBlockLayoutPasses = false
b .doneBlockLayout = false
b .donePostBlockLayoutPasses = false
for _ , sig := range b .signatures {
sig .used = false
}
b .redundantParams = b .redundantParams [:0 ]
b .blkStack = b .blkStack [:0 ]
b .blkStack2 = b .blkStack2 [:0 ]
b .dominators = b .dominators [:0 ]
b .loopNestingForestRoots = b .loopNestingForestRoots [:0 ]
b .basicBlocksPool .Reset ()
for v := ValueID (0 ); v < b .nextValueID ; v ++ {
delete (b .valueAnnotations , v )
b .valuesInfo [v ] = ValueInfo {alias : ValueInvalid }
}
b .nextValueID = 0
b .reversePostOrderedBasicBlocks = b .reversePostOrderedBasicBlocks [:0 ]
b .doneBlockLayout = false
b .currentSourceOffset = sourceOffsetUnknown
}
func (b *builder ) Signature () *Signature {
return b .currentSignature
}
func (b *builder ) AnnotateValue (value Value , a string ) {
b .valueAnnotations [value .ID ()] = a
}
func (b *builder ) AllocateInstruction () *Instruction {
instr := b .instructionsPool .Allocate ()
instr .id = b .instructionsPool .Allocated ()
return instr
}
func (b *builder ) DeclareSignature (s *Signature ) {
b .signatures [s .ID ] = s
s .used = false
}
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
}
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
}
func (b *builder ) ResolveSignature (id SignatureID ) *Signature {
return b .signatures [id ]
}
func (b *builder ) AllocateBasicBlock () BasicBlock {
return b .allocateBasicBlock ()
}
func (b *builder ) allocateBasicBlock () *basicBlock {
id := BasicBlockID (b .basicBlocksPool .Allocated ())
blk := b .basicBlocksPool .Allocate ()
blk .id = id
return blk
}
func (b *builder ) Idom (blk BasicBlock ) BasicBlock {
return b .dominators [blk .ID ()]
}
func (b *builder ) InsertInstruction (instr *Instruction ) {
b .currentBB .insertInstruction (b , instr )
if l := b .currentSourceOffset ; l .Valid () {
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 .setInstructionID (instr .id )
tsl := len (ts )
if tsl == 0 {
return
}
rValues := b .varLengthPool .Allocate (tsl )
for i := 0 ; i < tsl ; i ++ {
rn := b .allocateValue (ts [i ])
rValues = rValues .Append (&b .varLengthPool , rn .setInstructionID (instr .id ))
}
instr .rValues = rValues
}
func (b *builder ) DefineVariable (variable Variable , value Value , block BasicBlock ) {
bb := block .(*basicBlock )
bb .lastDefinitions [variable ] = value
}
func (b *builder ) DefineVariableInCurrentBB (variable Variable , value Value ) {
b .DefineVariable (variable , value , b .currentBB )
}
func (b *builder ) SetCurrentBlock (bb BasicBlock ) {
b .currentBB = bb .(*basicBlock )
}
func (b *builder ) CurrentBlock () BasicBlock {
return b .currentBB
}
func (b *builder ) EntryBlock () BasicBlock {
return b .entryBlk ()
}
func (b *builder ) DeclareVariable (typ Type ) Variable {
v := b .nextVariable
b .nextVariable ++
return v .setType (typ )
}
func (b *builder ) allocateValue (typ Type ) (v Value ) {
v = Value (b .nextValueID )
v = v .setType (typ )
b .nextValueID ++
return
}
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 {
return b .findValueInLinearPath (variable , pred )
}
if len (blk .preds ) == 1 {
panic ("BUG" )
}
return ValueInvalid
}
func (b *builder ) MustFindValue (variable Variable ) Value {
return b .findValue (variable .getType (), variable , b .currentBB )
}
func (b *builder ) findValue (typ Type , variable Variable , blk *basicBlock ) Value {
if val , ok := blk .lastDefinitions [variable ]; ok {
return val
} else if !blk .sealed {
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
} else if blk .EntryBlock () {
return b .zeros [variable .getType ()]
}
if pred := blk .singlePred ; pred != nil {
return b .findValue (typ , variable , pred )
} else if len (blk .preds ) == 0 {
panic ("BUG: value is not defined for " + variable .String ())
}
tmpValue := b .allocateValue (typ )
b .DefineVariable (variable , tmpValue , blk )
uniqueValue := ValueInvalid
for i := range blk .preds {
predValue := b .findValue (typ , variable , blk .preds [i ].blk )
if uniqueValue == ValueInvalid {
uniqueValue = predValue
} else if uniqueValue != predValue {
uniqueValue = ValueInvalid
break
}
}
if uniqueValue != ValueInvalid {
b .alias (tmpValue , uniqueValue )
return uniqueValue
} else {
blk .addParamOn (b , tmpValue )
for i := range blk .preds {
pred := &blk .preds [i ]
value := b .findValue (typ , variable , pred .blk )
pred .branch .addArgumentBranchInst (b , value )
}
return tmpValue
}
}
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 := variable .getType ()
blk .addParamOn (b , 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 )
}
}
}
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 ()
}
func (b *builder ) BlockIteratorNext () BasicBlock {
if blk := b .blockIteratorNext (); blk == nil {
return nil
} else {
return blk
}
}
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
}
}
}
func (b *builder ) BlockIteratorBegin () BasicBlock {
return b .blockIteratorBegin ()
}
func (b *builder ) blockIteratorBegin () *basicBlock {
b .blockIterCur = 0
return b .blockIteratorNext ()
}
func (b *builder ) BlockIteratorReversePostOrderBegin () BasicBlock {
return b .blockIteratorReversePostOrderBegin ()
}
func (b *builder ) blockIteratorReversePostOrderBegin () *basicBlock {
b .blockIterCur = 0
return b .blockIteratorReversePostOrderNext ()
}
func (b *builder ) BlockIteratorReversePostOrderNext () BasicBlock {
if blk := b .blockIteratorReversePostOrderNext (); blk == nil {
return nil
} else {
return blk
}
}
func (b *builder ) blockIteratorReversePostOrderNext () *basicBlock {
if b .blockIterCur >= len (b .reversePostOrderedBasicBlocks ) {
return nil
} else {
ret := b .reversePostOrderedBasicBlocks [b .blockIterCur ]
b .blockIterCur ++
return ret
}
}
func (b *builder ) ValuesInfo () []ValueInfo {
return b .valuesInfo
}
func (b *builder ) alias (dst , src Value ) {
did := int (dst .ID ())
if did >= len (b .valuesInfo ) {
l := did + 1 - len (b .valuesInfo )
b .valuesInfo = append (b .valuesInfo , make ([]ValueInfo , l )...)
view := b .valuesInfo [len (b .valuesInfo )-l :]
for i := range view {
view [i ].alias = ValueInvalid
}
}
b .valuesInfo [did ].alias = src
}
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 )
}
}
func (b *builder ) resolveAlias (v Value ) Value {
info := b .valuesInfo
l := ValueID (len (info ))
for {
vid := v .ID ()
if vid < l && info [vid ].alias .Valid () {
v = info [vid ].alias
} else {
break
}
}
return v
}
func (b *builder ) entryBlk () *basicBlock {
return b .basicBlocksPool .View (0 )
}
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
}
func (b *builder ) BlockIDMax () BasicBlockID {
return BasicBlockID (b .basicBlocksPool .Allocated ())
}
func (b *builder ) InsertUndefined () {
instr := b .AllocateInstruction ()
instr .opcode = OpcodeUndefined
b .InsertInstruction (instr )
}
func (b *builder ) LoopNestingForestRoots () []BasicBlock {
return b .loopNestingForestRoots
}
func (b *builder ) LowestCommonAncestor (blk1 , blk2 BasicBlock ) BasicBlock {
return b .sparseTree .findLCA (blk1 .ID (), blk2 .ID ())
}
func (b *builder ) InstructionOfValue (v Value ) *Instruction {
instrID := v .instructionID ()
if instrID <= 0 {
return nil
}
return b .instructionsPool .View (instrID - 1 )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .