package ssa import ( "maps" "slices" ) // Block is a list of instructions that can be targeted in branches. type Block struct { Identifiers Protected map[Value][]Value Loop *Block Label string Instructions []Value Predecessors []*Block } // NewBlock creates a new basic block. func NewBlock(label string) *Block { return &Block{ Instructions: make([]Value, 0, 8), Label: label, } } // AddSuccessor adds the given block as a successor. func (b *Block) AddSuccessor(successor *Block) { if slices.Contains(successor.Predecessors, b) { return } successor.Predecessors = append(successor.Predecessors, b) b.copyProtected(successor) mergeIdentifiers(b, successor) } // Append adds a new value to the block. func (b *Block) Append(value Value) { b.Instructions = append(b.Instructions, value) } // CanReachPredecessor checks if the `other` block appears as a predecessor or is the block itself. func (b *Block) CanReachPredecessor(other *Block) bool { return b.canReachPredecessor(other, make(map[*Block]bool)) } // Contains checks if the value exists within the block. func (b *Block) Contains(value Value) bool { return b.Index(value) != -1 } // FindExisting returns an equal instruction that's already appended or `nil` if none could be found. func (b *Block) FindExisting(instr Value) Value { if !instr.IsPure() { return nil } for _, existing := range slices.Backward(b.Instructions) { if existing.IsPure() && instr.Equals(existing) { return existing } // If we encounter an instruction with side effects, // we can't be sure that the value is still the same. switch other := existing.(type) { case *Call, *CallExtern, *CallPointer, *Syscall, *Cas: return nil case *Store: load, isLoad := instr.(*Load) if isLoad && load.Memory.Address == other.Memory.Address && load.Memory.Index == other.Memory.Index && load.Memory.Scale == other.Memory.Scale { return nil } } } return nil } // Index returns the position of the value or -1 if it doesn't exist within the block. func (b *Block) Index(search Value) int { for i, value := range b.Instructions { if value == search { return i } } return -1 } // InsertAt inserts the `value` at the given `index`. func (b *Block) InsertAt(index int, values ...Value) { b.Instructions = slices.Insert(b.Instructions, index, values...) } // Last returns the last value. func (b *Block) Last() Value { if len(b.Instructions) == 0 { return nil } return b.Instructions[len(b.Instructions)-1] } // Phis is an iterator for all phis at the top of the block. func (b *Block) Phis(yield func(*Phi) bool) { for _, instr := range b.Instructions { phi, isPhi := instr.(*Phi) if !isPhi || !yield(phi) { return } } } // Protect protects the given value from being accessed before the error value is checked. func (b *Block) Protect(err Value, protected []Value) { if b.Protected == nil { b.Protected = make(map[Value][]Value) } b.Protected[err] = protected } // RemoveAt sets the value at the given index to nil. func (b *Block) RemoveAt(index int) { value := b.Instructions[index] for _, input := range value.Inputs() { input.RemoveUser(value) } b.Instructions[index] = nil } // RemoveNilValues removes all nil values from the block. func (b *Block) RemoveNilValues() { b.Instructions = slices.DeleteFunc(b.Instructions, func(value Value) bool { return value == nil }) } // ReplaceAllUses replaces all uses of `old` with `new`. func (b *Block) ReplaceAllUses(old Value, new Value) { for _, instr := range b.Instructions { instr.Replace(old, new) } } // String returns the block label. func (b *Block) String() string { return CleanLabel(b.Label) } // Unprotect stops protecting the variables for the given error value. func (b *Block) Unprotect(err Value) { delete(b.Protected, err) } // canReachPredecessor checks if the `other` block appears as a predecessor or is the block itself. func (b *Block) canReachPredecessor(other *Block, traversed map[*Block]bool) bool { if other == b { return true } if traversed[b] { return false } traversed[b] = true for _, pre := range b.Predecessors { if pre.canReachPredecessor(other, traversed) { return true } } return false } // copyProtected transfers protected values to the successor. func (b *Block) copyProtected(successor *Block) { if len(b.Protected) == 0 { return } if successor.Protected == nil { successor.Protected = make(map[Value][]Value, len(b.Protected)) } maps.Copy(successor.Protected, b.Protected) }