/
if.go
142 lines (129 loc) · 3.33 KB
/
if.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package cfa
import (
"fmt"
"github.com/decomp/decomp/cfa/primitive"
"github.com/decomp/decomp/graph/cfg"
"gonum.org/v1/gonum/graph"
)
// If represents a 1-way conditional statement.
//
// Pseudo-code:
//
// if (A) {
// B
// }
// C
type If struct {
// Condition node (A).
Cond graph.Node
// Body node (B).
Body graph.Node
// Exit node (C).
Exit graph.Node
}
// Prim returns a representation of the high-level control flow primitive, as a
// mapping from control flow primitive node names to control flow graph node
// names.
//
// Example mapping:
//
// "cond": "A"
// "body": "B"
// "exit": "C"
func (prim If) Prim() *primitive.Primitive {
cond, body, exit := label(prim.Cond), label(prim.Body), label(prim.Exit)
return &primitive.Primitive{
Prim: "if",
Nodes: map[string]string{
"cond": cond,
"body": body,
"exit": exit,
},
Entry: cond,
Exit: exit,
}
}
// String returns a string representation of prim in DOT format.
//
// Example output:
//
// digraph if {
// cond -> body
// cond -> exit
// body -> exit
// }
func (prim If) String() string {
cond, body, exit := label(prim.Cond), label(prim.Body), label(prim.Exit)
const format = `
digraph if {
%v -> %v
%v -> %v
%v -> %v
}`
return fmt.Sprintf(format[1:], cond, body, cond, exit, body, exit)
}
// FindIf returns the first occurrence of a 1-way conditional statement in g,
// and a boolean indicating if such a primitive was found.
func FindIf(g graph.Directed, dom cfg.DominatorTree) (prim If, ok bool) {
// Range through cond node candidates.
condNodes := g.Nodes()
for condNodes.Next() {
cond := condNodes.Node()
// Verify that cond has two successors (body and exit).
condSuccs := graph.NodesOf(g.From(cond.ID()))
if len(condSuccs) != 2 {
continue
}
prim.Cond = cond
// Select body and exit node candidates.
prim.Body, prim.Exit = condSuccs[0], condSuccs[1]
if prim.IsValid(g, dom) {
return prim, true
}
// Swap body and exit node candidates and try again.
prim.Body, prim.Exit = prim.Exit, prim.Body
if prim.IsValid(g, dom) {
return prim, true
}
}
return If{}, false
}
// IsValid reports whether the cond, body and exit node candidates of prim form
// a valid 1-way conditional statement in g.
//
// Control flow graph:
//
// cond
// ↓ ↘
// ↓ body
// ↓ ↙
// exit
func (prim If) IsValid(g graph.Directed, dom cfg.DominatorTree) bool {
// Dominator sanity check.
cond, body, exit := prim.Cond, prim.Body, prim.Exit
if !dom.Dominates(cond, body) || !dom.Dominates(cond, exit) {
return false
}
// Verify that cond is dominated by its predecessors.
condPreds := g.To(cond.ID())
for condPreds.Next() {
condPred := condPreds.Node()
if !dom.Dominates(condPred, cond) {
return false
}
}
// Verify that cond has two successors (body and exit).
condSuccs := g.From(cond.ID())
if condSuccs.Len() != 2 || !g.HasEdgeFromTo(cond.ID(), body.ID()) || !g.HasEdgeFromTo(cond.ID(), exit.ID()) {
return false
}
// Verify that body has one predecessor (cond) and one successor (exit).
bodyPreds := g.To(body.ID())
bodySuccs := g.From(body.ID())
if bodyPreds.Len() != 1 || bodySuccs.Len() != 1 || !g.HasEdgeFromTo(body.ID(), exit.ID()) {
return false
}
// Verify that exit has two predecessors (cond and body).
exitPreds := g.To(exit.ID())
return exitPreds.Len() == 2
}