| // 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 gc | |
| import ( | |
| "cmd/compile/internal/types" | |
| "cmd/internal/obj" | |
| "fmt" | |
| "sort" | |
| ) | |
| // AlgKind describes the kind of algorithms used for comparing and | |
| // hashing a Type. | |
| type AlgKind int | |
| //go:generate stringer -type AlgKind -trimprefix A | |
| const ( | |
| // These values are known by runtime. | |
| ANOEQ AlgKind = iota | |
| AMEM0 | |
| AMEM8 | |
| AMEM16 | |
| AMEM32 | |
| AMEM64 | |
| AMEM128 | |
| ASTRING | |
| AINTER | |
| ANILINTER | |
| AFLOAT32 | |
| AFLOAT64 | |
| ACPLX64 | |
| ACPLX128 | |
| // Type can be compared/hashed as regular memory. | |
| AMEM AlgKind = 100 | |
| // Type needs special comparison/hashing functions. | |
| ASPECIAL AlgKind = -1 | |
| ) | |
| // IsComparable reports whether t is a comparable type. | |
| func IsComparable(t *types.Type) bool { | |
| a, _ := algtype1(t) | |
| return a != ANOEQ | |
| } | |
| // IsRegularMemory reports whether t can be compared/hashed as regular memory. | |
| func IsRegularMemory(t *types.Type) bool { | |
| a, _ := algtype1(t) | |
| return a == AMEM | |
| } | |
| // IncomparableField returns an incomparable Field of struct Type t, if any. | |
| func IncomparableField(t *types.Type) *types.Field { | |
| for _, f := range t.FieldSlice() { | |
| if !IsComparable(f.Type) { | |
| return f | |
| } | |
| } | |
| return nil | |
| } | |
| // EqCanPanic reports whether == on type t could panic (has an interface somewhere). | |
| // t must be comparable. | |
| func EqCanPanic(t *types.Type) bool { | |
| switch t.Etype { | |
| default: | |
| return false | |
| case TINTER: | |
| return true | |
| case TARRAY: | |
| return EqCanPanic(t.Elem()) | |
| case TSTRUCT: | |
| for _, f := range t.FieldSlice() { | |
| if !f.Sym.IsBlank() && EqCanPanic(f.Type) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| } | |
| // algtype is like algtype1, except it returns the fixed-width AMEMxx variants | |
| // instead of the general AMEM kind when possible. | |
| func algtype(t *types.Type) AlgKind { | |
| a, _ := algtype1(t) | |
| if a == AMEM { | |
| switch t.Width { | |
| case 0: | |
| return AMEM0 | |
| case 1: | |
| return AMEM8 | |
| case 2: | |
| return AMEM16 | |
| case 4: | |
| return AMEM32 | |
| case 8: | |
| return AMEM64 | |
| case 16: | |
| return AMEM128 | |
| } | |
| } | |
| return a | |
| } | |
| // algtype1 returns the AlgKind used for comparing and hashing Type t. | |
| // If it returns ANOEQ, it also returns the component type of t that | |
| // makes it incomparable. | |
| func algtype1(t *types.Type) (AlgKind, *types.Type) { | |
| if t.Broke() { | |
| return AMEM, nil | |
| } | |
| if t.Noalg() { | |
| return ANOEQ, t | |
| } | |
| switch t.Etype { | |
| case TANY, TFORW: | |
| // will be defined later. | |
| return ANOEQ, t | |
| case TINT8, TUINT8, TINT16, TUINT16, | |
| TINT32, TUINT32, TINT64, TUINT64, | |
| TINT, TUINT, TUINTPTR, | |
| TBOOL, TPTR, | |
| TCHAN, TUNSAFEPTR: | |
| return AMEM, nil | |
| case TFUNC, TMAP: | |
| return ANOEQ, t | |
| case TFLOAT32: | |
| return AFLOAT32, nil | |
| case TFLOAT64: | |
| return AFLOAT64, nil | |
| case TCOMPLEX64: | |
| return ACPLX64, nil | |
| case TCOMPLEX128: | |
| return ACPLX128, nil | |
| case TSTRING: | |
| return ASTRING, nil | |
| case TINTER: | |
| if t.IsEmptyInterface() { | |
| return ANILINTER, nil | |
| } | |
| return AINTER, nil | |
| case TSLICE: | |
| return ANOEQ, t | |
| case TARRAY: | |
| a, bad := algtype1(t.Elem()) | |
| switch a { | |
| case AMEM: | |
| return AMEM, nil | |
| case ANOEQ: | |
| return ANOEQ, bad | |
| } | |
| switch t.NumElem() { | |
| case 0: | |
| // We checked above that the element type is comparable. | |
| return AMEM, nil | |
| case 1: | |
| // Single-element array is same as its lone element. | |
| return a, nil | |
| } | |
| return ASPECIAL, nil | |
| case TSTRUCT: | |
| fields := t.FieldSlice() | |
| // One-field struct is same as that one field alone. | |
| if len(fields) == 1 && !fields[0].Sym.IsBlank() { | |
| return algtype1(fields[0].Type) | |
| } | |
| ret := AMEM | |
| for i, f := range fields { | |
| // All fields must be comparable. | |
| a, bad := algtype1(f.Type) | |
| if a == ANOEQ { | |
| return ANOEQ, bad | |
| } | |
| // Blank fields, padded fields, fields with non-memory | |
| // equality need special compare. | |
| if a != AMEM || f.Sym.IsBlank() || ispaddedfield(t, i) { | |
| ret = ASPECIAL | |
| } | |
| } | |
| return ret, nil | |
| } | |
| Fatalf("algtype1: unexpected type %v", t) | |
| return 0, nil | |
| } | |
| // 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 | |
| Fatalf("genhash %v", t) | |
| case AMEM0: | |
| return sysClosure("memhash0") | |
| case AMEM8: | |
| return sysClosure("memhash8") | |
| case AMEM16: | |
| return sysClosure("memhash16") | |
| case AMEM32: | |
| return sysClosure("memhash32") | |
| case AMEM64: | |
| return sysClosure("memhash64") | |
| case AMEM128: | |
| return sysClosure("memhash128") | |
| case ASTRING: | |
| return sysClosure("strhash") | |
| case AINTER: | |
| return sysClosure("interhash") | |
| case ANILINTER: | |
| return sysClosure("nilinterhash") | |
| case AFLOAT32: | |
| return sysClosure("f32hash") | |
| case AFLOAT64: | |
| return sysClosure("f64hash") | |
| case ACPLX64: | |
| return sysClosure("c64hash") | |
| case ACPLX128: | |
| return sysClosure("c128hash") | |
| case 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 := typeLookup(fmt.Sprintf(".hashfunc%d", t.Width)).Linksym() | |
| if len(closure.P) > 0 { // already generated | |
| return closure | |
| } | |
| if memhashvarlen == nil { | |
| memhashvarlen = sysfunc("memhash_varlen") | |
| } | |
| ot := 0 | |
| ot = dsymptr(closure, ot, memhashvarlen, 0) | |
| ot = duintptr(closure, ot, uint64(t.Width)) // size encoded in closure | |
| ggloblsym(closure, int32(ot), obj.DUPOK|obj.RODATA) | |
| return closure | |
| case ASPECIAL: | |
| break | |
| } | |
| closure := typesymprefix(".hashfunc", t).Linksym() | |
| 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.Etype { | |
| case types.TARRAY: | |
| genhash(t.Elem()) | |
| case types.TSTRUCT: | |
| for _, f := range t.FieldSlice() { | |
| genhash(f.Type) | |
| } | |
| } | |
| sym := typesymprefix(".hash", t) | |
| if Debug.r != 0 { | |
| fmt.Printf("genhash %v %v %v\n", closure, sym, t) | |
| } | |
| lineno = autogeneratedPos // less confusing than end of input | |
| dclcontext = PEXTERN | |
| // func sym(p *T, h uintptr) uintptr | |
| tfn := nod(OTFUNC, nil, nil) | |
| tfn.List.Set2( | |
| namedfield("p", types.NewPtr(t)), | |
| namedfield("h", types.Types[TUINTPTR]), | |
| ) | |
| tfn.Rlist.Set1(anonfield(types.Types[TUINTPTR])) | |
| fn := dclfunc(sym, tfn) | |
| np := asNode(tfn.Type.Params().Field(0).Nname) | |
| nh := asNode(tfn.Type.Params().Field(1).Nname) | |
| switch t.Etype { | |
| 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()) | |
| n := nod(ORANGE, nil, nod(ODEREF, np, nil)) | |
| ni := newname(lookup("i")) | |
| ni.Type = types.Types[TINT] | |
| n.List.Set1(ni) | |
| n.SetColas(true) | |
| colasdefn(n.List.Slice(), n) | |
| ni = n.List.First() | |
| // h = hashel(&p[i], h) | |
| call := nod(OCALL, hashel, nil) | |
| nx := nod(OINDEX, np, ni) | |
| nx.SetBounded(true) | |
| na := nod(OADDR, nx, nil) | |
| call.List.Append(na) | |
| call.List.Append(nh) | |
| n.Nbody.Append(nod(OAS, nh, call)) | |
| fn.Nbody.Append(n) | |
| 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 := nod(OCALL, hashel, nil) | |
| nx := nodSym(OXDOT, np, f.Sym) // TODO: fields from other packages? | |
| na := nod(OADDR, nx, nil) | |
| call.List.Append(na) | |
| call.List.Append(nh) | |
| fn.Nbody.Append(nod(OAS, 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 := nod(OCALL, hashel, nil) | |
| nx := nodSym(OXDOT, np, f.Sym) // TODO: fields from other packages? | |
| na := nod(OADDR, nx, nil) | |
| call.List.Append(na) | |
| call.List.Append(nh) | |
| call.List.Append(nodintconst(size)) | |
| fn.Nbody.Append(nod(OAS, nh, call)) | |
| i = next | |
| } | |
| } | |
| r := nod(ORETURN, nil, nil) | |
| r.List.Append(nh) | |
| fn.Nbody.Append(r) | |
| if Debug.r != 0 { | |
| dumplist("genhash body", fn.Nbody) | |
| } | |
| funcbody() | |
| fn.Func.SetDupok(true) | |
| fn = typecheck(fn, ctxStmt) | |
| Curfn = fn | |
| typecheckslice(fn.Nbody.Slice(), ctxStmt) | |
| Curfn = nil | |
| if debug_dclstack != 0 { | |
| testdclstack() | |
| } | |
| fn.Func.SetNilCheckDisabled(true) | |
| xtop = append(xtop, fn) | |
| // Build closure. It doesn't close over any variables, so | |
| // it contains just the function pointer. | |
| dsymptr(closure, 0, sym.Linksym(), 0) | |
| ggloblsym(closure, int32(Widthptr), obj.DUPOK|obj.RODATA) | |
| return closure | |
| } | |
| func hashfor(t *types.Type) *Node { | |
| var sym *types.Sym | |
| switch a, _ := algtype1(t); a { | |
| case AMEM: | |
| Fatalf("hashfor with AMEM type") | |
| case AINTER: | |
| sym = Runtimepkg.Lookup("interhash") | |
| case ANILINTER: | |
| sym = Runtimepkg.Lookup("nilinterhash") | |
| case ASTRING: | |
| sym = Runtimepkg.Lookup("strhash") | |
| case AFLOAT32: | |
| sym = Runtimepkg.Lookup("f32hash") | |
| case AFLOAT64: | |
| sym = Runtimepkg.Lookup("f64hash") | |
| case ACPLX64: | |
| sym = Runtimepkg.Lookup("c64hash") | |
| case ACPLX128: | |
| sym = Runtimepkg.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) | |
| } | |
| n := newname(sym) | |
| setNodeNameFunc(n) | |
| n.Type = functype(nil, []*Node{ | |
| anonfield(types.NewPtr(t)), | |
| anonfield(types.Types[TUINTPTR]), | |
| }, []*Node{ | |
| anonfield(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 := sysvar(name + "·f") | |
| if len(s.P) == 0 { | |
| f := sysfunc(name) | |
| dsymptr(s, 0, f, 0) | |
| ggloblsym(s, int32(Widthptr), 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 ANOEQ: | |
| // The runtime will panic if it tries to compare | |
| // a type with a nil equality function. | |
| return nil | |
| case AMEM0: | |
| return sysClosure("memequal0") | |
| case AMEM8: | |
| return sysClosure("memequal8") | |
| case AMEM16: | |
| return sysClosure("memequal16") | |
| case AMEM32: | |
| return sysClosure("memequal32") | |
| case AMEM64: | |
| return sysClosure("memequal64") | |
| case AMEM128: | |
| return sysClosure("memequal128") | |
| case ASTRING: | |
| return sysClosure("strequal") | |
| case AINTER: | |
| return sysClosure("interequal") | |
| case ANILINTER: | |
| return sysClosure("nilinterequal") | |
| case AFLOAT32: | |
| return sysClosure("f32equal") | |
| case AFLOAT64: | |
| return sysClosure("f64equal") | |
| case ACPLX64: | |
| return sysClosure("c64equal") | |
| case ACPLX128: | |
| return sysClosure("c128equal") | |
| case AMEM: | |
| // make equality closure. The size of the type | |
| // is encoded in the closure. | |
| closure := typeLookup(fmt.Sprintf(".eqfunc%d", t.Width)).Linksym() | |
| if len(closure.P) != 0 { | |
| return closure | |
| } | |
| if memequalvarlen == nil { | |
| memequalvarlen = sysvar("memequal_varlen") // asm func | |
| } | |
| ot := 0 | |
| ot = dsymptr(closure, ot, memequalvarlen, 0) | |
| ot = duintptr(closure, ot, uint64(t.Width)) | |
| ggloblsym(closure, int32(ot), obj.DUPOK|obj.RODATA) | |
| return closure | |
| case ASPECIAL: | |
| break | |
| } | |
| closure := typesymprefix(".eqfunc", t).Linksym() | |
| if len(closure.P) > 0 { // already generated | |
| return closure | |
| } | |
| sym := typesymprefix(".eq", t) | |
| if Debug.r != 0 { | |
| fmt.Printf("geneq %v\n", t) | |
| } | |
| // Autogenerate code for equality of structs and arrays. | |
| lineno = autogeneratedPos // less confusing than end of input | |
| dclcontext = PEXTERN | |
| // func sym(p, q *T) bool | |
| tfn := nod(OTFUNC, nil, nil) | |
| tfn.List.Set2( | |
| namedfield("p", types.NewPtr(t)), | |
| namedfield("q", types.NewPtr(t)), | |
| ) | |
| tfn.Rlist.Set1(namedfield("r", types.Types[TBOOL])) | |
| fn := dclfunc(sym, tfn) | |
| np := asNode(tfn.Type.Params().Field(0).Nname) | |
| nq := asNode(tfn.Type.Params().Field(1).Nname) | |
| // 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.Etype { | |
| default: | |
| Fatalf("geneq %v", t) | |
| case 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 { | |
| // return | |
| // } | |
| // } | |
| // | |
| // 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, eq func(pi, qi *Node) *Node) { | |
| // checkIdx generates a node to check for equality at index i. | |
| checkIdx := func(i *Node) *Node { | |
| // pi := p[i] | |
| pi := nod(OINDEX, np, i) | |
| pi.SetBounded(true) | |
| pi.Type = t.Elem() | |
| // qi := q[i] | |
| qi := nod(OINDEX, nq, i) | |
| qi.SetBounded(true) | |
| qi.Type = t.Elem() | |
| return eq(pi, qi) | |
| } | |
| if nelem <= unroll { | |
| // Generate a series of checks. | |
| var cond *Node | |
| for i := int64(0); i < nelem; i++ { | |
| c := nodintconst(i) | |
| check := checkIdx(c) | |
| if cond == nil { | |
| cond = check | |
| continue | |
| } | |
| cond = nod(OANDAND, cond, check) | |
| } | |
| nif := nod(OIF, cond, nil) | |
| nif.Rlist.Append(nod(ORETURN, nil, nil)) | |
| fn.Nbody.Append(nif) | |
| return | |
| } | |
| // Generate a for loop. | |
| // for i := 0; i < nelem; i++ | |
| i := temp(types.Types[TINT]) | |
| init := nod(OAS, i, nodintconst(0)) | |
| cond := nod(OLT, i, nodintconst(nelem)) | |
| post := nod(OAS, i, nod(OADD, i, nodintconst(1))) | |
| loop := nod(OFOR, cond, post) | |
| loop.Ninit.Append(init) | |
| // if eq(pi, qi) {} else { return } | |
| check := checkIdx(i) | |
| nif := nod(OIF, check, nil) | |
| nif.Rlist.Append(nod(ORETURN, nil, nil)) | |
| loop.Nbody.Append(nif) | |
| fn.Nbody.Append(loop) | |
| } | |
| switch t.Elem().Etype { | |
| case 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, func(pi, qi *Node) *Node { | |
| // Compare lengths. | |
| eqlen, _ := eqstring(pi, qi) | |
| return eqlen | |
| }) | |
| checkAll(1, func(pi, qi *Node) *Node { | |
| // Compare contents. | |
| _, eqmem := eqstring(pi, qi) | |
| return eqmem | |
| }) | |
| case TFLOAT32, TFLOAT64: | |
| checkAll(2, func(pi, qi *Node) *Node { | |
| // p[i] == q[i] | |
| return nod(OEQ, pi, qi) | |
| }) | |
| // TODO: pick apart structs, do them piecemeal too | |
| default: | |
| checkAll(1, 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)) | |
| fn.Nbody.Append(ret) | |
| case 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 [][]*Node | |
| conds = append(conds, []*Node{}) | |
| and := func(n *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, []*Node{}) | |
| } | |
| p := nodSym(OXDOT, np, f.Sym) | |
| q := nodSym(OXDOT, nq, f.Sym) | |
| switch { | |
| case f.Type.IsString(): | |
| eqlen, eqmem := eqstring(p, q) | |
| and(eqlen) | |
| and(eqmem) | |
| default: | |
| and(nod(OEQ, p, q)) | |
| } | |
| if EqCanPanic(f.Type) { | |
| // Also enforce ordering after something that can panic. | |
| conds = append(conds, []*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 []*Node | |
| for _, c := range conds { | |
| isCall := func(n *Node) bool { | |
| return n.Op == OCALL || n.Op == OCALLFUNC | |
| } | |
| sort.SliceStable(c, func(i, j int) bool { | |
| return !isCall(c[i]) && isCall(c[j]) | |
| }) | |
| flatConds = append(flatConds, c...) | |
| } | |
| var cond *Node | |
| if len(flatConds) == 0 { | |
| cond = nodbool(true) | |
| } else { | |
| cond = flatConds[0] | |
| for _, c := range flatConds[1:] { | |
| cond = nod(OANDAND, cond, c) | |
| } | |
| } | |
| ret := nod(ORETURN, nil, nil) | |
| ret.List.Append(cond) | |
| fn.Nbody.Append(ret) | |
| } | |
| if Debug.r != 0 { | |
| dumplist("geneq body", fn.Nbody) | |
| } | |
| funcbody() | |
| fn.Func.SetDupok(true) | |
| fn = typecheck(fn, ctxStmt) | |
| Curfn = fn | |
| typecheckslice(fn.Nbody.Slice(), ctxStmt) | |
| Curfn = nil | |
| if debug_dclstack != 0 { | |
| testdclstack() | |
| } | |
| // 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.Func.SetNilCheckDisabled(true) | |
| xtop = append(xtop, fn) | |
| // Generate a closure which points at the function we just generated. | |
| dsymptr(closure, 0, sym.Linksym(), 0) | |
| ggloblsym(closure, int32(Widthptr), obj.DUPOK|obj.RODATA) | |
| return closure | |
| } | |
| // eqfield returns the node | |
| // p.field == q.field | |
| func eqfield(p *Node, q *Node, field *types.Sym) *Node { | |
| nx := nodSym(OXDOT, p, field) | |
| ny := nodSym(OXDOT, q, field) | |
| ne := nod(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 *Node) (eqlen, eqmem *Node) { | |
| s = conv(s, types.Types[TSTRING]) | |
| t = conv(t, types.Types[TSTRING]) | |
| sptr := nod(OSPTR, s, nil) | |
| tptr := nod(OSPTR, t, nil) | |
| slen := conv(nod(OLEN, s, nil), types.Types[TUINTPTR]) | |
| tlen := conv(nod(OLEN, t, nil), types.Types[TUINTPTR]) | |
| fn := syslook("memequal") | |
| fn = substArgTypes(fn, types.Types[TUINT8], types.Types[TUINT8]) | |
| call := nod(OCALL, fn, nil) | |
| call.List.Append(sptr, tptr, slen.copy()) | |
| call = typecheck(call, ctxExpr|ctxMultiOK) | |
| cmp := nod(OEQ, slen, tlen) | |
| cmp = typecheck(cmp, ctxExpr) | |
| cmp.Type = 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 *Node) (eqtab, eqdata *Node) { | |
| if !types.Identical(s.Type, t.Type) { | |
| 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 *Node | |
| if s.Type.IsEmptyInterface() { | |
| fn = syslook("efaceeq") | |
| } else { | |
| fn = syslook("ifaceeq") | |
| } | |
| stab := nod(OITAB, s, nil) | |
| ttab := nod(OITAB, t, nil) | |
| sdata := nod(OIDATA, s, nil) | |
| tdata := nod(OIDATA, t, nil) | |
| sdata.Type = types.Types[TUNSAFEPTR] | |
| tdata.Type = types.Types[TUNSAFEPTR] | |
| sdata.SetTypecheck(1) | |
| tdata.SetTypecheck(1) | |
| call := nod(OCALL, fn, nil) | |
| call.List.Append(stab, sdata, tdata) | |
| call = typecheck(call, ctxExpr|ctxMultiOK) | |
| cmp := nod(OEQ, stab, ttab) | |
| cmp = typecheck(cmp, ctxExpr) | |
| cmp.Type = types.Types[TBOOL] | |
| return cmp, call | |
| } | |
| // eqmem returns the node | |
| // memequal(&p.field, &q.field [, size]) | |
| func eqmem(p *Node, q *Node, field *types.Sym, size int64) *Node { | |
| nx := nod(OADDR, nodSym(OXDOT, p, field), nil) | |
| ny := nod(OADDR, nodSym(OXDOT, q, field), nil) | |
| nx = typecheck(nx, ctxExpr) | |
| ny = typecheck(ny, ctxExpr) | |
| fn, needsize := eqmemfunc(size, nx.Type.Elem()) | |
| call := nod(OCALL, fn, nil) | |
| call.List.Append(nx) | |
| call.List.Append(ny) | |
| if needsize { | |
| call.List.Append(nodintconst(size)) | |
| } | |
| return call | |
| } | |
| func eqmemfunc(size int64, t *types.Type) (fn *Node, needsize bool) { | |
| switch size { | |
| default: | |
| fn = syslook("memequal") | |
| needsize = true | |
| case 1, 2, 4, 8, 16: | |
| buf := fmt.Sprintf("memequal%d", int(size)*8) | |
| fn = syslook(buf) | |
| } | |
| fn = 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 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 | |
| } | |
| } | |
| return t.Field(next-1).End() - t.Field(start).Offset, next | |
| } | |
| // ispaddedfield reports whether the i'th field of struct type t is followed | |
| // by padding. | |
| func ispaddedfield(t *types.Type, i int) bool { | |
| if !t.IsStruct() { | |
| Fatalf("ispaddedfield called non-struct %v", t) | |
| } | |
| end := t.Width | |
| if i+1 < t.NumFields() { | |
| end = t.Field(i + 1).Offset | |
| } | |
| return t.Field(i).End() != end | |
| } |