// 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" "math/bits" "sort" "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/objw" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/obj" ) // isRegularMemory reports whether t can be compared/hashed as regular memory. func isRegularMemory(t *types.Type) bool { a, _ := types.AlgType(t) return a == types.AMEM } // eqCanPanic reports whether == on type t could panic (has an interface somewhere). // t must be comparable. func eqCanPanic(t *types.Type) bool { switch t.Kind() { default: return false case types.TINTER: return true case types.TARRAY: return eqCanPanic(t.Elem()) case types.TSTRUCT: for _, f := range t.FieldSlice() { if !f.Sym.IsBlank() && eqCanPanic(f.Type) { return true } } return false } } // 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) } } sym := TypeSymPrefix(".hash", t) if base.Flag.LowerR != 0 { fmt.Printf("genhash %v %v %v\n", closure, sym, t) } 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"), nil, types.NewPtr(t)), ir.NewField(base.Pos, typecheck.Lookup("h"), nil, types.Types[types.TUINTPTR]), } results := []*ir.Field{ir.NewField(base.Pos, nil, nil, types.Types[types.TUINTPTR])} tfn := ir.NewFuncType(base.Pos, nil, args, results) fn := typecheck.DeclFunc(sym, tfn) np := ir.AsNode(tfn.Type().Params().Field(0).Nname) nh := ir.AsNode(tfn.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(0)) cond := ir.NewBinaryExpr(base.Pos, ir.OLT, ni, ir.NewInt(t.NumElem())) post := ir.NewAssignStmt(base.Pos, ni, ir.NewBinaryExpr(base.Pos, ir.OADD, ni, ir.NewInt(1))) loop := ir.NewForStmt(base.Pos, nil, cond, post, nil) 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 !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 := 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(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.CurFunc = fn typecheck.Stmts(fn.Body) ir.CurFunc = nil if base.Debug.DclStack != 0 { types.CheckDclstack() } fn.SetNilCheckDisabled(true) typecheck.Target.Decls = append(typecheck.Target.Decls, fn) // 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 hashfor(t *types.Type) ir.Node { var sym *types.Sym switch a, _ := types.AlgType(t); a { case types.AMEM: base.Fatalf("hashfor with AMEM type") case types.AINTER: sym = ir.Pkgs.Runtime.Lookup("interhash") case types.ANILINTER: sym = ir.Pkgs.Runtime.Lookup("nilinterhash") case types.ASTRING: sym = ir.Pkgs.Runtime.Lookup("strhash") case types.AFLOAT32: sym = ir.Pkgs.Runtime.Lookup("f32hash") case types.AFLOAT64: sym = ir.Pkgs.Runtime.Lookup("f64hash") case types.ACPLX64: sym = ir.Pkgs.Runtime.Lookup("c64hash") case types.ACPLX128: sym = ir.Pkgs.Runtime.Lookup("c128hash") default: // Note: the caller of hashfor ensured that this symbol // exists and has a body by calling genhash for t. sym = TypeSymPrefix(".hash", t) } // TODO(austin): This creates an ir.Name with a nil Func. n := typecheck.NewName(sym) ir.MarkFunc(n) n.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{ types.NewField(base.Pos, nil, types.NewPtr(t)), types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), }, []*types.Field{ types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), })) return n } // 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 } sym := TypeSymPrefix(".eq", t) if base.Flag.LowerR != 0 { fmt.Printf("geneq %v\n", t) } // Autogenerate code for equality of structs and arrays. base.Pos = base.AutogeneratedPos // less confusing than end of input typecheck.DeclContext = ir.PEXTERN // func sym(p, q *T) bool tfn := ir.NewFuncType(base.Pos, nil, []*ir.Field{ir.NewField(base.Pos, typecheck.Lookup("p"), nil, types.NewPtr(t)), ir.NewField(base.Pos, typecheck.Lookup("q"), nil, types.NewPtr(t))}, []*ir.Field{ir.NewField(base.Pos, typecheck.Lookup("r"), nil, types.Types[types.TBOOL])}) fn := typecheck.DeclFunc(sym, tfn) np := ir.AsNode(tfn.Type().Params().Field(0).Nname) nq := ir.AsNode(tfn.Type().Params().Field(1).Nname) nr := ir.AsNode(tfn.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 { // return // } // // And so on. // // Otherwise it generates: // // for i := 0; i < nelem; i++ { // if eq(p[i], q[i]) { // } else { // goto neq // } // } // // TODO(josharian): consider doing some loop unrolling // for larger nelem as well, processing a few elements at a time in a loop. 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) } if nelem <= unroll { if last { // Do last comparison in a different manner. nelem-- } // Generate a series of checks. for i := int64(0); i < nelem; i++ { // if check {} else { goto neq } nif := ir.NewIfStmt(base.Pos, checkIdx(ir.NewInt(i)), 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(nelem)))) } } else { // Generate a for loop. // for i := 0; i < nelem; i++ i := typecheck.Temp(types.Types[types.TINT]) init := ir.NewAssignStmt(base.Pos, i, ir.NewInt(0)) cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(nelem)) post := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(1))) loop := ir.NewForStmt(base.Pos, nil, cond, post, nil) loop.PtrInit().Append(init) // if eq(pi, qi) {} 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) fn.Body.Append(loop) if last { fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(true))) } } } 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). // TODO: when the array size is small, unroll the length match checks. checkAll(3, false, func(pi, qi ir.Node) ir.Node { // Compare lengths. eqlen, _ := EqString(pi, qi) return eqlen }) checkAll(1, true, func(pi, qi ir.Node) ir.Node { // Compare contents. _, eqmem := 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) }) // TODO: pick apart structs, do them piecemeal too 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: // Build a list of conditions to satisfy. // The conditions are a list-of-lists. Conditions are reorderable // within each inner list. The outer lists must be evaluated in order. var conds [][]ir.Node conds = append(conds, []ir.Node{}) and := func(n ir.Node) { i := len(conds) - 1 conds[i] = append(conds[i], n) } // Walk the struct using memequal for runs of AMEM // and calling specific equality tests for the others. for i, fields := 0, t.FieldSlice(); i < len(fields); { f := fields[i] // Skip blank-named fields. if f.Sym.IsBlank() { i++ continue } // Compare non-memory fields with field equality. if !isRegularMemory(f.Type) { if eqCanPanic(f.Type) { // Enforce ordering by starting a new set of reorderable conditions. conds = append(conds, []ir.Node{}) } p := ir.NewSelectorExpr(base.Pos, ir.OXDOT, np, f.Sym) q := ir.NewSelectorExpr(base.Pos, ir.OXDOT, nq, f.Sym) switch { case f.Type.IsString(): eqlen, eqmem := EqString(p, q) and(eqlen) and(eqmem) default: and(ir.NewBinaryExpr(base.Pos, ir.OEQ, p, q)) } if eqCanPanic(f.Type) { // Also enforce ordering after something that can panic. conds = append(conds, []ir.Node{}) } i++ continue } // Find maximal length run of memory-only fields. size, next := memrun(t, i) // TODO(rsc): All the calls to newname are wrong for // cross-package unexported fields. if s := fields[i:next]; len(s) <= 2 { // Two or fewer fields: use plain field equality. for _, f := range s { and(eqfield(np, nq, f.Sym)) } } else { // More than two fields: use memequal. and(eqmem(np, nq, f.Sym, size)) } i = next } // Sort conditions to put runtime calls last. // Preserve the rest of the ordering. var flatConds []ir.Node for _, c := range conds { isCall := func(n ir.Node) bool { return n.Op() == ir.OCALL || n.Op() == ir.OCALLFUNC } sort.SliceStable(c, func(i, j int) bool { return !isCall(c[i]) && isCall(c[j]) }) flatConds = append(flatConds, c...) } if len(flatConds) == 0 { fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(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(false))) if 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.CurFunc = fn typecheck.Stmts(fn.Body) ir.CurFunc = nil if base.Debug.DclStack != 0 { types.CheckDclstack() } // 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) // 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 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 }) } // eqfield returns the node // p.field == q.field func eqfield(p ir.Node, q ir.Node, field *types.Sym) ir.Node { nx := ir.NewSelectorExpr(base.Pos, ir.OXDOT, p, field) ny := ir.NewSelectorExpr(base.Pos, ir.OXDOT, q, field) ne := ir.NewBinaryExpr(base.Pos, ir.OEQ, nx, ny) return ne } // EqString returns the nodes // len(s) == len(t) // and // memequal(s.ptr, t.ptr, len(s)) // which can be used to construct string equality comparison. // eqlen must be evaluated before eqmem, and shortcircuiting is required. func EqString(s, t ir.Node) (eqlen *ir.BinaryExpr, eqmem *ir.CallExpr) { s = typecheck.Conv(s, types.Types[types.TSTRING]) t = typecheck.Conv(t, types.Types[types.TSTRING]) sptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, s) tptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, t) slen := typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OLEN, s), types.Types[types.TUINTPTR]) tlen := typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OLEN, t), types.Types[types.TUINTPTR]) fn := typecheck.LookupRuntime("memequal") fn = typecheck.SubstArgTypes(fn, types.Types[types.TUINT8], types.Types[types.TUINT8]) call := typecheck.Call(base.Pos, fn, []ir.Node{sptr, tptr, ir.Copy(slen)}, false).(*ir.CallExpr) cmp := ir.NewBinaryExpr(base.Pos, ir.OEQ, slen, tlen) cmp = typecheck.Expr(cmp).(*ir.BinaryExpr) cmp.SetType(types.Types[types.TBOOL]) return cmp, call } // EqInterface returns the nodes // s.tab == t.tab (or s.typ == t.typ, as appropriate) // and // ifaceeq(s.tab, s.data, t.data) (or efaceeq(s.typ, s.data, t.data), as appropriate) // which can be used to construct interface equality comparison. // eqtab must be evaluated before eqdata, and shortcircuiting is required. func EqInterface(s, t ir.Node) (eqtab *ir.BinaryExpr, eqdata *ir.CallExpr) { if !types.Identical(s.Type(), t.Type()) { base.Fatalf("EqInterface %v %v", s.Type(), t.Type()) } // func ifaceeq(tab *uintptr, x, y unsafe.Pointer) (ret bool) // func efaceeq(typ *uintptr, x, y unsafe.Pointer) (ret bool) var fn ir.Node if s.Type().IsEmptyInterface() { fn = typecheck.LookupRuntime("efaceeq") } else { fn = typecheck.LookupRuntime("ifaceeq") } stab := ir.NewUnaryExpr(base.Pos, ir.OITAB, s) ttab := ir.NewUnaryExpr(base.Pos, ir.OITAB, t) sdata := ir.NewUnaryExpr(base.Pos, ir.OIDATA, s) tdata := ir.NewUnaryExpr(base.Pos, ir.OIDATA, t) sdata.SetType(types.Types[types.TUNSAFEPTR]) tdata.SetType(types.Types[types.TUNSAFEPTR]) sdata.SetTypecheck(1) tdata.SetTypecheck(1) call := typecheck.Call(base.Pos, fn, []ir.Node{stab, sdata, tdata}, false).(*ir.CallExpr) cmp := ir.NewBinaryExpr(base.Pos, ir.OEQ, stab, ttab) cmp = typecheck.Expr(cmp).(*ir.BinaryExpr) cmp.SetType(types.Types[types.TBOOL]) return cmp, call } // eqmem returns the node // memequal(&p.field, &q.field [, size]) func eqmem(p ir.Node, q ir.Node, field *types.Sym, size int64) ir.Node { nx := typecheck.Expr(typecheck.NodAddr(ir.NewSelectorExpr(base.Pos, ir.OXDOT, p, field))) ny := typecheck.Expr(typecheck.NodAddr(ir.NewSelectorExpr(base.Pos, ir.OXDOT, q, field))) fn, needsize := eqmemfunc(size, nx.Type().Elem()) call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) call.Args.Append(nx) call.Args.Append(ny) if needsize { call.Args.Append(ir.NewInt(size)) } return call } func eqmemfunc(size int64, t *types.Type) (fn *ir.Name, needsize bool) { switch size { default: fn = typecheck.LookupRuntime("memequal") needsize = true case 1, 2, 4, 8, 16: buf := fmt.Sprintf("memequal%d", int(size)*8) fn = typecheck.LookupRuntime(buf) } fn = typecheck.SubstArgTypes(fn, t, t) return fn, needsize } // memrun finds runs of struct fields for which memory-only algs are appropriate. // t is the parent struct type, and start is the field index at which to start the run. // size is the length in bytes of the memory included in the run. // next is the index just after the end of the memory run. func memrun(t *types.Type, start int) (size int64, next int) { next = start for { next++ if next == t.NumFields() { break } // Stop run after a padded field. if types.IsPaddedField(t, next-1) { break } // Also, stop before a blank or non-memory field. if f := t.Field(next); f.Sym.IsBlank() || !isRegularMemory(f.Type) { break } // For issue 46283, don't combine fields if the resulting load would // require a larger alignment than the component fields. if base.Ctxt.Arch.Alignment > 1 { align := t.Alignment() if off := t.Field(start).Offset; off&(align-1) != 0 { // Offset is less aligned than the containing type. // Use offset to determine alignment. align = 1 << uint(bits.TrailingZeros64(uint64(off))) } size := t.Field(next).End() - t.Field(start).Offset if size > align { break } } } return t.Field(next-1).End() - t.Field(start).Offset, next } func hashmem(t *types.Type) ir.Node { sym := ir.Pkgs.Runtime.Lookup("memhash") // TODO(austin): This creates an ir.Name with a nil Func. n := typecheck.NewName(sym) ir.MarkFunc(n) n.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{ types.NewField(base.Pos, nil, types.NewPtr(t)), types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), }, []*types.Field{ types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]), })) return n }