func gclean() { for _, r := range Thearch.ReservedRegs { reg[r-Thearch.REGMIN]-- } for r := Thearch.REGMIN; r <= Thearch.REGMAX; r++ { n := reg[r-Thearch.REGMIN] if n != 0 { Yyerror("reg %v left allocated", obj.Rconv(r)) if Debug['v'] != 0 { Regdump() } } } for r := Thearch.FREGMIN; r <= Thearch.FREGMAX; r++ { n := reg[r-Thearch.REGMIN] if n != 0 { Yyerror("reg %v left allocated", obj.Rconv(r)) if Debug['v'] != 0 { Regdump() } } } }
func Regdump() { if Debug['v'] == 0 { fmt.Printf("run compiler with -v for register allocation sites\n") return } dump := func(r int) { stk := regstk[r-Thearch.REGMIN] if len(stk) == 0 { return } fmt.Printf("reg %v allocated at:\n", obj.Rconv(r)) fmt.Printf("\t%s\n", strings.Replace(strings.TrimSpace(string(stk)), "\n", "\n\t", -1)) } for r := Thearch.REGMIN; r <= Thearch.REGMAX; r++ { if reg[r-Thearch.REGMIN] != 0 { dump(r) } } for r := Thearch.FREGMIN; r <= Thearch.FREGMAX; r++ { if reg[r-Thearch.REGMIN] == 0 { dump(r) } } }
func regopt(firstp *obj.Prog) { mergetemp(firstp) /* * control flow is more complicated in generated go code * than in generated c code. define pseudo-variables for * registers, so we have complete register usage information. */ var nreg int regnames := Thearch.Regnames(&nreg) nvar = nreg for i := 0; i < nreg; i++ { vars[i] = Var{} } for i := 0; i < nreg; i++ { if regnodes[i] == nil { regnodes[i] = newname(Lookup(regnames[i])) } vars[i].node = regnodes[i] } regbits = Thearch.Excludedregs() externs = zbits params = zbits consts = zbits addrs = zbits ivar = zbits ovar = zbits /* * pass 1 * build aux data structure * allocate pcs * find use and set of variables */ g := Flowstart(firstp, func() interface{} { return new(Reg) }) if g == nil { for i := 0; i < nvar; i++ { vars[i].node.Opt = nil } return } firstf := g.Start for f := firstf; f != nil; f = f.Link { p := f.Prog if p.As == obj.AVARDEF || p.As == obj.AVARKILL { continue } // Avoid making variables for direct-called functions. if p.As == obj.ACALL && p.To.Type == obj.TYPE_MEM && p.To.Name == obj.NAME_EXTERN { continue } // from vs to doesn't matter for registers. r := f.Data.(*Reg) r.use1.b[0] |= p.Info.Reguse | p.Info.Regindex r.set.b[0] |= p.Info.Regset bit := mkvar(f, &p.From) if bany(&bit) { if p.Info.Flags&LeftAddr != 0 { setaddrs(bit) } if p.Info.Flags&LeftRead != 0 { for z := 0; z < BITS; z++ { r.use1.b[z] |= bit.b[z] } } if p.Info.Flags&LeftWrite != 0 { for z := 0; z < BITS; z++ { r.set.b[z] |= bit.b[z] } } } // Compute used register for reg if p.Info.Flags&RegRead != 0 { r.use1.b[0] |= Thearch.RtoB(int(p.Reg)) } // Currently we never generate three register forms. // If we do, this will need to change. if p.From3.Type != obj.TYPE_NONE { Fatal("regopt not implemented for from3") } bit = mkvar(f, &p.To) if bany(&bit) { if p.Info.Flags&RightAddr != 0 { setaddrs(bit) } if p.Info.Flags&RightRead != 0 { for z := 0; z < BITS; z++ { r.use2.b[z] |= bit.b[z] } } if p.Info.Flags&RightWrite != 0 { for z := 0; z < BITS; z++ { r.set.b[z] |= bit.b[z] } } } } for i := 0; i < nvar; i++ { v := &vars[i] if v.addr != 0 { bit := blsh(uint(i)) for z := 0; z < BITS; z++ { addrs.b[z] |= bit.b[z] } } if Debug['R'] != 0 && Debug['v'] != 0 { fmt.Printf("bit=%2d addr=%d et=%v w=%-2d s=%v + %d\n", i, v.addr, Econv(int(v.etype), 0), v.width, v.node, v.offset) } } if Debug['R'] != 0 && Debug['v'] != 0 { Dumpit("pass1", firstf, 1) } /* * pass 2 * find looping structure */ flowrpo(g) if Debug['R'] != 0 && Debug['v'] != 0 { Dumpit("pass2", firstf, 1) } /* * pass 2.5 * iterate propagating fat vardef covering forward * r->act records vars with a VARDEF since the last CALL. * (r->act will be reused in pass 5 for something else, * but we'll be done with it by then.) */ active := 0 for f := firstf; f != nil; f = f.Link { f.Active = 0 r := f.Data.(*Reg) r.act = zbits } for f := firstf; f != nil; f = f.Link { p := f.Prog if p.As == obj.AVARDEF && Isfat(((p.To.Node).(*Node)).Type) && ((p.To.Node).(*Node)).Opt != nil { active++ walkvardef(p.To.Node.(*Node), f, active) } } /* * pass 3 * iterate propagating usage * back until flow graph is complete */ var f1 *Flow var i int var f *Flow loop1: change = 0 for f = firstf; f != nil; f = f.Link { f.Active = 0 } for f = firstf; f != nil; f = f.Link { if f.Prog.As == obj.ARET { prop(f, zbits, zbits) } } /* pick up unreachable code */ loop11: i = 0 for f = firstf; f != nil; f = f1 { f1 = f.Link if f1 != nil && f1.Active != 0 && f.Active == 0 { prop(f, zbits, zbits) i = 1 } } if i != 0 { goto loop11 } if change != 0 { goto loop1 } if Debug['R'] != 0 && Debug['v'] != 0 { Dumpit("pass3", firstf, 1) } /* * pass 4 * iterate propagating register/variable synchrony * forward until graph is complete */ loop2: change = 0 for f = firstf; f != nil; f = f.Link { f.Active = 0 } synch(firstf, zbits) if change != 0 { goto loop2 } if Debug['R'] != 0 && Debug['v'] != 0 { Dumpit("pass4", firstf, 1) } /* * pass 4.5 * move register pseudo-variables into regu. */ mask := uint64((1 << uint(nreg)) - 1) for f := firstf; f != nil; f = f.Link { r := f.Data.(*Reg) r.regu = (r.refbehind.b[0] | r.set.b[0]) & mask r.set.b[0] &^= mask r.use1.b[0] &^= mask r.use2.b[0] &^= mask r.refbehind.b[0] &^= mask r.refahead.b[0] &^= mask r.calbehind.b[0] &^= mask r.calahead.b[0] &^= mask r.regdiff.b[0] &^= mask r.act.b[0] &^= mask } if Debug['R'] != 0 && Debug['v'] != 0 { Dumpit("pass4.5", firstf, 1) } /* * pass 5 * isolate regions * calculate costs (paint1) */ var bit Bits if f := firstf; f != nil { r := f.Data.(*Reg) for z := 0; z < BITS; z++ { bit.b[z] = (r.refahead.b[z] | r.calahead.b[z]) &^ (externs.b[z] | params.b[z] | addrs.b[z] | consts.b[z]) } if bany(&bit) && f.Refset == 0 { // should never happen - all variables are preset if Debug['w'] != 0 { fmt.Printf("%v: used and not set: %v\n", f.Prog.Line(), &bit) } f.Refset = 1 } } for f := firstf; f != nil; f = f.Link { (f.Data.(*Reg)).act = zbits } nregion = 0 region = region[:0] var rgp *Rgn for f := firstf; f != nil; f = f.Link { r := f.Data.(*Reg) for z := 0; z < BITS; z++ { bit.b[z] = r.set.b[z] &^ (r.refahead.b[z] | r.calahead.b[z] | addrs.b[z]) } if bany(&bit) && f.Refset == 0 { if Debug['w'] != 0 { fmt.Printf("%v: set and not used: %v\n", f.Prog.Line(), &bit) } f.Refset = 1 Thearch.Excise(f) } for z := 0; z < BITS; z++ { bit.b[z] = LOAD(r, z) &^ (r.act.b[z] | addrs.b[z]) } for bany(&bit) { i = bnum(bit) change = 0 paint1(f, i) biclr(&bit, uint(i)) if change <= 0 { continue } if nregion >= MaxRgn { nregion++ continue } region = append(region, Rgn{ enter: f, cost: int16(change), varno: int16(i), }) nregion++ } } if false && Debug['v'] != 0 && strings.Contains(Curfn.Nname.Sym.Name, "Parse") { Warn("regions: %d\n", nregion) } if nregion >= MaxRgn { if Debug['v'] != 0 { Warn("too many regions: %d\n", nregion) } nregion = MaxRgn } sort.Sort(rcmp(region[:nregion])) if Debug['R'] != 0 && Debug['v'] != 0 { Dumpit("pass5", firstf, 1) } /* * pass 6 * determine used registers (paint2) * replace code (paint3) */ if Debug['R'] != 0 && Debug['v'] != 0 { fmt.Printf("\nregisterizing\n") } var usedreg uint64 var vreg uint64 for i := 0; i < nregion; i++ { rgp = ®ion[i] if Debug['R'] != 0 && Debug['v'] != 0 { fmt.Printf("region %d: cost %d varno %d enter %d\n", i, rgp.cost, rgp.varno, rgp.enter.Prog.Pc) } bit = blsh(uint(rgp.varno)) usedreg = paint2(rgp.enter, int(rgp.varno), 0) vreg = allreg(usedreg, rgp) if rgp.regno != 0 { if Debug['R'] != 0 && Debug['v'] != 0 { v := &vars[rgp.varno] fmt.Printf("registerize %v+%d (bit=%2d et=%v) in %v usedreg=%#x vreg=%#x\n", v.node, v.offset, rgp.varno, Econv(int(v.etype), 0), obj.Rconv(int(rgp.regno)), usedreg, vreg) } paint3(rgp.enter, int(rgp.varno), vreg, int(rgp.regno)) } } /* * free aux structures. peep allocates new ones. */ for i := 0; i < nvar; i++ { vars[i].node.Opt = nil } Flowend(g) firstf = nil if Debug['R'] != 0 && Debug['v'] != 0 { // Rebuild flow graph, since we inserted instructions g := Flowstart(firstp, nil) firstf = g.Start Dumpit("pass6", firstf, 0) Flowend(g) firstf = nil } /* * pass 7 * peep-hole on basic block */ if Debug['R'] == 0 || Debug['P'] != 0 { Thearch.Peep(firstp) } /* * eliminate nops */ for p := firstp; p != nil; p = p.Link { for p.Link != nil && p.Link.As == obj.ANOP { p.Link = p.Link.Link } if p.To.Type == obj.TYPE_BRANCH { for p.To.Val.(*obj.Prog) != nil && p.To.Val.(*obj.Prog).As == obj.ANOP { p.To.Val = p.To.Val.(*obj.Prog).Link } } } if Debug['R'] != 0 { if Ostats.Ncvtreg != 0 || Ostats.Nspill != 0 || Ostats.Nreload != 0 || Ostats.Ndelmov != 0 || Ostats.Nvar != 0 || Ostats.Naddr != 0 || false { fmt.Printf("\nstats\n") } if Ostats.Ncvtreg != 0 { fmt.Printf("\t%4d cvtreg\n", Ostats.Ncvtreg) } if Ostats.Nspill != 0 { fmt.Printf("\t%4d spill\n", Ostats.Nspill) } if Ostats.Nreload != 0 { fmt.Printf("\t%4d reload\n", Ostats.Nreload) } if Ostats.Ndelmov != 0 { fmt.Printf("\t%4d delmov\n", Ostats.Ndelmov) } if Ostats.Nvar != 0 { fmt.Printf("\t%4d var\n", Ostats.Nvar) } if Ostats.Naddr != 0 { fmt.Printf("\t%4d addr\n", Ostats.Naddr) } Ostats = OptStats{} } }
func nodedump(n *Node, flag int) string { if n == nil { return "" } recur := flag&obj.FmtShort == 0 var buf bytes.Buffer if recur { indent(&buf) if dumpdepth > 10 { buf.WriteString("...") return buf.String() } if n.Ninit != nil { fmt.Fprintf(&buf, "%v-init%v", Oconv(int(n.Op), 0), n.Ninit) indent(&buf) } } switch n.Op { default: fmt.Fprintf(&buf, "%v%v", Oconv(int(n.Op), 0), Jconv(n, 0)) case OREGISTER, OINDREG: fmt.Fprintf(&buf, "%v-%v%v", Oconv(int(n.Op), 0), obj.Rconv(int(n.Reg)), Jconv(n, 0)) case OLITERAL: fmt.Fprintf(&buf, "%v-%v%v", Oconv(int(n.Op), 0), Vconv(&n.Val, 0), Jconv(n, 0)) case ONAME, ONONAME: if n.Sym != nil { fmt.Fprintf(&buf, "%v-%v%v", Oconv(int(n.Op), 0), n.Sym, Jconv(n, 0)) } else { fmt.Fprintf(&buf, "%v%v", Oconv(int(n.Op), 0), Jconv(n, 0)) } if recur && n.Type == nil && n.Ntype != nil { indent(&buf) fmt.Fprintf(&buf, "%v-ntype%v", Oconv(int(n.Op), 0), n.Ntype) } case OASOP: fmt.Fprintf(&buf, "%v-%v%v", Oconv(int(n.Op), 0), Oconv(int(n.Etype), 0), Jconv(n, 0)) case OTYPE: fmt.Fprintf(&buf, "%v %v%v type=%v", Oconv(int(n.Op), 0), n.Sym, Jconv(n, 0), n.Type) if recur && n.Type == nil && n.Ntype != nil { indent(&buf) fmt.Fprintf(&buf, "%v-ntype%v", Oconv(int(n.Op), 0), n.Ntype) } } if n.Sym != nil && n.Op != ONAME { fmt.Fprintf(&buf, " %v G%d", n.Sym, n.Vargen) } if n.Type != nil { fmt.Fprintf(&buf, " %v", n.Type) } if recur { if n.Left != nil { buf.WriteString(Nconv(n.Left, 0)) } if n.Right != nil { buf.WriteString(Nconv(n.Right, 0)) } if n.List != nil { indent(&buf) fmt.Fprintf(&buf, "%v-list%v", Oconv(int(n.Op), 0), n.List) } if n.Rlist != nil { indent(&buf) fmt.Fprintf(&buf, "%v-rlist%v", Oconv(int(n.Op), 0), n.Rlist) } if n.Ntest != nil { indent(&buf) fmt.Fprintf(&buf, "%v-test%v", Oconv(int(n.Op), 0), n.Ntest) } if n.Nbody != nil { indent(&buf) fmt.Fprintf(&buf, "%v-body%v", Oconv(int(n.Op), 0), n.Nbody) } if n.Nelse != nil { indent(&buf) fmt.Fprintf(&buf, "%v-else%v", Oconv(int(n.Op), 0), n.Nelse) } if n.Nincr != nil { indent(&buf) fmt.Fprintf(&buf, "%v-incr%v", Oconv(int(n.Op), 0), n.Nincr) } } return buf.String() }
func exprfmt(n *Node, prec int) string { for n != nil && n.Implicit && (n.Op == OIND || n.Op == OADDR) { n = n.Left } if n == nil { return "<N>" } nprec := opprec[n.Op] if n.Op == OTYPE && n.Sym != nil { nprec = 8 } if prec > nprec { return fmt.Sprintf("(%v)", n) } switch n.Op { case OPAREN: return fmt.Sprintf("(%v)", n.Left) case ODDDARG: return "... argument" case OREGISTER: return obj.Rconv(int(n.Reg)) case OLITERAL: // this is a bit of a mess if fmtmode == FErr { if n.Orig != nil && n.Orig != n { return exprfmt(n.Orig, prec) } if n.Sym != nil { return Sconv(n.Sym, 0) } } if n.Val.Ctype == CTNIL && n.Orig != nil && n.Orig != n { return exprfmt(n.Orig, prec) } if n.Type != nil && n.Type != Types[n.Type.Etype] && n.Type != idealbool && n.Type != idealstring { // Need parens when type begins with what might // be misinterpreted as a unary operator: * or <-. if Isptr[n.Type.Etype] || (n.Type.Etype == TCHAN && n.Type.Chan == Crecv) { return fmt.Sprintf("(%v)(%v)", n.Type, Vconv(&n.Val, 0)) } else { return fmt.Sprintf("%v(%v)", n.Type, Vconv(&n.Val, 0)) } } return Vconv(&n.Val, 0) // Special case: name used as local variable in export. // _ becomes ~b%d internally; print as _ for export case ONAME: if fmtmode == FExp && n.Sym != nil && n.Sym.Name[0] == '~' && n.Sym.Name[1] == 'b' { return "_" } if fmtmode == FExp && n.Sym != nil && !isblank(n) && n.Vargen > 0 { return fmt.Sprintf("%v·%d", n.Sym, n.Vargen) } // Special case: explicit name of func (*T) method(...) is turned into pkg.(*T).method, // but for export, this should be rendered as (*pkg.T).meth. // These nodes have the special property that they are names with a left OTYPE and a right ONAME. if fmtmode == FExp && n.Left != nil && n.Left.Op == OTYPE && n.Right != nil && n.Right.Op == ONAME { if Isptr[n.Left.Type.Etype] { return fmt.Sprintf("(%v).%v", n.Left.Type, Sconv(n.Right.Sym, obj.FmtShort|obj.FmtByte)) } else { return fmt.Sprintf("%v.%v", n.Left.Type, Sconv(n.Right.Sym, obj.FmtShort|obj.FmtByte)) } } fallthrough //fallthrough case OPACK, ONONAME: return Sconv(n.Sym, 0) case OTYPE: if n.Type == nil && n.Sym != nil { return Sconv(n.Sym, 0) } return Tconv(n.Type, 0) case OTARRAY: if n.Left != nil { return fmt.Sprintf("[]%v", n.Left) } var f string f += fmt.Sprintf("[]%v", n.Right) return f // happens before typecheck case OTMAP: return fmt.Sprintf("map[%v]%v", n.Left, n.Right) case OTCHAN: switch n.Etype { case Crecv: return fmt.Sprintf("<-chan %v", n.Left) case Csend: return fmt.Sprintf("chan<- %v", n.Left) default: if n.Left != nil && n.Left.Op == OTCHAN && n.Left.Sym == nil && n.Left.Etype == Crecv { return fmt.Sprintf("chan (%v)", n.Left) } else { return fmt.Sprintf("chan %v", n.Left) } } case OTSTRUCT: return "<struct>" case OTINTER: return "<inter>" case OTFUNC: return "<func>" case OCLOSURE: if fmtmode == FErr { return "func literal" } if n.Nbody != nil { return fmt.Sprintf("%v { %v }", n.Type, n.Nbody) } return fmt.Sprintf("%v { %v }", n.Type, n.Closure.Nbody) case OCOMPLIT: ptrlit := n.Right != nil && n.Right.Implicit && n.Right.Type != nil && Isptr[n.Right.Type.Etype] if fmtmode == FErr { if n.Right != nil && n.Right.Type != nil && !n.Implicit { if ptrlit { return fmt.Sprintf("&%v literal", n.Right.Type.Type) } else { return fmt.Sprintf("%v literal", n.Right.Type) } } return "composite literal" } if fmtmode == FExp && ptrlit { // typecheck has overwritten OIND by OTYPE with pointer type. return fmt.Sprintf("(&%v{ %v })", n.Right.Type.Type, Hconv(n.List, obj.FmtComma)) } return fmt.Sprintf("(%v{ %v })", n.Right, Hconv(n.List, obj.FmtComma)) case OPTRLIT: if fmtmode == FExp && n.Left.Implicit { return Nconv(n.Left, 0) } return fmt.Sprintf("&%v", n.Left) case OSTRUCTLIT: if fmtmode == FExp { // requires special handling of field names var f string if n.Implicit { f += "{" } else { f += fmt.Sprintf("(%v{", n.Type) } for l := n.List; l != nil; l = l.Next { f += fmt.Sprintf(" %v:%v", Sconv(l.N.Left.Sym, obj.FmtShort|obj.FmtByte), l.N.Right) if l.Next != nil { f += "," } else { f += " " } } if !n.Implicit { f += "})" return f } f += "}" return f } fallthrough case OARRAYLIT, OMAPLIT: if fmtmode == FErr { return fmt.Sprintf("%v literal", n.Type) } if fmtmode == FExp && n.Implicit { return fmt.Sprintf("{ %v }", Hconv(n.List, obj.FmtComma)) } return fmt.Sprintf("(%v{ %v })", n.Type, Hconv(n.List, obj.FmtComma)) case OKEY: if n.Left != nil && n.Right != nil { if fmtmode == FExp && n.Left.Type != nil && n.Left.Type.Etype == TFIELD { // requires special handling of field names return fmt.Sprintf("%v:%v", Sconv(n.Left.Sym, obj.FmtShort|obj.FmtByte), n.Right) } else { return fmt.Sprintf("%v:%v", n.Left, n.Right) } } if n.Left == nil && n.Right != nil { return fmt.Sprintf(":%v", n.Right) } if n.Left != nil && n.Right == nil { return fmt.Sprintf("%v:", n.Left) } return ":" case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH, OCALLPART: var f string f += exprfmt(n.Left, nprec) if n.Right == nil || n.Right.Sym == nil { f += ".<nil>" return f } f += fmt.Sprintf(".%v", Sconv(n.Right.Sym, obj.FmtShort|obj.FmtByte)) return f case ODOTTYPE, ODOTTYPE2: var f string f += exprfmt(n.Left, nprec) if n.Right != nil { f += fmt.Sprintf(".(%v)", n.Right) return f } f += fmt.Sprintf(".(%v)", n.Type) return f case OINDEX, OINDEXMAP, OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR: var f string f += exprfmt(n.Left, nprec) f += fmt.Sprintf("[%v]", n.Right) return f case OCOPY, OCOMPLEX: return fmt.Sprintf("%v(%v, %v)", Oconv(int(n.Op), obj.FmtSharp), n.Left, n.Right) case OCONV, OCONVIFACE, OCONVNOP, OARRAYBYTESTR, OARRAYRUNESTR, OSTRARRAYBYTE, OSTRARRAYRUNE, ORUNESTR: if n.Type == nil || n.Type.Sym == nil { return fmt.Sprintf("(%v)(%v)", n.Type, n.Left) } if n.Left != nil { return fmt.Sprintf("%v(%v)", n.Type, n.Left) } return fmt.Sprintf("%v(%v)", n.Type, Hconv(n.List, obj.FmtComma)) case OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, ORECOVER, OPRINT, OPRINTN: if n.Left != nil { return fmt.Sprintf("%v(%v)", Oconv(int(n.Op), obj.FmtSharp), n.Left) } if n.Isddd { return fmt.Sprintf("%v(%v...)", Oconv(int(n.Op), obj.FmtSharp), Hconv(n.List, obj.FmtComma)) } return fmt.Sprintf("%v(%v)", Oconv(int(n.Op), obj.FmtSharp), Hconv(n.List, obj.FmtComma)) case OCALL, OCALLFUNC, OCALLINTER, OCALLMETH, OGETG: var f string f += exprfmt(n.Left, nprec) if n.Isddd { f += fmt.Sprintf("(%v...)", Hconv(n.List, obj.FmtComma)) return f } f += fmt.Sprintf("(%v)", Hconv(n.List, obj.FmtComma)) return f case OMAKEMAP, OMAKECHAN, OMAKESLICE: if n.List != nil { // pre-typecheck return fmt.Sprintf("make(%v, %v)", n.Type, Hconv(n.List, obj.FmtComma)) } if n.Right != nil { return fmt.Sprintf("make(%v, %v, %v)", n.Type, n.Left, n.Right) } if n.Left != nil && (n.Op == OMAKESLICE || !isideal(n.Left.Type)) { return fmt.Sprintf("make(%v, %v)", n.Type, n.Left) } return fmt.Sprintf("make(%v)", n.Type) // Unary case OPLUS, OMINUS, OADDR, OCOM, OIND, ONOT, ORECV: var f string if n.Left.Op == n.Op { f += fmt.Sprintf("%v ", Oconv(int(n.Op), obj.FmtSharp)) } else { f += Oconv(int(n.Op), obj.FmtSharp) } f += exprfmt(n.Left, nprec+1) return f // Binary case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT, OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, OSUB, OXOR: var f string f += exprfmt(n.Left, nprec) f += fmt.Sprintf(" %v ", Oconv(int(n.Op), obj.FmtSharp)) f += exprfmt(n.Right, nprec+1) return f case OADDSTR: var f string for l := n.List; l != nil; l = l.Next { if l != n.List { f += " + " } f += exprfmt(l.N, nprec) } return f case OCMPSTR, OCMPIFACE: var f string f += exprfmt(n.Left, nprec) f += fmt.Sprintf(" %v ", Oconv(int(n.Etype), obj.FmtSharp)) f += exprfmt(n.Right, nprec+1) return f } return fmt.Sprintf("<node %v>", Oconv(int(n.Op), 0)) }