| // 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 "fmt" |
| |
| const ( |
| // These values are known by runtime. |
| ANOEQ = iota |
| AMEM0 |
| AMEM8 |
| AMEM16 |
| AMEM32 |
| AMEM64 |
| AMEM128 |
| ASTRING |
| AINTER |
| ANILINTER |
| AFLOAT32 |
| AFLOAT64 |
| ACPLX64 |
| ACPLX128 |
| AMEM = 100 |
| ) |
| |
| func algtype(t *Type) int { |
| a := algtype1(t, nil) |
| 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 |
| } |
| |
| func algtype1(t *Type, bad **Type) int { |
| if bad != nil { |
| *bad = nil |
| } |
| if t.Broke { |
| return AMEM |
| } |
| if t.Noalg { |
| return ANOEQ |
| } |
| |
| switch t.Etype { |
| case TANY, TFORW: |
| // will be defined later. |
| *bad = t |
| return -1 |
| |
| case TINT8, TUINT8, TINT16, TUINT16, |
| TINT32, TUINT32, TINT64, TUINT64, |
| TINT, TUINT, TUINTPTR, |
| TBOOL, TPTR32, TPTR64, |
| TCHAN, TUNSAFEPTR: |
| return AMEM |
| |
| case TFUNC, TMAP: |
| if bad != nil { |
| *bad = t |
| } |
| return ANOEQ |
| |
| case TFLOAT32: |
| return AFLOAT32 |
| |
| case TFLOAT64: |
| return AFLOAT64 |
| |
| case TCOMPLEX64: |
| return ACPLX64 |
| |
| case TCOMPLEX128: |
| return ACPLX128 |
| |
| case TSTRING: |
| return ASTRING |
| |
| case TINTER: |
| if isnilinter(t) { |
| return ANILINTER |
| } |
| return AINTER |
| |
| case TARRAY: |
| if Isslice(t) { |
| if bad != nil { |
| *bad = t |
| } |
| return ANOEQ |
| } |
| |
| a := algtype1(t.Type, bad) |
| switch a { |
| case AMEM: |
| return AMEM |
| case ANOEQ: |
| if bad != nil { |
| *bad = t |
| } |
| return ANOEQ |
| } |
| |
| switch t.Bound { |
| case 0: |
| // We checked above that the element type is comparable. |
| return AMEM |
| case 1: |
| // Single-element array is same as its lone element. |
| return a |
| } |
| |
| return -1 // needs special compare |
| |
| case TSTRUCT: |
| if t.Type != nil && t.Type.Down == nil && !isblanksym(t.Type.Sym) { |
| // One-field struct is same as that one field alone. |
| return algtype1(t.Type.Type, bad) |
| } |
| |
| ret := AMEM |
| for f, it := IterFields(t); f != nil; f = it.Next() { |
| // All fields must be comparable. |
| a := algtype1(f.Type, bad) |
| if a == ANOEQ { |
| return ANOEQ |
| } |
| |
| // Blank fields, padded fields, fields with non-memory |
| // equality need special compare. |
| if a != AMEM || isblanksym(f.Sym) || ispaddedfield(t, f) { |
| ret = -1 |
| } |
| } |
| |
| return ret |
| } |
| |
| Fatalf("algtype1: unexpected type %v", t) |
| return 0 |
| } |
| |
| // Generate a helper function to compute the hash of a value of type t. |
| func genhash(sym *Sym, t *Type) { |
| if Debug['r'] != 0 { |
| fmt.Printf("genhash %v %v\n", sym, t) |
| } |
| |
| lineno = 1 // less confusing than end of input |
| dclcontext = PEXTERN |
| markdcl() |
| |
| // func sym(p *T, h uintptr) uintptr |
| fn := Nod(ODCLFUNC, nil, nil) |
| |
| fn.Func.Nname = newname(sym) |
| fn.Func.Nname.Class = PFUNC |
| tfn := Nod(OTFUNC, nil, nil) |
| fn.Func.Nname.Name.Param.Ntype = tfn |
| |
| n := Nod(ODCLFIELD, newname(Lookup("p")), typenod(Ptrto(t))) |
| tfn.List.Append(n) |
| np := n.Left |
| n = Nod(ODCLFIELD, newname(Lookup("h")), typenod(Types[TUINTPTR])) |
| tfn.List.Append(n) |
| nh := n.Left |
| n = Nod(ODCLFIELD, nil, typenod(Types[TUINTPTR])) // return value |
| tfn.Rlist.Append(n) |
| |
| funchdr(fn) |
| typecheck(&fn.Func.Nname.Name.Param.Ntype, Etype) |
| |
| // genhash is only called 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("genhash %v", t) |
| |
| case TARRAY: |
| if Isslice(t) { |
| Fatalf("genhash %v", t) |
| } |
| |
| // An array of pure memory would be handled by the |
| // standard algorithm, so the element type must not be |
| // pure memory. |
| hashel := hashfor(t.Type) |
| |
| n := Nod(ORANGE, nil, Nod(OIND, np, nil)) |
| ni := newname(Lookup("i")) |
| ni.Type = Types[TINT] |
| n.List.Set1(ni) |
| n.Colas = true |
| colasdefn(n.List, n) |
| ni = n.List.First() |
| |
| // h = hashel(&p[i], h) |
| call := Nod(OCALL, hashel, nil) |
| |
| nx := Nod(OINDEX, np, ni) |
| nx.Bounded = true |
| na := Nod(OADDR, nx, nil) |
| na.Etype = 1 // no escape to heap |
| call.List.Append(na) |
| call.List.Append(nh) |
| n.Nbody.Append(Nod(OAS, nh, call)) |
| |
| fn.Nbody.Append(n) |
| |
| case TSTRUCT: |
| // Walk the struct using memhash for runs of AMEM |
| // and calling specific hash functions for the others. |
| for f := t.Type; f != nil; { |
| // Skip blank fields. |
| if isblanksym(f.Sym) { |
| f = f.Down |
| continue |
| } |
| |
| // Hash non-memory fields with appropriate hash function. |
| if algtype1(f.Type, nil) != AMEM { |
| hashel := hashfor(f.Type) |
| call := Nod(OCALL, hashel, nil) |
| nx := Nod(OXDOT, np, newname(f.Sym)) // TODO: fields from other packages? |
| na := Nod(OADDR, nx, nil) |
| na.Etype = 1 // no escape to heap |
| call.List.Append(na) |
| call.List.Append(nh) |
| fn.Nbody.Append(Nod(OAS, nh, call)) |
| f = f.Down |
| continue |
| } |
| |
| // Otherwise, hash a maximal length run of raw memory. |
| size, next := memrun(t, f) |
| |
| // h = hashel(&p.first, size, h) |
| hashel := hashmem(f.Type) |
| call := Nod(OCALL, hashel, nil) |
| nx := Nod(OXDOT, np, newname(f.Sym)) // TODO: fields from other packages? |
| na := Nod(OADDR, nx, nil) |
| na.Etype = 1 // no escape to heap |
| call.List.Append(na) |
| call.List.Append(nh) |
| call.List.Append(Nodintconst(size)) |
| fn.Nbody.Append(Nod(OAS, nh, call)) |
| |
| f = 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) |
| Curfn = fn |
| fn.Func.Dupok = true |
| typecheck(&fn, Etop) |
| typechecklist(fn.Nbody.Slice(), Etop) |
| Curfn = nil |
| |
| // Disable safemode while compiling this code: the code we |
| // generate internally can refer to unsafe.Pointer. |
| // In this case it can happen if we need to generate an == |
| // for a struct containing a reflect.Value, which itself has |
| // an unexported field of type unsafe.Pointer. |
| old_safemode := safemode |
| |
| safemode = 0 |
| Disable_checknil++ |
| funccompile(fn) |
| Disable_checknil-- |
| safemode = old_safemode |
| } |
| |
| func hashfor(t *Type) *Node { |
| var sym *Sym |
| |
| switch algtype1(t, nil) { |
| case AMEM: |
| Fatalf("hashfor with AMEM type") |
| case AINTER: |
| sym = Pkglookup("interhash", Runtimepkg) |
| case ANILINTER: |
| sym = Pkglookup("nilinterhash", Runtimepkg) |
| case ASTRING: |
| sym = Pkglookup("strhash", Runtimepkg) |
| case AFLOAT32: |
| sym = Pkglookup("f32hash", Runtimepkg) |
| case AFLOAT64: |
| sym = Pkglookup("f64hash", Runtimepkg) |
| case ACPLX64: |
| sym = Pkglookup("c64hash", Runtimepkg) |
| case ACPLX128: |
| sym = Pkglookup("c128hash", Runtimepkg) |
| default: |
| sym = typesymprefix(".hash", t) |
| } |
| |
| n := newname(sym) |
| n.Class = PFUNC |
| tfn := Nod(OTFUNC, nil, nil) |
| tfn.List.Append(Nod(ODCLFIELD, nil, typenod(Ptrto(t)))) |
| tfn.List.Append(Nod(ODCLFIELD, nil, typenod(Types[TUINTPTR]))) |
| tfn.Rlist.Append(Nod(ODCLFIELD, nil, typenod(Types[TUINTPTR]))) |
| typecheck(&tfn, Etype) |
| n.Type = tfn.Type |
| return n |
| } |
| |
| // geneq generates a helper function to |
| // check equality of two values of type t. |
| func geneq(sym *Sym, t *Type) { |
| if Debug['r'] != 0 { |
| fmt.Printf("geneq %v %v\n", sym, t) |
| } |
| |
| lineno = 1 // less confusing than end of input |
| dclcontext = PEXTERN |
| markdcl() |
| |
| // func sym(p, q *T) bool |
| fn := Nod(ODCLFUNC, nil, nil) |
| |
| fn.Func.Nname = newname(sym) |
| fn.Func.Nname.Class = PFUNC |
| tfn := Nod(OTFUNC, nil, nil) |
| fn.Func.Nname.Name.Param.Ntype = tfn |
| |
| n := Nod(ODCLFIELD, newname(Lookup("p")), typenod(Ptrto(t))) |
| tfn.List.Append(n) |
| np := n.Left |
| n = Nod(ODCLFIELD, newname(Lookup("q")), typenod(Ptrto(t))) |
| tfn.List.Append(n) |
| nq := n.Left |
| n = Nod(ODCLFIELD, nil, typenod(Types[TBOOL])) |
| tfn.Rlist.Append(n) |
| |
| funchdr(fn) |
| typecheck(&fn.Func.Nname.Name.Param.Ntype, Etype) |
| |
| // geneq is only called 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: |
| if Isslice(t) { |
| Fatalf("geneq %v", t) |
| } |
| |
| // 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(OIND, np, nil)) |
| |
| ni := newname(Lookup("i")) |
| ni.Type = Types[TINT] |
| nrange.List.Set1(ni) |
| nrange.Colas = true |
| colasdefn(nrange.List, nrange) |
| ni = nrange.List.First() |
| |
| // if p[i] != q[i] { return false } |
| nx := Nod(OINDEX, np, ni) |
| |
| nx.Bounded = true |
| ny := Nod(OINDEX, nq, ni) |
| ny.Bounded = 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) |
| |
| // return true |
| ret := Nod(ORETURN, nil, nil) |
| ret.List.Append(Nodbool(true)) |
| fn.Nbody.Append(ret) |
| |
| case TSTRUCT: |
| var conjuncts []*Node |
| |
| // Walk the struct using memequal for runs of AMEM |
| // and calling specific equality tests for the others. |
| for f := t.Type; f != nil; { |
| // Skip blank-named fields. |
| if isblanksym(f.Sym) { |
| f = f.Down |
| continue |
| } |
| |
| // Compare non-memory fields with field equality. |
| if algtype1(f.Type, nil) != AMEM { |
| conjuncts = append(conjuncts, eqfield(np, nq, newname(f.Sym))) |
| f = f.Down |
| continue |
| } |
| |
| // Find maximal length run of memory-only fields. |
| size, next := memrun(t, f) |
| |
| // Run memequal on fields from f to next. |
| // TODO(rsc): All the calls to newname are wrong for |
| // cross-package unexported fields. |
| if f.Down == next { |
| conjuncts = append(conjuncts, eqfield(np, nq, newname(f.Sym))) |
| } else if f.Down.Down == next { |
| conjuncts = append(conjuncts, eqfield(np, nq, newname(f.Sym))) |
| conjuncts = append(conjuncts, eqfield(np, nq, newname(f.Down.Sym))) |
| } else { |
| // More than two fields: use memequal. |
| conjuncts = append(conjuncts, eqmem(np, nq, newname(f.Sym), size)) |
| } |
| f = next |
| } |
| |
| var and *Node |
| switch len(conjuncts) { |
| case 0: |
| and = Nodbool(true) |
| case 1: |
| and = conjuncts[0] |
| default: |
| and = Nod(OANDAND, conjuncts[0], conjuncts[1]) |
| for _, conjunct := range conjuncts[2:] { |
| and = Nod(OANDAND, and, conjunct) |
| } |
| } |
| |
| ret := Nod(ORETURN, nil, nil) |
| ret.List.Append(and) |
| fn.Nbody.Append(ret) |
| } |
| |
| if Debug['r'] != 0 { |
| dumplist("geneq body", fn.Nbody) |
| } |
| |
| funcbody(fn) |
| Curfn = fn |
| fn.Func.Dupok = true |
| typecheck(&fn, Etop) |
| typechecklist(fn.Nbody.Slice(), Etop) |
| Curfn = nil |
| |
| // Disable safemode while compiling this code: the code we |
| // generate internally can refer to unsafe.Pointer. |
| // In this case it can happen if we need to generate an == |
| // for a struct containing a reflect.Value, which itself has |
| // an unexported field of type unsafe.Pointer. |
| old_safemode := safemode |
| safemode = 0 |
| |
| // 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. |
| Disable_checknil++ |
| |
| funccompile(fn) |
| |
| safemode = old_safemode |
| Disable_checknil-- |
| } |
| |
| // eqfield returns the node |
| // p.field == q.field |
| func eqfield(p *Node, q *Node, field *Node) *Node { |
| nx := Nod(OXDOT, p, field) |
| ny := Nod(OXDOT, q, field) |
| ne := Nod(OEQ, nx, ny) |
| return ne |
| } |
| |
| // eqmem returns the node |
| // memequal(&p.field, &q.field [, size]) |
| func eqmem(p *Node, q *Node, field *Node, size int64) *Node { |
| nx := Nod(OADDR, Nod(OXDOT, p, field), nil) |
| nx.Etype = 1 // does not escape |
| ny := Nod(OADDR, Nod(OXDOT, q, field), nil) |
| ny.Etype = 1 // does not escape |
| typecheck(&nx, Erv) |
| typecheck(&ny, Erv) |
| |
| fn, needsize := eqmemfunc(size, nx.Type.Type) |
| 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 *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) |
| } |
| |
| 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 that starts the run. |
| // size is the length in bytes of the memory included in the run. |
| // next is the next field after the memory run. |
| func memrun(t *Type, start *Type) (size int64, next *Type) { |
| var last *Type |
| next = start |
| for { |
| last, next = next, next.Down |
| if next == nil { |
| break |
| } |
| // Stop run after a padded field. |
| if ispaddedfield(t, last) { |
| break |
| } |
| // Also, stop before a blank or non-memory field. |
| if isblanksym(next.Sym) || algtype1(next.Type, nil) != AMEM { |
| break |
| } |
| } |
| end := last.Width + last.Type.Width |
| return end - start.Width, next |
| } |
| |
| // ispaddedfield reports whether the given field f, assumed to be |
| // a field in struct t, is followed by padding. |
| func ispaddedfield(t *Type, f *Type) bool { |
| if t.Etype != TSTRUCT { |
| Fatalf("ispaddedfield called non-struct %v", t) |
| } |
| if f.Etype != TFIELD { |
| Fatalf("ispaddedfield called non-field %v", f) |
| } |
| end := t.Width |
| if f.Down != nil { |
| end = f.Down.Width |
| } |
| return f.Width+f.Type.Width != end |
| } |