blob: 69de685ca0555268d179dad6354e5bb203bb52e4 [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package reflectdata
import (
"fmt"
"cmd/compile/internal/base"
"cmd/compile/internal/compare"
"cmd/compile/internal/ir"
"cmd/compile/internal/objw"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/src"
)
// AlgType returns the fixed-width AMEMxx variants instead of the general
// AMEM kind when possible.
func AlgType(t *types.Type) types.AlgKind {
a, _ := types.AlgType(t)
if a == types.AMEM {
if t.Alignment() < int64(base.Ctxt.Arch.Alignment) && t.Alignment() < t.Size() {
// For example, we can't treat [2]int16 as an int32 if int32s require
// 4-byte alignment. See issue 46283.
return a
}
switch t.Size() {
case 0:
return types.AMEM0
case 1:
return types.AMEM8
case 2:
return types.AMEM16
case 4:
return types.AMEM32
case 8:
return types.AMEM64
case 16:
return types.AMEM128
}
}
return a
}
// genhash returns a symbol which is the closure used to compute
// the hash of a value of type t.
// Note: the generated function must match runtime.typehash exactly.
func genhash(t *types.Type) *obj.LSym {
switch AlgType(t) {
default:
// genhash is only called for types that have equality
base.Fatalf("genhash %v", t)
case types.AMEM0:
return sysClosure("memhash0")
case types.AMEM8:
return sysClosure("memhash8")
case types.AMEM16:
return sysClosure("memhash16")
case types.AMEM32:
return sysClosure("memhash32")
case types.AMEM64:
return sysClosure("memhash64")
case types.AMEM128:
return sysClosure("memhash128")
case types.ASTRING:
return sysClosure("strhash")
case types.AINTER:
return sysClosure("interhash")
case types.ANILINTER:
return sysClosure("nilinterhash")
case types.AFLOAT32:
return sysClosure("f32hash")
case types.AFLOAT64:
return sysClosure("f64hash")
case types.ACPLX64:
return sysClosure("c64hash")
case types.ACPLX128:
return sysClosure("c128hash")
case types.AMEM:
// For other sizes of plain memory, we build a closure
// that calls memhash_varlen. The size of the memory is
// encoded in the first slot of the closure.
closure := TypeLinksymLookup(fmt.Sprintf(".hashfunc%d", t.Size()))
if len(closure.P) > 0 { // already generated
return closure
}
if memhashvarlen == nil {
memhashvarlen = typecheck.LookupRuntimeFunc("memhash_varlen")
}
ot := 0
ot = objw.SymPtr(closure, ot, memhashvarlen, 0)
ot = objw.Uintptr(closure, ot, uint64(t.Size())) // size encoded in closure
objw.Global(closure, int32(ot), obj.DUPOK|obj.RODATA)
return closure
case types.ASPECIAL:
break
}
closure := TypeLinksymPrefix(".hashfunc", t)
if len(closure.P) > 0 { // already generated
return closure
}
// Generate hash functions for subtypes.
// There are cases where we might not use these hashes,
// but in that case they will get dead-code eliminated.
// (And the closure generated by genhash will also get
// dead-code eliminated, as we call the subtype hashers
// directly.)
switch t.Kind() {
case types.TARRAY:
genhash(t.Elem())
case types.TSTRUCT:
for _, f := range t.FieldSlice() {
genhash(f.Type)
}
}
if base.Flag.LowerR != 0 {
fmt.Printf("genhash %v %v\n", closure, t)
}
fn := hashFunc(t)
// Build closure. It doesn't close over any variables, so
// it contains just the function pointer.
objw.SymPtr(closure, 0, fn.Linksym(), 0)
objw.Global(closure, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
return closure
}
func hashFunc(t *types.Type) *ir.Func {
sym := TypeSymPrefix(".hash", t)
if sym.Def != nil {
return sym.Def.(*ir.Name).Func
}
base.Pos = base.AutogeneratedPos // less confusing than end of input
typecheck.DeclContext = ir.PEXTERN
// func sym(p *T, h uintptr) uintptr
args := []*ir.Field{
ir.NewField(base.Pos, typecheck.Lookup("p"), types.NewPtr(t)),
ir.NewField(base.Pos, typecheck.Lookup("h"), types.Types[types.TUINTPTR]),
}
results := []*ir.Field{ir.NewField(base.Pos, nil, types.Types[types.TUINTPTR])}
fn := typecheck.DeclFunc(sym, nil, args, results)
sym.Def = fn.Nname
np := ir.AsNode(fn.Type().Params().Field(0).Nname)
nh := ir.AsNode(fn.Type().Params().Field(1).Nname)
switch t.Kind() {
case types.TARRAY:
// An array of pure memory would be handled by the
// standard algorithm, so the element type must not be
// pure memory.
hashel := hashfor(t.Elem())
// for i := 0; i < nelem; i++
ni := typecheck.Temp(types.Types[types.TINT])
init := ir.NewAssignStmt(base.Pos, ni, ir.NewInt(base.Pos, 0))
cond := ir.NewBinaryExpr(base.Pos, ir.OLT, ni, ir.NewInt(base.Pos, t.NumElem()))
post := ir.NewAssignStmt(base.Pos, ni, ir.NewBinaryExpr(base.Pos, ir.OADD, ni, ir.NewInt(base.Pos, 1)))
loop := ir.NewForStmt(base.Pos, nil, cond, post, nil, false)
loop.PtrInit().Append(init)
// h = hashel(&p[i], h)
call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil)
nx := ir.NewIndexExpr(base.Pos, np, ni)
nx.SetBounded(true)
na := typecheck.NodAddr(nx)
call.Args.Append(na)
call.Args.Append(nh)
loop.Body.Append(ir.NewAssignStmt(base.Pos, nh, call))
fn.Body.Append(loop)
case types.TSTRUCT:
// Walk the struct using memhash for runs of AMEM
// and calling specific hash functions for the others.
for i, fields := 0, t.FieldSlice(); i < len(fields); {
f := fields[i]
// Skip blank fields.
if f.Sym.IsBlank() {
i++
continue
}
// Hash non-memory fields with appropriate hash function.
if !compare.IsRegularMemory(f.Type) {
hashel := hashfor(f.Type)
call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil)
nx := ir.NewSelectorExpr(base.Pos, ir.OXDOT, np, f.Sym) // TODO: fields from other packages?
na := typecheck.NodAddr(nx)
call.Args.Append(na)
call.Args.Append(nh)
fn.Body.Append(ir.NewAssignStmt(base.Pos, nh, call))
i++
continue
}
// Otherwise, hash a maximal length run of raw memory.
size, next := compare.Memrun(t, i)
// h = hashel(&p.first, size, h)
hashel := hashmem(f.Type)
call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil)
nx := ir.NewSelectorExpr(base.Pos, ir.OXDOT, np, f.Sym) // TODO: fields from other packages?
na := typecheck.NodAddr(nx)
call.Args.Append(na)
call.Args.Append(nh)
call.Args.Append(ir.NewInt(base.Pos, size))
fn.Body.Append(ir.NewAssignStmt(base.Pos, nh, call))
i = next
}
}
r := ir.NewReturnStmt(base.Pos, nil)
r.Results.Append(nh)
fn.Body.Append(r)
if base.Flag.LowerR != 0 {
ir.DumpList("genhash body", fn.Body)
}
typecheck.FinishFuncBody()
fn.SetDupok(true)
typecheck.Func(fn)
ir.WithFunc(fn, func() {
typecheck.Stmts(fn.Body)
})
fn.SetNilCheckDisabled(true)
typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
return fn
}
func runtimeHashFor(name string, t *types.Type) *ir.Name {
n := typecheck.LookupRuntime(name)
n = typecheck.SubstArgTypes(n, t)
return n
}
// hashfor returns the function to compute the hash of a value of type t.
func hashfor(t *types.Type) *ir.Name {
switch a, _ := types.AlgType(t); a {
case types.AMEM:
base.Fatalf("hashfor with AMEM type")
case types.AINTER:
return runtimeHashFor("interhash", t)
case types.ANILINTER:
return runtimeHashFor("nilinterhash", t)
case types.ASTRING:
return runtimeHashFor("strhash", t)
case types.AFLOAT32:
return runtimeHashFor("f32hash", t)
case types.AFLOAT64:
return runtimeHashFor("f64hash", t)
case types.ACPLX64:
return runtimeHashFor("c64hash", t)
case types.ACPLX128:
return runtimeHashFor("c128hash", t)
}
fn := hashFunc(t)
return fn.Nname
}
// sysClosure returns a closure which will call the
// given runtime function (with no closed-over variables).
func sysClosure(name string) *obj.LSym {
s := typecheck.LookupRuntimeVar(name + "·f")
if len(s.P) == 0 {
f := typecheck.LookupRuntimeFunc(name)
objw.SymPtr(s, 0, f, 0)
objw.Global(s, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
}
return s
}
// geneq returns a symbol which is the closure used to compute
// equality for two objects of type t.
func geneq(t *types.Type) *obj.LSym {
switch AlgType(t) {
case types.ANOEQ:
// The runtime will panic if it tries to compare
// a type with a nil equality function.
return nil
case types.AMEM0:
return sysClosure("memequal0")
case types.AMEM8:
return sysClosure("memequal8")
case types.AMEM16:
return sysClosure("memequal16")
case types.AMEM32:
return sysClosure("memequal32")
case types.AMEM64:
return sysClosure("memequal64")
case types.AMEM128:
return sysClosure("memequal128")
case types.ASTRING:
return sysClosure("strequal")
case types.AINTER:
return sysClosure("interequal")
case types.ANILINTER:
return sysClosure("nilinterequal")
case types.AFLOAT32:
return sysClosure("f32equal")
case types.AFLOAT64:
return sysClosure("f64equal")
case types.ACPLX64:
return sysClosure("c64equal")
case types.ACPLX128:
return sysClosure("c128equal")
case types.AMEM:
// make equality closure. The size of the type
// is encoded in the closure.
closure := TypeLinksymLookup(fmt.Sprintf(".eqfunc%d", t.Size()))
if len(closure.P) != 0 {
return closure
}
if memequalvarlen == nil {
memequalvarlen = typecheck.LookupRuntimeFunc("memequal_varlen")
}
ot := 0
ot = objw.SymPtr(closure, ot, memequalvarlen, 0)
ot = objw.Uintptr(closure, ot, uint64(t.Size()))
objw.Global(closure, int32(ot), obj.DUPOK|obj.RODATA)
return closure
case types.ASPECIAL:
break
}
closure := TypeLinksymPrefix(".eqfunc", t)
if len(closure.P) > 0 { // already generated
return closure
}
if base.Flag.LowerR != 0 {
fmt.Printf("geneq %v\n", t)
}
fn := eqFunc(t)
// Generate a closure which points at the function we just generated.
objw.SymPtr(closure, 0, fn.Linksym(), 0)
objw.Global(closure, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
return closure
}
func eqFunc(t *types.Type) *ir.Func {
// Autogenerate code for equality of structs and arrays.
sym := TypeSymPrefix(".eq", t)
if sym.Def != nil {
return sym.Def.(*ir.Name).Func
}
base.Pos = base.AutogeneratedPos // less confusing than end of input
typecheck.DeclContext = ir.PEXTERN
// func sym(p, q *T) bool
fn := typecheck.DeclFunc(sym, nil,
[]*ir.Field{ir.NewField(base.Pos, typecheck.Lookup("p"), types.NewPtr(t)), ir.NewField(base.Pos, typecheck.Lookup("q"), types.NewPtr(t))},
[]*ir.Field{ir.NewField(base.Pos, typecheck.Lookup("r"), types.Types[types.TBOOL])},
)
sym.Def = fn.Nname
np := ir.AsNode(fn.Type().Params().Field(0).Nname)
nq := ir.AsNode(fn.Type().Params().Field(1).Nname)
nr := ir.AsNode(fn.Type().Results().Field(0).Nname)
// Label to jump to if an equality test fails.
neq := typecheck.AutoLabel(".neq")
// We reach here only for types that have equality but
// cannot be handled by the standard algorithms,
// so t must be either an array or a struct.
switch t.Kind() {
default:
base.Fatalf("geneq %v", t)
case types.TARRAY:
nelem := t.NumElem()
// checkAll generates code to check the equality of all array elements.
// If unroll is greater than nelem, checkAll generates:
//
// if eq(p[0], q[0]) && eq(p[1], q[1]) && ... {
// } else {
// goto neq
// }
//
// And so on.
//
// Otherwise it generates:
//
// iterateTo := nelem/unroll*unroll
// for i := 0; i < iterateTo; i += unroll {
// if eq(p[i+0], q[i+0]) && eq(p[i+1], q[i+1]) && ... && eq(p[i+unroll-1], q[i+unroll-1]) {
// } else {
// goto neq
// }
// }
// if eq(p[iterateTo+0], q[iterateTo+0]) && eq(p[iterateTo+1], q[iterateTo+1]) && ... {
// } else {
// goto neq
// }
//
checkAll := func(unroll int64, last bool, eq func(pi, qi ir.Node) ir.Node) {
// checkIdx generates a node to check for equality at index i.
checkIdx := func(i ir.Node) ir.Node {
// pi := p[i]
pi := ir.NewIndexExpr(base.Pos, np, i)
pi.SetBounded(true)
pi.SetType(t.Elem())
// qi := q[i]
qi := ir.NewIndexExpr(base.Pos, nq, i)
qi.SetBounded(true)
qi.SetType(t.Elem())
return eq(pi, qi)
}
iterations := nelem / unroll
iterateTo := iterations * unroll
// If a loop is iterated only once, there shouldn't be any loop at all.
if iterations == 1 {
iterateTo = 0
}
if iterateTo > 0 {
// Generate an unrolled for loop.
// for i := 0; i < nelem/unroll*unroll; i += unroll
i := typecheck.Temp(types.Types[types.TINT])
init := ir.NewAssignStmt(base.Pos, i, ir.NewInt(base.Pos, 0))
cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(base.Pos, iterateTo))
loop := ir.NewForStmt(base.Pos, nil, cond, nil, nil, false)
loop.PtrInit().Append(init)
// if eq(p[i+0], q[i+0]) && eq(p[i+1], q[i+1]) && ... && eq(p[i+unroll-1], q[i+unroll-1]) {
// } else {
// goto neq
// }
for j := int64(0); j < unroll; j++ {
// if check {} else { goto neq }
nif := ir.NewIfStmt(base.Pos, checkIdx(i), nil, nil)
nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq))
loop.Body.Append(nif)
post := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(base.Pos, 1)))
loop.Body.Append(post)
}
fn.Body.Append(loop)
if nelem == iterateTo {
if last {
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(base.Pos, true)))
}
return
}
}
// Generate remaining checks, if nelem is not a multiple of unroll.
if last {
// Do last comparison in a different manner.
nelem--
}
// if eq(p[iterateTo+0], q[iterateTo+0]) && eq(p[iterateTo+1], q[iterateTo+1]) && ... {
// } else {
// goto neq
// }
for j := iterateTo; j < nelem; j++ {
// if check {} else { goto neq }
nif := ir.NewIfStmt(base.Pos, checkIdx(ir.NewInt(base.Pos, j)), nil, nil)
nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq))
fn.Body.Append(nif)
}
if last {
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, checkIdx(ir.NewInt(base.Pos, nelem))))
}
}
switch t.Elem().Kind() {
case types.TSTRING:
// Do two loops. First, check that all the lengths match (cheap).
// Second, check that all the contents match (expensive).
checkAll(3, false, func(pi, qi ir.Node) ir.Node {
// Compare lengths.
eqlen, _ := compare.EqString(pi, qi)
return eqlen
})
checkAll(1, true, func(pi, qi ir.Node) ir.Node {
// Compare contents.
_, eqmem := compare.EqString(pi, qi)
return eqmem
})
case types.TFLOAT32, types.TFLOAT64:
checkAll(2, true, func(pi, qi ir.Node) ir.Node {
// p[i] == q[i]
return ir.NewBinaryExpr(base.Pos, ir.OEQ, pi, qi)
})
case types.TSTRUCT:
isCall := func(n ir.Node) bool {
return n.Op() == ir.OCALL || n.Op() == ir.OCALLFUNC
}
var expr ir.Node
var hasCallExprs bool
allCallExprs := true
and := func(cond ir.Node) {
if expr == nil {
expr = cond
} else {
expr = ir.NewLogicalExpr(base.Pos, ir.OANDAND, expr, cond)
}
}
var tmpPos src.XPos
pi := ir.NewIndexExpr(tmpPos, np, ir.NewInt(tmpPos, 0))
pi.SetBounded(true)
pi.SetType(t.Elem())
qi := ir.NewIndexExpr(tmpPos, nq, ir.NewInt(tmpPos, 0))
qi.SetBounded(true)
qi.SetType(t.Elem())
flatConds, canPanic := compare.EqStruct(t.Elem(), pi, qi)
for _, c := range flatConds {
if isCall(c) {
hasCallExprs = true
} else {
allCallExprs = false
}
}
if !hasCallExprs || allCallExprs || canPanic {
checkAll(1, true, func(pi, qi ir.Node) ir.Node {
// p[i] == q[i]
return ir.NewBinaryExpr(base.Pos, ir.OEQ, pi, qi)
})
} else {
checkAll(4, false, func(pi, qi ir.Node) ir.Node {
expr = nil
flatConds, _ := compare.EqStruct(t.Elem(), pi, qi)
if len(flatConds) == 0 {
return ir.NewBool(base.Pos, true)
}
for _, c := range flatConds {
if !isCall(c) {
and(c)
}
}
return expr
})
checkAll(2, true, func(pi, qi ir.Node) ir.Node {
expr = nil
flatConds, _ := compare.EqStruct(t.Elem(), pi, qi)
for _, c := range flatConds {
if isCall(c) {
and(c)
}
}
return expr
})
}
default:
checkAll(1, true, func(pi, qi ir.Node) ir.Node {
// p[i] == q[i]
return ir.NewBinaryExpr(base.Pos, ir.OEQ, pi, qi)
})
}
case types.TSTRUCT:
flatConds, _ := compare.EqStruct(t, np, nq)
if len(flatConds) == 0 {
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(base.Pos, true)))
} else {
for _, c := range flatConds[:len(flatConds)-1] {
// if cond {} else { goto neq }
n := ir.NewIfStmt(base.Pos, c, nil, nil)
n.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq))
fn.Body.Append(n)
}
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, flatConds[len(flatConds)-1]))
}
}
// ret:
// return
ret := typecheck.AutoLabel(".ret")
fn.Body.Append(ir.NewLabelStmt(base.Pos, ret))
fn.Body.Append(ir.NewReturnStmt(base.Pos, nil))
// neq:
// r = false
// return (or goto ret)
fn.Body.Append(ir.NewLabelStmt(base.Pos, neq))
fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(base.Pos, false)))
if compare.EqCanPanic(t) || anyCall(fn) {
// Epilogue is large, so share it with the equal case.
fn.Body.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, ret))
} else {
// Epilogue is small, so don't bother sharing.
fn.Body.Append(ir.NewReturnStmt(base.Pos, nil))
}
// TODO(khr): the epilogue size detection condition above isn't perfect.
// We should really do a generic CL that shares epilogues across
// the board. See #24936.
if base.Flag.LowerR != 0 {
ir.DumpList("geneq body", fn.Body)
}
typecheck.FinishFuncBody()
fn.SetDupok(true)
typecheck.Func(fn)
ir.WithFunc(fn, func() {
typecheck.Stmts(fn.Body)
})
// Disable checknils while compiling this code.
// We are comparing a struct or an array,
// neither of which can be nil, and our comparisons
// are shallow.
fn.SetNilCheckDisabled(true)
typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
return fn
}
// EqFor returns ONAME node represents type t's equal function, and a boolean
// to indicates whether a length needs to be passed when calling the function.
func EqFor(t *types.Type) (ir.Node, bool) {
switch a, _ := types.AlgType(t); a {
case types.AMEM:
n := typecheck.LookupRuntime("memequal")
n = typecheck.SubstArgTypes(n, t, t)
return n, true
case types.ASPECIAL:
fn := eqFunc(t)
return fn.Nname, false
}
base.Fatalf("EqFor %v", t)
return nil, false
}
func anyCall(fn *ir.Func) bool {
return ir.Any(fn, func(n ir.Node) bool {
// TODO(rsc): No methods?
op := n.Op()
return op == ir.OCALL || op == ir.OCALLFUNC
})
}
func hashmem(t *types.Type) ir.Node {
n := typecheck.LookupRuntime("memhash")
n = typecheck.SubstArgTypes(n, t)
return n
}