github.com/corona10/go@v0.0.0-20180224231303-7a218942be57/src/cmd/compile/internal/gc/pgen.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package gc 6 7 import ( 8 "cmd/compile/internal/ssa" 9 "cmd/compile/internal/types" 10 "cmd/internal/dwarf" 11 "cmd/internal/obj" 12 "cmd/internal/objabi" 13 "cmd/internal/src" 14 "cmd/internal/sys" 15 "fmt" 16 "math/rand" 17 "sort" 18 "strings" 19 "sync" 20 "time" 21 ) 22 23 // "Portable" code generation. 24 25 var ( 26 nBackendWorkers int // number of concurrent backend workers, set by a compiler flag 27 compilequeue []*Node // functions waiting to be compiled 28 ) 29 30 func emitptrargsmap() { 31 if Curfn.funcname() == "_" { 32 return 33 } 34 sym := lookup(fmt.Sprintf("%s.args_stackmap", Curfn.funcname())) 35 lsym := sym.Linksym() 36 37 nptr := int(Curfn.Type.ArgWidth() / int64(Widthptr)) 38 bv := bvalloc(int32(nptr) * 2) 39 nbitmap := 1 40 if Curfn.Type.NumResults() > 0 { 41 nbitmap = 2 42 } 43 off := duint32(lsym, 0, uint32(nbitmap)) 44 off = duint32(lsym, off, uint32(bv.n)) 45 46 if Curfn.IsMethod() { 47 onebitwalktype1(Curfn.Type.Recvs(), 0, bv) 48 } 49 if Curfn.Type.NumParams() > 0 { 50 onebitwalktype1(Curfn.Type.Params(), 0, bv) 51 } 52 off = dbvec(lsym, off, bv) 53 54 if Curfn.Type.NumResults() > 0 { 55 onebitwalktype1(Curfn.Type.Results(), 0, bv) 56 off = dbvec(lsym, off, bv) 57 } 58 59 ggloblsym(lsym, int32(off), obj.RODATA|obj.LOCAL) 60 } 61 62 // cmpstackvarlt reports whether the stack variable a sorts before b. 63 // 64 // Sort the list of stack variables. Autos after anything else, 65 // within autos, unused after used, within used, things with 66 // pointers first, zeroed things first, and then decreasing size. 67 // Because autos are laid out in decreasing addresses 68 // on the stack, pointers first, zeroed things first and decreasing size 69 // really means, in memory, things with pointers needing zeroing at 70 // the top of the stack and increasing in size. 71 // Non-autos sort on offset. 72 func cmpstackvarlt(a, b *Node) bool { 73 if (a.Class() == PAUTO) != (b.Class() == PAUTO) { 74 return b.Class() == PAUTO 75 } 76 77 if a.Class() != PAUTO { 78 return a.Xoffset < b.Xoffset 79 } 80 81 if a.Name.Used() != b.Name.Used() { 82 return a.Name.Used() 83 } 84 85 ap := types.Haspointers(a.Type) 86 bp := types.Haspointers(b.Type) 87 if ap != bp { 88 return ap 89 } 90 91 ap = a.Name.Needzero() 92 bp = b.Name.Needzero() 93 if ap != bp { 94 return ap 95 } 96 97 if a.Type.Width != b.Type.Width { 98 return a.Type.Width > b.Type.Width 99 } 100 101 return a.Sym.Name < b.Sym.Name 102 } 103 104 // byStackvar implements sort.Interface for []*Node using cmpstackvarlt. 105 type byStackVar []*Node 106 107 func (s byStackVar) Len() int { return len(s) } 108 func (s byStackVar) Less(i, j int) bool { return cmpstackvarlt(s[i], s[j]) } 109 func (s byStackVar) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 110 111 func (s *ssafn) AllocFrame(f *ssa.Func) { 112 s.stksize = 0 113 s.stkptrsize = 0 114 fn := s.curfn.Func 115 116 // Mark the PAUTO's unused. 117 for _, ln := range fn.Dcl { 118 if ln.Class() == PAUTO { 119 ln.Name.SetUsed(false) 120 } 121 } 122 123 for _, l := range f.RegAlloc { 124 if ls, ok := l.(ssa.LocalSlot); ok { 125 ls.N.(*Node).Name.SetUsed(true) 126 } 127 } 128 129 scratchUsed := false 130 for _, b := range f.Blocks { 131 for _, v := range b.Values { 132 if n, ok := v.Aux.(*Node); ok { 133 switch n.Class() { 134 case PPARAM, PPARAMOUT: 135 // Don't modify nodfp; it is a global. 136 if n != nodfp { 137 n.Name.SetUsed(true) 138 } 139 case PAUTO: 140 n.Name.SetUsed(true) 141 } 142 } 143 if !scratchUsed { 144 scratchUsed = v.Op.UsesScratch() 145 } 146 147 } 148 } 149 150 if f.Config.NeedsFpScratch && scratchUsed { 151 s.scratchFpMem = tempAt(src.NoXPos, s.curfn, types.Types[TUINT64]) 152 } 153 154 sort.Sort(byStackVar(fn.Dcl)) 155 156 // Reassign stack offsets of the locals that are used. 157 for i, n := range fn.Dcl { 158 if n.Op != ONAME || n.Class() != PAUTO { 159 continue 160 } 161 if !n.Name.Used() { 162 fn.Dcl = fn.Dcl[:i] 163 break 164 } 165 166 dowidth(n.Type) 167 w := n.Type.Width 168 if w >= thearch.MAXWIDTH || w < 0 { 169 Fatalf("bad width") 170 } 171 s.stksize += w 172 s.stksize = Rnd(s.stksize, int64(n.Type.Align)) 173 if types.Haspointers(n.Type) { 174 s.stkptrsize = s.stksize 175 } 176 if thearch.LinkArch.InFamily(sys.MIPS, sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64, sys.S390X) { 177 s.stksize = Rnd(s.stksize, int64(Widthptr)) 178 } 179 n.Xoffset = -s.stksize 180 } 181 182 s.stksize = Rnd(s.stksize, int64(Widthreg)) 183 s.stkptrsize = Rnd(s.stkptrsize, int64(Widthreg)) 184 } 185 186 func compile(fn *Node) { 187 Curfn = fn 188 dowidth(fn.Type) 189 190 if fn.Nbody.Len() == 0 { 191 emitptrargsmap() 192 return 193 } 194 195 saveerrors() 196 197 order(fn) 198 if nerrors != 0 { 199 return 200 } 201 202 walk(fn) 203 if nerrors != 0 { 204 return 205 } 206 if instrumenting { 207 instrument(fn) 208 } 209 210 // From this point, there should be no uses of Curfn. Enforce that. 211 Curfn = nil 212 213 // Set up the function's LSym early to avoid data races with the assemblers. 214 fn.Func.initLSym() 215 216 if compilenow() { 217 compileSSA(fn, 0) 218 } else { 219 compilequeue = append(compilequeue, fn) 220 } 221 } 222 223 // compilenow reports whether to compile immediately. 224 // If functions are not compiled immediately, 225 // they are enqueued in compilequeue, 226 // which is drained by compileFunctions. 227 func compilenow() bool { 228 return nBackendWorkers == 1 && Debug_compilelater == 0 229 } 230 231 const maxStackSize = 1 << 30 232 233 // compileSSA builds an SSA backend function, 234 // uses it to generate a plist, 235 // and flushes that plist to machine code. 236 // worker indicates which of the backend workers is doing the processing. 237 func compileSSA(fn *Node, worker int) { 238 f := buildssa(fn, worker) 239 if f.Frontend().(*ssafn).stksize >= maxStackSize { 240 largeStackFramesMu.Lock() 241 largeStackFrames = append(largeStackFrames, fn.Pos) 242 largeStackFramesMu.Unlock() 243 return 244 } 245 pp := newProgs(fn, worker) 246 genssa(f, pp) 247 pp.Flush() 248 // fieldtrack must be called after pp.Flush. See issue 20014. 249 fieldtrack(pp.Text.From.Sym, fn.Func.FieldTrack) 250 pp.Free() 251 } 252 253 func init() { 254 if raceEnabled { 255 rand.Seed(time.Now().UnixNano()) 256 } 257 } 258 259 // compileFunctions compiles all functions in compilequeue. 260 // It fans out nBackendWorkers to do the work 261 // and waits for them to complete. 262 func compileFunctions() { 263 if len(compilequeue) != 0 { 264 sizeCalculationDisabled = true // not safe to calculate sizes concurrently 265 if raceEnabled { 266 // Randomize compilation order to try to shake out races. 267 tmp := make([]*Node, len(compilequeue)) 268 perm := rand.Perm(len(compilequeue)) 269 for i, v := range perm { 270 tmp[v] = compilequeue[i] 271 } 272 copy(compilequeue, tmp) 273 } else { 274 // Compile the longest functions first, 275 // since they're most likely to be the slowest. 276 // This helps avoid stragglers. 277 obj.SortSlice(compilequeue, func(i, j int) bool { 278 return compilequeue[i].Nbody.Len() > compilequeue[j].Nbody.Len() 279 }) 280 } 281 var wg sync.WaitGroup 282 Ctxt.InParallel = true 283 c := make(chan *Node, nBackendWorkers) 284 for i := 0; i < nBackendWorkers; i++ { 285 wg.Add(1) 286 go func(worker int) { 287 for fn := range c { 288 compileSSA(fn, worker) 289 } 290 wg.Done() 291 }(i) 292 } 293 for _, fn := range compilequeue { 294 c <- fn 295 } 296 close(c) 297 compilequeue = nil 298 wg.Wait() 299 Ctxt.InParallel = false 300 sizeCalculationDisabled = false 301 } 302 } 303 304 func debuginfo(fnsym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) { 305 fn := curfn.(*Node) 306 if fn.Func.Nname != nil { 307 if expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect { 308 Fatalf("unexpected fnsym: %v != %v", fnsym, expect) 309 } 310 } 311 312 var automDecls []*Node 313 // Populate Automs for fn. 314 for _, n := range fn.Func.Dcl { 315 if n.Op != ONAME { // might be OTYPE or OLITERAL 316 continue 317 } 318 var name obj.AddrName 319 switch n.Class() { 320 case PAUTO: 321 if !n.Name.Used() { 322 // Text == nil -> generating abstract function 323 if fnsym.Func.Text != nil { 324 Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)") 325 } 326 continue 327 } 328 name = obj.NAME_AUTO 329 case PPARAM, PPARAMOUT: 330 name = obj.NAME_PARAM 331 default: 332 continue 333 } 334 automDecls = append(automDecls, n) 335 gotype := ngotype(n).Linksym() 336 fnsym.Func.Autom = append(fnsym.Func.Autom, &obj.Auto{ 337 Asym: Ctxt.Lookup(n.Sym.Name), 338 Aoffset: int32(n.Xoffset), 339 Name: name, 340 Gotype: gotype, 341 }) 342 } 343 344 decls, dwarfVars := createDwarfVars(fnsym, fn.Func, automDecls) 345 346 var varScopes []ScopeID 347 for _, decl := range decls { 348 pos := decl.Pos 349 if decl.Name.Defn != nil && (decl.Name.Captured() || decl.Name.Byval()) { 350 // It's not clear which position is correct for captured variables here: 351 // * decl.Pos is the wrong position for captured variables, in the inner 352 // function, but it is the right position in the outer function. 353 // * decl.Name.Defn is nil for captured variables that were arguments 354 // on the outer function, however the decl.Pos for those seems to be 355 // correct. 356 // * decl.Name.Defn is the "wrong" thing for variables declared in the 357 // header of a type switch, it's their position in the header, rather 358 // than the position of the case statement. In principle this is the 359 // right thing, but here we prefer the latter because it makes each 360 // instance of the header variable local to the lexical block of its 361 // case statement. 362 // This code is probably wrong for type switch variables that are also 363 // captured. 364 pos = decl.Name.Defn.Pos 365 } 366 varScopes = append(varScopes, findScope(fn.Func.Marks, pos)) 367 } 368 369 scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes) 370 var inlcalls dwarf.InlCalls 371 if genDwarfInline > 0 { 372 inlcalls = assembleInlines(fnsym, fn, dwarfVars) 373 } 374 return scopes, inlcalls 375 } 376 377 // createSimpleVars creates a DWARF entry for every variable declared in the 378 // function, claiming that they are permanently on the stack. 379 func createSimpleVars(automDecls []*Node) ([]*Node, []*dwarf.Var, map[*Node]bool) { 380 var vars []*dwarf.Var 381 var decls []*Node 382 selected := make(map[*Node]bool) 383 for _, n := range automDecls { 384 if n.IsAutoTmp() { 385 continue 386 } 387 var abbrev int 388 offs := n.Xoffset 389 390 switch n.Class() { 391 case PAUTO: 392 abbrev = dwarf.DW_ABRV_AUTO 393 if Ctxt.FixedFrameSize() == 0 { 394 offs -= int64(Widthptr) 395 } 396 if objabi.Framepointer_enabled(objabi.GOOS, objabi.GOARCH) { 397 offs -= int64(Widthptr) 398 } 399 400 case PPARAM, PPARAMOUT: 401 abbrev = dwarf.DW_ABRV_PARAM 402 offs += Ctxt.FixedFrameSize() 403 default: 404 Fatalf("createSimpleVars unexpected type %v for node %v", n.Class(), n) 405 } 406 407 selected[n] = true 408 typename := dwarf.InfoPrefix + typesymname(n.Type) 409 decls = append(decls, n) 410 inlIndex := 0 411 if genDwarfInline > 1 { 412 if n.InlFormal() || n.InlLocal() { 413 inlIndex = posInlIndex(n.Pos) + 1 414 if n.InlFormal() { 415 abbrev = dwarf.DW_ABRV_PARAM 416 } 417 } 418 } 419 declpos := Ctxt.InnermostPos(n.Pos) 420 vars = append(vars, &dwarf.Var{ 421 Name: n.Sym.Name, 422 IsReturnValue: n.Class() == PPARAMOUT, 423 IsInlFormal: n.InlFormal(), 424 Abbrev: abbrev, 425 StackOffset: int32(offs), 426 Type: Ctxt.Lookup(typename), 427 DeclFile: declpos.RelFilename(), 428 DeclLine: declpos.RelLine(), 429 DeclCol: declpos.Col(), 430 InlIndex: int32(inlIndex), 431 ChildIndex: -1, 432 }) 433 } 434 return decls, vars, selected 435 } 436 437 // createComplexVars creates recomposed DWARF vars with location lists, 438 // suitable for describing optimized code. 439 func createComplexVars(fnsym *obj.LSym, fn *Func, automDecls []*Node) ([]*Node, []*dwarf.Var, map[*Node]bool) { 440 debugInfo := fn.DebugInfo 441 442 // Produce a DWARF variable entry for each user variable. 443 var decls []*Node 444 var vars []*dwarf.Var 445 ssaVars := make(map[*Node]bool) 446 447 for varID := range debugInfo.Vars { 448 n := debugInfo.Vars[varID].(*Node) 449 ssaVars[n] = true 450 for _, slot := range debugInfo.VarSlots[varID] { 451 ssaVars[debugInfo.Slots[slot].N.(*Node)] = true 452 } 453 454 if dvar := createComplexVar(fn, ssa.VarID(varID)); dvar != nil { 455 decls = append(decls, n) 456 vars = append(vars, dvar) 457 } 458 } 459 460 return decls, vars, ssaVars 461 } 462 463 // createDwarfVars process fn, returning a list of DWARF variables and the 464 // Nodes they represent. 465 func createDwarfVars(fnsym *obj.LSym, fn *Func, automDecls []*Node) ([]*Node, []*dwarf.Var) { 466 // Collect a raw list of DWARF vars. 467 var vars []*dwarf.Var 468 var decls []*Node 469 var selected map[*Node]bool 470 if Ctxt.Flag_locationlists && Ctxt.Flag_optimize && fn.DebugInfo != nil { 471 decls, vars, selected = createComplexVars(fnsym, fn, automDecls) 472 } else { 473 decls, vars, selected = createSimpleVars(automDecls) 474 } 475 476 var dcl []*Node 477 if fnsym.WasInlined() { 478 dcl = preInliningDcls(fnsym) 479 } else { 480 dcl = automDecls 481 } 482 483 // If optimization is enabled, the list above will typically be 484 // missing some of the original pre-optimization variables in the 485 // function (they may have been promoted to registers, folded into 486 // constants, dead-coded away, etc). Here we add back in entries 487 // for selected missing vars. Note that the recipe below creates a 488 // conservative location. The idea here is that we want to 489 // communicate to the user that "yes, there is a variable named X 490 // in this function, but no, I don't have enough information to 491 // reliably report its contents." 492 for _, n := range dcl { 493 if _, found := selected[n]; found { 494 continue 495 } 496 c := n.Sym.Name[0] 497 if c == '.' || n.Type.IsUntyped() { 498 continue 499 } 500 typename := dwarf.InfoPrefix + typesymname(n.Type) 501 decls = append(decls, n) 502 abbrev := dwarf.DW_ABRV_AUTO_LOCLIST 503 if n.Class() == PPARAM || n.Class() == PPARAMOUT { 504 abbrev = dwarf.DW_ABRV_PARAM_LOCLIST 505 } 506 inlIndex := 0 507 if genDwarfInline > 1 { 508 if n.InlFormal() || n.InlLocal() { 509 inlIndex = posInlIndex(n.Pos) + 1 510 if n.InlFormal() { 511 abbrev = dwarf.DW_ABRV_PARAM_LOCLIST 512 } 513 } 514 } 515 declpos := Ctxt.InnermostPos(n.Pos) 516 vars = append(vars, &dwarf.Var{ 517 Name: n.Sym.Name, 518 IsReturnValue: n.Class() == PPARAMOUT, 519 Abbrev: abbrev, 520 StackOffset: int32(n.Xoffset), 521 Type: Ctxt.Lookup(typename), 522 DeclFile: declpos.RelFilename(), 523 DeclLine: declpos.RelLine(), 524 DeclCol: declpos.Col(), 525 InlIndex: int32(inlIndex), 526 ChildIndex: -1, 527 }) 528 // Append a "deleted auto" entry to the autom list so as to 529 // insure that the type in question is picked up by the linker. 530 // See issue 22941. 531 gotype := ngotype(n).Linksym() 532 fnsym.Func.Autom = append(fnsym.Func.Autom, &obj.Auto{ 533 Asym: Ctxt.Lookup(n.Sym.Name), 534 Aoffset: int32(-1), 535 Name: obj.NAME_DELETED_AUTO, 536 Gotype: gotype, 537 }) 538 539 } 540 541 return decls, vars 542 } 543 544 // Given a function that was inlined at some point during the 545 // compilation, return a sorted list of nodes corresponding to the 546 // autos/locals in that function prior to inlining. If this is a 547 // function that is not local to the package being compiled, then the 548 // names of the variables may have been "versioned" to avoid conflicts 549 // with local vars; disregard this versioning when sorting. 550 func preInliningDcls(fnsym *obj.LSym) []*Node { 551 fn := Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*Node) 552 var dcl, rdcl []*Node 553 if fn.Name.Defn != nil { 554 dcl = fn.Func.Inldcl.Slice() // local function 555 } else { 556 dcl = fn.Func.Dcl // imported function 557 } 558 for _, n := range dcl { 559 c := n.Sym.Name[0] 560 // Avoid reporting "_" parameters, since if there are more than 561 // one, it can result in a collision later on, as in #23179. 562 if unversion(n.Sym.Name) == "_" || c == '.' || n.Type.IsUntyped() { 563 continue 564 } 565 rdcl = append(rdcl, n) 566 } 567 sort.Sort(byNodeName(rdcl)) 568 return rdcl 569 } 570 571 func cmpNodeName(a, b *Node) bool { 572 aart := 0 573 if strings.HasPrefix(a.Sym.Name, "~") { 574 aart = 1 575 } 576 bart := 0 577 if strings.HasPrefix(b.Sym.Name, "~") { 578 bart = 1 579 } 580 if aart != bart { 581 return aart < bart 582 } 583 584 aname := unversion(a.Sym.Name) 585 bname := unversion(b.Sym.Name) 586 return aname < bname 587 } 588 589 // byNodeName implements sort.Interface for []*Node using cmpNodeName. 590 type byNodeName []*Node 591 592 func (s byNodeName) Len() int { return len(s) } 593 func (s byNodeName) Less(i, j int) bool { return cmpNodeName(s[i], s[j]) } 594 func (s byNodeName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 595 596 // stackOffset returns the stack location of a LocalSlot relative to the 597 // stack pointer, suitable for use in a DWARF location entry. This has nothing 598 // to do with its offset in the user variable. 599 func stackOffset(slot ssa.LocalSlot) int32 { 600 n := slot.N.(*Node) 601 var base int64 602 switch n.Class() { 603 case PAUTO: 604 if Ctxt.FixedFrameSize() == 0 { 605 base -= int64(Widthptr) 606 } 607 if objabi.Framepointer_enabled(objabi.GOOS, objabi.GOARCH) { 608 base -= int64(Widthptr) 609 } 610 case PPARAM, PPARAMOUT: 611 base += Ctxt.FixedFrameSize() 612 } 613 return int32(base + n.Xoffset + slot.Off) 614 } 615 616 // createComplexVar builds a single DWARF variable entry and location list. 617 func createComplexVar(fn *Func, varID ssa.VarID) *dwarf.Var { 618 debug := fn.DebugInfo 619 n := debug.Vars[varID].(*Node) 620 621 var abbrev int 622 switch n.Class() { 623 case PAUTO: 624 abbrev = dwarf.DW_ABRV_AUTO_LOCLIST 625 case PPARAM, PPARAMOUT: 626 abbrev = dwarf.DW_ABRV_PARAM_LOCLIST 627 default: 628 return nil 629 } 630 631 gotype := ngotype(n).Linksym() 632 typename := dwarf.InfoPrefix + gotype.Name[len("type."):] 633 inlIndex := 0 634 if genDwarfInline > 1 { 635 if n.InlFormal() || n.InlLocal() { 636 inlIndex = posInlIndex(n.Pos) + 1 637 if n.InlFormal() { 638 abbrev = dwarf.DW_ABRV_PARAM_LOCLIST 639 } 640 } 641 } 642 declpos := Ctxt.InnermostPos(n.Pos) 643 dvar := &dwarf.Var{ 644 Name: n.Sym.Name, 645 IsReturnValue: n.Class() == PPARAMOUT, 646 IsInlFormal: n.InlFormal(), 647 Abbrev: abbrev, 648 Type: Ctxt.Lookup(typename), 649 // The stack offset is used as a sorting key, so for decomposed 650 // variables just give it the first one. It's not used otherwise. 651 // This won't work well if the first slot hasn't been assigned a stack 652 // location, but it's not obvious how to do better. 653 StackOffset: stackOffset(debug.Slots[debug.VarSlots[varID][0]]), 654 DeclFile: declpos.RelFilename(), 655 DeclLine: declpos.RelLine(), 656 DeclCol: declpos.Col(), 657 InlIndex: int32(inlIndex), 658 ChildIndex: -1, 659 } 660 list := debug.LocationLists[varID] 661 if len(list) != 0 { 662 dvar.PutLocationList = func(listSym, startPC dwarf.Sym) { 663 debug.PutLocationList(list, Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym)) 664 } 665 } 666 return dvar 667 } 668 669 // fieldtrack adds R_USEFIELD relocations to fnsym to record any 670 // struct fields that it used. 671 func fieldtrack(fnsym *obj.LSym, tracked map[*types.Sym]struct{}) { 672 if fnsym == nil { 673 return 674 } 675 if objabi.Fieldtrack_enabled == 0 || len(tracked) == 0 { 676 return 677 } 678 679 trackSyms := make([]*types.Sym, 0, len(tracked)) 680 for sym := range tracked { 681 trackSyms = append(trackSyms, sym) 682 } 683 sort.Sort(symByName(trackSyms)) 684 for _, sym := range trackSyms { 685 r := obj.Addrel(fnsym) 686 r.Sym = sym.Linksym() 687 r.Type = objabi.R_USEFIELD 688 } 689 } 690 691 type symByName []*types.Sym 692 693 func (a symByName) Len() int { return len(a) } 694 func (a symByName) Less(i, j int) bool { return a[i].Name < a[j].Name } 695 func (a symByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }