1
0
mirror of https://github.com/golang/go synced 2024-11-23 16:50:06 -07:00

cmd/compile: improve equality algs for arrays of interfaces

type T [8]interface{}

Prior to this change, we generated this equality algorithm for T:

func eqT(p, q *T) bool {
    for i := range *p {
        if p[i] != q[i] {
            return false
        }
    }
    return true
}

This change splits this into two loops, so that we can do the
cheap (type) half early and only then do the expensive (data) half.

We now generate:

func eqT(p, q *T) (r bool) {
    for i := range *p {
        if p[i].type == q[i].type {
        } else {
            return
        }
    }
    for j := range *p {
        if runtime.efaceeq(p[j].type, p[j].data, q[j].data) {
        } else {
            return
        }
    }
    return true
}

The use of a named return value and a bare return is to work
around some typechecking problems that stymied me.

The structure of using equals and else (instead of not equals and then)
was for implementation convenience and clarity. As a bonus,
it generates slightly shorter code on AMD64, because zeroing a register
to return is cheaper than writing $1 to it.

The generated code is typically ~17% larger because it contains
two loops instead of one. In the future, we might want to unroll
the first loop when the array is small.

Change-Id: I5b2c8dd3384852f085c4f3e1f6ad20bc5ae59062
Reviewed-on: https://go-review.googlesource.com/c/go/+/230208
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Josh Bleecher Snyder 2020-04-24 15:59:17 -07:00
parent 1cc7be89a9
commit 7eab9506c9

View File

@ -505,7 +505,7 @@ func geneq(t *types.Type) *obj.LSym {
namedfield("p", types.NewPtr(t)),
namedfield("q", types.NewPtr(t)),
)
tfn.Rlist.Set1(anonfield(types.Types[TBOOL]))
tfn.Rlist.Set1(namedfield("r", types.Types[TBOOL]))
fn := dclfunc(sym, tfn)
np := asNode(tfn.Type.Params().Field(0).Nname)
@ -519,35 +519,72 @@ func geneq(t *types.Type) *obj.LSym {
Fatalf("geneq %v", t)
case TARRAY:
// An array of pure memory would be handled by the
// standard memequal, so the element type must not be
// pure memory. Even if we unrolled the range loop,
// each iteration would be a function call, so don't bother
// unrolling.
nrange := nod(ORANGE, nil, nod(ODEREF, np, nil))
// rangedCheck generates:
//
// for idx := range *p {
// if eq(p[i], q[i]) {
// } else {
// return
// }
// }
rangedCheck := func(idx string, eq func(pi, qi *Node) *Node) {
// for idx := range *p
nrange := nod(ORANGE, nil, nod(ODEREF, np, nil))
ni := newname(lookup(idx))
ni.Type = types.Types[TINT]
nrange.List.Set1(ni)
nrange.SetColas(true)
colasdefn(nrange.List.Slice(), nrange)
ni = nrange.List.First()
ni := newname(lookup("i"))
ni.Type = types.Types[TINT]
nrange.List.Set1(ni)
nrange.SetColas(true)
colasdefn(nrange.List.Slice(), nrange)
ni = nrange.List.First()
// pi := p[i]
pi := nod(OINDEX, np, ni)
pi.SetBounded(true)
pi.Type = t.Elem()
// qi := q[i]
qi := nod(OINDEX, nq, ni)
qi.SetBounded(true)
qi.Type = t.Elem()
// if p[i] != q[i] { return false }
nx := nod(OINDEX, np, ni)
nx.SetBounded(true)
ny := nod(OINDEX, nq, ni)
ny.SetBounded(true)
nif := nod(OIF, nil, nil)
nif.Left = nod(ONE, nx, ny)
r := nod(ORETURN, nil, nil)
r.List.Append(nodbool(false))
nif.Nbody.Append(r)
nrange.Nbody.Append(nif)
fn.Nbody.Append(nrange)
// if eq(pi, qi) {} else { return }
cmp := eq(pi, qi)
nif := nod(OIF, cmp, nil)
ret := nod(ORETURN, nil, nil)
nif.Rlist.Append(ret)
nrange.Nbody.Append(nif)
fn.Nbody.Append(nrange)
}
switch t.Elem().Etype {
case TINTER:
// Do two loops. First, check that all the types match (cheap).
// Second, check that all the data match (expensive).
// TODO: when the array size is small, unroll the tab match checks.
rangedCheck("i", func(pi, qi *Node) *Node {
// Compare types.
pi = typecheck(pi, ctxExpr)
qi = typecheck(qi, ctxExpr)
eqtab, _ := eqinterface(pi, qi)
return eqtab
})
rangedCheck("j", func(pi, qi *Node) *Node {
// Compare data.
pi = typecheck(pi, ctxExpr)
qi = typecheck(qi, ctxExpr)
_, eqdata := eqinterface(pi, qi)
return eqdata
})
default:
// An array of pure memory would be handled by the standard memequal,
// so the element type must not be pure memory.
// Loop over each element, checking for equality.
// TODO(josharian): For element types that don't involve
// a function call, such as floats, unroll when array size is small.
rangedCheck("i", func(pi, qi *Node) *Node {
// p[i] == q[i]
return nod(OEQ, pi, qi)
})
}
// return true
ret := nod(ORETURN, nil, nil)
ret.List.Append(nodbool(true))