Skip to content

Commit 6286188

Browse files
committed
cmd/compile: optimize integer "in range" expressions
Use unsigned comparisons to reduce from two comparisons to one for integer "in range" checks, such as a <= b && b < c. We already do this for bounds checks. Extend it to user code. This is much easier to do in the front end than SSA. A back end optimization would be more powerful, but this is a good start. This reduces the power of some of SSA prove inferences (#16653), but those regressions appear to be rare and not worth holding this CL for. Fixes #15844. Fixes #16697. strconv benchmarks: name old time/op new time/op delta Atof64Decimal-8 41.4ns ± 3% 38.9ns ± 2% -5.89% (p=0.000 n=24+25) Atof64Float-8 48.5ns ± 0% 46.8ns ± 3% -3.64% (p=0.000 n=20+23) Atof64FloatExp-8 97.7ns ± 4% 93.5ns ± 1% -4.25% (p=0.000 n=25+20) Atof64Big-8 187ns ± 8% 162ns ± 2% -13.54% (p=0.000 n=24+22) Atof64RandomBits-8 250ns ± 6% 233ns ± 5% -6.76% (p=0.000 n=25+25) Atof64RandomFloats-8 160ns ± 0% 152ns ± 0% -5.00% (p=0.000 n=21+22) Atof32Decimal-8 41.1ns ± 1% 38.7ns ± 2% -5.86% (p=0.000 n=24+24) Atof32Float-8 46.1ns ± 1% 43.5ns ± 3% -5.63% (p=0.000 n=21+24) Atof32FloatExp-8 101ns ± 4% 100ns ± 2% -1.59% (p=0.000 n=24+23) Atof32Random-8 136ns ± 3% 133ns ± 3% -2.83% (p=0.000 n=22+22) Atoi-8 33.8ns ± 3% 30.6ns ± 3% -9.51% (p=0.000 n=24+25) AtoiNeg-8 31.6ns ± 3% 29.1ns ± 2% -8.05% (p=0.000 n=23+24) Atoi64-8 48.6ns ± 1% 43.8ns ± 1% -9.81% (p=0.000 n=20+23) Atoi64Neg-8 47.1ns ± 4% 42.0ns ± 2% -10.83% (p=0.000 n=25+25) FormatFloatDecimal-8 177ns ± 9% 178ns ± 6% ~ (p=0.460 n=25+25) FormatFloat-8 282ns ± 6% 282ns ± 3% ~ (p=0.954 n=25+22) FormatFloatExp-8 259ns ± 7% 255ns ± 6% ~ (p=0.089 n=25+24) FormatFloatNegExp-8 253ns ± 6% 254ns ± 6% ~ (p=0.941 n=25+24) FormatFloatBig-8 340ns ± 6% 341ns ± 8% ~ (p=0.600 n=22+25) AppendFloatDecimal-8 79.4ns ± 0% 80.6ns ± 6% ~ (p=0.861 n=20+25) AppendFloat-8 175ns ± 3% 174ns ± 0% ~ (p=0.722 n=25+20) AppendFloatExp-8 142ns ± 4% 142ns ± 2% ~ (p=0.948 n=25+24) AppendFloatNegExp-8 137ns ± 2% 138ns ± 2% +0.70% (p=0.001 n=24+25) AppendFloatBig-8 218ns ± 3% 218ns ± 4% ~ (p=0.596 n=25+25) AppendFloatBinaryExp-8 80.0ns ± 4% 78.0ns ± 1% -2.43% (p=0.000 n=24+21) AppendFloat32Integer-8 82.3ns ± 3% 79.3ns ± 4% -3.69% (p=0.000 n=24+25) AppendFloat32ExactFraction-8 143ns ± 2% 143ns ± 0% ~ (p=0.177 n=23+19) AppendFloat32Point-8 175ns ± 3% 175ns ± 3% ~ (p=0.062 n=24+25) AppendFloat32Exp-8 139ns ± 2% 137ns ± 4% -1.05% (p=0.001 n=24+24) AppendFloat32NegExp-8 134ns ± 0% 137ns ± 4% +2.06% (p=0.000 n=22+25) AppendFloat64Fixed1-8 97.8ns ± 0% 98.6ns ± 3% ~ (p=0.711 n=20+25) AppendFloat64Fixed2-8 110ns ± 3% 110ns ± 5% -0.45% (p=0.037 n=24+24) AppendFloat64Fixed3-8 102ns ± 3% 102ns ± 3% ~ (p=0.684 n=24+24) AppendFloat64Fixed4-8 112ns ± 3% 110ns ± 0% -1.43% (p=0.000 n=25+18) FormatInt-8 3.18µs ± 4% 3.10µs ± 6% -2.54% (p=0.001 n=24+25) AppendInt-8 1.81µs ± 5% 1.80µs ± 5% ~ (p=0.648 n=25+25) FormatUint-8 812ns ± 6% 816ns ± 6% ~ (p=0.777 n=25+25) AppendUint-8 536ns ± 4% 538ns ± 3% ~ (p=0.798 n=20+22) Quote-8 605ns ± 6% 602ns ± 9% ~ (p=0.573 n=25+25) QuoteRune-8 99.5ns ± 8% 100.2ns ± 7% ~ (p=0.432 n=25+25) AppendQuote-8 361ns ± 3% 363ns ± 4% ~ (p=0.085 n=25+25) AppendQuoteRune-8 23.3ns ± 3% 22.4ns ± 2% -3.79% (p=0.000 n=25+24) UnquoteEasy-8 146ns ± 4% 145ns ± 5% ~ (p=0.112 n=24+24) UnquoteHard-8 804ns ± 6% 771ns ± 6% -4.10% (p=0.000 n=25+24) Change-Id: Ibd384e46e90f1cfa40503c8c6352a54c65b72980 Reviewed-on: https://go-review.googlesource.com/27652 Run-TryBot: Josh Bleecher Snyder <[email protected]> Reviewed-by: Matthew Dempsky <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent d61c07f commit 6286188

File tree

4 files changed

+155
-33
lines changed

4 files changed

+155
-33
lines changed

src/cmd/compile/internal/gc/subr.go

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2130,37 +2130,6 @@ func powtwo(n *Node) int {
21302130
return -1
21312131
}
21322132

2133-
// return the unsigned type for
2134-
// a signed integer type.
2135-
// returns T if input is not a
2136-
// signed integer type.
2137-
func tounsigned(t *Type) *Type {
2138-
// this is types[et+1], but not sure
2139-
// that this relation is immutable
2140-
switch t.Etype {
2141-
default:
2142-
fmt.Printf("tounsigned: unknown type %v\n", t)
2143-
t = nil
2144-
2145-
case TINT:
2146-
t = Types[TUINT]
2147-
2148-
case TINT8:
2149-
t = Types[TUINT8]
2150-
2151-
case TINT16:
2152-
t = Types[TUINT16]
2153-
2154-
case TINT32:
2155-
t = Types[TUINT32]
2156-
2157-
case TINT64:
2158-
t = Types[TUINT64]
2159-
}
2160-
2161-
return t
2162-
}
2163-
21642133
func ngotype(n *Node) *Sym {
21652134
if n.Type != nil {
21662135
return typenamesym(n.Type)

src/cmd/compile/internal/gc/type.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,28 @@ func (t *Type) IsBoolean() bool {
10761076
return t.Etype == TBOOL
10771077
}
10781078

1079+
var unsignedEType = [...]EType{
1080+
TINT8: TUINT8,
1081+
TUINT8: TUINT8,
1082+
TINT16: TUINT16,
1083+
TUINT16: TUINT16,
1084+
TINT32: TUINT32,
1085+
TUINT32: TUINT32,
1086+
TINT64: TUINT64,
1087+
TUINT64: TUINT64,
1088+
TINT: TUINT,
1089+
TUINT: TUINT,
1090+
TUINTPTR: TUINTPTR,
1091+
}
1092+
1093+
// toUnsigned returns the unsigned equivalent of integer type t.
1094+
func (t *Type) toUnsigned() *Type {
1095+
if !t.IsInteger() {
1096+
Fatalf("unsignedType(%v)", t)
1097+
}
1098+
return Types[unsignedEType[t.Etype]]
1099+
}
1100+
10791101
func (t *Type) IsInteger() bool {
10801102
switch t.Etype {
10811103
case TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64, TUINT64, TINT, TUINT, TUINTPTR:

src/cmd/compile/internal/gc/walk.go

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ opswitch:
631631

632632
n.Right = walkexpr(n.Right, &ll)
633633
n.Right = addinit(n.Right, ll.Slice())
634+
n = walkinrange(n, init)
634635

635636
case OPRINT, OPRINTN:
636637
walkexprlist(n.List.Slice(), init)
@@ -3406,6 +3407,134 @@ func walkrotate(n *Node) *Node {
34063407
return n
34073408
}
34083409

3410+
// isIntOrdering reports whether n is a <, ≤, >, or ≥ ordering between integers.
3411+
func (n *Node) isIntOrdering() bool {
3412+
switch n.Op {
3413+
case OLE, OLT, OGE, OGT:
3414+
default:
3415+
return false
3416+
}
3417+
return n.Left.Type.IsInteger() && n.Right.Type.IsInteger()
3418+
}
3419+
3420+
// walkinrange optimizes integer-in-range checks, such as 4 <= x && x < 10.
3421+
// n must be an OANDAND or OOROR node.
3422+
// The result of walkinrange MUST be assigned back to n, e.g.
3423+
// n.Left = walkinrange(n.Left)
3424+
func walkinrange(n *Node, init *Nodes) *Node {
3425+
// We are looking for something equivalent to a opl b OP b opr c, where:
3426+
// * a, b, and c have integer type
3427+
// * b is side-effect-free
3428+
// * opl and opr are each < or ≤
3429+
// * OP is &&
3430+
l := n.Left
3431+
r := n.Right
3432+
if !l.isIntOrdering() || !r.isIntOrdering() {
3433+
return n
3434+
}
3435+
3436+
// Find b, if it exists, and rename appropriately.
3437+
// Input is: l.Left l.Op l.Right ANDAND/OROR r.Left r.Op r.Right
3438+
// Output is: a opl b(==x) ANDAND/OROR b(==x) opr c
3439+
a, opl, b := l.Left, l.Op, l.Right
3440+
x, opr, c := r.Left, r.Op, r.Right
3441+
for i := 0; ; i++ {
3442+
if samesafeexpr(b, x) {
3443+
break
3444+
}
3445+
if i == 3 {
3446+
// Tried all permutations and couldn't find an appropriate b == x.
3447+
return n
3448+
}
3449+
if i&1 == 0 {
3450+
a, opl, b = b, Brrev(opl), a
3451+
} else {
3452+
x, opr, c = c, Brrev(opr), x
3453+
}
3454+
}
3455+
3456+
// If n.Op is ||, apply de Morgan.
3457+
// Negate the internal ops now; we'll negate the top level op at the end.
3458+
// Henceforth assume &&.
3459+
negateResult := n.Op == OOROR
3460+
if negateResult {
3461+
opl = Brcom(opl)
3462+
opr = Brcom(opr)
3463+
}
3464+
3465+
cmpdir := func(o Op) int {
3466+
switch o {
3467+
case OLE, OLT:
3468+
return -1
3469+
case OGE, OGT:
3470+
return +1
3471+
}
3472+
Fatalf("walkinrange cmpdir %v", o)
3473+
return 0
3474+
}
3475+
if cmpdir(opl) != cmpdir(opr) {
3476+
// Not a range check; something like b < a && b < c.
3477+
return n
3478+
}
3479+
3480+
switch opl {
3481+
case OGE, OGT:
3482+
// We have something like a > b && b ≥ c.
3483+
// Switch and reverse ops and rename constants,
3484+
// to make it look like a ≤ b && b < c.
3485+
a, c = c, a
3486+
opl, opr = Brrev(opr), Brrev(opl)
3487+
}
3488+
3489+
// We must ensure that c-a is non-negative.
3490+
// For now, require a and c to be constants.
3491+
// In the future, we could also support a == 0 and c == len/cap(...).
3492+
// Unfortunately, by this point, most len/cap expressions have been
3493+
// stored into temporary variables.
3494+
if !Isconst(a, CTINT) || !Isconst(c, CTINT) {
3495+
return n
3496+
}
3497+
3498+
if opl == OLT {
3499+
// We have a < b && ...
3500+
// We need a ≤ b && ... to safely use unsigned comparison tricks.
3501+
// If a is not the maximum constant for b's type,
3502+
// we can increment a and switch to ≤.
3503+
if a.Int64() >= Maxintval[b.Type.Etype].Int64() {
3504+
return n
3505+
}
3506+
a = Nodintconst(a.Int64() + 1)
3507+
opl = OLE
3508+
}
3509+
3510+
bound := c.Int64() - a.Int64()
3511+
if bound < 0 {
3512+
// Bad news. Something like 5 <= x && x < 3.
3513+
// Rare in practice, and we still need to generate side-effects,
3514+
// so just leave it alone.
3515+
return n
3516+
}
3517+
3518+
// We have a ≤ b && b < c (or a ≤ b && b ≤ c).
3519+
// This is equivalent to (a-a) ≤ (b-a) && (b-a) < (c-a),
3520+
// which is equivalent to 0 ≤ (b-a) && (b-a) < (c-a),
3521+
// which is equivalent to uint(b-a) < uint(c-a).
3522+
ut := b.Type.toUnsigned()
3523+
lhs := conv(Nod(OSUB, b, a), ut)
3524+
rhs := Nodintconst(bound)
3525+
if negateResult {
3526+
// Negate top level.
3527+
opr = Brcom(opr)
3528+
}
3529+
cmp := Nod(opr, lhs, rhs)
3530+
cmp.Lineno = n.Lineno
3531+
cmp = addinit(cmp, l.Ninit.Slice())
3532+
cmp = addinit(cmp, r.Ninit.Slice())
3533+
cmp = typecheck(cmp, Erv)
3534+
cmp = walkexpr(cmp, init)
3535+
return cmp
3536+
}
3537+
34093538
// walkmul rewrites integer multiplication by powers of two as shifts.
34103539
// The result of walkmul MUST be assigned back to n, e.g.
34113540
// n.Left = walkmul(n.Left, init)
@@ -3694,7 +3823,7 @@ func walkdiv(n *Node, init *Nodes) *Node {
36943823
var nc Node
36953824

36963825
Nodconst(&nc, Types[Simtype[TUINT]], int64(w)-int64(pow))
3697-
n2 := Nod(ORSH, conv(n1, tounsigned(nl.Type)), &nc)
3826+
n2 := Nod(ORSH, conv(n1, nl.Type.toUnsigned()), &nc)
36983827
n.Left = Nod(OADD, nl, conv(n2, nl.Type))
36993828
}
37003829

test/checkbce.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ func f1(a [256]int, i int) {
2121
if 4 <= i && i < len(a) {
2222
useInt(a[i])
2323
useInt(a[i-1]) // ERROR "Found IsInBounds$"
24-
useInt(a[i-4]) // ERROR "Found IsInBounds$"
24+
// TODO: 'if 4 <= i && i < len(a)' gets rewritten to 'if uint(i - 4) < 256 - 4',
25+
// which the bounds checker cannot yet use to infer that the next line doesn't need a bounds check.
26+
useInt(a[i-4])
2527
}
2628
}
2729

0 commit comments

Comments
 (0)