// 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" "go/constant" "strconv" "strings" "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/objw" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/obj" ) // genhash returns a symbol which is the closure used to compute // the hash of a value of type t. func genhash(t *types.Type) *obj.LSym { return genhashSig(eqSignature(t)) } func genhashSig(sig string) *obj.LSym { if len(sig) > 0 && sig[0] == sigAlign { _, sig = parseNum(sig[1:]) } switch sig { case "": return sysClosure("memhash0") case string(sigMemory) + "1": return sysClosure("memhash8") case string(sigMemory) + "2": return sysClosure("memhash16") case string(sigMemory) + "4": return sysClosure("memhash32") case string(sigMemory) + "8": return sysClosure("memhash64") case string(sigMemory) + "16": return sysClosure("memhash128") case string(sigString): return sysClosure("strhash") case string(sigIface): return sysClosure("interhash") case string(sigEface): return sysClosure("nilinterhash") case string(sigFloat32): return sysClosure("f32hash") case string(sigFloat64): return sysClosure("f64hash") case string(sigFloat32) + string(sigFloat32): return sysClosure("c64hash") case string(sigFloat64) + string(sigFloat64): return sysClosure("c128hash") } closure := TypeLinksymLookup(".hashfunc." + sig) if len(closure.P) > 0 { // already generated return closure } if sig[0] == sigMemory { n, rest := parseNum(sig[1:]) if rest == "" { // Just M%d. We can make a memhash_varlen closure. // The size of the memory region to hash is encoded in the closure. if memhashvarlen == nil { memhashvarlen = typecheck.LookupRuntimeFunc("memhash_varlen") } ot := 0 ot = objw.SymPtr(closure, ot, memhashvarlen, 0) ot = objw.Uintptr(closure, ot, uint64(n)) // size encoded in closue objw.Global(closure, int32(ot), obj.DUPOK|obj.RODATA) return closure } } if base.Flag.LowerR != 0 { fmt.Printf("genhash %s\n", sig) } fn := hashFunc(sig) // 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(sig string) *ir.Func { sym := types.TypeSymLookup(".hash." + sig) if sym.Def != nil { return sym.Def.(*ir.Name).Func } pos := base.AutogeneratedPos // less confusing than end of input base.Pos = pos // func sym(p unsafe.Pointer, h uintptr) uintptr fn := ir.NewFunc(pos, pos, sym, types.NewSignature(nil, []*types.Field{ types.NewField(pos, typecheck.Lookup("p"), types.Types[types.TUNSAFEPTR]), types.NewField(pos, typecheck.Lookup("h"), types.Types[types.TUINTPTR]), }, []*types.Field{ types.NewField(pos, nil, types.Types[types.TUINTPTR]), }, )) sym.Def = fn.Nname fn.Pragma |= ir.Noinline // TODO(mdempsky): We need to emit this during the unified frontend instead, to allow inlining. typecheck.DeclFunc(fn) np := fn.Dcl[0] nh := fn.Dcl[1] // Skip alignment, hash functions can handle unaligned data. if len(sig) > 0 && sig[0] == sigAlign { _, sig = parseNum(sig[1:]) } // offset from np that we're currently working on var off int64 // Return np+off (as an unsafe.Pointer). ptr := func() ir.Node { c := ir.NewBasicLit(pos, types.Types[types.TUINTPTR], constant.MakeInt64(off)) return ir.NewBinaryExpr(pos, ir.OUNSAFEADD, np, c) } // hash data using function name at np+off. // Increment off by size. hash := func(name string, size int64) { p := ptr() hashFn := typecheck.LookupRuntime(name) call := ir.NewCallExpr(pos, ir.OCALL, hashFn, []ir.Node{p, nh}) fn.Body.Append(ir.NewAssignStmt(pos, nh, call)) off += size } for len(sig) > 0 { kind := sig[0] sig = sig[1:] switch kind { case sigMemory: var n int64 n, sig = parseNum(sig) switch { case n == 4: p := ptr() memhash := typecheck.LookupRuntime("memhash32") call := ir.NewCallExpr(pos, ir.OCALL, memhash, []ir.Node{p, nh}) fn.Body.Append(ir.NewAssignStmt(pos, nh, call)) case n == 8: p := ptr() memhash := typecheck.LookupRuntime("memhash64") call := ir.NewCallExpr(pos, ir.OCALL, memhash, []ir.Node{p, nh}) fn.Body.Append(ir.NewAssignStmt(pos, nh, call)) default: p := ptr() memhash := typecheck.LookupRuntime("memhash") size := ir.NewBasicLit(pos, types.Types[types.TUINTPTR], constant.MakeInt64(n)) call := ir.NewCallExpr(pos, ir.OCALL, memhash, []ir.Node{p, nh, size}) fn.Body.Append(ir.NewAssignStmt(pos, nh, call)) } off += n case sigFloat32: hash("f32hash", 4) case sigFloat64: hash("f64hash", 8) case sigString: hash("strhash", 2*int64(types.PtrSize)) case sigEface: hash("nilinterhash", 2*int64(types.PtrSize)) case sigIface: hash("interhash", 2*int64(types.PtrSize)) case sigSkip: var n int64 n, sig = parseNum(sig) off += n case sigArrayStart: var n int64 var elemSig string n, elemSig, sig = parseArray(sig) elemSize := sigSize(elemSig) // Loop N times, calling hash function for the element. // for i := off; i < off + N*elemSize; i += elemSize { // h = elemfn(p+i, h) // } elemFn := hashFunc(elemSig).Nname idx := typecheck.TempAt(pos, ir.CurFunc, types.Types[types.TUINTPTR]) init := ir.NewAssignStmt(pos, idx, ir.NewInt(pos, off)) cond := ir.NewBinaryExpr(pos, ir.OLT, idx, ir.NewInt(pos, off+n*elemSize)) post := ir.NewAssignStmt(pos, idx, ir.NewBinaryExpr(pos, ir.OADD, idx, ir.NewInt(pos, elemSize))) p := ir.NewBinaryExpr(pos, ir.OUNSAFEADD, np, idx) call := typecheck.Call(pos, elemFn, []ir.Node{p, nh}, false) as := ir.NewAssignStmt(pos, nh, call) loop := ir.NewForStmt(pos, init, cond, post, []ir.Node{as}, false) fn.Body.Append(loop) off += n * elemSize } } fn.Body.Append(ir.NewReturnStmt(pos, []ir.Node{nh})) if base.Flag.LowerR != 0 { ir.DumpList("genhash body", fn.Body) } typecheck.FinishFuncBody() fn.SetDupok(true) ir.WithFunc(fn, func() { typecheck.Stmts(fn.Body) }) fn.SetNilCheckDisabled(true) return fn } // 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 types.AlgType(t) { case types.ANOEQ, types.ANOALG: // The runtime will panic if it tries to compare // a type with a nil equality function. return nil } return geneqSig(eqSignature(t)) } // geneqSig returns a symbol which is the closure used to compute // equality for two objects with equality signature sig. func geneqSig(sig string) *obj.LSym { align := int64(types.PtrSize) if len(sig) > 0 && sig[0] == sigAlign { align, sig = parseNum(sig[1:]) } if base.Ctxt.Arch.CanMergeLoads { align = 8 } switch sig { case "": return sysClosure("memequal0") case string(sigMemory) + "1": return sysClosure("memequal8") case string(sigMemory) + "2": if align >= 2 { return sysClosure("memequal16") } case string(sigMemory) + "4": if align >= 4 { return sysClosure("memequal32") } case string(sigMemory) + "8": if align >= 8 { return sysClosure("memequal64") } case string(sigMemory) + "16": if align >= 8 { return sysClosure("memequal128") } case string(sigString): return sysClosure("strequal") case string(sigIface): return sysClosure("interequal") case string(sigEface): return sysClosure("nilinterequal") case string(sigFloat32): return sysClosure("f32equal") case string(sigFloat64): return sysClosure("f64equal") case string(sigFloat32) + string(sigFloat32): return sysClosure("c64equal") case string(sigFloat64) + string(sigFloat64): return sysClosure("c128equal") } closure := TypeLinksymLookup(".eqfunc." + sig) if len(closure.P) > 0 { // already generated return closure } if sig[0] == sigMemory { n, rest := parseNum(sig[1:]) if rest == "" { // Just M%d. We can make a memequal_varlen closure. // The size of the memory region to compare is encoded in the closure. if memequalvarlen == nil { memequalvarlen = typecheck.LookupRuntimeFunc("memequal_varlen") } ot := 0 ot = objw.SymPtr(closure, ot, memequalvarlen, 0) ot = objw.Uintptr(closure, ot, uint64(n)) objw.Global(closure, int32(ot), obj.DUPOK|obj.RODATA) return closure } } if base.Flag.LowerR != 0 { fmt.Printf("geneqSig %s\n", sig) } fn := eqFunc(sig) // 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(sig string) *ir.Func { sym := types.TypeSymLookup(".eq." + sig) if sym.Def != nil { return sym.Def.(*ir.Name).Func } pos := base.AutogeneratedPos // less confusing than end of input base.Pos = pos // func sym(p, q unsafe.Pointer) bool fn := ir.NewFunc(pos, pos, sym, types.NewSignature(nil, []*types.Field{ types.NewField(pos, typecheck.Lookup("p"), types.Types[types.TUNSAFEPTR]), types.NewField(pos, typecheck.Lookup("q"), types.Types[types.TUNSAFEPTR]), }, []*types.Field{ types.NewField(pos, typecheck.Lookup("r"), types.Types[types.TBOOL]), }, )) sym.Def = fn.Nname fn.Pragma |= ir.Noinline // TODO(mdempsky): We need to emit this during the unified frontend instead, to allow inlining. typecheck.DeclFunc(fn) np := fn.Dcl[0] nq := fn.Dcl[1] nr := fn.Dcl[2] // Label to jump to if an equality test fails. neq := typecheck.AutoLabel(".neq") // Grab known alignment of argument pointers. (ptrSize is the default.) align := int64(types.PtrSize) if len(sig) > 0 && sig[0] == sigAlign { sig = sig[1:] align, sig = parseNum(sig) } unalignedOk := base.Ctxt.Arch.CanMergeLoads // offset from np/nq that we're currently working on var off int64 var hasCall bool // test takes a boolean. If it evaluates to false, short circuit // and return false immediately. Otherwise, keep checking. var lastTest ir.Node test := func(eq ir.Node) { // Buffer one test in lastTest so we can use the // last one as the return value. if lastTest != nil { nif := ir.NewIfStmt(pos, lastTest, nil, []ir.Node{ir.NewBranchStmt(pos, ir.OGOTO, neq)}) fn.Body.Append(nif) } lastTest = eq } // load loads data of type t from np+off and nq+off. // Increments off by the size of t. load := func(t *types.Type) (ir.Node, ir.Node) { c := ir.NewBasicLit(pos, types.Types[types.TUINTPTR], constant.MakeInt64(off)) p := ir.NewBinaryExpr(pos, ir.OUNSAFEADD, np, c) q := ir.NewBinaryExpr(pos, ir.OUNSAFEADD, nq, c) x := ir.NewStarExpr(pos, ir.NewConvExpr(pos, ir.OCONVNOP, t.PtrTo(), p)) y := ir.NewStarExpr(pos, ir.NewConvExpr(pos, ir.OCONVNOP, t.PtrTo(), q)) off += t.Size() return x, y } // compare compares x and y and jumps to neq if they are not equal. compare := func(x, y ir.Node) { test(ir.NewBinaryExpr(pos, ir.OEQ, x, y)) } // We keep track of string contents that we don't compare immediately. // We delay comparing string contents because they might be large and // we'd rather compare scalars farther along in the signature first. var pendingStrings []int64 flushStrings := func() { defer func(saveOff int64) { off = saveOff }(off) for _, x := range pendingStrings { off = x ptrA, ptrB := load(types.Types[types.TUNSAFEPTR]) len, _ := load(types.Types[types.TUINTPTR]) // Note: we already checked that the lengths are equal. memeq := typecheck.LookupRuntime("memequal") test(typecheck.Call(pos, memeq, []ir.Node{ptrA, ptrB, len}, false)) hasCall = true } pendingStrings = pendingStrings[:0] } for len(sig) > 0 { kind := sig[0] sig = sig[1:] switch kind { case sigMemory: var n int64 n, sig = parseNum(sig) if n > 64 { // TODO: why 64? // For big regions, call memequal. c := ir.NewBasicLit(pos, types.Types[types.TUINTPTR], constant.MakeInt64(off)) p := ir.NewBinaryExpr(pos, ir.OUNSAFEADD, np, c) q := ir.NewBinaryExpr(pos, ir.OUNSAFEADD, nq, c) len := ir.NewBasicLit(pos, types.Types[types.TUINTPTR], constant.MakeInt64(n)) memeq := typecheck.LookupRuntime("memequal") test(typecheck.Call(pos, memeq, []ir.Node{p, q, len}, false)) hasCall = true off += n n = 0 } n0 := n for n != 0 { switch { case n >= 8 && (unalignedOk || align >= 8 && off%8 == 0): compare(load(types.Types[types.TUINT64])) n -= 8 case (n == 5 || n == 6 || n == 7) && unalignedOk && n0 >= 8: off -= 8 - n compare(load(types.Types[types.TUINT64])) n = 0 case n >= 4 && (unalignedOk || align >= 4 && off%4 == 0): compare(load(types.Types[types.TUINT32])) n -= 4 case n == 3 && unalignedOk && n0 >= 4: off-- compare(load(types.Types[types.TUINT32])) n = 0 case n >= 2 && (unalignedOk || align >= 2 && off%2 == 0): compare(load(types.Types[types.TUINT16])) n -= 2 default: compare(load(types.Types[types.TUINT8])) n-- } } case sigFloat32: compare(load(types.Types[types.TFLOAT32])) case sigFloat64: compare(load(types.Types[types.TFLOAT64])) case sigString: // Compare just the lengths right now. // Save the contents for later. pendingStrings = append(pendingStrings, off) off += int64(types.PtrSize) compare(load(types.Types[types.TUINTPTR])) case sigEface, sigIface: // flushStrings here to ensure that we only get a panic from // this interface test if all previous equality checks pass. flushStrings() typeX, typeY := load(types.Types[types.TUINTPTR].PtrTo()) compare(typeX, typeY) dataX, dataY := load(types.Types[types.TUNSAFEPTR]) var eqFn *ir.Name if kind == sigEface { eqFn = typecheck.LookupRuntime("efaceeq") } else { eqFn = typecheck.LookupRuntime("ifaceeq") } test(typecheck.Call(pos, eqFn, []ir.Node{typeX, dataX, dataY}, false)) hasCall = true case sigSkip: var n int64 n, sig = parseNum(sig) off += n case sigArrayStart: // Flush any pending test. flushStrings() // TODO: if the element comparison can't panic (no E or I), then // maybe we don't need to do this flushStrings? // On the other hand, maybe the unflushed string is not equal, but // a big following array is all equal. if lastTest != nil { nif := ir.NewIfStmt(pos, lastTest, nil, []ir.Node{ir.NewBranchStmt(pos, ir.OGOTO, neq)}) fn.Body.Append(nif) lastTest = nil } var n int64 var elemSig string n, elemSig, sig = parseArray(sig) elemSize := sigSize(elemSig) // Loop N times, calling comparison function for the element. // for i := off; i < off + N*elemSize; i += elemSize { // if !eqfn(p+i, q+i) { goto neq } // } elemFn := eqFunc(sigTrimSkip(elemSig)).Nname idx := typecheck.TempAt(pos, ir.CurFunc, types.Types[types.TUINTPTR]) init := ir.NewAssignStmt(pos, idx, ir.NewInt(pos, off)) cond := ir.NewBinaryExpr(pos, ir.OLT, idx, ir.NewInt(pos, off+n*elemSize)) post := ir.NewAssignStmt(pos, idx, ir.NewBinaryExpr(pos, ir.OADD, idx, ir.NewInt(pos, elemSize))) p := ir.NewBinaryExpr(pos, ir.OUNSAFEADD, np, idx) q := ir.NewBinaryExpr(pos, ir.OUNSAFEADD, nq, idx) call := typecheck.Call(pos, elemFn, []ir.Node{p, q}, false) nif := ir.NewIfStmt(pos, call, nil, []ir.Node{ir.NewBranchStmt(pos, ir.OGOTO, neq)}) loop := ir.NewForStmt(pos, init, cond, post, []ir.Node{nif}, false) fn.Body.Append(loop) off += n * elemSize // TODO: if the element comparison can't panic, but has strings // in it, maybe we do a loop first without string contents and a // second loop with string contents. There is no way to accomplish // this now they way this code works (to call the equality // function of the sub-signature). } } // Flush any pending tests. // The last test is used directly as a result (instead of branching using it). flushStrings() if lastTest == nil { lastTest = ir.NewBool(pos, true) } as := ir.NewAssignStmt(pos, nr, lastTest) fn.Body.Append(as) // ret: // return ret := typecheck.AutoLabel(".ret") fn.Body.Append(ir.NewLabelStmt(pos, ret)) fn.Body.Append(ir.NewReturnStmt(pos, nil)) // neq: // r = false // return (or goto ret) fn.Body.Append(ir.NewLabelStmt(pos, neq)) fn.Body.Append(ir.NewAssignStmt(pos, nr, ir.NewBool(pos, false))) if hasCall { // Epilogue is large, so share it with the equal case. fn.Body.Append(ir.NewBranchStmt(pos, ir.OGOTO, ret)) } else { // Epilogue is small, so don't bother sharing. fn.Body.Append(ir.NewReturnStmt(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) 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) 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 types.AlgType(t) { case types.AMEM: return typecheck.LookupRuntime("memequal"), true case types.ASPECIAL: fn := eqFunc(eqSignature(t)) return fn.Nname, false } base.Fatalf("EqFor %v", t) return nil, false } // eqSignature returns a signature of the equality function for type t. // If two types have the same signature, they can use the same equality function. // The signature lists the comparisons that the equality function needs // to make, in order. So for instance, a type like: // // type S struct { // i int32 // j uint32 // s string // e error // } // // Will have the signature "M8SI". // // M8 = 8 bytes of regular memory // S = string // I = nonempty interface // // The content of the signature is not intended for users. It is an // internal condensation of the comparison operations that need to be // performed. // (Although, note that these names might be seen in tracebacks where // the equality test panics due to incomparable interfaces.) // // Full signature spec: // // M%d = %d bytes of memory that should be compared directly // K%d = %d bytes of memory that should not be compared (sKip) // F = float32 // G = float64 // S = string // I = non-empty interface // E = empty interface // [%d%s] = array: repeat signature %s %d times. // A%d = known alignment of type pointers (defaults to ptrSize) // // An alignment directive is only needed on platforms that can't do // unaligned loads. // If an alignment directive is present, it must be first. // Signatures can end early; a K%d is not required at the end. func eqSignature(t *types.Type) string { var e eqSigBuilder if !base.Ctxt.Arch.CanMergeLoads { // alignment only matters if we can't use unaligned loads if a := t.Alignment(); a != int64(types.PtrSize) { e.r.WriteString(fmt.Sprintf("%c%d", sigAlign, a)) } } e.build(t) e.flush(true) return e.r.String() } const ( sigMemory = 'M' // followed by an integer number of bytes sigSkip = 'K' // followed by an integer number of bytes sigFloat32 = 'F' sigFloat64 = 'G' sigString = 'S' sigIface = 'I' // non-empty interface sigEface = 'E' // empty interface sigArrayStart = '[' // followed by an iteration count, element signature, and sigArrayEnd sigArrayEnd = ']' sigAlign = 'A' // followed by an integer byte alignment ) type eqSigBuilder struct { r strings.Builder regMem int64 // queued up region of regular memory skipMem int64 // queued up region of memory to skip } func (e *eqSigBuilder) flush(atEnd bool) { if e.regMem > 0 { e.r.WriteString(fmt.Sprintf("%c%d", sigMemory, e.regMem)) e.regMem = 0 } if e.skipMem > 0 { if !atEnd { e.r.WriteString(fmt.Sprintf("%c%d", sigSkip, e.skipMem)) } e.skipMem = 0 } } func (e *eqSigBuilder) regular(n int64) { if e.regMem == 0 { e.flush(false) } e.regMem += n } func (e *eqSigBuilder) skip(n int64) { if e.skipMem == 0 { e.flush(false) } e.skipMem += n } func (e *eqSigBuilder) float32() { e.flush(false) e.r.WriteByte(sigFloat32) } func (e *eqSigBuilder) float64() { e.flush(false) e.r.WriteByte(sigFloat64) } func (e *eqSigBuilder) string() { e.flush(false) e.r.WriteByte(sigString) } func (e *eqSigBuilder) eface() { e.flush(false) e.r.WriteByte(sigEface) } func (e *eqSigBuilder) iface() { e.flush(false) e.r.WriteByte(sigIface) } func (e *eqSigBuilder) build(t *types.Type) { switch t.Kind() { case types.TINT8, types.TUINT8, types.TBOOL: e.regular(1) case types.TINT16, types.TUINT16: e.regular(2) case types.TINT32, types.TUINT32: e.regular(4) case types.TINT64, types.TUINT64: e.regular(8) case types.TINT, types.TUINT, types.TUINTPTR, types.TPTR, types.TUNSAFEPTR, types.TCHAN: e.regular(int64(types.PtrSize)) case types.TFLOAT32: e.float32() case types.TFLOAT64: e.float64() case types.TCOMPLEX64: e.float32() e.float32() case types.TCOMPLEX128: e.float64() e.float64() case types.TSTRING: e.string() case types.TINTER: if t.IsEmptyInterface() { e.eface() } else { e.iface() } case types.TSTRUCT: var off int64 for _, f := range t.Fields() { if f.Sym.IsBlank() { continue } if off < f.Offset { e.skip(f.Offset - off) } e.build(f.Type) off = f.Offset + f.Type.Size() } if off < t.Size() { e.skip(t.Size() - off) } case types.TARRAY: if types.AlgType(t) == types.AMEM { // TODO: some "regular equality" types don't hit here, // like [8]sync/atomic.Pointer. Figure out how to // handle the subtle difference between "AMEM" and // "can be compared byte-by-byte for equality". e.regular(t.Size()) break } et := t.Elem() n := t.NumElem() switch n { case 0: case 1: e.build(et) default: // To keep signatures small, we can't just repeat // the element signature N times. Instead, we issue // an array into the signature. Note that this can // lead to a situation where two types which could // share an equality function do not, like // struct { a, b, c, d string } sig: SSSS // [4]string sig: [4S] // That's ok, just a tad inefficient. // // The generated loops are kind of inefficient as well, // so unroll the loop a bit. const unrollSize = 32 // make loop body compare around this many bytes if et.Size() == 0 { break // zero-size elements need no comparison } unroll := max(1, unrollSize/et.Size()) // Do partial loops directly. for n%unroll != 0 { e.build(et) n-- } if n == 0 { break } // If we only have one loop left, do it directly. if n == unroll { for range n { e.build(et) } break } e.flush(false) e.r.WriteString(fmt.Sprintf("%c%d", sigArrayStart, n/unroll)) for range unroll { e.build(et) } e.flush(false) e.r.WriteByte(sigArrayEnd) } default: base.Fatalf("eqSigBuilder %v", t) } } // Parse and remove the number at the start of s. func parseNum(s string) (int64, string) { n := 0 for n < len(s) && s[n] >= '0' && s[n] <= '9' { n++ } x, err := strconv.ParseInt(s[:n], 10, 64) if err != nil { base.Fatalf("bad integer: %s", s[:n]) } return x, s[n:] } // parseArray parses "%d%s]" from the front of a signature. // Returns the repeat count (the %d), the element signature // (the %s), and any remaining signature after the closing ']'. func parseArray(sig string) (int64, string, string) { var n int64 n, sig = parseNum(sig) // Find matching closing brace. i := 0 depth := 1 for { if i == len(sig) { base.Fatalf("mismatched brackets in %s", sig) } switch sig[i] { case sigArrayStart: depth++ case sigArrayEnd: depth-- if depth == 0 { return n, sig[:i], sig[i+1:] } } i++ } } // sigSize returns the size of the type described by the signature. func sigSize(sig string) int64 { var size int64 for len(sig) > 0 { kind := sig[0] sig = sig[1:] switch kind { case sigMemory, sigSkip: var n int64 n, sig = parseNum(sig) size += n case sigFloat32: size += 4 case sigFloat64: size += 8 case sigString, sigIface, sigEface: size += 2 * int64(types.PtrSize) case sigArrayStart: var n int64 var elemSig string n, elemSig, sig = parseArray(sig) size += n * sigSize(elemSig) } } return size } func sigTrimSkip(s string) string { i := strings.LastIndexByte(s, sigSkip) if i < 0 { return s } for j := i + 1; j < len(s); j++ { if s[j] < '0' || s[j] > '9' { return s } } return s[:i] }