1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package ssa
6
7// fuseIntegerComparisons optimizes inequalities such as '1 <= x && x < 5',
8// which can be optimized to 'unsigned(x-1) < 4'.
9//
10// Look for branch structure like:
11//
12//	p
13//	|\
14//	| b
15//	|/ \
16//	s0 s1
17//
18// In our example, p has control '1 <= x', b has control 'x < 5',
19// and s0 and s1 are the if and else results of the comparison.
20//
21// This will be optimized into:
22//
23//	p
24//	 \
25//	  b
26//	 / \
27//	s0 s1
28//
29// where b has the combined control value 'unsigned(x-1) < 4'.
30// Later passes will then fuse p and b.
31func fuseIntegerComparisons(b *Block) bool {
32	if len(b.Preds) != 1 {
33		return false
34	}
35	p := b.Preds[0].Block()
36	if b.Kind != BlockIf || p.Kind != BlockIf {
37		return false
38	}
39
40	// Don't merge control values if b is likely to be bypassed anyway.
41	if p.Likely == BranchLikely && p.Succs[0].Block() != b {
42		return false
43	}
44	if p.Likely == BranchUnlikely && p.Succs[1].Block() != b {
45		return false
46	}
47
48	// Check if the control values combine to make an integer inequality that
49	// can be further optimized later.
50	bc := b.Controls[0]
51	pc := p.Controls[0]
52	if !areMergeableInequalities(bc, pc) {
53		return false
54	}
55
56	// If the first (true) successors match then we have a disjunction (||).
57	// If the second (false) successors match then we have a conjunction (&&).
58	for i, op := range [2]Op{OpOrB, OpAndB} {
59		if p.Succs[i].Block() != b.Succs[i].Block() {
60			continue
61		}
62
63		// TODO(mundaym): should we also check the cost of executing b?
64		// Currently we might speculatively execute b even if b contains
65		// a lot of instructions. We could just check that len(b.Values)
66		// is lower than a fixed amount. Bear in mind however that the
67		// other optimization passes might yet reduce the cost of b
68		// significantly so we shouldn't be overly conservative.
69		if !canSpeculativelyExecute(b) {
70			return false
71		}
72
73		// Logically combine the control values for p and b.
74		v := b.NewValue0(bc.Pos, op, bc.Type)
75		v.AddArg(pc)
76		v.AddArg(bc)
77
78		// Set the combined control value as the control value for b.
79		b.SetControl(v)
80
81		// Modify p so that it jumps directly to b.
82		p.removeEdge(i)
83		p.Kind = BlockPlain
84		p.Likely = BranchUnknown
85		p.ResetControls()
86
87		return true
88	}
89
90	// TODO: could negate condition(s) to merge controls.
91	return false
92}
93
94// getConstIntArgIndex returns the index of the first argument that is a
95// constant integer or -1 if no such argument exists.
96func getConstIntArgIndex(v *Value) int {
97	for i, a := range v.Args {
98		switch a.Op {
99		case OpConst8, OpConst16, OpConst32, OpConst64:
100			return i
101		}
102	}
103	return -1
104}
105
106// isSignedInequality reports whether op represents the inequality < or ≤
107// in the signed domain.
108func isSignedInequality(v *Value) bool {
109	switch v.Op {
110	case OpLess64, OpLess32, OpLess16, OpLess8,
111		OpLeq64, OpLeq32, OpLeq16, OpLeq8:
112		return true
113	}
114	return false
115}
116
117// isUnsignedInequality reports whether op represents the inequality < or ≤
118// in the unsigned domain.
119func isUnsignedInequality(v *Value) bool {
120	switch v.Op {
121	case OpLess64U, OpLess32U, OpLess16U, OpLess8U,
122		OpLeq64U, OpLeq32U, OpLeq16U, OpLeq8U:
123		return true
124	}
125	return false
126}
127
128func areMergeableInequalities(x, y *Value) bool {
129	// We need both inequalities to be either in the signed or unsigned domain.
130	// TODO(mundaym): it would also be good to merge when we have an Eq op that
131	// could be transformed into a Less/Leq. For example in the unsigned
132	// domain 'x == 0 || 3 < x' is equivalent to 'x <= 0 || 3 < x'
133	inequalityChecks := [...]func(*Value) bool{
134		isSignedInequality,
135		isUnsignedInequality,
136	}
137	for _, f := range inequalityChecks {
138		if !f(x) || !f(y) {
139			continue
140		}
141
142		// Check that both inequalities are comparisons with constants.
143		xi := getConstIntArgIndex(x)
144		if xi < 0 {
145			return false
146		}
147		yi := getConstIntArgIndex(y)
148		if yi < 0 {
149			return false
150		}
151
152		// Check that the non-constant arguments to the inequalities
153		// are the same.
154		return x.Args[xi^1] == y.Args[yi^1]
155	}
156	return false
157}
158