diff --git a/src/ssa/Block.go b/src/ssa/Block.go index b442548..f0e03d8 100644 --- a/src/ssa/Block.go +++ b/src/ssa/Block.go @@ -3,9 +3,6 @@ package ssa import ( "maps" "slices" - "strings" - - "git.urbach.dev/cli/q/src/types" ) // Block is a list of instructions that can be targeted in branches. @@ -33,130 +30,8 @@ func (b *Block) AddSuccessor(successor *Block) { } successor.Predecessors = append(successor.Predecessors, b) - - if len(b.Protected) > 0 { - if successor.Protected == nil { - successor.Protected = make(map[Value][]Value, len(b.Protected)) - } - - maps.Copy(successor.Protected, b.Protected) - } - - if b.Identifiers.After == nil { - return - } - - if successor.Identifiers.After == nil { - successor.Identifiers.Before = make(map[string]Value, len(b.Identifiers.After)) - successor.Identifiers.After = make(map[string]Value, len(b.Identifiers.After)) - - if len(successor.Predecessors) == 1 { - maps.Copy(successor.Identifiers.Before, b.Identifiers.After) - maps.Copy(successor.Identifiers.After, b.Identifiers.After) - return - } - } - - keys := make([]string, 0, max(len(b.Identifiers.After), len(successor.Identifiers.After))) - - for name := range successor.Identifiers.Before { - if !slices.Contains(keys, name) { - keys = append(keys, name) - } - } - - for name := range b.Identifiers.After { - if !slices.Contains(keys, name) { - keys = append(keys, name) - } - } - - slices.SortFunc(keys, func(a string, b string) int { - return strings.Compare(b, a) - }) - - var modifiedStructs []string - - for _, name := range keys { - oldValue, oldExists := successor.Identifiers.Before[name] - newValue, newExists := b.Identifiers.After[name] - - switch { - case oldExists: - if oldValue == newValue { - continue - } - - _, isStruct := oldValue.(*Struct) - - if isStruct { - modifiedStructs = append(modifiedStructs, name) - continue - } - - definedLocally := successor.Index(oldValue) != -1 - - if definedLocally { - phi, isPhi := oldValue.(*Phi) - - if isPhi { - if newExists { - phi.Arguments = append(phi.Arguments, newValue) - } else { - phi.Arguments = append(phi.Arguments, Undefined) - } - } - - continue - } - - phi := &Phi{ - Arguments: make([]Value, len(successor.Predecessors)-1, len(successor.Predecessors)), - Typ: oldValue.Type(), - } - - for i := range phi.Arguments { - phi.Arguments[i] = oldValue - } - - successor.InsertAt(0, phi) - successor.ReplaceIdentifier(name, oldValue, phi) - - if newExists { - phi.Arguments = append(phi.Arguments, newValue) - } else { - phi.Arguments = append(phi.Arguments, Undefined) - } - - case newExists: - phi := &Phi{ - Arguments: make([]Value, len(successor.Predecessors)-1, len(successor.Predecessors)), - Typ: newValue.Type(), - } - - for i := range phi.Arguments { - phi.Arguments[i] = Undefined - } - - successor.InsertAt(0, phi) - successor.ReplaceIdentifier(name, oldValue, phi) - phi.Arguments = append(phi.Arguments, newValue) - } - } - - // Structs that were modified in branches need to be recreated - // to use the new Phi values as their arguments. - for _, name := range modifiedStructs { - structure := successor.Identifiers.Before[name].(*Struct) - structType := types.Unwrap(structure.Typ).(*types.Struct) - newStruct := &Struct{Typ: structure.Typ, Arguments: make(Arguments, len(structure.Arguments))} - - for i, field := range structType.Fields { - newStruct.Arguments[i] = successor.Identifiers.Before[name+"."+field.Name] - } - - successor.ReplaceIdentifier(name, structure, newStruct) - } + b.copyProtected(successor) + mergeIdentifiers(b, successor) } // Append adds a new value to the block. @@ -169,27 +44,6 @@ func (b *Block) CanReachPredecessor(other *Block) bool { return b.canReachPredecessor(other, make(map[*Block]bool)) } -// 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 -} - // Contains checks if the value exists within the block. func (b *Block) Contains(value Value) bool { return b.Index(value) != -1 @@ -253,6 +107,15 @@ func (b *Block) Phis(yield func(*Phi) bool) { } } +// 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] @@ -283,16 +146,41 @@ func (b *Block) String() string { return CleanLabel(b.Label) } -// 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) +// 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 } - b.Protected[err] = protected + if traversed[b] { + return false + } + + traversed[b] = true + + for _, pre := range b.Predecessors { + if pre.canReachPredecessor(other, traversed) { + return true + } + } + + return false } -// Unprotect stops protecting the variables for the given error value. -func (b *Block) Unprotect(err Value) { - delete(b.Protected, err) +// 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) } \ No newline at end of file diff --git a/src/ssa/Identifiers.go b/src/ssa/Identifiers.go index c899561..432176d 100644 --- a/src/ssa/Identifiers.go +++ b/src/ssa/Identifiers.go @@ -59,7 +59,7 @@ func (i *Identifiers) IsIdentified(value Value) bool { } // ReplaceIdentifier replaces an existing identifier. -func (i *Identifiers) ReplaceIdentifier(name string, oldValue, newValue Value) { +func (i *Identifiers) ReplaceIdentifier(name string, oldValue Value, newValue Value) { i.Before[name] = newValue if i.After[name] == oldValue { diff --git a/src/ssa/mergeIdentifiers.go b/src/ssa/mergeIdentifiers.go new file mode 100644 index 0000000..bf9de78 --- /dev/null +++ b/src/ssa/mergeIdentifiers.go @@ -0,0 +1,151 @@ +package ssa + +import ( + "maps" + "slices" + "strings" + + "git.urbach.dev/cli/q/src/types" +) + +// mergeIdentifiers merges identifier mappings into the successor, +// inserting phi functions where values differ across predecessors. +func mergeIdentifiers(predecessor *Block, successor *Block) { + if predecessor.Identifiers.After == nil { + return + } + + if successor.Identifiers.After == nil { + successor.Identifiers.Before = make(map[string]Value, len(predecessor.Identifiers.After)) + successor.Identifiers.After = make(map[string]Value, len(predecessor.Identifiers.After)) + + if len(successor.Predecessors) == 1 { + maps.Copy(successor.Identifiers.Before, predecessor.Identifiers.After) + maps.Copy(successor.Identifiers.After, predecessor.Identifiers.After) + return + } + } + + var ( + modifiedStructs []string + names = collectIdentifierNames(predecessor, successor) + ) + + for _, name := range names { + mergeIdentifier(predecessor, successor, name, &modifiedStructs) + } + + // Structs that were modified in branches need to be recreated + // to use the new Phi values as their arguments. + for _, name := range modifiedStructs { + structure := successor.Identifiers.Before[name].(*Struct) + structType := types.Unwrap(structure.Typ).(*types.Struct) + newStruct := &Struct{Typ: structure.Typ, Arguments: make(Arguments, len(structure.Arguments))} + + for i, field := range structType.Fields { + newStruct.Arguments[i] = successor.Identifiers.Before[name+"."+field.Name] + } + + successor.ReplaceIdentifier(name, structure, newStruct) + } +} + +// collectIdentifierNames returns all identifier names from both maps in deterministic order. +func collectIdentifierNames(predecessor *Block, successor *Block) []string { + keys := make([]string, 0, max(len(predecessor.Identifiers.After), len(successor.Identifiers.After))) + + for name := range successor.Identifiers.Before { + if !slices.Contains(keys, name) { + keys = append(keys, name) + } + } + + for name := range predecessor.Identifiers.After { + if !slices.Contains(keys, name) { + keys = append(keys, name) + } + } + + slices.SortFunc(keys, func(a string, b string) int { + return strings.Compare(b, a) + }) + + return keys +} + +// mergeIdentifier merges a single identifier into the successor. +func mergeIdentifier(predecessor *Block, successor *Block, name string, modifiedStructs *[]string) { + oldValue, oldExists := successor.Identifiers.Before[name] + newValue, newExists := predecessor.Identifiers.After[name] + + switch { + case oldExists: + mergeOldIdentifier(successor, name, oldValue, newValue, newExists, modifiedStructs) + case newExists: + mergeNewIdentifier(successor, name, newValue) + } +} + +// mergeOldIdentifier handles the case where the successor already has a binding for this name. +func mergeOldIdentifier(successor *Block, name string, oldValue Value, newValue Value, newExists bool, modifiedStructs *[]string) { + if oldValue == newValue { + return + } + + _, isStruct := oldValue.(*Struct) + + if isStruct { + *modifiedStructs = append(*modifiedStructs, name) + return + } + + definedLocally := successor.Index(oldValue) != -1 + + if definedLocally { + phi, isPhi := oldValue.(*Phi) + + if isPhi { + if newExists { + phi.Arguments = append(phi.Arguments, newValue) + } else { + phi.Arguments = append(phi.Arguments, Undefined) + } + } + + return + } + + phi := &Phi{ + Arguments: make([]Value, len(successor.Predecessors)-1, len(successor.Predecessors)), + Typ: oldValue.Type(), + } + + for i := range phi.Arguments { + phi.Arguments[i] = oldValue + } + + successor.InsertAt(0, phi) + successor.ReplaceIdentifier(name, oldValue, phi) + + if newExists { + phi.Arguments = append(phi.Arguments, newValue) + } else { + phi.Arguments = append(phi.Arguments, Undefined) + } +} + +// mergeNewIdentifier handles the case where only the predecessor has a binding for this name. +func mergeNewIdentifier(successor *Block, name string, newValue Value) { + phi := &Phi{ + Arguments: make([]Value, len(successor.Predecessors)-1, len(successor.Predecessors)), + Typ: newValue.Type(), + } + + for i := range phi.Arguments { + phi.Arguments[i] = Undefined + } + + successor.InsertAt(0, phi) + successor.ReplaceIdentifier(name, nil, phi) + phi.Arguments = append(phi.Arguments, newValue) +} \ No newline at end of file