From cca4ddb497a2d56654b38991566e45be1ef18f4d Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Tue, 5 Apr 2016 12:48:49 -0700 Subject: cmd/compile: add comments explaining how declarations/scopes work Change-Id: I301760b015eb69ff12eee53473fdbf5e9f168413 Reviewed-on: https://go-review.googlesource.com/21542 Reviewed-by: Brad Fitzpatrick Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/gc/dcl.go | 43 +++++++++++++++++++++++++++----------- src/cmd/compile/internal/gc/go.go | 10 +++++++-- 2 files changed, 39 insertions(+), 14 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index bd5a1f6f07..8553e2f1e8 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -11,6 +11,8 @@ import ( "strings" ) +// Declaration stack & operations + func dflag() bool { if Debug['d'] == 0 { return false @@ -24,8 +26,21 @@ func dflag() bool { return true } -// declaration stack & operations -func dcopy(a *Sym, b *Sym) { +// dclstack maintains a stack of shadowed symbol declarations so that +// popdcl can restore their declarations when a block scope ends. +// The stack is maintained as a linked list, using Sym's Link field. +// +// In practice, the "stack" actually ends up forming a tree: goto and label +// statements record the current state of dclstack so that checkgoto can +// validate that a goto statement does not jump over any declarations or +// into a new block scope. +// +// Finally, the Syms in this list are not "real" Syms as they don't actually +// represent object names. Sym is just a convenient type for saving shadowed +// Sym definitions, and only a subset of its fields are actually used. +var dclstack *Sym + +func dcopy(a, b *Sym) { a.Pkg = b.Pkg a.Name = b.Name a.Def = b.Def @@ -41,6 +56,8 @@ func push() *Sym { return d } +// pushdcl pushes the current declaration for symbol s (if any) so that +// it can be shadowed by a new declaration within a nested block scope. func pushdcl(s *Sym) *Sym { d := push() dcopy(d, s) @@ -50,6 +67,8 @@ func pushdcl(s *Sym) *Sym { return d } +// popdcl pops the innermost block scope and restores all symbol declarations +// to their previous state. func popdcl() { d := dclstack for ; d != nil && d.Name != ""; d = d.Link { @@ -70,6 +89,7 @@ func popdcl() { block = d.Block } +// markdcl records the start of a new block scope for declarations. func markdcl() { d := push() d.Name = "" // used as a mark in fifo @@ -104,6 +124,7 @@ func testdclstack() { } } +// redeclare emits a diagnostic about symbol s being redeclared somewhere. func redeclare(s *Sym, where string) { if s.Lastlineno == 0 { var tmp string @@ -137,6 +158,8 @@ var vargen int var declare_typegen int +// declare records that Node n declares symbol n.Sym in the specified +// declaration context. func declare(n *Node, ctxt Class) { if ctxt == PDISCARD { return @@ -318,8 +341,7 @@ func constiter(vl []*Node, t *Node, cl []*Node) []*Node { return vv } -// this generates a new name node, -// typically for labels or other one-off names. +// newname returns a new ONAME Node associated with symbol s. func newname(s *Sym) *Node { if s == nil { Fatalf("newname nil") @@ -364,17 +386,14 @@ func typenod(t *Type) *Node { return t.Nod } -// this will return an old name -// that has already been pushed on the -// declaration list. a diagnostic is -// generated if no name has been defined. +// oldname returns the Node that declares symbol s in the current scope. +// If no such Node currently exists, an ONONAME Node is returned instead. func oldname(s *Sym) *Node { n := s.Def if n == nil { - // maybe a top-level name will come along - // to give this a definition later. - // walkdef will check s->def again once - // all the input source has been processed. + // Maybe a top-level declaration will come along later to + // define s. resolve will check s.Def again once all input + // source has been processed. n = newname(s) n.Op = ONONAME n.Name.Iota = iota_ // save current iota value in const declarations diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index fdea1f2fba..f4b3dc9326 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -66,6 +66,14 @@ type Pkg struct { Syms map[string]*Sym } +// Sym represents an object name. Most commonly, this is a Go identifier naming +// an object declared within a package, but Syms are also used to name internal +// synthesized objects. +// +// As a special exception, field and method names that are exported use the Sym +// associated with localpkg instead of the package that declared them. This +// allows using Sym pointer equality to test for Go identifier uniqueness when +// handling selector expressions. type Sym struct { Flags SymFlags Link *Sym @@ -111,8 +119,6 @@ const ( SymAlgGen ) -var dclstack *Sym - // Ctype describes the constant kind of an "ideal" (untyped) constant. type Ctype int8 -- cgit v1.3 From 5ba797bd189b460854a0aea877381abcaef8105b Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Tue, 5 Apr 2016 14:20:04 -0700 Subject: cmd/compile: move a lot of declarations outside of go.go go.go is currently a grab bag of various unrelated type and variable declarations. Move a bunch of them into other more relevant source files. There are still more that can be moved, but these were the low hanging fruit with obvious homes. No code/comment changes. Just shuffling stuff around. Change-Id: I43dbe1a5b8b707709c1a3a034c693d38b8465063 Reviewed-on: https://go-review.googlesource.com/21561 Run-TryBot: Matthew Dempsky Reviewed-by: Brad Fitzpatrick --- src/cmd/compile/internal/gc/const.go | 53 ++++++++++++ src/cmd/compile/internal/gc/dcl.go | 6 ++ src/cmd/compile/internal/gc/go.go | 137 ------------------------------- src/cmd/compile/internal/gc/lex.go | 12 +++ src/cmd/compile/internal/gc/popt.go | 28 +++++++ src/cmd/compile/internal/gc/reflect.go | 10 +++ src/cmd/compile/internal/gc/type.go | 14 ++++ src/cmd/compile/internal/gc/typecheck.go | 14 ++++ 8 files changed, 137 insertions(+), 137 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/const.go b/src/cmd/compile/internal/gc/const.go index 5c9a67c8b5..c7fb4d97e5 100644 --- a/src/cmd/compile/internal/gc/const.go +++ b/src/cmd/compile/internal/gc/const.go @@ -10,6 +10,59 @@ import ( "strings" ) +// Ctype describes the constant kind of an "ideal" (untyped) constant. +type Ctype int8 + +const ( + CTxxx Ctype = iota + + CTINT + CTRUNE + CTFLT + CTCPLX + CTSTR + CTBOOL + CTNIL +) + +type Val struct { + // U contains one of: + // bool bool when n.ValCtype() == CTBOOL + // *Mpint int when n.ValCtype() == CTINT, rune when n.ValCtype() == CTRUNE + // *Mpflt float when n.ValCtype() == CTFLT + // *Mpcplx pair of floats when n.ValCtype() == CTCPLX + // string string when n.ValCtype() == CTSTR + // *Nilval when n.ValCtype() == CTNIL + U interface{} +} + +func (v Val) Ctype() Ctype { + switch x := v.U.(type) { + default: + Fatalf("unexpected Ctype for %T", v.U) + panic("not reached") + case nil: + return 0 + case *NilVal: + return CTNIL + case bool: + return CTBOOL + case *Mpint: + if x.Rune { + return CTRUNE + } + return CTINT + case *Mpflt: + return CTFLT + case *Mpcplx: + return CTCPLX + case string: + return CTSTR + } +} + +type NilVal struct{} + // IntLiteral returns the Node's literal value as an integer. func (n *Node) IntLiteral() (x int64, ok bool) { switch { diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index 8553e2f1e8..fb81545a46 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -26,6 +26,12 @@ func dflag() bool { return true } +var externdcl []*Node + +var blockgen int32 // max block number + +var block int32 // current block number + // dclstack maintains a stack of shadowed symbol declarations so that // popdcl can restore their declarations when a block scope ends. // The stack is maintained as a linked list, using Sym's Link field. diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index f4b3dc9326..4cb985b1be 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -5,7 +5,6 @@ package gc import ( - "bytes" "cmd/compile/internal/ssa" "cmd/internal/obj" ) @@ -16,44 +15,6 @@ const ( MaxStackVarSize = 10 * 1024 * 1024 ) -type Val struct { - // U contains one of: - // bool bool when n.ValCtype() == CTBOOL - // *Mpint int when n.ValCtype() == CTINT, rune when n.ValCtype() == CTRUNE - // *Mpflt float when n.ValCtype() == CTFLT - // *Mpcplx pair of floats when n.ValCtype() == CTCPLX - // string string when n.ValCtype() == CTSTR - // *Nilval when n.ValCtype() == CTNIL - U interface{} -} - -type NilVal struct{} - -func (v Val) Ctype() Ctype { - switch x := v.U.(type) { - default: - Fatalf("unexpected Ctype for %T", v.U) - panic("not reached") - case nil: - return 0 - case *NilVal: - return CTNIL - case bool: - return CTBOOL - case *Mpint: - if x.Rune { - return CTRUNE - } - return CTINT - case *Mpflt: - return CTFLT - case *Mpcplx: - return CTCPLX - case string: - return CTSTR - } -} - type Pkg struct { Name string // package name, e.g. "sys" Path string // string literal used in import statement, e.g. "runtime/internal/sys" @@ -119,35 +80,6 @@ const ( SymAlgGen ) -// Ctype describes the constant kind of an "ideal" (untyped) constant. -type Ctype int8 - -const ( - CTxxx Ctype = iota - - CTINT - CTRUNE - CTFLT - CTCPLX - CTSTR - CTBOOL - CTNIL -) - -// ChanDir is whether a channel can send, receive, or both. -type ChanDir uint8 - -func (c ChanDir) CanRecv() bool { return c&Crecv != 0 } -func (c ChanDir) CanSend() bool { return c&Csend != 0 } - -const ( - // types of channel - // must match ../../../../reflect/type.go:/ChanDir - Crecv ChanDir = 1 << 0 - Csend ChanDir = 1 << 1 - Cboth ChanDir = Crecv | Csend -) - // The Class of a variable/function describes the "storage class" // of a variable or function. During parsing, storage classes are // called declaration contexts. @@ -167,30 +99,6 @@ const ( PHEAP = 1 << 7 // an extra bit to identify an escaped variable ) -const ( - Etop = 1 << 1 // evaluated at statement level - Erv = 1 << 2 // evaluated in value context - Etype = 1 << 3 - Ecall = 1 << 4 // call-only expressions are ok - Efnstruct = 1 << 5 // multivalue function returns are ok - Eiota = 1 << 6 // iota is ok - Easgn = 1 << 7 // assigning to expression - Eindir = 1 << 8 // indirecting through expression - Eaddr = 1 << 9 // taking address of expression - Eproc = 1 << 10 // inside a go statement - Ecomplit = 1 << 11 // type in composite literal -) - -type Sig struct { - name string - pkg *Pkg - isym *Sym - tsym *Sym - type_ *Type - mtype *Type - offset int32 -} - // note this is the runtime representation // of the compilers arrays. // @@ -218,13 +126,6 @@ var sizeof_Array int // runtime sizeof(Array) // } String; var sizeof_String int // runtime sizeof(String) -// lexlineno is the line number _after_ the most recently read rune. -// In particular, it's advanced (or rewound) as newlines are read (or unread). -var lexlineno int32 - -// lineno is the line number at the start of the most recently lexed token. -var lineno int32 - var pragcgobuf string var infile string @@ -245,10 +146,6 @@ var safemode int var nolocalimports int -var lexbuf bytes.Buffer -var strbuf bytes.Buffer -var litbuf string // LLITERAL value for use in syntax error messages - var Debug [256]int var debugstr string @@ -324,8 +221,6 @@ var maxfltval [NTYPE]*Mpflt var xtop []*Node -var externdcl []*Node - var exportlist []*Node var importlist []*Node // imported functions and methods with inlinable bodies @@ -350,10 +245,6 @@ var Stksize int64 // stack size for current frame var stkptrsize int64 // prefix of stack containing pointers -var blockgen int32 // max block number - -var block int32 // current block number - var hasdefer bool // flag that curfn has defer statement var Curfn *Node @@ -410,34 +301,6 @@ var nodfp *Node var Disable_checknil int -type Flow struct { - Prog *obj.Prog // actual instruction - P1 *Flow // predecessors of this instruction: p1, - P2 *Flow // and then p2 linked though p2link. - P2link *Flow - S1 *Flow // successors of this instruction (at most two: s1 and s2). - S2 *Flow - Link *Flow // next instruction in function code - - Active int32 // usable by client - - Id int32 // sequence number in flow graph - Rpo int32 // reverse post ordering - Loop uint16 // x5 for every loop - Refset bool // diagnostic generated - - Data interface{} // for use by client -} - -type Graph struct { - Start *Flow - Num int - - // After calling flowrpo, rpo lists the flow nodes in reverse postorder, - // and each non-dead Flow node f has g->rpo[f->rpo] == f. - Rpo []*Flow -} - // interface to back end const ( diff --git a/src/cmd/compile/internal/gc/lex.go b/src/cmd/compile/internal/gc/lex.go index 6f1331ca89..2dbbd9276b 100644 --- a/src/cmd/compile/internal/gc/lex.go +++ b/src/cmd/compile/internal/gc/lex.go @@ -6,6 +6,7 @@ package gc import ( "bufio" + "bytes" "cmd/internal/obj" "fmt" "io" @@ -20,6 +21,17 @@ const ( BOM = 0xFEFF ) +// lexlineno is the line number _after_ the most recently read rune. +// In particular, it's advanced (or rewound) as newlines are read (or unread). +var lexlineno int32 + +// lineno is the line number at the start of the most recently lexed token. +var lineno int32 + +var lexbuf bytes.Buffer +var strbuf bytes.Buffer +var litbuf string // LLITERAL value for use in syntax error messages + func isSpace(c rune) bool { return c == ' ' || c == '\t' || c == '\n' || c == '\r' } diff --git a/src/cmd/compile/internal/gc/popt.go b/src/cmd/compile/internal/gc/popt.go index 41f8ba9fcc..001a715f7b 100644 --- a/src/cmd/compile/internal/gc/popt.go +++ b/src/cmd/compile/internal/gc/popt.go @@ -235,6 +235,34 @@ func fixjmp(firstp *obj.Prog) { // for every f.Data field, for use by the client. // If newData is nil, f.Data will be nil. +type Graph struct { + Start *Flow + Num int + + // After calling flowrpo, rpo lists the flow nodes in reverse postorder, + // and each non-dead Flow node f has g->rpo[f->rpo] == f. + Rpo []*Flow +} + +type Flow struct { + Prog *obj.Prog // actual instruction + P1 *Flow // predecessors of this instruction: p1, + P2 *Flow // and then p2 linked though p2link. + P2link *Flow + S1 *Flow // successors of this instruction (at most two: s1 and s2). + S2 *Flow + Link *Flow // next instruction in function code + + Active int32 // usable by client + + Id int32 // sequence number in flow graph + Rpo int32 // reverse post ordering + Loop uint16 // x5 for every loop + Refset bool // diagnostic generated + + Data interface{} // for use by client +} + var flowmark int // MaxFlowProg is the maximum size program (counted in instructions) diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go index 11bcd4cdc6..c069b35787 100644 --- a/src/cmd/compile/internal/gc/reflect.go +++ b/src/cmd/compile/internal/gc/reflect.go @@ -22,6 +22,16 @@ type itabEntry struct { var signatlist []*Node var itabs []itabEntry +type Sig struct { + name string + pkg *Pkg + isym *Sym + tsym *Sym + type_ *Type + mtype *Type + offset int32 +} + // byMethodNameAndPackagePath sorts method signatures by name, then package path. type byMethodNameAndPackagePath []*Sig diff --git a/src/cmd/compile/internal/gc/type.go b/src/cmd/compile/internal/gc/type.go index b89c5dbf22..05e30df271 100644 --- a/src/cmd/compile/internal/gc/type.go +++ b/src/cmd/compile/internal/gc/type.go @@ -75,6 +75,20 @@ const ( dddBound = -100 // arrays declared as [...]T start life with Bound=dddBound ) +// ChanDir is whether a channel can send, receive, or both. +type ChanDir uint8 + +func (c ChanDir) CanRecv() bool { return c&Crecv != 0 } +func (c ChanDir) CanSend() bool { return c&Csend != 0 } + +const ( + // types of channel + // must match ../../../../reflect/type.go:/ChanDir + Crecv ChanDir = 1 << 0 + Csend ChanDir = 1 << 1 + Cboth ChanDir = Crecv | Csend +) + // Types stores pointers to predeclared named types. // // It also stores pointers to several special types: diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index 688936e926..d21552d180 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -11,6 +11,20 @@ import ( "strings" ) +const ( + Etop = 1 << 1 // evaluated at statement level + Erv = 1 << 2 // evaluated in value context + Etype = 1 << 3 + Ecall = 1 << 4 // call-only expressions are ok + Efnstruct = 1 << 5 // multivalue function returns are ok + Eiota = 1 << 6 // iota is ok + Easgn = 1 << 7 // assigning to expression + Eindir = 1 << 8 // indirecting through expression + Eaddr = 1 << 9 // taking address of expression + Eproc = 1 << 10 // inside a go statement + Ecomplit = 1 << 11 // type in composite literal +) + // type check the whole tree of an expression. // calculates expression types. // evaluates compile time constants. -- cgit v1.3 From fda831ed3f904c659fe41f253f75fe76528a28ee Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 5 Apr 2016 16:44:07 -0700 Subject: cmd/compile: encapsulate reads of gc.Type.Funarg Changes generated with eg and then manually checked and in some cases simplified. Passes toolstash -cmp. Change-Id: I2119f37f003368ce1884d2863b406d6ffbfe38c7 Reviewed-on: https://go-review.googlesource.com/21563 Reviewed-by: Brad Fitzpatrick Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/align.go | 4 ++-- src/cmd/compile/internal/gc/bexport.go | 2 +- src/cmd/compile/internal/gc/esc.go | 2 +- src/cmd/compile/internal/gc/export.go | 2 +- src/cmd/compile/internal/gc/fmt.go | 2 +- src/cmd/compile/internal/gc/gsubr.go | 2 +- src/cmd/compile/internal/gc/order.go | 2 +- src/cmd/compile/internal/gc/type.go | 7 ++++++- src/cmd/compile/internal/gc/typecheck.go | 8 ++++---- src/cmd/compile/internal/gc/walk.go | 2 +- 10 files changed, 19 insertions(+), 14 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/align.go b/src/cmd/compile/internal/gc/align.go index b7ed9f19b9..9d5c3a550c 100644 --- a/src/cmd/compile/internal/gc/align.go +++ b/src/cmd/compile/internal/gc/align.go @@ -263,7 +263,7 @@ func dowidth(t *Type) { } case TSTRUCT: - if t.Funarg { + if t.IsFuncArgStruct() { Fatalf("dowidth fn struct %v", t) } w = widstruct(t, t, 0, 1) @@ -335,7 +335,7 @@ func checkwidth(t *Type) { // function arg structs should not be checked // outside of the enclosing function. - if t.Funarg { + if t.IsFuncArgStruct() { Fatalf("checkwidth %v", t) } diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index 8968ce8924..f88afd2488 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -742,7 +742,7 @@ func basetypeName(t *Type) string { } func (p *exporter) paramList(params *Type, numbered bool) { - if !params.IsStruct() || !params.Funarg { + if !params.IsFuncArgStruct() { Fatalf("exporter: parameter list expected") } diff --git a/src/cmd/compile/internal/gc/esc.go b/src/cmd/compile/internal/gc/esc.go index 9b8f134178..d7a63668a6 100644 --- a/src/cmd/compile/internal/gc/esc.go +++ b/src/cmd/compile/internal/gc/esc.go @@ -1435,7 +1435,7 @@ func esccall(e *EscState, n *Node, up *Node) { ll := n.List if n.List.Len() == 1 { a := n.List.First() - if a.Type.IsStruct() && a.Type.Funarg { // f(g()). + if a.Type.IsFuncArgStruct() { // f(g()) ll = e.nodeEscState(a).Escretval } } diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index 9fc6e56275..6de7da0667 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -592,7 +592,7 @@ func dumpasmhdr() { case OTYPE: t := n.Type - if !t.IsStruct() || t.Map != nil || t.Funarg { + if !t.IsStruct() || t.Map != nil || t.IsFuncArgStruct() { break } fmt.Fprintf(b, "#define %s__size %d\n", t.Sym.Name, int(t.Width)) diff --git a/src/cmd/compile/internal/gc/fmt.go b/src/cmd/compile/internal/gc/fmt.go index ab9bad3c2a..27ccdfbdcf 100644 --- a/src/cmd/compile/internal/gc/fmt.go +++ b/src/cmd/compile/internal/gc/fmt.go @@ -690,7 +690,7 @@ func typefmt(t *Type, flag FmtFlag) string { } var buf bytes.Buffer - if t.Funarg { + if t.IsFuncArgStruct() { buf.WriteString("(") var flag1 FmtFlag if fmtmode == FTypeId || fmtmode == FErr { // no argument names on function signature, and no "noescape"/"nosplit" tags diff --git a/src/cmd/compile/internal/gc/gsubr.go b/src/cmd/compile/internal/gc/gsubr.go index 353d90f593..a2fa5f8b31 100644 --- a/src/cmd/compile/internal/gc/gsubr.go +++ b/src/cmd/compile/internal/gc/gsubr.go @@ -541,7 +541,7 @@ func nodarg(t interface{}, fp int) *Node { switch t := t.(type) { case *Type: // entire argument struct, not just one arg - if !t.IsStruct() || !t.Funarg { + if !t.IsFuncArgStruct() { Fatalf("nodarg: bad type %v", t) } n = Nod(ONAME, nil, nil) diff --git a/src/cmd/compile/internal/gc/order.go b/src/cmd/compile/internal/gc/order.go index 8410a236cd..3b83e3bcc0 100644 --- a/src/cmd/compile/internal/gc/order.go +++ b/src/cmd/compile/internal/gc/order.go @@ -324,7 +324,7 @@ func ismulticall(l Nodes) bool { // Copyret emits t1, t2, ... = n, where n is a function call, // and then returns the list t1, t2, .... func copyret(n *Node, order *Order) []*Node { - if !n.Type.IsStruct() || !n.Type.Funarg { + if !n.Type.IsFuncArgStruct() { Fatalf("copyret %v %d", n.Type, n.Left.Type.Results().NumFields()) } diff --git a/src/cmd/compile/internal/gc/type.go b/src/cmd/compile/internal/gc/type.go index 05e30df271..e04cfcda63 100644 --- a/src/cmd/compile/internal/gc/type.go +++ b/src/cmd/compile/internal/gc/type.go @@ -127,7 +127,7 @@ type Type struct { Chan ChanDir Trecur uint8 // to detect loops Printed bool - Funarg bool // on TSTRUCT and TFIELD + Funarg bool // TSTRUCT only: whether this struct represents function parameters Local bool // created in this file Deferwidth bool Broke bool // broken type definition. @@ -566,6 +566,11 @@ func (t *Type) SetNname(n *Node) { t.nname = n } +// IsFuncArgStruct reports whether t is a struct representing function parameters. +func (t *Type) IsFuncArgStruct() bool { + return t.Etype == TSTRUCT && t.Funarg +} + func (t *Type) Methods() *Fields { // TODO(mdempsky): Validate t? return &t.methods diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index d21552d180..db74a0d246 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -1607,7 +1607,7 @@ OpSwitch: // Unpack multiple-return result before type-checking. var funarg *Type - if t.IsStruct() && t.Funarg { + if t.IsFuncArgStruct() { funarg = t t = t.Field(0).Type } @@ -2159,7 +2159,7 @@ OpSwitch: } t := n.Type - if t != nil && !t.Funarg && n.Op != OTYPE { + if t != nil && !t.IsFuncArgStruct() && n.Op != OTYPE { switch t.Etype { case TFUNC, // might have TANY; wait until its called TANY, @@ -2611,7 +2611,7 @@ func typecheckaste(op Op, call *Node, isddd bool, tstruct *Type, nl Nodes, desc if nl.Len() == 1 { n = nl.First() if n.Type != nil { - if n.Type.IsStruct() && n.Type.Funarg { + if n.Type.IsFuncArgStruct() { if !hasddd(tstruct) { n1 := tstruct.NumFields() n2 := n.Type.NumFields() @@ -3359,7 +3359,7 @@ func typecheckas2(n *Node) { } switch r.Op { case OCALLMETH, OCALLINTER, OCALLFUNC: - if !r.Type.IsStruct() || !r.Type.Funarg { + if !r.Type.IsFuncArgStruct() { break } cr = r.Type.NumFields() diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index b7edae5af4..392dae0fa9 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -1783,7 +1783,7 @@ func ascompatte(op Op, call *Node, isddd bool, nl *Type, lr []*Node, fp int, ini var nn []*Node // f(g()) where g has multiple return values - if r != nil && len(lr) <= 1 && r.Type.IsStruct() && r.Type.Funarg { + if r != nil && len(lr) <= 1 && r.Type.IsFuncArgStruct() { // optimization - can do block copy if eqtypenoname(r.Type, nl) { arg := nodarg(nl, fp) -- cgit v1.3 From 309144b7f1090cbc7c3a90eb252d20a939caf398 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Fri, 1 Apr 2016 11:05:30 -0700 Subject: cmd/compile: fix x=x assignments No point in doing anything for x=x assignments. In addition, skipping these assignments prevents generating: VARDEF x COPY x -> x which is bad because x is incorrectly considered dead before the vardef. Fixes #14904 Change-Id: I6817055ec20bcc34a9648617e0439505ee355f82 Reviewed-on: https://go-review.googlesource.com/21470 Reviewed-by: Brad Fitzpatrick Reviewed-by: Dave Cheney --- src/cmd/compile/internal/gc/ssa.go | 11 +++ src/cmd/compile/internal/gc/ssa_test.go | 2 + .../compile/internal/gc/testdata/namedReturn.go | 101 +++++++++++++++++++++ test/live_ssa.go | 13 ++- 4 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 src/cmd/compile/internal/gc/testdata/namedReturn.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 359f4b22a2..1c2e528384 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -661,6 +661,17 @@ func (s *state) stmt(n *Node) { return } + if n.Left == n.Right && n.Left.Op == ONAME { + // An x=x assignment. No point in doing anything + // here. In addition, skipping this assignment + // prevents generating: + // VARDEF x + // COPY x -> x + // which is bad because x is incorrectly considered + // dead before the vardef. See issue #14904. + return + } + var t *Type if n.Right != nil { t = n.Right.Type diff --git a/src/cmd/compile/internal/gc/ssa_test.go b/src/cmd/compile/internal/gc/ssa_test.go index 59a240237b..0fb0f17778 100644 --- a/src/cmd/compile/internal/gc/ssa_test.go +++ b/src/cmd/compile/internal/gc/ssa_test.go @@ -99,3 +99,5 @@ func TestUnsafe(t *testing.T) { runTest(t, "unsafe_ssa.go") } func TestPhi(t *testing.T) { runTest(t, "phi_ssa.go") } func TestSlice(t *testing.T) { runTest(t, "slice.go") } + +func TestNamedReturn(t *testing.T) { runTest(t, "namedReturn.go") } diff --git a/src/cmd/compile/internal/gc/testdata/namedReturn.go b/src/cmd/compile/internal/gc/testdata/namedReturn.go new file mode 100644 index 0000000000..dafb5d719f --- /dev/null +++ b/src/cmd/compile/internal/gc/testdata/namedReturn.go @@ -0,0 +1,101 @@ +// run + +// This test makes sure that naming named +// return variables in a return statement works. +// See issue #14904. + +package main + +import ( + "fmt" + "runtime" +) + +// Our heap-allocated object that will be GC'd incorrectly. +// Note that we always check the second word because that's +// where 0xdeaddeaddeaddead is written. +type B [4]int + +// small (SSAable) array +type T1 [3]*B + +//go:noinline +func f1() (t T1) { + t[0] = &B{91, 92, 93, 94} + runtime.GC() + return t +} + +// large (non-SSAable) array +type T2 [8]*B + +//go:noinline +func f2() (t T2) { + t[0] = &B{91, 92, 93, 94} + runtime.GC() + return t +} + +// small (SSAable) struct +type T3 struct { + a, b, c *B +} + +//go:noinline +func f3() (t T3) { + t.a = &B{91, 92, 93, 94} + runtime.GC() + return t +} + +// large (non-SSAable) struct +type T4 struct { + a, b, c, d, e, f *B +} + +//go:noinline +func f4() (t T4) { + t.a = &B{91, 92, 93, 94} + runtime.GC() + return t +} + +var sink *B + +func f5() int { + b := &B{91, 92, 93, 94} + t := T4{b, nil, nil, nil, nil, nil} + sink = b // make sure b is heap allocated ... + sink = nil // ... but not live + runtime.GC() + t = t + return t.a[1] +} + +func main() { + failed := false + + if v := f1()[0][1]; v != 92 { + fmt.Printf("f1()[0][1]=%d, want 92\n", v) + failed = true + } + if v := f2()[0][1]; v != 92 { + fmt.Printf("f2()[0][1]=%d, want 92\n", v) + failed = true + } + if v := f3().a[1]; v != 92 { + fmt.Printf("f3().a[1]=%d, want 92\n", v) + failed = true + } + if v := f4().a[1]; v != 92 { + fmt.Printf("f4().a[1]=%d, want 92\n", v) + failed = true + } + if v := f5(); v != 92 { + fmt.Printf("f5()=%d, want 92\n", v) + failed = true + } + if failed { + panic("bad") + } +} diff --git a/test/live_ssa.go b/test/live_ssa.go index fe2541395f..fae0a2b82a 100644 --- a/test/live_ssa.go +++ b/test/live_ssa.go @@ -606,13 +606,12 @@ func f39a() (x []int) { return } -// TODO: Reenable after #14904 is fixed. -//func f39b() (x [10]*int) { -// x = [10]*int{} -// x[0] = new(int) // E.R.R.O.R. "live at call to newobject: x$" -// printnl() // E.R.R.O.R. "live at call to printnl: x$" -// return x -//} +func f39b() (x [10]*int) { + x = [10]*int{} + x[0] = new(int) // ERROR "live at call to newobject: x$" + printnl() // ERROR "live at call to printnl: x$" + return x +} func f39c() (x [10]*int) { x = [10]*int{} -- cgit v1.3 From 0d375381963d1236b1f70546386ffe92dddb59fc Mon Sep 17 00:00:00 2001 From: Konstantin Shaposhnikov Date: Tue, 5 Apr 2016 23:54:50 +0800 Subject: cmd/vet: do not treat declaration as asignment in atomic check Fixes #15118 Change-Id: Iad56ed412535c8ac0a01c4bd7769fd3d37688ac9 Reviewed-on: https://go-review.googlesource.com/21526 Run-TryBot: Rob Pike Reviewed-by: Rob Pike --- src/cmd/vet/atomic.go | 3 +++ src/cmd/vet/testdata/atomic.go | 9 +++++++++ 2 files changed, 12 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/vet/atomic.go b/src/cmd/vet/atomic.go index c084f13ab3..b2ca2d80f3 100644 --- a/src/cmd/vet/atomic.go +++ b/src/cmd/vet/atomic.go @@ -23,6 +23,9 @@ func checkAtomicAssignment(f *File, node ast.Node) { if len(n.Lhs) != len(n.Rhs) { return } + if len(n.Lhs) == 1 && n.Tok == token.DEFINE { + return + } for i, right := range n.Rhs { call, ok := right.(*ast.CallExpr) diff --git a/src/cmd/vet/testdata/atomic.go b/src/cmd/vet/testdata/atomic.go index 1ba261d941..d5a8e61184 100644 --- a/src/cmd/vet/testdata/atomic.go +++ b/src/cmd/vet/testdata/atomic.go @@ -40,4 +40,13 @@ func AtomicTests() { *ap[1] = atomic.AddUint64(ap[0], 1) x = atomic.AddUint64() // Used to make vet crash; now silently ignored. + + { + // A variable declaration creates a new variable in the current scope. + x := atomic.AddUint64(&x, 1) // ERROR "declaration of .x. shadows declaration at testdata/atomic.go:16" + + // Re-declaration assigns a new value. + x, w := atomic.AddUint64(&x, 1), 10 // ERROR "direct assignment to atomic value" + _ = w + } } -- cgit v1.3 From ec3c5b9d178ee373a41e46f6074c8729cfd11084 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Tue, 5 Apr 2016 23:01:10 -0700 Subject: cmd/link: eliminate a bunch of open coded elf64/rela switches We already have variables to track whether the target platform is 64-bit vs 32-bit or RELA vs REL, so no point in repeating the list of obscure architecture characters everywhere. Passes toolstash/buildall. Change-Id: I6a07f74188ac592ef229a7c65848a9ba93013cdb Reviewed-on: https://go-review.googlesource.com/21569 Run-TryBot: Matthew Dempsky Reviewed-by: Michael Hudson-Doyle Reviewed-by: Brad Fitzpatrick --- src/cmd/link/internal/ld/dwarf.go | 21 ++++++--------------- src/cmd/link/internal/ld/elf.go | 10 ++++------ src/cmd/link/internal/ld/symtab.go | 6 ++---- 3 files changed, 12 insertions(+), 25 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index fd177cfef0..4465a727a5 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -2169,18 +2169,10 @@ func dwarfaddshstrings(shstrtab *LSym) { elfstrdbg[ElfStrDebugStr] = Addstring(shstrtab, ".debug_str") elfstrdbg[ElfStrGDBScripts] = Addstring(shstrtab, ".debug_gdb_scripts") if Linkmode == LinkExternal { - switch Thearch.Thechar { - case '0', '6', '7', '9', 'z': - elfstrdbg[ElfStrRelDebugInfo] = Addstring(shstrtab, ".rela.debug_info") - elfstrdbg[ElfStrRelDebugAranges] = Addstring(shstrtab, ".rela.debug_aranges") - elfstrdbg[ElfStrRelDebugLine] = Addstring(shstrtab, ".rela.debug_line") - elfstrdbg[ElfStrRelDebugFrame] = Addstring(shstrtab, ".rela.debug_frame") - default: - elfstrdbg[ElfStrRelDebugInfo] = Addstring(shstrtab, ".rel.debug_info") - elfstrdbg[ElfStrRelDebugAranges] = Addstring(shstrtab, ".rel.debug_aranges") - elfstrdbg[ElfStrRelDebugLine] = Addstring(shstrtab, ".rel.debug_line") - elfstrdbg[ElfStrRelDebugFrame] = Addstring(shstrtab, ".rel.debug_frame") - } + elfstrdbg[ElfStrRelDebugInfo] = Addstring(shstrtab, elfRelType+".debug_info") + elfstrdbg[ElfStrRelDebugAranges] = Addstring(shstrtab, elfRelType+".debug_aranges") + elfstrdbg[ElfStrRelDebugLine] = Addstring(shstrtab, elfRelType+".debug_line") + elfstrdbg[ElfStrRelDebugFrame] = Addstring(shstrtab, elfRelType+".debug_frame") infosym = Linklookup(Ctxt, ".debug_info", 0) infosym.Attr |= AttrHidden @@ -2222,10 +2214,9 @@ func dwarfaddelfsectionsyms() { func dwarfaddelfrelocheader(elfstr int, shdata *ElfShdr, off int64, size int64) { sh := newElfShdr(elfstrdbg[elfstr]) - switch Thearch.Thechar { - case '0', '6', '7', '9', 'z': + if elfRelType == ".rela" { sh.type_ = SHT_RELA - default: + } else { sh.type_ = SHT_REL } diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index ffb7c4bdde..3b40c66592 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -1885,10 +1885,9 @@ func doelf() { s.Type = obj.SELFROSECT s.Attr |= AttrReachable - switch Thearch.Thechar { - case '0', '6', '7', '9', 'z': + if elf64 { s.Size += ELF64SYMSIZE - default: + } else { s.Size += ELF32SYMSIZE } @@ -1967,10 +1966,9 @@ func doelf() { elfwritedynentsym(s, DT_HASH, Linklookup(Ctxt, ".hash", 0)) elfwritedynentsym(s, DT_SYMTAB, Linklookup(Ctxt, ".dynsym", 0)) - switch Thearch.Thechar { - case '0', '6', '7', '9', 'z': + if elf64 { Elfwritedynent(s, DT_SYMENT, ELF64SYMSIZE) - default: + } else { Elfwritedynent(s, DT_SYMENT, ELF32SYMSIZE) } elfwritedynentsym(s, DT_STRTAB, Linklookup(Ctxt, ".dynstr", 0)) diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index 3258bc1ff9..0fe0a68c65 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -66,8 +66,7 @@ func putelfstr(s string) int { } func putelfsyment(off int, addr int64, size int64, info int, shndx int, other int) { - switch Thearch.Thechar { - case '0', '6', '7', '9', 'z': + if elf64 { Thearch.Lput(uint32(off)) Cput(uint8(info)) Cput(uint8(other)) @@ -75,8 +74,7 @@ func putelfsyment(off int, addr int64, size int64, info int, shndx int, other in Thearch.Vput(uint64(addr)) Thearch.Vput(uint64(size)) Symsize += ELF64SYMSIZE - - default: + } else { Thearch.Lput(uint32(off)) Thearch.Lput(uint32(addr)) Thearch.Lput(uint32(size)) -- cgit v1.3 From d7ddee78ddca805e6609149ff4320b1547698259 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Tue, 5 Apr 2016 18:42:07 +0300 Subject: icmd/vet: improved checking for variadic Println-like functions - Automatically determine the first argument to check. - Skip checking matching non-variadic functions. - Skip checking matching functions accepting non-interface{} variadic arguments. - Removed fragile 'magic' code for special cases such as math.Log and error interface. Fixes #15067 Fixes #15099 Change-Id: Ib313557f18b12b36daa493f4b02c598b9503b55b Reviewed-on: https://go-review.googlesource.com/21513 Run-TryBot: Rob Pike Reviewed-by: Rob Pike --- src/cmd/vet/doc.go | 9 +--- src/cmd/vet/print.go | 104 ++++++++++++++++++++---------------------- src/cmd/vet/testdata/print.go | 38 +++++++++++++-- src/cmd/vet/types.go | 66 --------------------------- 4 files changed, 86 insertions(+), 131 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/vet/doc.go b/src/cmd/vet/doc.go index d295fb4345..2b5e8fcb59 100644 --- a/src/cmd/vet/doc.go +++ b/src/cmd/vet/doc.go @@ -188,13 +188,8 @@ These flags configure the behavior of vet: -v Verbose mode -printfuncs - A comma-separated list of print-like functions to supplement the - standard list. Each entry is in the form Name:N where N is the - zero-based argument position of the first argument involved in the - print: either the format or the first print argument for non-formatted - prints. For example, if you have Warn and Warnf functions that - take an io.Writer as their first argument, like Fprintf, - -printfuncs=Warn:1,Warnf:1 + A comma-separated list of print-like function names + to supplement the standard list. For more information, see the discussion of the -printf flag. -shadowstrict Whether to be strict about shadowing; can be noisy. diff --git a/src/cmd/vet/print.go b/src/cmd/vet/print.go index 4e3252f2fb..07499e6ae6 100644 --- a/src/cmd/vet/print.go +++ b/src/cmd/vet/print.go @@ -35,20 +35,18 @@ func initPrintFlags() { if len(name) == 0 { flag.Usage() } - skip := 0 + + // Backwards compatibility: skip optional first argument + // index after the colon. if colon := strings.LastIndex(name, ":"); colon > 0 { - var err error - skip, err = strconv.Atoi(name[colon+1:]) - if err != nil { - errorf(`illegal format for "Func:N" argument %q; %s`, name, err) - } name = name[:colon] } + name = strings.ToLower(name) if name[len(name)-1] == 'f' { isFormattedPrint[name] = true } else { - printList[name] = skip + isPrint[name] = true } } } @@ -65,17 +63,20 @@ var isFormattedPrint = map[string]bool{ "sprintf": true, } -// printList records the unformatted-print functions. The value is the location -// of the first parameter to be printed. Names are lower-cased so the lookup is -// case insensitive. -var printList = map[string]int{ - "error": 0, - "fatal": 0, - "fprint": 1, "fprintln": 1, - "log": 0, - "panic": 0, "panicln": 0, - "print": 0, "println": 0, - "sprint": 0, "sprintln": 0, +// isPrint records the unformatted-print functions. Names are lower-cased +// so the lookup is case insensitive. +var isPrint = map[string]bool{ + "error": true, + "fatal": true, + "fprint": true, + "fprintln": true, + "log": true, + "panic": true, + "panicln": true, + "print": true, + "println": true, + "sprint": true, + "sprintln": true, } // formatString returns the format string argument and its index within @@ -171,8 +172,8 @@ func checkFmtPrintfCall(f *File, node ast.Node) { f.checkPrintf(call, Name) return } - if skip, ok := printList[name]; ok { - f.checkPrint(call, Name, skip) + if _, ok := isPrint[name]; ok { + f.checkPrint(call, Name) return } } @@ -583,25 +584,36 @@ func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, isStar bool, s } // checkPrint checks a call to an unformatted print routine such as Println. -// call.Args[firstArg] is the first argument to be printed. -func (f *File) checkPrint(call *ast.CallExpr, name string, firstArg int) { - isLn := strings.HasSuffix(name, "ln") - isF := strings.HasPrefix(name, "F") - args := call.Args - if name == "Log" && len(args) > 0 { - // Special case: Don't complain about math.Log or cmplx.Log. - // Not strictly necessary because the only complaint likely is for Log("%d") - // but it feels wrong to check that math.Log is a good print function. - if sel, ok := args[0].(*ast.SelectorExpr); ok { - if x, ok := sel.X.(*ast.Ident); ok { - if x.Name == "math" || x.Name == "cmplx" { - return - } +func (f *File) checkPrint(call *ast.CallExpr, name string) { + firstArg := 0 + typ := f.pkg.types[call.Fun].Type + if typ != nil { + if sig, ok := typ.(*types.Signature); ok { + if !sig.Variadic() { + // Skip checking non-variadic functions. + return + } + params := sig.Params() + firstArg = params.Len() - 1 + + typ := params.At(firstArg).Type() + typ = typ.(*types.Slice).Elem() + it, ok := typ.(*types.Interface) + if !ok || !it.Empty() { + // Skip variadic functions accepting non-interface{} args. + return } } } + args := call.Args + if len(args) <= firstArg { + // Skip calls without variadic args. + return + } + args = args[firstArg:] + // check for Println(os.Stderr, ...) - if firstArg == 0 && !isF && len(args) > 0 { + if firstArg == 0 { if sel, ok := args[0].(*ast.SelectorExpr); ok { if x, ok := sel.X.(*ast.Ident); ok { if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") { @@ -610,31 +622,15 @@ func (f *File) checkPrint(call *ast.CallExpr, name string, firstArg int) { } } } - if len(args) <= firstArg { - // If we have a call to a method called Error that satisfies the Error interface, - // then it's ok. Otherwise it's something like (*T).Error from the testing package - // and we need to check it. - if name == "Error" && f.isErrorMethodCall(call) { - return - } - // If it's an Error call now, it's probably for printing errors. - if !isLn { - // Check the signature to be sure: there are niladic functions called "error". - if firstArg != 0 || f.numArgsInSignature(call) != firstArg { - f.Badf(call.Pos(), "no args in %s call", name) - } - } - return - } - arg := args[firstArg] + arg := args[0] if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { if strings.Contains(lit.Value, "%") { f.Badf(call.Pos(), "possible formatting directive in %s call", name) } } - if isLn { + if strings.HasSuffix(name, "ln") { // The last item, if a string, should not have a newline. - arg = args[len(call.Args)-1] + arg = args[len(args)-1] if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { if strings.HasSuffix(lit.Value, `\n"`) { f.Badf(call.Pos(), "%s call ends with newline", name) diff --git a/src/cmd/vet/testdata/print.go b/src/cmd/vet/testdata/print.go index 5c7ff35c90..261ee788c7 100644 --- a/src/cmd/vet/testdata/print.go +++ b/src/cmd/vet/testdata/print.go @@ -185,11 +185,11 @@ func PrintfTests() { // Something that looks like an error interface but isn't, such as the (*T).Error method // in the testing package. var et1 errorTest1 - fmt.Println(et1.Error()) // ERROR "no args in Error call" + fmt.Println(et1.Error()) // ok fmt.Println(et1.Error("hi")) // ok fmt.Println(et1.Error("%d", 3)) // ERROR "possible formatting directive in Error call" var et2 errorTest2 - et2.Error() // ERROR "no args in Error call" + et2.Error() // ok et2.Error("hi") // ok, not an error method. et2.Error("%d", 3) // ERROR "possible formatting directive in Error call" var et3 errorTest3 @@ -231,11 +231,41 @@ func PrintfTests() { externalprintf.Logf(level, "%d", 42) // OK externalprintf.Errorf(level, level, "foo %q bar", "foobar") // OK externalprintf.Logf(level, "%d") // ERROR "format reads arg 1, have only 0 args" + + // user-defined Println-like functions + ss := &someStruct{} + ss.Log(someFunction, "foo") // OK + ss.Error(someFunction, someFunction) // OK + ss.Println() // OK + ss.Println(1.234, "foo") // OK + ss.Println(1, someFunction) // ERROR "arg someFunction in Println call is a function value, not a function call" + ss.log(someFunction) // OK + ss.log(someFunction, "bar", 1.33) // OK + ss.log(someFunction, someFunction) // ERROR "arg someFunction in log call is a function value, not a function call" } +type someStruct struct{} + +// Log is non-variadic user-define Println-like function. +// Calls to this func must be skipped when checking +// for Println-like arguments. +func (ss *someStruct) Log(f func(), s string) {} + +// Error is variadic user-define Println-like function. +// Calls to this func mustn't be checked for Println-like arguments, +// since variadic arguments type isn't interface{}. +func (ss *someStruct) Error(args ...func()) {} + +// Println is variadic user-defined Println-like function. +// Calls to this func must be checked for Println-like arguments. +func (ss *someStruct) Println(args ...interface{}) {} + +// log is variadic user-defined Println-like function. +// Calls to this func must be checked for Println-like arguments. +func (ss *someStruct) log(f func(), args ...interface{}) {} + // A function we use as a function value; it has no other purpose. -func someFunction() { -} +func someFunction() {} // Printf is used by the test so we must declare it. func Printf(format string, args ...interface{}) { diff --git a/src/cmd/vet/types.go b/src/cmd/vet/types.go index 692bae6192..4358955d93 100644 --- a/src/cmd/vet/types.go +++ b/src/cmd/vet/types.go @@ -292,72 +292,6 @@ func (f *File) matchStructArgType(t printfArgType, typ *types.Struct, arg ast.Ex return true } -// numArgsInSignature tells how many formal arguments the function type -// being called has. -func (f *File) numArgsInSignature(call *ast.CallExpr) int { - // Check the type of the function or method declaration - typ := f.pkg.types[call.Fun].Type - if typ == nil { - return 0 - } - // The type must be a signature, but be sure for safety. - sig, ok := typ.(*types.Signature) - if !ok { - return 0 - } - return sig.Params().Len() -} - -// isErrorMethodCall reports whether the call is of a method with signature -// func Error() string -// where "string" is the universe's string type. We know the method is called "Error". -func (f *File) isErrorMethodCall(call *ast.CallExpr) bool { - typ := f.pkg.types[call].Type - if typ != nil { - // We know it's called "Error", so just check the function signature - // (stringerType has exactly one method, String). - if stringerType != nil && stringerType.NumMethods() == 1 { - return types.Identical(f.pkg.types[call.Fun].Type, stringerType.Method(0).Type()) - } - } - // Without types, we can still check by hand. - // Is it a selector expression? Otherwise it's a function call, not a method call. - sel, ok := call.Fun.(*ast.SelectorExpr) - if !ok { - return false - } - // The package is type-checked, so if there are no arguments, we're done. - if len(call.Args) > 0 { - return false - } - // Check the type of the method declaration - typ = f.pkg.types[sel].Type - if typ == nil { - return false - } - // The type must be a signature, but be sure for safety. - sig, ok := typ.(*types.Signature) - if !ok { - return false - } - // There must be a receiver for it to be a method call. Otherwise it is - // a function, not something that satisfies the error interface. - if sig.Recv() == nil { - return false - } - // There must be no arguments. Already verified by type checking, but be thorough. - if sig.Params().Len() > 0 { - return false - } - // Finally the real questions. - // There must be one result. - if sig.Results().Len() != 1 { - return false - } - // It must have return type "string" from the universe. - return sig.Results().At(0).Type() == types.Typ[types.String] -} - // hasMethod reports whether the type contains a method with the given name. // It is part of the workaround for Formatters and should be deleted when // that workaround is no longer necessary. -- cgit v1.3 From ff7ba773f43bf04c34ffb6ed67da464072e476f7 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 6 Apr 2016 10:49:12 -0700 Subject: cmd/gofmt: make gofmt -s simplify slices in presence of dot-imports A dot-import cannot possibly introduce a `len` function since that function would not be exported (it's lowercase). Furthermore, the existing code already (incorrectly) assumed that there was no other `len` function in another file of the package. Since this has been an ok assumption for years, let's leave it, but remove the dot-import restriction. Fixes #15153. Change-Id: I18fbb27acc5a5668833b4b4aead0cca540862b52 Reviewed-on: https://go-review.googlesource.com/21613 Reviewed-by: Alan Donovan Run-TryBot: Robert Griesemer TryBot-Result: Gobot Gobot --- src/cmd/gofmt/simplify.go | 28 ++++++---------- src/cmd/gofmt/testdata/slices2.golden | 63 ----------------------------------- src/cmd/gofmt/testdata/slices2.input | 63 ----------------------------------- 3 files changed, 10 insertions(+), 144 deletions(-) delete mode 100644 src/cmd/gofmt/testdata/slices2.golden delete mode 100644 src/cmd/gofmt/testdata/slices2.input (limited to 'src/cmd') diff --git a/src/cmd/gofmt/simplify.go b/src/cmd/gofmt/simplify.go index 69f7bf23c0..2ebf4cde0b 100644 --- a/src/cmd/gofmt/simplify.go +++ b/src/cmd/gofmt/simplify.go @@ -10,11 +10,9 @@ import ( "reflect" ) -type simplifier struct { - hasDotImport bool // package file contains: import . "some/import/path" -} +type simplifier struct{} -func (s *simplifier) Visit(node ast.Node) ast.Visitor { +func (s simplifier) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.CompositeLit: // array, slice, and map composite literals may be simplified @@ -68,10 +66,13 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor { // a slice expression of the form: s[a:len(s)] // can be simplified to: s[a:] // if s is "simple enough" (for now we only accept identifiers) - if n.Max != nil || s.hasDotImport { + // + // Note: This may not be correct because len may have been redeclared in another + // file belonging to the same package. However, this is extremely unlikely + // and so far (April 2016, after years of supporting this rewrite feature) + // has never come up, so let's keep it working as is (see also #15153). + if n.Max != nil { // - 3-index slices always require the 2nd and 3rd index - // - if dot imports are present, we cannot be certain that an - // unresolved "len" identifier refers to the predefined len() break } if s, _ := n.X.(*ast.Ident); s != nil && s.Obj != nil { @@ -118,20 +119,11 @@ func isBlank(x ast.Expr) bool { } func simplify(f *ast.File) { - var s simplifier - - // determine if f contains dot imports - for _, imp := range f.Imports { - if imp.Name != nil && imp.Name.Name == "." { - s.hasDotImport = true - break - } - } - // remove empty declarations such as "const ()", etc removeEmptyDeclGroups(f) - ast.Walk(&s, f) + var s simplifier + ast.Walk(s, f) } func removeEmptyDeclGroups(f *ast.File) { diff --git a/src/cmd/gofmt/testdata/slices2.golden b/src/cmd/gofmt/testdata/slices2.golden deleted file mode 100644 index ab657004e6..0000000000 --- a/src/cmd/gofmt/testdata/slices2.golden +++ /dev/null @@ -1,63 +0,0 @@ -//gofmt -s - -// Test cases for slice expression simplification. -// Because of a dot import, these slices must remain untouched. -package p - -import . "math" - -var ( - a [10]byte - b [20]float32 - s []int - t struct { - s []byte - } - - _ = a[0:] - _ = a[1:10] - _ = a[2:len(a)] - _ = a[3:(len(a))] - _ = a[len(a) : len(a)-1] - _ = a[0:len(b)] - - _ = a[:] - _ = a[:10] - _ = a[:len(a)] - _ = a[:(len(a))] - _ = a[:len(a)-1] - _ = a[:len(b)] - - _ = s[0:] - _ = s[1:10] - _ = s[2:len(s)] - _ = s[3:(len(s))] - _ = s[len(a) : len(s)-1] - _ = s[0:len(b)] - - _ = s[:] - _ = s[:10] - _ = s[:len(s)] - _ = s[:(len(s))] - _ = s[:len(s)-1] - _ = s[:len(b)] - - _ = t.s[0:] - _ = t.s[1:10] - _ = t.s[2:len(t.s)] - _ = t.s[3:(len(t.s))] - _ = t.s[len(a) : len(t.s)-1] - _ = t.s[0:len(b)] - - _ = t.s[:] - _ = t.s[:10] - _ = t.s[:len(t.s)] - _ = t.s[:(len(t.s))] - _ = t.s[:len(t.s)-1] - _ = t.s[:len(b)] -) - -func _() { - s := s[0:len(s)] - _ = s -} diff --git a/src/cmd/gofmt/testdata/slices2.input b/src/cmd/gofmt/testdata/slices2.input deleted file mode 100644 index ab657004e6..0000000000 --- a/src/cmd/gofmt/testdata/slices2.input +++ /dev/null @@ -1,63 +0,0 @@ -//gofmt -s - -// Test cases for slice expression simplification. -// Because of a dot import, these slices must remain untouched. -package p - -import . "math" - -var ( - a [10]byte - b [20]float32 - s []int - t struct { - s []byte - } - - _ = a[0:] - _ = a[1:10] - _ = a[2:len(a)] - _ = a[3:(len(a))] - _ = a[len(a) : len(a)-1] - _ = a[0:len(b)] - - _ = a[:] - _ = a[:10] - _ = a[:len(a)] - _ = a[:(len(a))] - _ = a[:len(a)-1] - _ = a[:len(b)] - - _ = s[0:] - _ = s[1:10] - _ = s[2:len(s)] - _ = s[3:(len(s))] - _ = s[len(a) : len(s)-1] - _ = s[0:len(b)] - - _ = s[:] - _ = s[:10] - _ = s[:len(s)] - _ = s[:(len(s))] - _ = s[:len(s)-1] - _ = s[:len(b)] - - _ = t.s[0:] - _ = t.s[1:10] - _ = t.s[2:len(t.s)] - _ = t.s[3:(len(t.s))] - _ = t.s[len(a) : len(t.s)-1] - _ = t.s[0:len(b)] - - _ = t.s[:] - _ = t.s[:10] - _ = t.s[:len(t.s)] - _ = t.s[:(len(t.s))] - _ = t.s[:len(t.s)-1] - _ = t.s[:len(b)] -) - -func _() { - s := s[0:len(s)] - _ = s -} -- cgit v1.3 From f38f43d029de16f21f9102226d5c24684fb0ea25 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 1 Apr 2016 20:11:30 -0700 Subject: cmd/compile: shrink gc.Type in half MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Many of Type's fields are etype-specific. This CL organizes them into their own auxiliary types, duplicating a few fields as necessary, and adds an Extra field to hold them. It also sorts the remaining fields for better struct packing. It also improves documentation for most fields. This reduces the size of Type at the cost of some extra allocations. There's no CPU impact; memory impact below. It also makes the natural structure of Type clearer. Passes toolstash -cmp on all architectures. Ideas for future work in this vein: (1) Width and Align probably only need to be stored for Struct and Array types. The refactoring to accomplish this would hopefully also eliminate TFUNCARGS and TCHANARGS entirely. (2) Maplineno is sparsely used and could probably better be stored in a separate map[*Type]int32, with mapqueue updated to store both a Node and a line number. (3) The Printed field may be removable once the old (non-binary) importer/exported has been removed. (4) StructType's fields field could be changed from *[]*Field to []*Field, which would remove a common allocation. (5) I believe that Type.Nod can be moved to ForwardType. Separate CL. name old alloc/op new alloc/op delta Template 57.9MB ± 0% 55.9MB ± 0% -3.43% (p=0.000 n=50+50) Unicode 38.3MB ± 0% 37.8MB ± 0% -1.39% (p=0.000 n=50+50) GoTypes 185MB ± 0% 180MB ± 0% -2.56% (p=0.000 n=50+50) Compiler 824MB ± 0% 806MB ± 0% -2.19% (p=0.000 n=50+50) name old allocs/op new allocs/op delta Template 486k ± 0% 497k ± 0% +2.25% (p=0.000 n=50+50) Unicode 377k ± 0% 379k ± 0% +0.55% (p=0.000 n=50+50) GoTypes 1.39M ± 0% 1.42M ± 0% +1.63% (p=0.000 n=50+50) Compiler 5.52M ± 0% 5.57M ± 0% +0.84% (p=0.000 n=47+50) Change-Id: I828488eeb74902b013d5ae4cf844de0b6c0dfc87 Reviewed-on: https://go-review.googlesource.com/21611 Reviewed-by: Matthew Dempsky Reviewed-by: Brad Fitzpatrick --- src/cmd/compile/internal/gc/align.go | 12 +- src/cmd/compile/internal/gc/bexport.go | 4 +- src/cmd/compile/internal/gc/bimport.go | 28 +- src/cmd/compile/internal/gc/dcl.go | 10 +- src/cmd/compile/internal/gc/export.go | 2 +- src/cmd/compile/internal/gc/fmt.go | 17 +- src/cmd/compile/internal/gc/pgen.go | 2 +- src/cmd/compile/internal/gc/pgen_test.go | 20 +- src/cmd/compile/internal/gc/reflect.go | 90 +++--- src/cmd/compile/internal/gc/sizeof_test.go | 16 +- src/cmd/compile/internal/gc/ssa.go | 2 +- src/cmd/compile/internal/gc/type.go | 487 ++++++++++++++++++++++------- src/cmd/compile/internal/gc/typecheck.go | 17 +- src/cmd/compile/internal/gc/universe.go | 6 +- src/cmd/compile/internal/gc/walk.go | 2 +- 15 files changed, 490 insertions(+), 225 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/align.go b/src/cmd/compile/internal/gc/align.go index 9d5c3a550c..e43ed7b225 100644 --- a/src/cmd/compile/internal/gc/align.go +++ b/src/cmd/compile/internal/gc/align.go @@ -198,11 +198,11 @@ func dowidth(t *Type) { // make fake type to check later to // trigger channel argument check. - t1 := typWrapper(TCHANARGS, t) + t1 := typChanArgs(t) checkwidth(t1) case TCHANARGS: - t1 := t.Wrapped() + t1 := t.ChanArgs() dowidth(t1) // just in case if t1.Elem().Width >= 1<<16 { Yyerror("channel element type too large (>64kB)") @@ -271,18 +271,18 @@ func dowidth(t *Type) { // make fake type to check later to // trigger function argument computation. case TFUNC: - t1 := typWrapper(TFUNCARGS, t) + t1 := typFuncArgs(t) checkwidth(t1) w = int64(Widthptr) // width of func type is pointer // function is 3 cated structures; // compute their widths as side-effect. case TFUNCARGS: - t1 := t.Wrapped() + t1 := t.FuncArgs() w = widstruct(t1, t1.Recvs(), 0, 0) w = widstruct(t1, t1.Params(), w, Widthreg) w = widstruct(t1, t1.Results(), w, Widthreg) - t1.Argwid = w + t1.Extra.(*FuncType).Argwid = w if w%int64(Widthreg) != 0 { Warn("bad type %v %d\n", t1, w) } @@ -386,7 +386,7 @@ func Argsize(t *Type) int { } } - w = (w + int64(Widthptr) - 1) &^ (int64(Widthptr) - 1) + w = Rnd(w, int64(Widthptr)) if int64(int(w)) != w { Fatalf("argsize too big") } diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index f88afd2488..8dcf97b31d 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -602,7 +602,7 @@ func (p *exporter) typ(t *Type) { case TDDDFIELD: // see p.param use of TDDDFIELD p.tag(dddTag) - p.typ(t.Wrapped()) + p.typ(t.DDDField()) case TSTRUCT: p.tag(structTag) @@ -768,7 +768,7 @@ func (p *exporter) param(q *Field, n int, numbered bool) { t := q.Type if q.Isddd { // create a fake type to encode ... just for the p.typ call - t = typWrapper(TDDDFIELD, t.Elem()) + t = typDDDField(t.Elem()) } p.typ(t) if n > 0 { diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go index 8c53372b80..7ad4d9dbb0 100644 --- a/src/cmd/compile/internal/gc/bimport.go +++ b/src/cmd/compile/internal/gc/bimport.go @@ -359,16 +359,20 @@ func (p *importer) typ() *Type { case arrayTag, sliceTag: t = p.newtyp(TARRAY) + var bound int64 if i == arrayTag { - t.SetNumElem(p.int64()) + bound = p.int64() + } + elem := p.typ() + if i == arrayTag { + t.Extra = &ArrayType{Elem: elem, Bound: bound} } else { - t.SetNumElem(sliceBound) + t.Extra = SliceType{Elem: elem} } - t.Type = p.typ() case dddTag: t = p.newtyp(TDDDFIELD) - t.Type = p.typ() + t.Extra = DDDFieldType{T: p.typ()} case structTag: t = p.newtyp(TSTRUCT) @@ -376,7 +380,7 @@ func (p *importer) typ() *Type { case pointerTag: t = p.newtyp(Tptr) - t.Type = p.typ() + t.Extra = PtrType{Elem: p.typ()} case signatureTag: t = p.newtyp(TFUNC) @@ -393,13 +397,15 @@ func (p *importer) typ() *Type { case mapTag: t = p.newtyp(TMAP) - t.Down = p.typ() // key - t.Type = p.typ() // val + mt := t.MapType() + mt.Key = p.typ() + mt.Val = p.typ() case chanTag: t = p.newtyp(TCHAN) - t.Chan = ChanDir(p.int()) - t.Type = p.typ() + ct := t.ChanType() + ct.Dir = ChanDir(p.int()) + ct.Elem = p.typ() default: Fatalf("importer: unexpected type (tag = %d)", i) @@ -444,7 +450,7 @@ func (p *importer) field() *Node { // anonymous field - typ must be T or *T and T must be a type name s := typ.Sym if s == nil && typ.IsPtr() { - s = typ.Type.Sym // deref + s = typ.Elem().Sym // deref } pkg := importpkg if sym != nil { @@ -531,7 +537,7 @@ func (p *importer) param(named bool) *Node { isddd := false if typ.Etype == TDDDFIELD { // TDDDFIELD indicates wrapped ... slice type - typ = typSlice(typ.Wrapped()) + typ = typSlice(typ.DDDField()) isddd = true } diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index fb81545a46..c652c65962 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -743,8 +743,8 @@ func checkembeddedtype(t *Type) { if t.IsPtr() { Yyerror("embedded type cannot be a pointer") - } else if t.Etype == TFORW && t.Embedlineno == 0 { - t.Embedlineno = lineno + } else if t.Etype == TFORW && t.ForwardType().Embedlineno == 0 { + t.ForwardType().Embedlineno = lineno } } @@ -855,7 +855,7 @@ func tostruct0(t *Type, l []*Node) { func tofunargs(l []*Node) *Type { t := typ(TSTRUCT) - t.Funarg = true + t.StructType().Funarg = true fields := make([]*Field, len(l)) for i, n := range l { @@ -1061,11 +1061,11 @@ func functype0(t *Type, this *Node, in, out []*Node) { t.Broke = true } - t.Outnamed = false + t.FuncType().Outnamed = false if len(out) > 0 && out[0].Left != nil && out[0].Left.Orig != nil { s := out[0].Left.Orig.Sym if s != nil && (s.Name[0] != '~' || s.Name[1] != 'r') { // ~r%d is the name invented for an unnamed result - t.Outnamed = true + t.FuncType().Outnamed = true } } } diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index 6de7da0667..17311cf6af 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -592,7 +592,7 @@ func dumpasmhdr() { case OTYPE: t := n.Type - if !t.IsStruct() || t.Map != nil || t.IsFuncArgStruct() { + if !t.IsStruct() || t.StructType().Map != nil || t.IsFuncArgStruct() { break } fmt.Fprintf(b, "#define %s__size %d\n", t.Sym.Name, int(t.Width)) diff --git a/src/cmd/compile/internal/gc/fmt.go b/src/cmd/compile/internal/gc/fmt.go index 27ccdfbdcf..5c5503619f 100644 --- a/src/cmd/compile/internal/gc/fmt.go +++ b/src/cmd/compile/internal/gc/fmt.go @@ -671,19 +671,20 @@ func typefmt(t *Type, flag FmtFlag) string { return buf.String() case TSTRUCT: - if t.Map != nil { + if m := t.StructType().Map; m != nil { + mt := m.MapType() // Format the bucket struct for map[x]y as map.bucket[x]y. // This avoids a recursive print that generates very long names. - if t.Map.Bucket == t { - return "map.bucket[" + t.Map.Key().String() + "]" + t.Map.Val().String() + if mt.Bucket == t { + return "map.bucket[" + m.Key().String() + "]" + m.Val().String() } - if t.Map.Hmap == t { - return "map.hdr[" + t.Map.Key().String() + "]" + t.Map.Val().String() + if mt.Hmap == t { + return "map.hdr[" + m.Key().String() + "]" + m.Val().String() } - if t.Map.Hiter == t { - return "map.iter[" + t.Map.Key().String() + "]" + t.Map.Val().String() + if mt.Hiter == t { + return "map.iter[" + m.Key().String() + "]" + m.Val().String() } Yyerror("unknown internal map type") @@ -735,7 +736,7 @@ func typefmt(t *Type, flag FmtFlag) string { if fmtmode == FExp { Fatalf("cannot use TDDDFIELD with old exporter") } - return fmt.Sprintf("%v <%v> %v", Econv(t.Etype), t.Sym, t.Wrapped()) + return fmt.Sprintf("%v <%v> %v", Econv(t.Etype), t.Sym, t.DDDField()) } if fmtmode == FExp { diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index efe10a419c..63f7bf825e 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -375,7 +375,7 @@ func compile(fn *Node) { // set up domain for labels clearlabels() - if Curfn.Type.Outnamed { + if Curfn.Type.FuncType().Outnamed { // add clearing of the output parameters for _, t := range Curfn.Type.Results().Fields().Slice() { if t.Nname != nil { diff --git a/src/cmd/compile/internal/gc/pgen_test.go b/src/cmd/compile/internal/gc/pgen_test.go index fcb8bfa0c2..44dc1db12e 100644 --- a/src/cmd/compile/internal/gc/pgen_test.go +++ b/src/cmd/compile/internal/gc/pgen_test.go @@ -10,6 +10,14 @@ import ( "testing" ) +func typeWithoutPointers() *Type { + return &Type{Etype: TSTRUCT, Extra: &StructType{Haspointers: 1}} // haspointers -> false +} + +func typeWithPointers() *Type { + return &Type{Etype: TSTRUCT, Extra: &StructType{Haspointers: 2}} // haspointers -> true +} + // Test all code paths for cmpstackvarlt. func TestCmpstackvar(t *testing.T) { testdata := []struct { @@ -62,13 +70,13 @@ func TestCmpstackvar(t *testing.T) { false, }, { - Node{Class: PAUTO, Type: &Type{Haspointers: 1}}, // haspointers -> false - Node{Class: PAUTO, Type: &Type{Haspointers: 2}}, // haspointers -> true + Node{Class: PAUTO, Type: typeWithoutPointers()}, + Node{Class: PAUTO, Type: typeWithPointers()}, false, }, { - Node{Class: PAUTO, Type: &Type{Haspointers: 2}}, // haspointers -> true - Node{Class: PAUTO, Type: &Type{Haspointers: 1}}, // haspointers -> false + Node{Class: PAUTO, Type: typeWithPointers()}, + Node{Class: PAUTO, Type: typeWithoutPointers()}, true, }, { @@ -127,7 +135,7 @@ func TestStackvarSort(t *testing.T) { {Class: PFUNC, Xoffset: 10, Type: &Type{}, Name: &Name{}, Sym: &Sym{}}, {Class: PFUNC, Xoffset: 20, Type: &Type{}, Name: &Name{}, Sym: &Sym{}}, {Class: PAUTO, Used: true, Type: &Type{}, Name: &Name{}, Sym: &Sym{}}, - {Class: PAUTO, Type: &Type{Haspointers: 1}, Name: &Name{}, Sym: &Sym{}}, // haspointers -> false + {Class: PAUTO, Type: typeWithoutPointers(), Name: &Name{}, Sym: &Sym{}}, {Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{}}, {Class: PAUTO, Type: &Type{}, Name: &Name{Needzero: true}, Sym: &Sym{}}, {Class: PAUTO, Type: &Type{Width: 1}, Name: &Name{}, Sym: &Sym{}}, @@ -148,7 +156,7 @@ func TestStackvarSort(t *testing.T) { {Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{}}, {Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{Name: "abc"}}, {Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{Name: "xyz"}}, - {Class: PAUTO, Type: &Type{Haspointers: 1}, Name: &Name{}, Sym: &Sym{}}, // haspointers -> false + {Class: PAUTO, Type: typeWithoutPointers(), Name: &Name{}, Sym: &Sym{}}, } // haspointers updates Type.Haspointers as a side effect, so // exercise this function on all inputs so that reflect.DeepEqual diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go index c069b35787..df9ef27b7a 100644 --- a/src/cmd/compile/internal/gc/reflect.go +++ b/src/cmd/compile/internal/gc/reflect.go @@ -86,8 +86,8 @@ func makefield(name string, t *Type) *Field { } func mapbucket(t *Type) *Type { - if t.Bucket != nil { - return t.Bucket + if t.MapType().Bucket != nil { + return t.MapType().Bucket } bucket := typ(TSTRUCT) @@ -157,17 +157,17 @@ func mapbucket(t *Type) *Type { Yyerror("bad math in mapbucket for %v", t) } - t.Bucket = bucket + t.MapType().Bucket = bucket - bucket.Map = t + bucket.StructType().Map = t return bucket } // Builds a type representing a Hmap structure for the given map type. // Make sure this stays in sync with ../../../../runtime/hashmap.go! func hmap(t *Type) *Type { - if t.Hmap != nil { - return t.Hmap + if t.MapType().Hmap != nil { + return t.MapType().Hmap } bucket := mapbucket(t) @@ -186,14 +186,14 @@ func hmap(t *Type) *Type { h.Local = t.Local h.SetFields(field[:]) dowidth(h) - t.Hmap = h - h.Map = t + t.MapType().Hmap = h + h.StructType().Map = t return h } func hiter(t *Type) *Type { - if t.Hiter != nil { - return t.Hiter + if t.MapType().Hiter != nil { + return t.MapType().Hiter } // build a struct: @@ -234,8 +234,8 @@ func hiter(t *Type) *Type { if i.Width != int64(12*Widthptr) { Yyerror("hash_iter size not correct %d %d", i.Width, 12*Widthptr) } - t.Hiter = i - i.Map = t + t.MapType().Hiter = i + i.StructType().Map = t return i } @@ -664,67 +664,47 @@ var kinds = []int{ } func haspointers(t *Type) bool { - if t.Haspointers != 0 { - return t.Haspointers-1 != 0 - } - - var ret bool switch t.Etype { - case TINT, - TUINT, - TINT8, - TUINT8, - TINT16, - TUINT16, - TINT32, - TUINT32, - TINT64, - TUINT64, - TUINTPTR, - TFLOAT32, - TFLOAT64, - TCOMPLEX64, - TCOMPLEX128, - TBOOL: - ret = false + case TINT, TUINT, TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64, + TUINT64, TUINTPTR, TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128, TBOOL: + return false case TARRAY: if t.IsSlice() { - ret = true - break + return true } - if t.NumElem() == 0 { // empty array - ret = false - break + at := t.Extra.(*ArrayType) + if at.Haspointers != 0 { + return at.Haspointers-1 != 0 } - ret = haspointers(t.Elem()) + ret := false + if t.NumElem() != 0 { // non-empty array + ret = haspointers(t.Elem()) + } + + at.Haspointers = 1 + uint8(obj.Bool2int(ret)) + return ret case TSTRUCT: - ret = false + st := t.StructType() + if st.Haspointers != 0 { + return st.Haspointers-1 != 0 + } + + ret := false for _, t1 := range t.Fields().Slice() { if haspointers(t1.Type) { ret = true break } } - - case TSTRING, - TPTR32, - TPTR64, - TUNSAFEPTR, - TINTER, - TCHAN, - TMAP, - TFUNC: - fallthrough - default: - ret = true + st.Haspointers = 1 + uint8(obj.Bool2int(ret)) + return ret } - t.Haspointers = 1 + uint8(obj.Bool2int(ret)) - return ret + return true } // typeptrdata returns the length in bytes of the prefix of t diff --git a/src/cmd/compile/internal/gc/sizeof_test.go b/src/cmd/compile/internal/gc/sizeof_test.go index 11c0f419da..8b0dfe538e 100644 --- a/src/cmd/compile/internal/gc/sizeof_test.go +++ b/src/cmd/compile/internal/gc/sizeof_test.go @@ -27,7 +27,21 @@ func TestSizeof(t *testing.T) { {Name{}, 52, 80}, {Node{}, 92, 144}, {Sym{}, 60, 112}, - {Type{}, 116, 184}, + {Type{}, 56, 88}, + {MapType{}, 20, 40}, + {ForwardType{}, 16, 32}, + {FuncType{}, 28, 48}, + {StructType{}, 12, 24}, + {InterType{}, 4, 8}, + {ChanType{}, 8, 16}, + {ArrayType{}, 16, 24}, + {InterMethType{}, 4, 8}, + {DDDFieldType{}, 4, 8}, + {FuncArgsType{}, 4, 8}, + {ChanArgsType{}, 4, 8}, + {PtrType{}, 4, 8}, + {SliceType{}, 4, 8}, + {DDDArrayType{}, 4, 8}, } for _, tt := range tests { diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 1c2e528384..127a7c4698 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -4218,7 +4218,7 @@ func (e *ssaExport) SplitInterface(name ssa.LocalSlot) (ssa.LocalSlot, ssa.Local func (e *ssaExport) SplitSlice(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot, ssa.LocalSlot) { n := name.N.(*Node) - ptrType := Ptrto(n.Type.Type) + ptrType := Ptrto(n.Type.Elem()) lenType := Types[TINT] if n.Class == PAUTO && !n.Addrtaken { // Split this slice up into three separate variables. diff --git a/src/cmd/compile/internal/gc/type.go b/src/cmd/compile/internal/gc/type.go index e04cfcda63..3d2f01ef7d 100644 --- a/src/cmd/compile/internal/gc/type.go +++ b/src/cmd/compile/internal/gc/type.go @@ -122,55 +122,174 @@ var ( // A Type represents a Go type. type Type struct { - Etype EType - Noalg bool - Chan ChanDir - Trecur uint8 // to detect loops - Printed bool - Funarg bool // TSTRUCT only: whether this struct represents function parameters - Local bool // created in this file - Deferwidth bool - Broke bool // broken type definition. - Align uint8 - Haspointers uint8 // 0 unknown, 1 no, 2 yes - Outnamed bool // on TFUNC - - Nod *Node // canonical OTYPE node - Orig *Type // original type (type literal or predefined type) + // Extra contains extra etype-specific fields. + // As an optimization, those etype-specific structs which contain exactly + // one pointer-shaped field are stored as values rather than pointers when possible. + // + // TMAP: *MapType + // TFORW: *ForwardType + // TFUNC: *FuncType + // TINTERMETHOD: InterMethType + // TSTRUCT: *StructType + // TINTER: *InterType + // TDDDFIELD: DDDFieldType + // TFUNCARGS: FuncArgsType + // TCHANARGS: ChanArgsType + // TCHAN: *ChanType + // TPTR32, TPTR64: PtrType + // TARRAY: *ArrayType, SliceType, or DDDArrayType + Extra interface{} + + // Width is the width of this Type in bytes. + Width int64 methods Fields allMethods Fields - Sym *Sym + Nod *Node // canonical OTYPE node + Orig *Type // original type (type literal or predefined type) + + Sym *Sym // symbol containing name, for named types Vargen int32 // unique name for OTYPE/ONAME - Lineno int32 + Lineno int32 // line at which this type was declared, implicitly or explicitly + + Maplineno int32 // first use of this type as a map key + + Etype EType // kind of type + Noalg bool // suppress hash and eq algorithm generation + Trecur uint8 // to detect loops + Printed bool // prevent duplicate export printing + Local bool // created in this file + Deferwidth bool + Broke bool // broken type definition. + Align uint8 // the required alignment of this type, in bytes +} + +// MapType contains Type fields specific to maps. +type MapType struct { + Key *Type // Key type + Val *Type // Val (elem) type + + Bucket *Type // internal struct type representing a hash bucket + Hmap *Type // internal struct type representing the Hmap (map header object) + Hiter *Type // internal struct type representing hash iterator state +} + +// MapType returns t's extra map-specific fields. +func (t *Type) MapType() *MapType { + t.wantEtype(TMAP) + return t.Extra.(*MapType) +} + +// ForwardType contains Type fields specific to forward types. +type ForwardType struct { + Copyto []*Node // where to copy the eventual value to + Embedlineno int32 // first use of this type as an embedded type +} + +// ForwardType returns t's extra forward-type-specific fields. +func (t *Type) ForwardType() *ForwardType { + t.wantEtype(TFORW) + return t.Extra.(*ForwardType) +} + +// FuncType contains Type fields specific to func types. +type FuncType struct { + Receiver *Type // function receiver + Results *Type // function results + Params *Type // function params + + Nname *Node - nname *Node + // Argwid is the total width of the function receiver, params, and results. + // It gets calculated via a temporary TFUNCARGS type. + // Note that TFUNC's Width is Widthptr. Argwid int64 - // most nodes - Type *Type // element type for TARRAY, TCHAN, TMAP, TPTRxx - Width int64 + Outnamed bool +} + +// FuncType returns t's extra func-specific fields. +func (t *Type) FuncType() *FuncType { + t.wantEtype(TFUNC) + return t.Extra.(*FuncType) +} + +// InterMethType contains Type fields specific to interface method psuedo-types. +type InterMethType struct { + Nname *Node +} - // TSTRUCT +// StructType contains Type fields specific to struct types. +type StructType struct { fields Fields - Down *Type // key type in TMAP; next struct in Funarg TSTRUCT + // Maps have three associated internal structs (see struct MapType). + // Map links such structs back to their map type. + Map *Type - // TARRAY - Bound int64 // negative is slice + Funarg bool // whether this struct represents function parameters + Haspointers uint8 // 0 unknown, 1 no, 2 yes +} + +// StructType returns t's extra struct-specific fields. +func (t *Type) StructType() *StructType { + t.wantEtype(TSTRUCT) + return t.Extra.(*StructType) +} + +// InterType contains Type fields specific to interface types. +type InterType struct { + fields Fields +} + +// PtrType contains Type fields specific to pointer types. +type PtrType struct { + Elem *Type // element type +} + +// DDDFieldType contains Type fields specific to TDDDFIELD types. +type DDDFieldType struct { + T *Type // reference to a slice type for ... args +} + +// ChanArgsType contains Type fields specific to TCHANARGS types. +type ChanArgsType struct { + T *Type // reference to a chan type whose elements need a width check +} - // TMAP - Bucket *Type // internal type representing a hash bucket - Hmap *Type // internal type representing a Hmap (map header object) - Hiter *Type // internal type representing hash iterator state - Map *Type // link from the above 3 internal types back to the map type. +// // FuncArgsType contains Type fields specific to TFUNCARGS types. +type FuncArgsType struct { + T *Type // reference to a func type whose elements need a width check +} - Maplineno int32 // first use of TFORW as map key - Embedlineno int32 // first use of TFORW as embedded type +// ChanType contains Type fields specific to channel types. +type ChanType struct { + Elem *Type // element type + Dir ChanDir // channel direction +} - // for TFORW, where to copy the eventual value to - Copyto []*Node +// ChanType returns t's extra channel-specific fields. +func (t *Type) ChanType() *ChanType { + t.wantEtype(TCHAN) + return t.Extra.(*ChanType) +} + +// ArrayType contains Type fields specific to array types with known lengths. +type ArrayType struct { + Elem *Type // element type + Bound int64 // number of elements; always >= 0; do not use with sliceBound or dddBound + Haspointers uint8 // 0 unknown, 1 no, 2 yes +} + +// SliceType contains Type fields specific to slice types. +type SliceType struct { + Elem *Type // element type +} + +// DDDArrayType contains Type fields specific to ddd array types. +type DDDArrayType struct { + Elem *Type // element type } // A Field represents a field in a struct or a method in an interface or @@ -252,38 +371,61 @@ func typ(et EType) *Type { Lineno: lineno, } t.Orig = t + // TODO(josharian): lazily initialize some of these? + switch t.Etype { + case TMAP: + t.Extra = new(MapType) + case TFORW: + t.Extra = new(ForwardType) + case TFUNC: + t.Extra = new(FuncType) + case TINTERMETH: + t.Extra = InterMethType{} + case TSTRUCT: + t.Extra = new(StructType) + case TINTER: + t.Extra = new(InterType) + case TPTR32, TPTR64: + t.Extra = PtrType{} + case TCHANARGS: + t.Extra = ChanArgsType{} + case TFUNCARGS: + t.Extra = FuncArgsType{} + case TDDDFIELD: + t.Extra = DDDFieldType{} + case TCHAN: + t.Extra = new(ChanType) + } return t } // typArray returns a new fixed-length array Type. func typArray(elem *Type, bound int64) *Type { t := typ(TARRAY) - t.Type = elem - t.Bound = bound + t.Extra = &ArrayType{Elem: elem, Bound: bound} return t } // typSlice returns a new slice Type. func typSlice(elem *Type) *Type { t := typ(TARRAY) - t.Type = elem - t.Bound = sliceBound + t.Extra = SliceType{Elem: elem} return t } // typDDDArray returns a new [...]T array Type. func typDDDArray(elem *Type) *Type { t := typ(TARRAY) - t.Type = elem - t.Bound = dddBound + t.Extra = DDDArrayType{Elem: elem} return t } // typChan returns a new chan Type with direction dir. func typChan(elem *Type, dir ChanDir) *Type { t := typ(TCHAN) - t.Type = elem - t.Chan = dir + ct := t.ChanType() + ct.Elem = elem + ct.Dir = dir return t } @@ -294,29 +436,39 @@ func typMap(k, v *Type) *Type { } t := typ(TMAP) - t.Down = k - t.Type = v + mt := t.MapType() + mt.Key = k + mt.Val = v return t } // typPtr returns a new pointer type pointing to t. func typPtr(elem *Type) *Type { t := typ(Tptr) - t.Type = elem + t.Extra = PtrType{Elem: elem} t.Width = int64(Widthptr) t.Align = uint8(Widthptr) return t } -// typWrapper returns a new wrapper psuedo-type. -func typWrapper(et EType, wrapped *Type) *Type { - switch et { - case TCHANARGS, TFUNCARGS, TDDDFIELD: - default: - Fatalf("typWrapper bad etype %s", et) - } - t := typ(et) - t.Type = wrapped +// typDDDField returns a new TDDDFIELD type for slice type s. +func typDDDField(s *Type) *Type { + t := typ(TDDDFIELD) + t.Extra = DDDFieldType{T: s} + return t +} + +// typChanArgs returns a new TCHANARGS type for channel type c. +func typChanArgs(c *Type) *Type { + t := typ(TCHANARGS) + t.Extra = ChanArgsType{T: c} + return t +} + +// typFuncArgs returns a new TFUNCARGS type for func type f. +func typFuncArgs(f *Type) *Type { + t := typ(TFUNCARGS) + t.Extra = FuncArgsType{T: f} return t } @@ -362,20 +514,43 @@ func substAny(t *Type, types *[]*Type) *Type { t = (*types)[0] *types = (*types)[1:] - case TPTR32, TPTR64, TCHAN, TARRAY: - elem := substAny(t.Type, types) - if elem != t.Type { + case TPTR32, TPTR64: + elem := substAny(t.Elem(), types) + if elem != t.Elem() { + t = t.Copy() + t.Extra = PtrType{Elem: elem} + } + + case TARRAY: + elem := substAny(t.Elem(), types) + if elem != t.Elem() { + t = t.Copy() + switch x := t.Extra.(type) { + case *ArrayType: + x.Elem = elem + case SliceType: + t.Extra = SliceType{Elem: elem} + case DDDArrayType: + t.Extra = DDDArrayType{Elem: elem} + default: + Fatalf("substAny bad array elem type %T %v", x, t) + } + } + + case TCHAN: + elem := substAny(t.Elem(), types) + if elem != t.Elem() { t = t.Copy() - t.Type = elem + t.Extra.(*ChanType).Elem = elem } case TMAP: - key := substAny(t.Down, types) - val := substAny(t.Type, types) - if key != t.Down || val != t.Type { + key := substAny(t.Key(), types) + val := substAny(t.Val(), types) + if key != t.Key() || val != t.Val() { t = t.Copy() - t.Down = key - t.Type = val + t.Extra.(*MapType).Key = key + t.Extra.(*MapType).Val = val } case TFUNC: @@ -426,6 +601,32 @@ func (t *Type) Copy() *Type { return nil } nt := *t + // copy any *T Extra fields, to avoid aliasing + switch t.Etype { + case TMAP: + x := *t.Extra.(*MapType) + nt.Extra = &x + case TFORW: + x := *t.Extra.(*ForwardType) + nt.Extra = &x + case TFUNC: + x := *t.Extra.(*FuncType) + nt.Extra = &x + case TSTRUCT: + x := *t.Extra.(*StructType) + nt.Extra = &x + case TINTER: + x := *t.Extra.(*InterType) + nt.Extra = &x + case TCHAN: + x := *t.Extra.(*ChanType) + nt.Extra = &x + case TARRAY: + if arr, ok := t.Extra.(*ArrayType); ok { + x := *arr + nt.Extra = &x + } + } // TODO(mdempsky): Find out why this is necessary and explain. if t.Orig == t { nt.Orig = &nt @@ -483,17 +684,17 @@ func (t *Type) wantEtype2(et1, et2 EType) { func (t *Type) RecvsP() **Type { t.wantEtype(TFUNC) - return &t.Type + return &t.Extra.(*FuncType).Receiver } func (t *Type) ParamsP() **Type { t.wantEtype(TFUNC) - return &t.Type.Down.Down + return &t.Extra.(*FuncType).Params } func (t *Type) ResultsP() **Type { t.wantEtype(TFUNC) - return &t.Type.Down + return &t.Extra.(*FuncType).Results } func (t *Type) Recvs() *Type { return *t.RecvsP() } @@ -524,51 +725,82 @@ var paramsResults = [2]func(*Type) *Type{ // Key returns the key type of map type t. func (t *Type) Key() *Type { t.wantEtype(TMAP) - return t.Down + return t.Extra.(*MapType).Key } // Val returns the value type of map type t. func (t *Type) Val() *Type { t.wantEtype(TMAP) - return t.Type + return t.Extra.(*MapType).Val } // Elem returns the type of elements of t. // Usable with pointers, channels, arrays, and slices. func (t *Type) Elem() *Type { switch t.Etype { - case TPTR32, TPTR64, TCHAN, TARRAY: - default: - Fatalf("Type.Elem %s", t.Etype) + case TPTR32, TPTR64: + return t.Extra.(PtrType).Elem + case TARRAY: + switch t := t.Extra.(type) { + case *ArrayType: + return t.Elem + case SliceType: + return t.Elem + case DDDArrayType: + return t.Elem + } + case TCHAN: + return t.Extra.(*ChanType).Elem } - return t.Type + Fatalf("Type.Elem %s", t.Etype) + return nil } -// Wrapped returns the type that pseudo-type t wraps. -func (t *Type) Wrapped() *Type { - switch t.Etype { - case TCHANARGS, TFUNCARGS, TDDDFIELD: - default: - Fatalf("Type.Wrapped %s", t.Etype) - } - return t.Type +// DDDField returns the slice ... type for TDDDFIELD type t. +func (t *Type) DDDField() *Type { + t.wantEtype(TDDDFIELD) + return t.Extra.(DDDFieldType).T +} + +// ChanArgs returns the channel type for TCHANARGS type t. +func (t *Type) ChanArgs() *Type { + t.wantEtype(TCHANARGS) + return t.Extra.(ChanArgsType).T +} + +// FuncArgs returns the channel type for TFUNCARGS type t. +func (t *Type) FuncArgs() *Type { + t.wantEtype(TFUNCARGS) + return t.Extra.(FuncArgsType).T } // Nname returns the associated function's nname. func (t *Type) Nname() *Node { - t.wantEtype2(TFUNC, TINTERMETH) - return t.nname + switch t.Etype { + case TFUNC: + return t.Extra.(*FuncType).Nname + case TINTERMETH: + return t.Extra.(InterMethType).Nname + } + Fatalf("Type.Nname %v %v", t.Etype, t) + return nil } // Nname sets the associated function's nname. func (t *Type) SetNname(n *Node) { - t.wantEtype2(TFUNC, TINTERMETH) - t.nname = n + switch t.Etype { + case TFUNC: + t.Extra.(*FuncType).Nname = n + case TINTERMETH: + t.Extra = InterMethType{Nname: n} + default: + Fatalf("Type.SetNname %v %v", t.Etype, t) + } } // IsFuncArgStruct reports whether t is a struct representing function parameters. func (t *Type) IsFuncArgStruct() bool { - return t.Etype == TSTRUCT && t.Funarg + return t.Etype == TSTRUCT && t.Extra.(*StructType).Funarg } func (t *Type) Methods() *Fields { @@ -582,10 +814,14 @@ func (t *Type) AllMethods() *Fields { } func (t *Type) Fields() *Fields { - if t.Etype != TSTRUCT && t.Etype != TINTER { - Fatalf("Fields: type %v does not have fields", t) + switch t.Etype { + case TSTRUCT: + return &t.Extra.(*StructType).fields + case TINTER: + return &t.Extra.(*InterType).fields } - return &t.fields + Fatalf("Fields: type %v does not have fields", t) + return nil } // Field returns the i'th field/method of struct/interface type t. @@ -608,15 +844,15 @@ func (t *Type) isDDDArray() bool { if t.Etype != TARRAY { return false } - t.checkBound() - return t.Bound == dddBound + _, ok := t.Extra.(DDDArrayType) + return ok } // ArgWidth returns the total aligned argument size for a function. // It includes the receiver, parameters, and results. func (t *Type) ArgWidth() int64 { t.wantEtype(TFUNC) - return t.Argwid + return t.Extra.(*FuncType).Argwid } func (t *Type) Size() int64 { @@ -764,20 +1000,20 @@ func (t *Type) cmp(x *Type) ssa.Cmp { // by the general code after the switch. case TSTRUCT: - if t.Map == nil { - if x.Map != nil { + if t.StructType().Map == nil { + if x.StructType().Map != nil { return ssa.CMPlt // nil < non-nil } // to the fallthrough - } else if x.Map == nil { + } else if x.StructType().Map == nil { return ssa.CMPgt // nil > non-nil - } else if t.Map.Bucket == t { + } else if t.StructType().Map.MapType().Bucket == t { // Both have non-nil Map // Special case for Maps which include a recursive type where the recursion is not broken with a named type - if x.Map.Bucket != x { + if x.StructType().Map.MapType().Bucket != x { return ssa.CMPlt // bucket maps are least } - return t.Map.cmp(x.Map) + return t.StructType().Map.cmp(x.StructType().Map) } // If t != t.Map.Bucket, fall through to general case fallthrough @@ -910,21 +1146,22 @@ func (t *Type) IsChan() bool { return t.Etype == TCHAN } -// checkBound enforces that Bound has an acceptable value. -func (t *Type) checkBound() { - if t.Bound != sliceBound && t.Bound < 0 && t.Bound != dddBound { - Fatalf("bad TARRAY bounds %d %s", t.Bound, t) - } -} - +// TODO: Remove noinline when issue 15084 is resolved. +//go:noinline func (t *Type) IsSlice() bool { - t.checkBound() - return t.Etype == TARRAY && t.Bound == sliceBound + if t.Etype != TARRAY { + return false + } + _, ok := t.Extra.(SliceType) + return ok } func (t *Type) IsArray() bool { - t.checkBound() - return t.Etype == TARRAY && t.Bound >= 0 + if t.Etype != TARRAY { + return false + } + _, ok := t.Extra.(*ArrayType) + return ok } func (t *Type) IsStruct() bool { @@ -961,24 +1198,48 @@ func (t *Type) FieldOff(i int) int64 { func (t *Type) NumElem() int64 { t.wantEtype(TARRAY) - t.checkBound() - return t.Bound + switch t := t.Extra.(type) { + case *ArrayType: + return t.Bound + case SliceType: + return sliceBound + case DDDArrayType: + return dddBound + } + Fatalf("NumElem on non-array %T %v", t.Extra, t) + return 0 } // SetNumElem sets the number of elements in an array type. // It should not be used if at all possible. // Create a new array/slice/dddArray with typX instead. -// TODO(josharian): figure out how to get rid of this. +// The only allowed uses are: +// * array -> slice as a hack to suppress extra error output +// * ddd array -> array +// TODO(josharian): figure out how to get rid of this entirely. func (t *Type) SetNumElem(n int64) { t.wantEtype(TARRAY) - t.Bound = n + switch { + case n >= 0: + if !t.isDDDArray() { + Fatalf("SetNumElem non-ddd -> array %v", t) + } + t.Extra = &ArrayType{Elem: t.Elem(), Bound: n} + case n == sliceBound: + if !t.IsArray() { + Fatalf("SetNumElem non-array -> slice %v", t) + } + t.Extra = SliceType{Elem: t.Elem()} + default: + Fatalf("SetNumElem %d %v", n, t) + } } // ChanDir returns the direction of a channel type t. // The direction will be one of Crecv, Csend, or Cboth. func (t *Type) ChanDir() ChanDir { t.wantEtype(TCHAN) - return t.Chan + return t.Extra.(*ChanType).Dir } func (t *Type) IsMemory() bool { return false } diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index db74a0d246..ab7d257aac 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -2103,7 +2103,7 @@ OpSwitch: return n } - if Curfn.Type.Outnamed && n.List.Len() == 0 { + if Curfn.Type.FuncType().Outnamed && n.List.Len() == 0 { break OpSwitch } typecheckaste(ORETURN, nil, false, Curfn.Type.Results(), n.List, func() string { return "return argument" }) @@ -2161,12 +2161,8 @@ OpSwitch: t := n.Type if t != nil && !t.IsFuncArgStruct() && n.Op != OTYPE { switch t.Etype { - case TFUNC, // might have TANY; wait until its called - TANY, - TFORW, - TIDEAL, - TNIL, - TBLANK: + case TFUNC, // might have TANY; wait until it's called + TANY, TFORW, TIDEAL, TNIL, TBLANK: break default: @@ -3522,13 +3518,13 @@ var mapqueue []*Node func copytype(n *Node, t *Type) { if t.Etype == TFORW { // This type isn't computed yet; when it is, update n. - t.Copyto = append(t.Copyto, n) + t.ForwardType().Copyto = append(t.ForwardType().Copyto, n) return } maplineno := n.Type.Maplineno - embedlineno := n.Type.Embedlineno - l := n.Type.Copyto + embedlineno := n.Type.ForwardType().Embedlineno + l := n.Type.ForwardType().Copyto // TODO(mdempsky): Fix Type rekinding. *n.Type = *t @@ -3544,7 +3540,6 @@ func copytype(n *Node, t *Type) { t.Nod = nil t.Printed = false t.Deferwidth = false - t.Copyto = nil // Update nodes waiting on this type. for _, n := range l { diff --git a/src/cmd/compile/internal/gc/universe.go b/src/cmd/compile/internal/gc/universe.go index c2ba9c9a93..3330fbbab2 100644 --- a/src/cmd/compile/internal/gc/universe.go +++ b/src/cmd/compile/internal/gc/universe.go @@ -359,16 +359,16 @@ func lexinit1() { // t = interface { Error() string } rcvr := typ(TSTRUCT) - rcvr.Funarg = true + rcvr.StructType().Funarg = true field := newField() field.Type = Ptrto(typ(TSTRUCT)) rcvr.SetFields([]*Field{field}) in := typ(TSTRUCT) - in.Funarg = true + in.StructType().Funarg = true out := typ(TSTRUCT) - out.Funarg = true + out.StructType().Funarg = true field = newField() field.Type = Types[TSTRING] out.SetFields([]*Field{field}) diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index 392dae0fa9..ff8ddea7f6 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -287,7 +287,7 @@ func walkstmt(n *Node) *Node { if n.List.Len() == 0 { break } - if (Curfn.Type.Outnamed && n.List.Len() > 1) || paramoutheap(Curfn) { + if (Curfn.Type.FuncType().Outnamed && n.List.Len() > 1) || paramoutheap(Curfn) { // assign to the function out parameters, // so that reorder3 can fix up conflicts var rl []*Node -- cgit v1.3 From 04945edd40fff4d66321a4f98c1bb070b6356008 Mon Sep 17 00:00:00 2001 From: Alexandru Moșoi Date: Mon, 4 Apr 2016 19:23:41 +0200 Subject: cmd/compile: replaces ANDQ with MOV?ZX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Where possible replace ANDQ with MOV?ZX. Takes care that we don't regress wrt bounds checking, for example [1000]int{}[i&255]. According to "Intel 64 and IA-32 Architectures Optimization Reference Manual" Section: "3.5.1.13 Zero-Latency MOV Instructions" MOV?ZX instructions have zero latency on newer processors. Updates #15105 Change-Id: I63539fdbc5812d5563aa1ebc49eca035bd307997 Reviewed-on: https://go-review.googlesource.com/21508 Reviewed-by: Айнар Гарипов Reviewed-by: David Chase --- src/cmd/compile/internal/ssa/gen/AMD64.rules | 8 +++ src/cmd/compile/internal/ssa/rewriteAMD64.go | 81 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index 4ad0f883b0..b37720eb39 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -587,6 +587,11 @@ (CMPB x (MOVBconst [c])) -> (CMPBconst x [c]) (CMPB (MOVBconst [c]) x) -> (InvertFlags (CMPBconst x [c])) +// Using MOVBQZX instead of ANDQ is cheaper. +(ANDQconst [0xFF] x) -> (MOVBQZX x) +(ANDQconst [0xFFFF] x) -> (MOVWQZX x) +(ANDQconst [0xFFFFFFFF] x) -> (MOVLQZX x) + // strength reduction // Assumes that the following costs from https://gmplib.org/~tege/x86-timing.pdf: // 1 - addq, shlq, leaq, negq @@ -1093,6 +1098,9 @@ (CMPBconst (MOVBconst [x]) [y]) && int8(x)>int8(y) && uint8(x)>uint8(y) -> (FlagGT_UGT) // Other known comparisons. +(CMPQconst (MOVBQZX _) [c]) && 0xFF < c -> (FlagLT_ULT) +(CMPQconst (MOVWQZX _) [c]) && 0xFFFF < c -> (FlagLT_ULT) +(CMPQconst (MOVLQZX _) [c]) && 0xFFFFFFFF < c -> (FlagLT_ULT) (CMPQconst (ANDQconst _ [m]) [n]) && 0 <= m && m < n -> (FlagLT_ULT) (CMPLconst (ANDLconst _ [m]) [n]) && 0 <= int32(m) && int32(m) < int32(n) -> (FlagLT_ULT) (CMPWconst (ANDWconst _ [m]) [n]) && 0 <= int16(m) && int16(m) < int16(n) -> (FlagLT_ULT) diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index 11c2de391c..a1d1e4edd9 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -1838,6 +1838,42 @@ func rewriteValueAMD64_OpAMD64ANDQconst(v *Value, config *Config) bool { v.AddArg(x) return true } + // match: (ANDQconst [0xFF] x) + // cond: + // result: (MOVBQZX x) + for { + if v.AuxInt != 0xFF { + break + } + x := v.Args[0] + v.reset(OpAMD64MOVBQZX) + v.AddArg(x) + return true + } + // match: (ANDQconst [0xFFFF] x) + // cond: + // result: (MOVWQZX x) + for { + if v.AuxInt != 0xFFFF { + break + } + x := v.Args[0] + v.reset(OpAMD64MOVWQZX) + v.AddArg(x) + return true + } + // match: (ANDQconst [0xFFFFFFFF] x) + // cond: + // result: (MOVLQZX x) + for { + if v.AuxInt != 0xFFFFFFFF { + break + } + x := v.Args[0] + v.reset(OpAMD64MOVLQZX) + v.AddArg(x) + return true + } // match: (ANDQconst [0] _) // cond: // result: (MOVQconst [0]) @@ -3026,6 +3062,51 @@ func rewriteValueAMD64_OpAMD64CMPQconst(v *Value, config *Config) bool { v.reset(OpAMD64FlagGT_UGT) return true } + // match: (CMPQconst (MOVBQZX _) [c]) + // cond: 0xFF < c + // result: (FlagLT_ULT) + for { + v_0 := v.Args[0] + if v_0.Op != OpAMD64MOVBQZX { + break + } + c := v.AuxInt + if !(0xFF < c) { + break + } + v.reset(OpAMD64FlagLT_ULT) + return true + } + // match: (CMPQconst (MOVWQZX _) [c]) + // cond: 0xFFFF < c + // result: (FlagLT_ULT) + for { + v_0 := v.Args[0] + if v_0.Op != OpAMD64MOVWQZX { + break + } + c := v.AuxInt + if !(0xFFFF < c) { + break + } + v.reset(OpAMD64FlagLT_ULT) + return true + } + // match: (CMPQconst (MOVLQZX _) [c]) + // cond: 0xFFFFFFFF < c + // result: (FlagLT_ULT) + for { + v_0 := v.Args[0] + if v_0.Op != OpAMD64MOVLQZX { + break + } + c := v.AuxInt + if !(0xFFFFFFFF < c) { + break + } + v.reset(OpAMD64FlagLT_ULT) + return true + } // match: (CMPQconst (ANDQconst _ [m]) [n]) // cond: 0 <= m && m < n // result: (FlagLT_ULT) -- cgit v1.3 From 007b12977aa8f3373b358361fe21802d5a8408b4 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 6 Apr 2016 14:12:48 -0700 Subject: cmd/compile: move Type.Maplineno to separate data structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relatively few types are ever used as map keys, so tracking this separately is a net win. Passes toolstash -cmp. name old alloc/op new alloc/op delta Template 55.9MB ± 0% 55.5MB ± 0% -0.71% (p=0.000 n=10+10) Unicode 37.8MB ± 0% 37.7MB ± 0% -0.27% (p=0.000 n=10+10) GoTypes 180MB ± 0% 179MB ± 0% -0.52% (p=0.000 n=7+10) Compiler 806MB ± 0% 803MB ± 0% -0.41% (p=0.000 n=10+10) CPU and number of allocs are unchanged. Change-Id: I6d60d74a4866995a231dfed3dd5792d75d904292 Reviewed-on: https://go-review.googlesource.com/21622 Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/sizeof_test.go | 2 +- src/cmd/compile/internal/gc/subr.go | 4 ++-- src/cmd/compile/internal/gc/type.go | 2 -- src/cmd/compile/internal/gc/typecheck.go | 14 +++++++++----- 4 files changed, 12 insertions(+), 10 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/sizeof_test.go b/src/cmd/compile/internal/gc/sizeof_test.go index 8b0dfe538e..f2b1461bc8 100644 --- a/src/cmd/compile/internal/gc/sizeof_test.go +++ b/src/cmd/compile/internal/gc/sizeof_test.go @@ -27,7 +27,7 @@ func TestSizeof(t *testing.T) { {Name{}, 52, 80}, {Node{}, 92, 144}, {Sym{}, 60, 112}, - {Type{}, 56, 88}, + {Type{}, 52, 80}, {MapType{}, 20, 40}, {ForwardType{}, 16, 32}, {FuncType{}, 28, 48}, diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go index a61b8bcd27..035bd815c2 100644 --- a/src/cmd/compile/internal/gc/subr.go +++ b/src/cmd/compile/internal/gc/subr.go @@ -390,8 +390,8 @@ func checkMapKeyType(key *Type) { // before key is fully defined, the error // will only be printed for the first one. // good enough. - if key.Maplineno == 0 { - key.Maplineno = lineno + if maplineno[key] == 0 { + maplineno[key] = lineno } } } diff --git a/src/cmd/compile/internal/gc/type.go b/src/cmd/compile/internal/gc/type.go index 3d2f01ef7d..eee8e0384a 100644 --- a/src/cmd/compile/internal/gc/type.go +++ b/src/cmd/compile/internal/gc/type.go @@ -153,8 +153,6 @@ type Type struct { Vargen int32 // unique name for OTYPE/ONAME Lineno int32 // line at which this type was declared, implicitly or explicitly - Maplineno int32 // first use of this type as a map key - Etype EType // kind of type Noalg bool // suppress hash and eq algorithm generation Trecur uint8 // to detect loops diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index ab7d257aac..a20f87d940 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -3513,7 +3513,11 @@ func domethod(n *Node) { checkwidth(n.Type) } -var mapqueue []*Node +var ( + mapqueue []*Node + // maplineno tracks the line numbers at which types are first used as map keys + maplineno = map[*Type]int32{} +) func copytype(n *Node, t *Type) { if t.Etype == TFORW { @@ -3522,7 +3526,7 @@ func copytype(n *Node, t *Type) { return } - maplineno := n.Type.Maplineno + mapline := maplineno[n.Type] embedlineno := n.Type.ForwardType().Embedlineno l := n.Type.ForwardType().Copyto @@ -3559,8 +3563,8 @@ func copytype(n *Node, t *Type) { lineno = lno // Queue check for map until all the types are done settling. - if maplineno != 0 { - t.Maplineno = maplineno + if mapline != 0 { + maplineno[t] = mapline mapqueue = append(mapqueue, n) } } @@ -3609,7 +3613,7 @@ ret: } for _, n := range mapqueue { - lineno = n.Type.Maplineno + lineno = maplineno[n.Type] checkMapKeyType(n.Type) } -- cgit v1.3 From 81aacb80d55eddcb95cbe2c87392cc922e026e45 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 6 Apr 2016 15:27:30 -0700 Subject: cmd/compile, go/importer: minor cleanups Change-Id: I4ffb79d8cb08b0b44f59757fb7f0ec3ed1e4479f Reviewed-on: https://go-review.googlesource.com/21624 Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/bexport.go | 8 +++----- src/go/internal/gcimporter/bimport.go | 5 ++--- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index 8dcf97b31d..092cdac2f6 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -109,8 +109,6 @@ import ( // // NOTE: This flag is the first flag to enable if importing dies because of // (suspected) format errors, and whenever a change is made to the format. -// Having debugFormat enabled increases the export data size massively (by -// several factors) - avoid running with the flag enabled in general. const debugFormat = false // default: false // TODO(gri) remove eventually @@ -515,19 +513,19 @@ func (p *exporter) typ(t *Type) { p.typIndex[t] = len(p.typIndex) // pick off named types - if sym := t.Sym; sym != nil { + if tsym := t.Sym; tsym != nil { // Predeclared types should have been found in the type map. if t.Orig == t { Fatalf("exporter: predeclared type missing from type map?") } // TODO(gri) The assertion below seems incorrect (crashes during all.bash). // we expect the respective definition to point to us - // if sym.Def.Type != t { + // if tsym.Def.Type != t { // Fatalf("exporter: type definition doesn't point to us?") // } p.tag(namedTag) - p.qualifiedName(sym) + p.qualifiedName(tsym) // write underlying type p.typ(t.Orig) diff --git a/src/go/internal/gcimporter/bimport.go b/src/go/internal/gcimporter/bimport.go index 12efb2aaf3..aa9569de52 100644 --- a/src/go/internal/gcimporter/bimport.go +++ b/src/go/internal/gcimporter/bimport.go @@ -232,8 +232,7 @@ func (p *importer) typ(parent *types.Package) types.Type { switch i { case namedTag: // read type object - name := p.string() - parent = p.pkg() + parent, name := p.qualifiedName() scope := parent.Scope() obj := scope.Lookup(name) @@ -258,7 +257,7 @@ func (p *importer) typ(parent *types.Package) types.Type { t0.SetUnderlying(p.typ(parent)) // interfaces don't have associated methods - if _, ok := t0.Underlying().(*types.Interface); ok { + if types.IsInterface(t0) { return t } -- cgit v1.3 From a037c73ccfc7c35a389e95ffa7996c8dd38e0cde Mon Sep 17 00:00:00 2001 From: Ryan Brown Date: Mon, 14 Mar 2016 09:23:04 -0700 Subject: cmd/link: generate DWARF info using symbols This updates dwarf.go to generate debug information as symbols instead of directly writing to the output file. This should make it easier to move generation of some of the debug info into the compiler. Change-Id: Id2358988bfb689865ab4d68f82716f0676336df4 Reviewed-on: https://go-review.googlesource.com/20679 Reviewed-by: David Crawshaw Run-TryBot: David Crawshaw TryBot-Result: Gobot Gobot --- src/cmd/internal/obj/link.go | 4 + src/cmd/link/internal/amd64/asm.go | 22 +- src/cmd/link/internal/arm/asm.go | 21 +- src/cmd/link/internal/arm64/asm.go | 21 +- src/cmd/link/internal/ld/data.go | 114 +++ src/cmd/link/internal/ld/dwarf.go | 1493 +++++++++++++---------------------- src/cmd/link/internal/ld/elf.go | 23 +- src/cmd/link/internal/ld/macho.go | 29 +- src/cmd/link/internal/ld/symtab.go | 12 - src/cmd/link/internal/mips64/asm.go | 10 +- src/cmd/link/internal/ppc64/asm.go | 10 +- src/cmd/link/internal/s390x/asm.go | 6 +- src/cmd/link/internal/x86/asm.go | 24 +- 13 files changed, 727 insertions(+), 1062 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index 24f028f737..9c06e8dec6 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -423,6 +423,8 @@ const ( SCONST SDYNIMPORT SHOSTOBJ + SDWARFSECT + SDWARFINFO SSUB = 1 << 8 SMASK = SSUB - 1 SHIDDEN = 1 << 9 @@ -495,6 +497,8 @@ const ( // of a JMP instruction, by encoding the address into the instruction. // The stack nosplit check ignores this since it is not a function call. R_JMPMIPS + // R_DWARFREF resolves to the offset of the symbol from its section. + R_DWARFREF // Platform dependent relocations. Architectures with fixed width instructions // have the inherent issue that a 32-bit (or 64-bit!) displacement cannot be diff --git a/src/cmd/link/internal/amd64/asm.go b/src/cmd/link/internal/amd64/asm.go index d809f6e8ed..25232e6e01 100644 --- a/src/cmd/link/internal/amd64/asm.go +++ b/src/cmd/link/internal/amd64/asm.go @@ -650,19 +650,11 @@ func asmb() { ld.Cseek(int64(ld.Segdata.Fileoff)) ld.Datblk(int64(ld.Segdata.Vaddr), int64(ld.Segdata.Filelen)) + ld.Cseek(int64(ld.Segdwarf.Fileoff)) + ld.Dwarfblk(int64(ld.Segdwarf.Vaddr), int64(ld.Segdwarf.Filelen)) + machlink := int64(0) if ld.HEADTYPE == obj.Hdarwin { - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - - dwarfoff := ld.Rnd(int64(uint64(ld.HEADR)+ld.Segtext.Length), int64(ld.INITRND)) + ld.Rnd(int64(ld.Segdata.Filelen), int64(ld.INITRND)) - ld.Cseek(dwarfoff) - - ld.Segdwarf.Fileoff = uint64(ld.Cpos()) - ld.Dwarfemitdebugsections() - ld.Segdwarf.Filelen = uint64(ld.Cpos()) - ld.Segdwarf.Fileoff - machlink = ld.Domacholink() } @@ -715,11 +707,11 @@ func asmb() { obj.Hdragonfly, obj.Hsolaris, obj.Hnacl: - symo = int64(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = int64(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = ld.Rnd(symo, int64(ld.INITRND)) case obj.Hwindows: - symo = int64(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = int64(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = ld.Rnd(symo, ld.PEFILEALIGN) } @@ -736,8 +728,6 @@ func asmb() { fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) } - ld.Dwarfemitdebugsections() - if ld.Linkmode == ld.LinkExternal { ld.Elfemitreloc() } @@ -762,8 +752,6 @@ func asmb() { fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) } - ld.Dwarfemitdebugsections() - case obj.Hdarwin: if ld.Linkmode == ld.LinkExternal { ld.Machoemitreloc() diff --git a/src/cmd/link/internal/arm/asm.go b/src/cmd/link/internal/arm/asm.go index bb90cf77b6..e2718bfac8 100644 --- a/src/cmd/link/internal/arm/asm.go +++ b/src/cmd/link/internal/arm/asm.go @@ -597,19 +597,11 @@ func asmb() { ld.Cseek(int64(ld.Segdata.Fileoff)) ld.Datblk(int64(ld.Segdata.Vaddr), int64(ld.Segdata.Filelen)) + ld.Cseek(int64(ld.Segdwarf.Fileoff)) + ld.Dwarfblk(int64(ld.Segdwarf.Vaddr), int64(ld.Segdwarf.Filelen)) + machlink := uint32(0) if ld.HEADTYPE == obj.Hdarwin { - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - - dwarfoff := uint32(ld.Rnd(int64(uint64(ld.HEADR)+ld.Segtext.Length), int64(ld.INITRND)) + ld.Rnd(int64(ld.Segdata.Filelen), int64(ld.INITRND))) - ld.Cseek(int64(dwarfoff)) - - ld.Segdwarf.Fileoff = uint64(ld.Cpos()) - ld.Dwarfemitdebugsections() - ld.Segdwarf.Filelen = uint64(ld.Cpos()) - ld.Segdwarf.Fileoff - machlink = uint32(ld.Domacholink()) } @@ -627,7 +619,7 @@ func asmb() { switch ld.HEADTYPE { default: if ld.Iself { - symo = uint32(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = uint32(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = uint32(ld.Rnd(int64(symo), int64(ld.INITRND))) } @@ -649,11 +641,6 @@ func asmb() { ld.Cflush() ld.Cwrite(ld.Elfstrdat) - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - ld.Dwarfemitdebugsections() - if ld.Linkmode == ld.LinkExternal { ld.Elfemitreloc() } diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index 250f0afb16..8227dc0d28 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -435,19 +435,11 @@ func asmb() { ld.Cseek(int64(ld.Segdata.Fileoff)) ld.Datblk(int64(ld.Segdata.Vaddr), int64(ld.Segdata.Filelen)) + ld.Cseek(int64(ld.Segdwarf.Fileoff)) + ld.Dwarfblk(int64(ld.Segdwarf.Vaddr), int64(ld.Segdwarf.Filelen)) + machlink := uint32(0) if ld.HEADTYPE == obj.Hdarwin { - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - - dwarfoff := uint32(ld.Rnd(int64(uint64(ld.HEADR)+ld.Segtext.Length), int64(ld.INITRND)) + ld.Rnd(int64(ld.Segdata.Filelen), int64(ld.INITRND))) - ld.Cseek(int64(dwarfoff)) - - ld.Segdwarf.Fileoff = uint64(ld.Cpos()) - ld.Dwarfemitdebugsections() - ld.Segdwarf.Filelen = uint64(ld.Cpos()) - ld.Segdwarf.Fileoff - machlink = uint32(ld.Domacholink()) } @@ -465,7 +457,7 @@ func asmb() { switch ld.HEADTYPE { default: if ld.Iself { - symo = uint32(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = uint32(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = uint32(ld.Rnd(int64(symo), int64(ld.INITRND))) } @@ -487,11 +479,6 @@ func asmb() { ld.Cflush() ld.Cwrite(ld.Elfstrdat) - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - ld.Dwarfemitdebugsections() - if ld.Linkmode == ld.LinkExternal { ld.Elfemitreloc() } diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index fe74cc9208..6bbd6c7d5c 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -84,6 +84,20 @@ func setuintxx(ctxt *Link, s *LSym, off int64, v uint64, wid int64) int64 { return off + wid } +func Addbytes(ctxt *Link, s *LSym, bytes []byte) int64 { + if s.Type == 0 { + s.Type = obj.SDATA + } + s.Attr |= AttrReachable + s.Size += int64(len(bytes)) + if int64(int(s.Size)) != s.Size { + log.Fatalf("Addbytes size %d too long", s.Size) + } + s.P = append(s.P, bytes...) + + return s.Size +} + func adduintxx(ctxt *Link, s *LSym, v uint64, wid int) int64 { off := s.Size setuintxx(ctxt, s, off, v, int64(wid)) @@ -489,6 +503,25 @@ func relocsym(s *LSym) { errorexit() } + case obj.R_DWARFREF: + if r.Sym.Sect == nil { + Diag("missing DWARF section: %s from %s", r.Sym.Name, s.Name) + } + if Linkmode == LinkExternal { + r.Done = 0 + r.Type = obj.R_ADDR + + r.Xsym = Linkrlookup(Ctxt, r.Sym.Sect.Name, 0) + r.Xadd = r.Add + Symaddr(r.Sym) - int64(r.Sym.Sect.Vaddr) + o = r.Xadd + rs = r.Xsym + if Iself && Thearch.Thechar == '6' { + o = 0 + } + break + } + o = Symaddr(r.Sym) + r.Add - int64(r.Sym.Sect.Vaddr) + // r->sym can be null when CALL $(constant) is transformed from absolute PC to relative PC call. case obj.R_CALL, obj.R_GOTPCREL, obj.R_PCREL: if Linkmode == LinkExternal && r.Sym != nil && r.Sym.Type != obj.SCONST && (r.Sym.Sect != Ctxt.Cursym.Sect || r.Type == obj.R_GOTPCREL) { @@ -614,6 +647,9 @@ func reloc() { for s := datap; s != nil; s = s.Next { relocsym(s) } + for s := dwarfp; s != nil; s = s.Next { + relocsym(s) + } } func dynrelocsym(s *LSym) { @@ -893,6 +929,14 @@ func Datblk(addr int64, size int64) { fmt.Fprintf(&Bso, "\t%.8x|\n", uint(eaddr)) } +func Dwarfblk(addr int64, size int64) { + if Debug['a'] != 0 { + fmt.Fprintf(&Bso, "dwarfblk [%#x,%#x) at offset %#x\n", addr, addr+size, Cpos()) + } + + blk(dwarfp, addr, size) +} + var zeros [512]byte // strnput writes the first n bytes of s. @@ -1691,6 +1735,40 @@ func dodata() { Diag("read-only data segment too large") } + dwarfgeneratedebugsyms() + + for s = dwarfp; s != nil && s.Type == obj.SDWARFSECT; s = s.Next { + sect = addsection(&Segdwarf, s.Name, 04) + sect.Align = 1 + datsize = Rnd(datsize, int64(sect.Align)) + sect.Vaddr = uint64(datsize) + s.Sect = sect + s.Type = obj.SRODATA + s.Value = int64(uint64(datsize) - sect.Vaddr) + growdatsize(&datsize, s) + sect.Length = uint64(datsize) - sect.Vaddr + } + + if s != nil { + sect = addsection(&Segdwarf, ".debug_info", 04) + sect.Align = 1 + datsize = Rnd(datsize, int64(sect.Align)) + sect.Vaddr = uint64(datsize) + for ; s != nil && s.Type == obj.SDWARFINFO; s = s.Next { + s.Sect = sect + s.Type = obj.SRODATA + s.Value = int64(uint64(datsize) - sect.Vaddr) + s.Attr |= AttrLocal + growdatsize(&datsize, s) + } + sect.Length = uint64(datsize) - sect.Vaddr + } + + // The compiler uses 4-byte relocation offsets, so the entire segment must fit in 32 bits. + if datsize != int64(uint32(datsize)) { + Diag("dwarf segment too large") + } + /* number the sections */ n := int32(1) @@ -1706,6 +1784,10 @@ func dodata() { sect.Extnum = int16(n) n++ } + for sect := Segdwarf.Sect; sect != nil; sect = sect.Next { + sect.Extnum = int16(n) + n++ + } } // Add buildid to beginning of text segment, on non-ELF systems. @@ -1857,6 +1939,29 @@ func address() { Segdata.Filelen = bss.Vaddr - Segdata.Vaddr + va = uint64(Rnd(int64(va), int64(INITRND))) + Segdwarf.Rwx = 06 + Segdwarf.Vaddr = va + Segdwarf.Fileoff = Segdata.Fileoff + uint64(Rnd(int64(Segdata.Filelen), int64(INITRND))) + Segdwarf.Filelen = 0 + if HEADTYPE == obj.Hwindows { + Segdwarf.Fileoff = Segdata.Fileoff + uint64(Rnd(int64(Segdata.Filelen), int64(PEFILEALIGN))) + } + for s := Segdwarf.Sect; s != nil; s = s.Next { + vlen = int64(s.Length) + if s.Next != nil { + vlen = int64(s.Next.Vaddr - s.Vaddr) + } + s.Vaddr = va + va += uint64(vlen) + if HEADTYPE == obj.Hwindows { + va = uint64(Rnd(int64(va), PEFILEALIGN)) + } + Segdwarf.Length = va - Segdwarf.Vaddr + } + + Segdwarf.Filelen = va - Segdwarf.Vaddr + text := Segtext.Sect var rodata *Section if Segrodata.Sect != nil { @@ -1884,6 +1989,15 @@ func address() { sub.Value += sym.Value } } + for sym := dwarfp; sym != nil; sym = sym.Next { + Ctxt.Cursym = sym + if sym.Sect != nil { + sym.Value += int64(sym.Sect.Vaddr) + } + for sub = sym.Sub; sub != nil; sub = sub.Sub { + sub.Value += sym.Value + } + } if Buildmode == BuildmodeShared { s := Linklookup(Ctxt, "go.link.abihashbytes", 0) diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 4465a727a5..eaa0bdbb41 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -17,97 +17,34 @@ package ld import ( "cmd/internal/obj" "fmt" + "log" "os" "strings" ) +const infoprefix = "go.dwarf.info." + /* * Offsets and sizes of the debug_* sections in the cout file. */ -var abbrevo int64 - -var abbrevsize int64 - var abbrevsym *LSym - -var abbrevsympos int64 - -var lineo int64 - -var linesize int64 - -var linesym *LSym - -var linesympos int64 - -var infoo int64 // also the base for DWDie->offs and reference attributes. - -var infosize int64 - -var infosym *LSym - -var infosympos int64 - -var frameo int64 - -var framesize int64 - -var framesym *LSym - -var framesympos int64 - -var pubnameso int64 - -var pubnamessize int64 - -var pubtypeso int64 - -var pubtypessize int64 - -var arangeso int64 - -var arangessize int64 - -var gdbscripto int64 - -var gdbscriptsize int64 - -var infosec *LSym - -var inforeloco int64 - -var inforelocsize int64 - var arangessec *LSym - -var arangesreloco int64 - -var arangesrelocsize int64 - -var linesec *LSym - -var linereloco int64 - -var linerelocsize int64 - var framesec *LSym - -var framereloco int64 - -var framerelocsize int64 +var infosec *LSym +var linesec *LSym var gdbscript string /* * Basic I/O */ -func addrput(addr int64) { +func addrput(s *LSym, addr int64) { switch Thearch.Ptrsize { case 4: - Thearch.Lput(uint32(addr)) + Adduint32(Ctxt, s, uint32(addr)) case 8: - Thearch.Vput(uint64(addr)) + Adduint64(Ctxt, s, uint64(addr)) } } @@ -144,14 +81,16 @@ func appendSleb128(b []byte, v int64) []byte { var encbuf [10]byte -func uleb128put(v int64) { +func uleb128put(s *LSym, v int64) { b := appendUleb128(encbuf[:0], uint64(v)) - Cwrite(b) + Addbytes(Ctxt, s, b) } -func sleb128put(v int64) { +func sleb128put(s *LSym, v int64) { b := appendSleb128(encbuf[:0], v) - Cwrite(b) + for _, x := range b { + Adduint8(Ctxt, s, x) + } } /* @@ -462,24 +401,29 @@ var abbrevs = [DW_NABRV]DWAbbrev{ }, } -func writeabbrev() { - abbrevo = Cpos() +var dwarfp *LSym + +func writeabbrev() *LSym { + s := Linklookup(Ctxt, ".debug_abbrev", 0) + s.Type = obj.SDWARFSECT + abbrevsym = s + for i := 1; i < DW_NABRV; i++ { // See section 7.5.3 - uleb128put(int64(i)) + uleb128put(s, int64(i)) - uleb128put(int64(abbrevs[i].tag)) - Cput(abbrevs[i].children) + uleb128put(s, int64(abbrevs[i].tag)) + Adduint8(Ctxt, s, abbrevs[i].children) for _, f := range abbrevs[i].attr { - uleb128put(int64(f.attr)) - uleb128put(int64(f.form)) + uleb128put(s, int64(f.attr)) + uleb128put(s, int64(f.form)) } - uleb128put(0) - uleb128put(0) + uleb128put(s, 0) + uleb128put(s, 0) } - Cput(0) - abbrevsize = Cpos() - abbrevo + Adduint8(Ctxt, s, 0) + return s } /* @@ -504,10 +448,7 @@ type DWDie struct { link *DWDie child *DWDie attr *DWAttr - // offset into .debug_info section, i.e relative to - // infoo. only valid after call to putdie() - offs int64 - hash map[string]*DWDie // optional index of DWAttr by name, enabled by mkindex() + sym *LSym } /* @@ -556,9 +497,8 @@ func getattr(die *DWDie, attr uint16) *DWAttr { } // Every DIE has at least a DW_AT_name attribute (but it will only be -// written out if it is listed in the abbrev). If its parent is -// keeping an index, the new DIE will be inserted there. -func newdie(parent *DWDie, abbrev int, name string) *DWDie { +// written out if it is listed in the abbrev). +func newdie(parent *DWDie, abbrev int, name string, version int) *DWDie { die := new(DWDie) die.abbrev = abbrev die.link = parent.child @@ -566,17 +506,16 @@ func newdie(parent *DWDie, abbrev int, name string) *DWDie { newattr(die, DW_AT_name, DW_CLS_STRING, int64(len(name)), name) - if parent.hash != nil { - parent.hash[name] = die + if name != "" && (abbrev <= DW_ABRV_VARIABLE || abbrev >= DW_ABRV_NULLTYPE) { + if abbrev != DW_ABRV_VARIABLE || version == 0 { + die.sym = Linklookup(Ctxt, infoprefix+name, version) + die.sym.Type = obj.SDWARFINFO + } } return die } -func mkindex(die *DWDie) { - die.hash = make(map[string]*DWDie) -} - func walktypedef(die *DWDie) *DWDie { // Resolve typedef if present. if die.abbrev == DW_ABRV_TYPEDECL { @@ -590,159 +529,150 @@ func walktypedef(die *DWDie) *DWDie { return die } +func walksymtypedef(s *LSym) *LSym { + if t := Linkrlookup(Ctxt, s.Name+".def", int(s.Version)); t != nil { + return t + } + return s +} + // Find child by AT_name using hashtable if available or linear scan // if not. -func find(die *DWDie, name string) *DWDie { +func findchild(die *DWDie, name string) *DWDie { var prev *DWDie for ; die != prev; prev, die = die, walktypedef(die) { - if die.hash == nil { - for a := die.child; a != nil; a = a.link { - if name == getattr(a, DW_AT_name).data { - return a - } + for a := die.child; a != nil; a = a.link { + if name == getattr(a, DW_AT_name).data { + return a } - continue - } - if a := die.hash[name]; a != nil { - return a } + continue } return nil } -func mustFind(die *DWDie, name string) *DWDie { - r := find(die, name) +func find(name string) *LSym { + return Linkrlookup(Ctxt, infoprefix+name, 0) +} + +func mustFind(name string) *LSym { + r := find(name) if r == nil { - Exitf("dwarf find: %s %p has no %s", getattr(die, DW_AT_name).data, die, name) + Exitf("dwarf find: cannot find %s", name) } return r } -func adddwarfrel(sec *LSym, sym *LSym, offsetbase int64, siz int, addend int64) { - r := Addrel(sec) - r.Sym = sym - r.Xsym = sym - r.Off = int32(Cpos() - offsetbase) - r.Siz = uint8(siz) - r.Type = obj.R_ADDR - r.Add = addend - r.Xadd = addend - if Iself && Thearch.Thechar == '6' { - addend = 0 - } - if HEADTYPE == obj.Hdarwin { - addend += sym.Value - } - switch siz { - case 4: - Thearch.Lput(uint32(addend)) - - case 8: - Thearch.Vput(uint64(addend)) - +func adddwarfref(ctxt *Link, s *LSym, t *LSym, size int) int64 { + var result int64 + switch size { default: - Diag("bad size in adddwarfrel") + Diag("invalid size %d in adddwarfref\n", size) + fallthrough + case Thearch.Ptrsize: + result = Addaddr(ctxt, s, t) + case 4: + result = addaddrplus4(ctxt, s, t, 0) } + r := &s.R[len(s.R)-1] + r.Type = obj.R_DWARFREF + return result } -func newrefattr(die *DWDie, attr uint16, ref *DWDie) *DWAttr { +func newrefattr(die *DWDie, attr uint16, ref *LSym) *DWAttr { if ref == nil { return nil } return newattr(die, attr, DW_CLS_REFERENCE, 0, ref) } -var fwdcount int - -func putattr(abbrev int, form int, cls int, value int64, data interface{}) { +func putattr(s *LSym, abbrev int, form int, cls int, value int64, data interface{}) { switch form { case DW_FORM_addr: // address if Linkmode == LinkExternal { value -= (data.(*LSym)).Value - adddwarfrel(infosec, data.(*LSym), infoo, Thearch.Ptrsize, value) + Addaddrplus(Ctxt, s, data.(*LSym), value) break } - addrput(value) + addrput(s, value) case DW_FORM_block1: // block if cls == DW_CLS_ADDRESS { - Cput(uint8(1 + Thearch.Ptrsize)) - Cput(DW_OP_addr) - if Linkmode == LinkExternal { - value -= (data.(*LSym)).Value - adddwarfrel(infosec, data.(*LSym), infoo, Thearch.Ptrsize, value) - break - } - - addrput(value) + Adduint8(Ctxt, s, uint8(1+Thearch.Ptrsize)) + Adduint8(Ctxt, s, DW_OP_addr) + Addaddr(Ctxt, s, data.(*LSym)) break } value &= 0xff - Cput(uint8(value)) + Adduint8(Ctxt, s, uint8(value)) p := data.([]byte) for i := 0; int64(i) < value; i++ { - Cput(uint8(p[i])) + Adduint8(Ctxt, s, uint8(p[i])) } case DW_FORM_block2: // block value &= 0xffff - Thearch.Wput(uint16(value)) + Adduint16(Ctxt, s, uint16(value)) p := data.([]byte) for i := 0; int64(i) < value; i++ { - Cput(uint8(p[i])) + Adduint8(Ctxt, s, uint8(p[i])) } case DW_FORM_block4: // block value &= 0xffffffff - Thearch.Lput(uint32(value)) + Adduint32(Ctxt, s, uint32(value)) p := data.([]byte) for i := 0; int64(i) < value; i++ { - Cput(uint8(p[i])) + Adduint8(Ctxt, s, uint8(p[i])) } case DW_FORM_block: // block - uleb128put(value) + uleb128put(s, value) p := data.([]byte) for i := 0; int64(i) < value; i++ { - Cput(uint8(p[i])) + Adduint8(Ctxt, s, uint8(p[i])) } case DW_FORM_data1: // constant - Cput(uint8(value)) + Adduint8(Ctxt, s, uint8(value)) case DW_FORM_data2: // constant - Thearch.Wput(uint16(value)) + Adduint16(Ctxt, s, uint16(value)) case DW_FORM_data4: // constant, {line,loclist,mac,rangelist}ptr if Linkmode == LinkExternal && cls == DW_CLS_PTR { - adddwarfrel(infosec, linesym, infoo, 4, value) + adddwarfref(Ctxt, s, linesec, 4) break } - Thearch.Lput(uint32(value)) + Adduint32(Ctxt, s, uint32(value)) case DW_FORM_data8: // constant, {line,loclist,mac,rangelist}ptr - Thearch.Vput(uint64(value)) + Adduint64(Ctxt, s, uint64(value)) case DW_FORM_sdata: // constant - sleb128put(value) + sleb128put(s, value) case DW_FORM_udata: // constant - uleb128put(value) + uleb128put(s, value) case DW_FORM_string: // string - strnput(data.(string), int(value+1)) + str := data.(string) + Addstring(s, str) + for i := int64(len(str)); i < value; i++ { + Adduint8(Ctxt, s, 0) + } case DW_FORM_flag: // flag if value != 0 { - Cput(1) + Adduint8(Ctxt, s, 1) } else { - Cput(0) + Adduint8(Ctxt, s, 0) } // In DWARF 2 (which is what we claim to generate), @@ -753,21 +683,13 @@ func putattr(abbrev int, form int, cls int, value int64, data interface{}) { if data == nil { Diag("dwarf: null reference in %d", abbrev) if Thearch.Ptrsize == 8 { - Thearch.Vput(0) // invalid dwarf, gdb will complain. + Adduint64(Ctxt, s, 0) // invalid dwarf, gdb will complain. } else { - Thearch.Lput(0) // invalid dwarf, gdb will complain. + Adduint32(Ctxt, s, 0) // invalid dwarf, gdb will complain. } } else { - off := (data.(*DWDie)).offs - if off == 0 { - fwdcount++ - } - if Linkmode == LinkExternal { - adddwarfrel(infosec, infosym, infoo, Thearch.Ptrsize, off) - break - } - - addrput(off) + dsym := data.(*LSym) + adddwarfref(Ctxt, s, dsym, Thearch.Ptrsize) } case DW_FORM_ref1, // reference within the compilation unit @@ -786,34 +708,45 @@ func putattr(abbrev int, form int, cls int, value int64, data interface{}) { // Note that we can (and do) add arbitrary attributes to a DIE, but // only the ones actually listed in the Abbrev will be written out. -func putattrs(abbrev int, attr *DWAttr) { +func putattrs(s *LSym, abbrev int, attr *DWAttr) { Outer: for _, f := range abbrevs[abbrev].attr { for ap := attr; ap != nil; ap = ap.link { if ap.atr == f.attr { - putattr(abbrev, int(f.form), int(ap.cls), ap.value, ap.data) + putattr(s, abbrev, int(f.form), int(ap.cls), ap.value, ap.data) continue Outer } } - putattr(abbrev, int(f.form), 0, 0, nil) + putattr(s, abbrev, int(f.form), 0, 0, nil) } } -func putdies(die *DWDie) { +func putdies(prev *LSym, die *DWDie) *LSym { for ; die != nil; die = die.link { - putdie(die) + prev = putdie(prev, die) } + Adduint8(Ctxt, prev, 0) + return prev } -func putdie(die *DWDie) { - die.offs = Cpos() - infoo - uleb128put(int64(die.abbrev)) - putattrs(die.abbrev, die.attr) +func putdie(prev *LSym, die *DWDie) *LSym { + s := die.sym + if s == nil { + s = prev + } else { + if s.Attr.OnList() { + log.Fatalf("symbol %s listed multiple times", s.Name) + } + s.Attr |= AttrOnList + prev.Next = s + } + uleb128put(s, int64(die.abbrev)) + putattrs(s, die.abbrev, die.attr) if abbrevs[die.abbrev].children != 0 { - putdies(die.child) - Cput(0) + return putdies(s, die.child) } + return s } func reverselist(list **DWDie) { @@ -880,44 +813,49 @@ func dotypedef(parent *DWDie, name string, def *DWDie) { Diag("dwarf: bad def in dotypedef") } + def.sym = Linklookup(Ctxt, def.sym.Name+".def", 0) + def.sym.Type = obj.SDWARFINFO + // The typedef entry must be created after the def, // so that future lookups will find the typedef instead // of the real definition. This hooks the typedef into any // circular definition loops, so that gdb can understand them. - die := newdie(parent, DW_ABRV_TYPEDECL, name) + die := newdie(parent, DW_ABRV_TYPEDECL, name, 0) - newrefattr(die, DW_AT_type, def) + newrefattr(die, DW_AT_type, def.sym) } // Define gotype, for composite ones recurse into constituents. -func defgotype(gotype *LSym) *DWDie { +func defgotype(gotype *LSym) *LSym { if gotype == nil { - return mustFind(&dwtypes, "") + return mustFind("") } if !strings.HasPrefix(gotype.Name, "type.") { Diag("dwarf: type name doesn't start with \"type.\": %s", gotype.Name) - return mustFind(&dwtypes, "") + return mustFind("") } name := gotype.Name[5:] // could also decode from Type.string - die := find(&dwtypes, name) + sdie := find(name) - if die != nil { - return die + if sdie != nil { + return sdie } - if false && Debug['v'] > 2 { - fmt.Printf("new type: %v\n", gotype) - } + return newtype(gotype).sym +} +func newtype(gotype *LSym) *DWDie { + name := gotype.Name[5:] // could also decode from Type.string kind := decodetype_kind(gotype) bytesize := decodetype_size(gotype) + var die *DWDie switch kind { case obj.KindBool: - die = newdie(&dwtypes, DW_ABRV_BASETYPE, name) + die = newdie(&dwtypes, DW_ABRV_BASETYPE, name, 0) newattr(die, DW_AT_encoding, DW_CLS_CONSTANT, DW_ATE_boolean, 0) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) @@ -926,7 +864,7 @@ func defgotype(gotype *LSym) *DWDie { obj.KindInt16, obj.KindInt32, obj.KindInt64: - die = newdie(&dwtypes, DW_ABRV_BASETYPE, name) + die = newdie(&dwtypes, DW_ABRV_BASETYPE, name, 0) newattr(die, DW_AT_encoding, DW_CLS_CONSTANT, DW_ATE_signed, 0) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) @@ -936,66 +874,69 @@ func defgotype(gotype *LSym) *DWDie { obj.KindUint32, obj.KindUint64, obj.KindUintptr: - die = newdie(&dwtypes, DW_ABRV_BASETYPE, name) + die = newdie(&dwtypes, DW_ABRV_BASETYPE, name, 0) newattr(die, DW_AT_encoding, DW_CLS_CONSTANT, DW_ATE_unsigned, 0) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) case obj.KindFloat32, obj.KindFloat64: - die = newdie(&dwtypes, DW_ABRV_BASETYPE, name) + die = newdie(&dwtypes, DW_ABRV_BASETYPE, name, 0) newattr(die, DW_AT_encoding, DW_CLS_CONSTANT, DW_ATE_float, 0) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) case obj.KindComplex64, obj.KindComplex128: - die = newdie(&dwtypes, DW_ABRV_BASETYPE, name) + die = newdie(&dwtypes, DW_ABRV_BASETYPE, name, 0) newattr(die, DW_AT_encoding, DW_CLS_CONSTANT, DW_ATE_complex_float, 0) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) case obj.KindArray: - die = newdie(&dwtypes, DW_ABRV_ARRAYTYPE, name) + die = newdie(&dwtypes, DW_ABRV_ARRAYTYPE, name, 0) dotypedef(&dwtypes, name, die) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) s := decodetype_arrayelem(gotype) newrefattr(die, DW_AT_type, defgotype(s)) - fld := newdie(die, DW_ABRV_ARRAYRANGE, "range") + fld := newdie(die, DW_ABRV_ARRAYRANGE, "range", 0) // use actual length not upper bound; correct for 0-length arrays. newattr(fld, DW_AT_count, DW_CLS_CONSTANT, decodetype_arraylen(gotype), 0) - newrefattr(fld, DW_AT_type, mustFind(&dwtypes, "uintptr")) + newrefattr(fld, DW_AT_type, mustFind("uintptr")) case obj.KindChan: - die = newdie(&dwtypes, DW_ABRV_CHANTYPE, name) + die = newdie(&dwtypes, DW_ABRV_CHANTYPE, name, 0) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) s := decodetype_chanelem(gotype) newrefattr(die, DW_AT_go_elem, defgotype(s)) + // Save elem type for synthesizechantypes. We could synthesize here + // but that would change the order of DIEs we output. + newrefattr(die, DW_AT_type, s) case obj.KindFunc: - die = newdie(&dwtypes, DW_ABRV_FUNCTYPE, name) + die = newdie(&dwtypes, DW_ABRV_FUNCTYPE, name, 0) dotypedef(&dwtypes, name, die) - newrefattr(die, DW_AT_type, mustFind(&dwtypes, "void")) + newrefattr(die, DW_AT_type, mustFind("void")) nfields := decodetype_funcincount(gotype) var fld *DWDie var s *LSym for i := 0; i < nfields; i++ { s = decodetype_funcintype(gotype, i) - fld = newdie(die, DW_ABRV_FUNCTYPEPARAM, s.Name[5:]) + fld = newdie(die, DW_ABRV_FUNCTYPEPARAM, s.Name[5:], 0) newrefattr(fld, DW_AT_type, defgotype(s)) } if decodetype_funcdotdotdot(gotype) { - newdie(die, DW_ABRV_DOTDOTDOT, "...") + newdie(die, DW_ABRV_DOTDOTDOT, "...", 0) } nfields = decodetype_funcoutcount(gotype) for i := 0; i < nfields; i++ { s = decodetype_funcouttype(gotype, i) - fld = newdie(die, DW_ABRV_FUNCTYPEPARAM, s.Name[5:]) + fld = newdie(die, DW_ABRV_FUNCTYPEPARAM, s.Name[5:], 0) newrefattr(fld, DW_AT_type, defptrto(defgotype(s))) } case obj.KindInterface: - die = newdie(&dwtypes, DW_ABRV_IFACETYPE, name) + die = newdie(&dwtypes, DW_ABRV_IFACETYPE, name, 0) dotypedef(&dwtypes, name, die) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) nfields := int(decodetype_ifacemethodcount(gotype)) @@ -1008,31 +949,35 @@ func defgotype(gotype *LSym) *DWDie { newrefattr(die, DW_AT_type, defgotype(s)) case obj.KindMap: - die = newdie(&dwtypes, DW_ABRV_MAPTYPE, name) + die = newdie(&dwtypes, DW_ABRV_MAPTYPE, name, 0) s := decodetype_mapkey(gotype) newrefattr(die, DW_AT_go_key, defgotype(s)) s = decodetype_mapvalue(gotype) newrefattr(die, DW_AT_go_elem, defgotype(s)) + // Save gotype for use in synthesizemaptypes. We could synthesize here, + // but that would change the order of the DIEs. + newrefattr(die, DW_AT_type, gotype) case obj.KindPtr: - die = newdie(&dwtypes, DW_ABRV_PTRTYPE, name) + die = newdie(&dwtypes, DW_ABRV_PTRTYPE, name, 0) dotypedef(&dwtypes, name, die) s := decodetype_ptrelem(gotype) newrefattr(die, DW_AT_type, defgotype(s)) case obj.KindSlice: - die = newdie(&dwtypes, DW_ABRV_SLICETYPE, name) + die = newdie(&dwtypes, DW_ABRV_SLICETYPE, name, 0) dotypedef(&dwtypes, name, die) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) s := decodetype_arrayelem(gotype) - newrefattr(die, DW_AT_go_elem, defgotype(s)) + elem := defgotype(s) + newrefattr(die, DW_AT_go_elem, elem) case obj.KindString: - die = newdie(&dwtypes, DW_ABRV_STRINGTYPE, name) + die = newdie(&dwtypes, DW_ABRV_STRINGTYPE, name, 0) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) case obj.KindStruct: - die = newdie(&dwtypes, DW_ABRV_STRUCTTYPE, name) + die = newdie(&dwtypes, DW_ABRV_STRUCTTYPE, name, 0) dotypedef(&dwtypes, name, die) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0) nfields := decodetype_structfieldcount(gotype) @@ -1045,32 +990,41 @@ func defgotype(gotype *LSym) *DWDie { if f == "" { f = s.Name[5:] // skip "type." } - fld = newdie(die, DW_ABRV_STRUCTFIELD, f) + fld = newdie(die, DW_ABRV_STRUCTFIELD, f, 0) newrefattr(fld, DW_AT_type, defgotype(s)) newmemberoffsetattr(fld, int32(decodetype_structfieldoffs(gotype, i))) } case obj.KindUnsafePointer: - die = newdie(&dwtypes, DW_ABRV_BARE_PTRTYPE, name) + die = newdie(&dwtypes, DW_ABRV_BARE_PTRTYPE, name, 0) default: Diag("dwarf: definition of unknown kind %d: %s", kind, gotype.Name) - die = newdie(&dwtypes, DW_ABRV_TYPEDECL, name) - newrefattr(die, DW_AT_type, mustFind(&dwtypes, "")) + die = newdie(&dwtypes, DW_ABRV_TYPEDECL, name, 0) + newrefattr(die, DW_AT_type, mustFind("")) } newattr(die, DW_AT_go_kind, DW_CLS_CONSTANT, int64(kind), 0) + if _, ok := prototypedies[gotype.Name]; ok { + prototypedies[gotype.Name] = die + } + return die } +func nameFromDIESym(dwtype *LSym) string { + return strings.TrimSuffix(dwtype.Name[len(infoprefix):], ".def") +} + // Find or construct *T given T. -func defptrto(dwtype *DWDie) *DWDie { - ptrname := fmt.Sprintf("*%s", getattr(dwtype, DW_AT_name).data) - die := find(&dwtypes, ptrname) +func defptrto(dwtype *LSym) *LSym { + ptrname := "*" + nameFromDIESym(dwtype) + die := find(ptrname) if die == nil { - die = newdie(&dwtypes, DW_ABRV_PTRTYPE, ptrname) - newrefattr(die, DW_AT_type, dwtype) + pdie := newdie(&dwtypes, DW_ABRV_PTRTYPE, ptrname, 0) + newrefattr(pdie, DW_AT_type, dwtype) + return pdie.sym } return die @@ -1084,7 +1038,7 @@ func copychildrenexcept(dst *DWDie, src *DWDie, except *DWDie) { if src == except { continue } - c := newdie(dst, src.abbrev, getattr(src, DW_AT_name).data.(string)) + c := newdie(dst, src.abbrev, getattr(src, DW_AT_name).data.(string), 0) for a := src.attr; a != nil; a = a.link { newattr(c, a.atr, int(a.cls), a.value, a.data) } @@ -1100,9 +1054,11 @@ func copychildren(dst *DWDie, src *DWDie) { // Search children (assumed to have DW_TAG_member) for the one named // field and set its DW_AT_type to dwtype -func substitutetype(structdie *DWDie, field string, dwtype *DWDie) { - child := mustFind(structdie, field) +func substitutetype(structdie *DWDie, field string, dwtype *LSym) { + child := findchild(structdie, field) if child == nil { + Exitf("dwarf substitutetype: %s does not have member %s", + getattr(structdie, DW_AT_name).data, field) return } @@ -1114,8 +1070,17 @@ func substitutetype(structdie *DWDie, field string, dwtype *DWDie) { } } +func findprotodie(name string) *DWDie { + die, ok := prototypedies[name] + if ok && die == nil { + defgotype(lookup_or_diag(name)) + die = prototypedies[name] + } + return die +} + func synthesizestringtypes(die *DWDie) { - prototype := walktypedef(defgotype(lookup_or_diag("type.runtime.stringStructDWARF"))) + prototype := walktypedef(findprotodie("type.runtime.stringStructDWARF")) if prototype == nil { return } @@ -1129,7 +1094,7 @@ func synthesizestringtypes(die *DWDie) { } func synthesizeslicetypes(die *DWDie) { - prototype := walktypedef(defgotype(lookup_or_diag("type.runtime.slice"))) + prototype := walktypedef(findprotodie("type.runtime.slice")) if prototype == nil { return } @@ -1139,7 +1104,7 @@ func synthesizeslicetypes(die *DWDie) { continue } copychildren(die, prototype) - elem := getattr(die, DW_AT_go_elem).data.(*DWDie) + elem := getattr(die, DW_AT_go_elem).data.(*LSym) substitutetype(die, "array", defptrto(elem)) } } @@ -1163,9 +1128,21 @@ const ( BucketSize = 8 ) +func mkinternaltype(abbrev int, typename, keyname, valname string, f func(*DWDie)) *LSym { + name := mkinternaltypename(typename, keyname, valname) + symname := infoprefix + name + s := Linkrlookup(Ctxt, symname, 0) + if s != nil { + return s + } + die := newdie(&dwtypes, abbrev, name, 0) + f(die) + return die.sym +} + func synthesizemaptypes(die *DWDie) { - hash := walktypedef(defgotype(lookup_or_diag("type.runtime.hmap"))) - bucket := walktypedef(defgotype(lookup_or_diag("type.runtime.bmap"))) + hash := walktypedef(findprotodie("type.runtime.hmap")) + bucket := walktypedef(findprotodie("type.runtime.bmap")) if hash == nil { return @@ -1175,97 +1152,92 @@ func synthesizemaptypes(die *DWDie) { if die.abbrev != DW_ABRV_MAPTYPE { continue } - - keytype := walktypedef(getattr(die, DW_AT_go_key).data.(*DWDie)) - valtype := walktypedef(getattr(die, DW_AT_go_elem).data.(*DWDie)) + gotype := getattr(die, DW_AT_type).data.(*LSym) + keytype := decodetype_mapkey(gotype) + valtype := decodetype_mapvalue(gotype) + keysize, valsize := decodetype_size(keytype), decodetype_size(valtype) + keytype, valtype = walksymtypedef(defgotype(keytype)), walksymtypedef(defgotype(valtype)) // compute size info like hashmap.c does. - keysize, valsize := Thearch.Ptrsize, Thearch.Ptrsize - a := getattr(keytype, DW_AT_byte_size) - if a != nil { - keysize = int(a.value) - } - a = getattr(valtype, DW_AT_byte_size) - if a != nil { - valsize = int(a.value) - } indirect_key, indirect_val := false, false if keysize > MaxKeySize { - keysize = Thearch.Ptrsize + keysize = int64(Thearch.Ptrsize) indirect_key = true } if valsize > MaxValSize { - valsize = Thearch.Ptrsize + valsize = int64(Thearch.Ptrsize) indirect_val = true } // Construct type to represent an array of BucketSize keys - dwhk := newdie(&dwtypes, DW_ABRV_ARRAYTYPE, mkinternaltypename("[]key", getattr(keytype, DW_AT_name).data.(string), "")) - - newattr(dwhk, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize*int64(keysize), 0) - t := keytype - if indirect_key { - t = defptrto(keytype) - } - newrefattr(dwhk, DW_AT_type, t) - fld := newdie(dwhk, DW_ABRV_ARRAYRANGE, "size") - newattr(fld, DW_AT_count, DW_CLS_CONSTANT, BucketSize, 0) - newrefattr(fld, DW_AT_type, mustFind(&dwtypes, "uintptr")) + keyname := nameFromDIESym(keytype) + dwhks := mkinternaltype(DW_ABRV_ARRAYTYPE, "[]key", keyname, "", func(dwhk *DWDie) { + newattr(dwhk, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize*int64(keysize), 0) + t := keytype + if indirect_key { + t = defptrto(keytype) + } + newrefattr(dwhk, DW_AT_type, t) + fld := newdie(dwhk, DW_ABRV_ARRAYRANGE, "size", 0) + newattr(fld, DW_AT_count, DW_CLS_CONSTANT, BucketSize, 0) + newrefattr(fld, DW_AT_type, mustFind("uintptr")) + }) // Construct type to represent an array of BucketSize values - dwhv := newdie(&dwtypes, DW_ABRV_ARRAYTYPE, mkinternaltypename("[]val", getattr(valtype, DW_AT_name).data.(string), "")) - - newattr(dwhv, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize*int64(valsize), 0) - t = valtype - if indirect_val { - t = defptrto(valtype) - } - newrefattr(dwhv, DW_AT_type, t) - fld = newdie(dwhv, DW_ABRV_ARRAYRANGE, "size") - newattr(fld, DW_AT_count, DW_CLS_CONSTANT, BucketSize, 0) - newrefattr(fld, DW_AT_type, mustFind(&dwtypes, "uintptr")) + valname := nameFromDIESym(valtype) + dwhvs := mkinternaltype(DW_ABRV_ARRAYTYPE, "[]val", valname, "", func(dwhv *DWDie) { + newattr(dwhv, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize*int64(valsize), 0) + t := valtype + if indirect_val { + t = defptrto(valtype) + } + newrefattr(dwhv, DW_AT_type, t) + fld := newdie(dwhv, DW_ABRV_ARRAYRANGE, "size", 0) + newattr(fld, DW_AT_count, DW_CLS_CONSTANT, BucketSize, 0) + newrefattr(fld, DW_AT_type, mustFind("uintptr")) + }) // Construct bucket - dwhb := newdie(&dwtypes, DW_ABRV_STRUCTTYPE, mkinternaltypename("bucket", getattr(keytype, DW_AT_name).data.(string), getattr(valtype, DW_AT_name).data.(string))) - - // Copy over all fields except the field "data" from the generic bucket. - // "data" will be replaced with keys/values below. - copychildrenexcept(dwhb, bucket, find(bucket, "data")) - - fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "keys") - newrefattr(fld, DW_AT_type, dwhk) - newmemberoffsetattr(fld, BucketSize) - fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "values") - newrefattr(fld, DW_AT_type, dwhv) - newmemberoffsetattr(fld, BucketSize+BucketSize*int32(keysize)) - fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "overflow") - newrefattr(fld, DW_AT_type, defptrto(dwhb)) - newmemberoffsetattr(fld, BucketSize+BucketSize*(int32(keysize)+int32(valsize))) - if Thearch.Regsize > Thearch.Ptrsize { - fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "pad") - newrefattr(fld, DW_AT_type, mustFind(&dwtypes, "uintptr")) - newmemberoffsetattr(fld, BucketSize+BucketSize*(int32(keysize)+int32(valsize))+int32(Thearch.Ptrsize)) - } + dwhbs := mkinternaltype(DW_ABRV_STRUCTTYPE, "bucket", keyname, valname, func(dwhb *DWDie) { + // Copy over all fields except the field "data" from the generic + // bucket. "data" will be replaced with keys/values below. + copychildrenexcept(dwhb, bucket, findchild(bucket, "data")) + + fld := newdie(dwhb, DW_ABRV_STRUCTFIELD, "keys", 0) + newrefattr(fld, DW_AT_type, dwhks) + newmemberoffsetattr(fld, BucketSize) + fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "values", 0) + newrefattr(fld, DW_AT_type, dwhvs) + newmemberoffsetattr(fld, BucketSize+BucketSize*int32(keysize)) + fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "overflow", 0) + newrefattr(fld, DW_AT_type, defptrto(dwhb.sym)) + newmemberoffsetattr(fld, BucketSize+BucketSize*(int32(keysize)+int32(valsize))) + if Thearch.Regsize > Thearch.Ptrsize { + fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "pad", 0) + newrefattr(fld, DW_AT_type, mustFind("uintptr")) + newmemberoffsetattr(fld, BucketSize+BucketSize*(int32(keysize)+int32(valsize))+int32(Thearch.Ptrsize)) + } - newattr(dwhb, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize+BucketSize*int64(keysize)+BucketSize*int64(valsize)+int64(Thearch.Regsize), 0) + newattr(dwhb, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize+BucketSize*int64(keysize)+BucketSize*int64(valsize)+int64(Thearch.Regsize), 0) + }) // Construct hash - dwh := newdie(&dwtypes, DW_ABRV_STRUCTTYPE, mkinternaltypename("hash", getattr(keytype, DW_AT_name).data.(string), getattr(valtype, DW_AT_name).data.(string))) - - copychildren(dwh, hash) - substitutetype(dwh, "buckets", defptrto(dwhb)) - substitutetype(dwh, "oldbuckets", defptrto(dwhb)) - newattr(dwh, DW_AT_byte_size, DW_CLS_CONSTANT, getattr(hash, DW_AT_byte_size).value, nil) + dwhs := mkinternaltype(DW_ABRV_STRUCTTYPE, "hash", keyname, valname, func(dwh *DWDie) { + copychildren(dwh, hash) + substitutetype(dwh, "buckets", defptrto(dwhbs)) + substitutetype(dwh, "oldbuckets", defptrto(dwhbs)) + newattr(dwh, DW_AT_byte_size, DW_CLS_CONSTANT, getattr(hash, DW_AT_byte_size).value, nil) + }) // make map type a pointer to hash - newrefattr(die, DW_AT_type, defptrto(dwh)) + newrefattr(die, DW_AT_type, defptrto(dwhs)) } } func synthesizechantypes(die *DWDie) { - sudog := walktypedef(defgotype(lookup_or_diag("type.runtime.sudog"))) - waitq := walktypedef(defgotype(lookup_or_diag("type.runtime.waitq"))) - hchan := walktypedef(defgotype(lookup_or_diag("type.runtime.hchan"))) + sudog := walktypedef(findprotodie("type.runtime.sudog")) + waitq := walktypedef(findprotodie("type.runtime.waitq")) + hchan := walktypedef(findprotodie("type.runtime.hchan")) if sudog == nil || waitq == nil || hchan == nil { return } @@ -1276,42 +1248,41 @@ func synthesizechantypes(die *DWDie) { if die.abbrev != DW_ABRV_CHANTYPE { continue } - elemsize := Thearch.Ptrsize - elemtype := getattr(die, DW_AT_go_elem).data.(*DWDie) - a := getattr(elemtype, DW_AT_byte_size) - if a != nil { - elemsize = int(a.value) - } + elemgotype := getattr(die, DW_AT_type).data.(*LSym) + elemsize := decodetype_size(elemgotype) + elemname := elemgotype.Name[5:] + elemtype := walksymtypedef(defgotype(elemgotype)) // sudog - dws := newdie(&dwtypes, DW_ABRV_STRUCTTYPE, mkinternaltypename("sudog", getattr(elemtype, DW_AT_name).data.(string), "")) - - copychildren(dws, sudog) - substitutetype(dws, "elem", elemtype) - if elemsize > 8 { - elemsize -= 8 - } else { - elemsize = 0 - } - newattr(dws, DW_AT_byte_size, DW_CLS_CONSTANT, int64(sudogsize)+int64(elemsize), nil) + dwss := mkinternaltype(DW_ABRV_STRUCTTYPE, "sudog", elemname, "", func(dws *DWDie) { + copychildren(dws, sudog) + substitutetype(dws, "elem", elemtype) + if elemsize > 8 { + elemsize -= 8 + } else { + elemsize = 0 + } + newattr(dws, DW_AT_byte_size, DW_CLS_CONSTANT, int64(sudogsize)+int64(elemsize), nil) + }) // waitq - dww := newdie(&dwtypes, DW_ABRV_STRUCTTYPE, mkinternaltypename("waitq", getattr(elemtype, DW_AT_name).data.(string), "")) + dwws := mkinternaltype(DW_ABRV_STRUCTTYPE, "waitq", elemname, "", func(dww *DWDie) { - copychildren(dww, waitq) - substitutetype(dww, "first", defptrto(dws)) - substitutetype(dww, "last", defptrto(dws)) - newattr(dww, DW_AT_byte_size, DW_CLS_CONSTANT, getattr(waitq, DW_AT_byte_size).value, nil) + copychildren(dww, waitq) + substitutetype(dww, "first", defptrto(dwss)) + substitutetype(dww, "last", defptrto(dwss)) + newattr(dww, DW_AT_byte_size, DW_CLS_CONSTANT, getattr(waitq, DW_AT_byte_size).value, nil) + }) // hchan - dwh := newdie(&dwtypes, DW_ABRV_STRUCTTYPE, mkinternaltypename("hchan", getattr(elemtype, DW_AT_name).data.(string), "")) + dwhs := mkinternaltype(DW_ABRV_STRUCTTYPE, "hchan", elemname, "", func(dwh *DWDie) { + copychildren(dwh, hchan) + substitutetype(dwh, "recvq", dwws) + substitutetype(dwh, "sendq", dwws) + newattr(dwh, DW_AT_byte_size, DW_CLS_CONSTANT, getattr(hchan, DW_AT_byte_size).value, nil) + }) - copychildren(dwh, hchan) - substitutetype(dwh, "recvq", dww) - substitutetype(dwh, "sendq", dww) - newattr(dwh, DW_AT_byte_size, DW_CLS_CONSTANT, getattr(hchan, DW_AT_byte_size).value, nil) - - newrefattr(die, DW_AT_type, defptrto(dwh)) + newrefattr(die, DW_AT_type, defptrto(dwhs)) } } @@ -1331,13 +1302,13 @@ func defdwsymb(sym *LSym, s string, t int, v int64, size int64, ver int, gotype var dv *DWDie - var dt *DWDie + var dt *LSym switch t { default: return case 'd', 'b', 'D', 'B': - dv = newdie(&dwglobals, DW_ABRV_VARIABLE, s) + dv = newdie(&dwglobals, DW_ABRV_VARIABLE, s, ver) newabslocexprattr(dv, v, sym) if ver == 0 { newattr(dv, DW_AT_external, DW_CLS_FLAG, 1, 0) @@ -1386,23 +1357,23 @@ const ( OPCODE_BASE = 10 ) -func putpclcdelta(delta_pc int64, delta_lc int64) { +func putpclcdelta(s *LSym, delta_pc int64, delta_lc int64) { if LINE_BASE <= delta_lc && delta_lc < LINE_BASE+LINE_RANGE { var opcode int64 = OPCODE_BASE + (delta_lc - LINE_BASE) + (LINE_RANGE * delta_pc) if OPCODE_BASE <= opcode && opcode < 256 { - Cput(uint8(opcode)) + Adduint8(Ctxt, s, uint8(opcode)) return } } if delta_pc != 0 { - Cput(DW_LNS_advance_pc) - sleb128put(delta_pc) + Adduint8(Ctxt, s, DW_LNS_advance_pc) + sleb128put(s, delta_pc) } - Cput(DW_LNS_advance_line) - sleb128put(delta_lc) - Cput(DW_LNS_copy) + Adduint8(Ctxt, s, DW_LNS_advance_line) + sleb128put(s, delta_lc) + Adduint8(Ctxt, s, DW_LNS_copy) } func newcfaoffsetattr(die *DWDie, offs int32) { @@ -1428,26 +1399,6 @@ func mkvarname(name string, da int) string { * Walk prog table, emit line program and build DIE tree. */ -// flush previous compilation unit. -func flushunit(dwinfo *DWDie, pc int64, pcsym *LSym, unitstart int64, header_length int32) { - if dwinfo != nil && pc != 0 { - newattr(dwinfo, DW_AT_high_pc, DW_CLS_ADDRESS, pc+1, pcsym) - } - - if unitstart >= 0 { - Cput(0) // start extended opcode - uleb128put(1) - Cput(DW_LNE_end_sequence) - - here := Cpos() - Cseek(unitstart) - Thearch.Lput(uint32(here - unitstart - 4)) // unit_length - Thearch.Wput(2) // dwarf version - Thearch.Lput(uint32(header_length)) // header length starting here - Cseek(here) - } -} - func getCompilationDir() string { if dir, err := os.Getwd(); err == nil { return dir @@ -1455,28 +1406,30 @@ func getCompilationDir() string { return "/" } -func writelines() { +func writelines(prev *LSym) *LSym { if linesec == nil { - linesec = Linklookup(Ctxt, ".dwarfline", 0) + linesec = Linklookup(Ctxt, ".debug_line", 0) } + linesec.Type = obj.SDWARFSECT linesec.R = linesec.R[:0] + ls := linesec + prev.Next = ls + unitstart := int64(-1) + headerstart := int64(-1) headerend := int64(-1) epc := int64(0) var epcs *LSym - lineo = Cpos() var dwinfo *DWDie - flushunit(dwinfo, epc, epcs, unitstart, int32(headerend-unitstart-10)) - unitstart = Cpos() lang := DW_LANG_Go s := Ctxt.Textp - dwinfo = newdie(&dwroot, DW_ABRV_COMPUNIT, "go") + dwinfo = newdie(&dwroot, DW_ABRV_COMPUNIT, "go", 0) newattr(dwinfo, DW_AT_language, DW_CLS_CONSTANT, int64(lang), 0) - newattr(dwinfo, DW_AT_stmt_list, DW_CLS_PTR, unitstart-lineo, 0) + newattr(dwinfo, DW_AT_stmt_list, DW_CLS_PTR, 0, 0) newattr(dwinfo, DW_AT_low_pc, DW_CLS_ADDRESS, s.Value, s) // OS X linker requires compilation dir or absolute path in comp unit name to output debug info. compDir := getCompilationDir() @@ -1484,26 +1437,30 @@ func writelines() { // Write .debug_line Line Number Program Header (sec 6.2.4) // Fields marked with (*) must be changed for 64-bit dwarf - Thearch.Lput(0) // unit_length (*), will be filled in by flushunit. - Thearch.Wput(2) // dwarf version (appendix F) - Thearch.Lput(0) // header_length (*), filled in by flushunit. + unit_length_offset := ls.Size + Adduint32(Ctxt, ls, 0) // unit_length (*), filled in at end. + unitstart = ls.Size + Adduint16(Ctxt, ls, 2) // dwarf version (appendix F) + header_length_offset := ls.Size + Adduint32(Ctxt, ls, 0) // header_length (*), filled in at end. + headerstart = ls.Size // cpos == unitstart + 4 + 2 + 4 - Cput(1) // minimum_instruction_length - Cput(1) // default_is_stmt - Cput(LINE_BASE & 0xFF) // line_base - Cput(LINE_RANGE) // line_range - Cput(OPCODE_BASE) // opcode_base - Cput(0) // standard_opcode_lengths[1] - Cput(1) // standard_opcode_lengths[2] - Cput(1) // standard_opcode_lengths[3] - Cput(1) // standard_opcode_lengths[4] - Cput(1) // standard_opcode_lengths[5] - Cput(0) // standard_opcode_lengths[6] - Cput(0) // standard_opcode_lengths[7] - Cput(0) // standard_opcode_lengths[8] - Cput(1) // standard_opcode_lengths[9] - Cput(0) // include_directories (empty) + Adduint8(Ctxt, ls, 1) // minimum_instruction_length + Adduint8(Ctxt, ls, 1) // default_is_stmt + Adduint8(Ctxt, ls, LINE_BASE&0xFF) // line_base + Adduint8(Ctxt, ls, LINE_RANGE) // line_range + Adduint8(Ctxt, ls, OPCODE_BASE) // opcode_base + Adduint8(Ctxt, ls, 0) // standard_opcode_lengths[1] + Adduint8(Ctxt, ls, 1) // standard_opcode_lengths[2] + Adduint8(Ctxt, ls, 1) // standard_opcode_lengths[3] + Adduint8(Ctxt, ls, 1) // standard_opcode_lengths[4] + Adduint8(Ctxt, ls, 1) // standard_opcode_lengths[5] + Adduint8(Ctxt, ls, 0) // standard_opcode_lengths[6] + Adduint8(Ctxt, ls, 0) // standard_opcode_lengths[7] + Adduint8(Ctxt, ls, 0) // standard_opcode_lengths[8] + Adduint8(Ctxt, ls, 1) // standard_opcode_lengths[9] + Adduint8(Ctxt, ls, 0) // include_directories (empty) files := make([]*LSym, Ctxt.Nhistfile) @@ -1512,25 +1469,28 @@ func writelines() { } for i := 0; int32(i) < Ctxt.Nhistfile; i++ { - strnput(files[i].Name, len(files[i].Name)+4) + Addstring(ls, files[i].Name) + Adduint8(Ctxt, ls, 0) + Adduint8(Ctxt, ls, 0) + Adduint8(Ctxt, ls, 0) } // 4 zeros: the string termination + 3 fields. - Cput(0) + Adduint8(Ctxt, ls, 0) // terminate file_names. - headerend = Cpos() + headerend = ls.Size - Cput(0) // start extended opcode - uleb128put(1 + int64(Thearch.Ptrsize)) - Cput(DW_LNE_set_address) + Adduint8(Ctxt, ls, 0) // start extended opcode + uleb128put(ls, 1+int64(Thearch.Ptrsize)) + Adduint8(Ctxt, ls, DW_LNE_set_address) pc := s.Value line := 1 file := 1 if Linkmode == LinkExternal { - adddwarfrel(linesec, s, lineo, Thearch.Ptrsize, 0) + Addaddr(Ctxt, ls, s) } else { - addrput(pc) + addrput(ls, pc) } var pcfile Pciter @@ -1538,7 +1498,7 @@ func writelines() { for Ctxt.Cursym = Ctxt.Textp; Ctxt.Cursym != nil; Ctxt.Cursym = Ctxt.Cursym.Next { s = Ctxt.Cursym - dwfunc := newdie(dwinfo, DW_ABRV_FUNCTION, s.Name) + dwfunc := newdie(dwinfo, DW_ABRV_FUNCTION, s.Name, int(s.Version)) newattr(dwfunc, DW_AT_low_pc, DW_CLS_ADDRESS, s.Value, s) epc = s.Value + s.Size epcs = s @@ -1568,12 +1528,12 @@ func writelines() { } if int32(file) != pcfile.value { - Cput(DW_LNS_set_file) - uleb128put(int64(pcfile.value)) + Adduint8(Ctxt, ls, DW_LNS_set_file) + uleb128put(ls, int64(pcfile.value)) file = int(pcfile.value) } - putpclcdelta(s.Value+int64(pcline.pc)-pc, int64(pcline.value)-int64(line)) + putpclcdelta(ls, s.Value+int64(pcline.pc)-pc, int64(pcline.value)-int64(line)) pc = s.Value + int64(pcline.pc) line = int(pcline.value) @@ -1610,7 +1570,7 @@ func writelines() { continue } var n string - if find(dwfunc, a.Asym.Name) != nil { + if findchild(dwfunc, a.Asym.Name) != nil { n = mkvarname(a.Asym.Name, da) } else { n = a.Asym.Name @@ -1621,7 +1581,7 @@ func writelines() { n = n[i+1:] } - dwvar := newdie(dwfunc, dt, n) + dwvar := newdie(dwfunc, dt, n, 0) newcfaoffsetattr(dwvar, int32(offs)) newrefattr(dwvar, DW_AT_type, defgotype(a.Gotype)) @@ -1642,8 +1602,16 @@ func writelines() { } } - flushunit(dwinfo, epc, epcs, unitstart, int32(headerend-unitstart-10)) - linesize = Cpos() - lineo + Adduint8(Ctxt, ls, 0) // start extended opcode + uleb128put(ls, 1) + Adduint8(Ctxt, ls, DW_LNE_end_sequence) + + newattr(dwinfo, DW_AT_high_pc, DW_CLS_ADDRESS, epc+1, epcs) + + setuint32(Ctxt, ls, unit_length_offset, uint32(ls.Size-unitstart)) + setuint32(Ctxt, ls, header_length_offset, uint32(headerend-headerstart)) + + return ls } /* @@ -1675,47 +1643,49 @@ func appendPCDeltaCFA(b []byte, deltapc, cfa int64) []byte { return b } -func writeframes() { +func writeframes(prev *LSym) *LSym { if framesec == nil { - framesec = Linklookup(Ctxt, ".dwarfframe", 0) + framesec = Linklookup(Ctxt, ".debug_frame", 0) } + framesec.Type = obj.SDWARFSECT framesec.R = framesec.R[:0] - frameo = Cpos() + fs := framesec + prev.Next = fs // Emit the CIE, Section 6.4.1 - Thearch.Lput(CIERESERVE) // initial length, must be multiple of thearch.ptrsize - Thearch.Lput(0xffffffff) // cid. - Cput(3) // dwarf version (appendix F) - Cput(0) // augmentation "" - uleb128put(1) // code_alignment_factor - sleb128put(DATAALIGNMENTFACTOR) // guess - uleb128put(int64(Thearch.Dwarfreglr)) // return_address_register + Adduint32(Ctxt, fs, CIERESERVE) // initial length, must be multiple of thearch.ptrsize + Adduint32(Ctxt, fs, 0xffffffff) // cid. + Adduint8(Ctxt, fs, 3) // dwarf version (appendix F) + Adduint8(Ctxt, fs, 0) // augmentation "" + uleb128put(fs, 1) // code_alignment_factor + sleb128put(fs, DATAALIGNMENTFACTOR) // guess + uleb128put(fs, int64(Thearch.Dwarfreglr)) // return_address_register - Cput(DW_CFA_def_cfa) + Adduint8(Ctxt, fs, DW_CFA_def_cfa) - uleb128put(int64(Thearch.Dwarfregsp)) // register SP (**ABI-dependent, defined in l.h) + uleb128put(fs, int64(Thearch.Dwarfregsp)) // register SP (**ABI-dependent, defined in l.h) if haslinkregister() { - uleb128put(int64(0)) // offset + uleb128put(fs, int64(0)) // offset } else { - uleb128put(int64(Thearch.Ptrsize)) // offset + uleb128put(fs, int64(Thearch.Ptrsize)) // offset } - Cput(DW_CFA_offset_extended) - uleb128put(int64(Thearch.Dwarfreglr)) // return address + Adduint8(Ctxt, fs, DW_CFA_offset_extended) + uleb128put(fs, int64(Thearch.Dwarfreglr)) // return address if haslinkregister() { - uleb128put(int64(0) / DATAALIGNMENTFACTOR) // at cfa - 0 + uleb128put(fs, int64(0)/DATAALIGNMENTFACTOR) // at cfa - 0 } else { - uleb128put(int64(-Thearch.Ptrsize) / DATAALIGNMENTFACTOR) // at cfa - x*4 + uleb128put(fs, int64(-Thearch.Ptrsize)/DATAALIGNMENTFACTOR) // at cfa - x*4 } // 4 is to exclude the length field. - pad := CIERESERVE + frameo + 4 - Cpos() + pad := CIERESERVE + 4 - fs.Size if pad < 0 { Exitf("dwarf: CIERESERVE too small by %d bytes.", -pad) } - strnput("", int(pad)) + Addbytes(Ctxt, fs, zeros[:pad]) var deltaBuf []byte var pcsp Pciter @@ -1754,21 +1724,17 @@ func writeframes() { // 4 bytes: Pointer to the CIE above, at offset 0 // ptrsize: initial location // ptrsize: address range - Thearch.Lput(uint32(4 + 2*Thearch.Ptrsize + len(deltaBuf))) // length (excludes itself) + Adduint32(Ctxt, fs, uint32(4+2*Thearch.Ptrsize+len(deltaBuf))) // length (excludes itself) if Linkmode == LinkExternal { - adddwarfrel(framesec, framesym, frameo, 4, 0) // CIE offset - adddwarfrel(framesec, s, frameo, Thearch.Ptrsize, 0) // initial location + adddwarfref(Ctxt, fs, framesec, 4) } else { - Thearch.Lput(0) // CIE offset - addrput(s.Value) // initial location + Adduint32(Ctxt, fs, 0) // CIE offset } - addrput(s.Size) // address range - - Cwrite(deltaBuf) + Addaddr(Ctxt, fs, s) + addrput(fs, s.Size) // address range + Addbytes(Ctxt, fs, deltaBuf) } - - Cflush() - framesize = Cpos() - frameo + return fs } /* @@ -1778,12 +1744,14 @@ const ( COMPUNITHEADERSIZE = 4 + 2 + 4 + 1 ) -func writeinfo() { - fwdcount = 0 +func writeinfo(prev *LSym) *LSym { if infosec == nil { - infosec = Linklookup(Ctxt, ".dwarfinfo", 0) + infosec = Linklookup(Ctxt, ".debug_info", 0) } infosec.R = infosec.R[:0] + infosec.Type = obj.SDWARFINFO + infosec.Attr |= AttrReachable + prev.Next, prev = infosec, infosec if arangessec == nil { arangessec = Linklookup(Ctxt, ".dwarfaranges", 0) @@ -1791,32 +1759,30 @@ func writeinfo() { arangessec.R = arangessec.R[:0] for compunit := dwroot.child; compunit != nil; compunit = compunit.link { - unitstart := Cpos() + s := compunit.sym + prev.Next, prev = s, s // Write .debug_info Compilation Unit Header (sec 7.5.1) // Fields marked with (*) must be changed for 64-bit dwarf // This must match COMPUNITHEADERSIZE above. - Thearch.Lput(0) // unit_length (*), will be filled in later. - Thearch.Wput(2) // dwarf version (appendix F) + Adduint32(Ctxt, s, 0) // unit_length (*), will be filled in later. + Adduint16(Ctxt, s, 2) // dwarf version (appendix F) // debug_abbrev_offset (*) - if Linkmode == LinkExternal { - adddwarfrel(infosec, abbrevsym, infoo, 4, 0) - } else { - Thearch.Lput(0) - } + adddwarfref(Ctxt, s, abbrevsym, 4) - Cput(uint8(Thearch.Ptrsize)) // address_size + Adduint8(Ctxt, s, uint8(Thearch.Ptrsize)) // address_size - putdie(compunit) + prev = putdie(prev, compunit) + cusize := s.Size - 4 // exclude the length field. + for child := s.Next; child != nil; child = child.Next { + cusize += child.Size + } - here := Cpos() - Cseek(unitstart) - Thearch.Lput(uint32(here - unitstart - 4)) // exclude the length field. - Cseek(here) + setuint32(Ctxt, s, 0, uint32(cusize)) + newattr(compunit, DW_AT_byte_size, DW_CLS_CONSTANT, int64(cusize), 0) } - - Cflush() + return prev } /* @@ -1837,48 +1803,49 @@ func ispubtype(die *DWDie) bool { return die.abbrev >= DW_ABRV_NULLTYPE } -func writepub(ispub func(*DWDie) bool) int64 { - sectionstart := Cpos() +func writepub(sname string, ispub func(*DWDie) bool, prev *LSym) *LSym { + s := Linklookup(Ctxt, sname, 0) + s.Type = obj.SDWARFSECT + prev.Next = s for compunit := dwroot.child; compunit != nil; compunit = compunit.link { - unitend := infoo + infosize - unitstart := compunit.offs - COMPUNITHEADERSIZE - if compunit.link != nil { - unitend = compunit.link.offs - COMPUNITHEADERSIZE - } + sectionstart := s.Size + culength := uint32(getattr(compunit, DW_AT_byte_size).value) + 4 // Write .debug_pubnames/types Header (sec 6.1.1) - Thearch.Lput(0) // unit_length (*), will be filled in later. - Thearch.Wput(2) // dwarf version (appendix F) - Thearch.Lput(uint32(unitstart)) // debug_info_offset (of the Comp unit Header) - Thearch.Lput(uint32(unitend - unitstart)) // debug_info_length + Adduint32(Ctxt, s, 0) // unit_length (*), will be filled in later. + Adduint16(Ctxt, s, 2) // dwarf version (appendix F) + adddwarfref(Ctxt, s, compunit.sym, 4) // debug_info_offset (of the Comp unit Header) + Adduint32(Ctxt, s, culength) // debug_info_length for die := compunit.child; die != nil; die = die.link { if !ispub(die) { continue } - Thearch.Lput(uint32(die.offs - unitstart)) dwa := getattr(die, DW_AT_name) - strnput(dwa.data.(string), int(dwa.value+1)) + name := dwa.data.(string) + if die.sym == nil { + fmt.Println("Missing sym for ", name) + } + adddwarfref(Ctxt, s, die.sym, 4) + Addstring(s, name) } - Thearch.Lput(0) + Adduint32(Ctxt, s, 0) - here := Cpos() - Cseek(sectionstart) - Thearch.Lput(uint32(here - sectionstart - 4)) // exclude the length field. - Cseek(here) + setuint32(Ctxt, s, sectionstart, uint32(s.Size-sectionstart)-4) // exclude the length field. } - return sectionstart + return s } /* * emit .debug_aranges. _info must have been written before, * because we need die->offs of dw_globals. */ -func writearanges() int64 { - sectionstart := Cpos() +func writearanges(prev *LSym) *LSym { + s := Linklookup(Ctxt, ".debug_aranges", 0) + s.Type = obj.SDWARFSECT // The first tuple is aligned to a multiple of the size of a single tuple // (twice the size of an address) headersize := int(Rnd(4+2+4+1+1, int64(Thearch.Ptrsize*2))) // don't count unit_length field itself @@ -1894,78 +1861,46 @@ func writearanges() int64 { } // Write .debug_aranges Header + entry (sec 6.1.2) - Thearch.Lput(uint32(headersize) + 4*uint32(Thearch.Ptrsize) - 4) // unit_length (*) - Thearch.Wput(2) // dwarf version (appendix F) - - value := compunit.offs - COMPUNITHEADERSIZE // debug_info_offset - if Linkmode == LinkExternal { - adddwarfrel(arangessec, infosym, sectionstart, 4, value) - } else { - Thearch.Lput(uint32(value)) - } + unitlength := uint32(headersize) + 4*uint32(Thearch.Ptrsize) - 4 + Adduint32(Ctxt, s, unitlength) // unit_length (*) + Adduint16(Ctxt, s, 2) // dwarf version (appendix F) - Cput(uint8(Thearch.Ptrsize)) // address_size - Cput(0) // segment_size - strnput("", headersize-(4+2+4+1+1)) // align to thearch.ptrsize + adddwarfref(Ctxt, s, compunit.sym, 4) - if Linkmode == LinkExternal { - adddwarfrel(arangessec, b.data.(*LSym), sectionstart, Thearch.Ptrsize, b.value-(b.data.(*LSym)).Value) - } else { - addrput(b.value) + Adduint8(Ctxt, s, uint8(Thearch.Ptrsize)) // address_size + Adduint8(Ctxt, s, 0) // segment_size + padding := headersize - (4 + 2 + 4 + 1 + 1) + for i := 0; i < padding; i++ { + Adduint8(Ctxt, s, 0) } - addrput(e.value - b.value) - addrput(0) - addrput(0) + Addaddrplus(Ctxt, s, b.data.(*LSym), b.value-(b.data.(*LSym)).Value) + addrput(s, e.value-b.value) + addrput(s, 0) + addrput(s, 0) } - - Cflush() - return sectionstart -} - -func writegdbscript() int64 { - sectionstart := Cpos() - - if gdbscript != "" { - Cput(1) // magic 1 byte? - strnput(gdbscript, len(gdbscript)+1) - Cflush() + if s.Size > 0 { + prev.Next = s + prev = s } - - return sectionstart + return prev } -func align(size int64) { - if HEADTYPE == obj.Hwindows { // Only Windows PE need section align. - strnput("", int(Rnd(size, PEFILEALIGN)-size)) - } -} +func writegdbscript(prev *LSym) *LSym { -func writedwarfreloc(s *LSym) int64 { - start := Cpos() - for ri := 0; ri < len(s.R); ri++ { - r := &s.R[ri] - i := -1 - if Iself { - i = Thearch.Elfreloc1(r, int64(r.Off)) - } else if HEADTYPE == obj.Hdarwin { - i = Thearch.Machoreloc1(r, int64(r.Off)) - } - if i < 0 { - Diag("unsupported obj reloc %d/%d to %s", r.Type, r.Siz, r.Sym.Name) - } + if gdbscript != "" { + s := Linklookup(Ctxt, ".debug_gdb_scripts", 0) + s.Type = obj.SDWARFSECT + prev.Next = s + prev = s + Adduint8(Ctxt, s, 1) // magic 1 byte? + Addstring(s, gdbscript) } - return start + return prev } -func addmachodwarfsect(prev *Section, name string) *Section { - sect := addsection(&Segdwarf, name, 04) - sect.Extnum = prev.Extnum + 1 - sym := Linklookup(Ctxt, name, 0) - sym.Sect = sect - return sect -} +var prototypedies map[string]*DWDie /* * This is the main entry point for generating dwarf. After emitting @@ -1976,58 +1911,49 @@ func addmachodwarfsect(prev *Section, name string) *Section { * passes. * */ -func Dwarfemitdebugsections() { +func dwarfgeneratedebugsyms() { if Debug['w'] != 0 { // disable dwarf return } + if HEADTYPE == obj.Hplan9 { + return + } if Linkmode == LinkExternal { if !Iself && HEADTYPE != obj.Hdarwin { return } - if HEADTYPE == obj.Hdarwin { - sect := Segdata.Sect - // find the last section. - for sect.Next != nil { - sect = sect.Next - } - sect = addmachodwarfsect(sect, ".debug_abbrev") - sect = addmachodwarfsect(sect, ".debug_line") - sect = addmachodwarfsect(sect, ".debug_frame") - sect = addmachodwarfsect(sect, ".debug_info") - - infosym = Linklookup(Ctxt, ".debug_info", 0) - infosym.Attr |= AttrHidden - - abbrevsym = Linklookup(Ctxt, ".debug_abbrev", 0) - abbrevsym.Attr |= AttrHidden - - linesym = Linklookup(Ctxt, ".debug_line", 0) - linesym.Attr |= AttrHidden + } - framesym = Linklookup(Ctxt, ".debug_frame", 0) - framesym.Attr |= AttrHidden - } + if Debug['v'] != 0 { + fmt.Fprintf(&Bso, "%5.2f dwarf\n", obj.Cputime()) } // For diagnostic messages. newattr(&dwtypes, DW_AT_name, DW_CLS_STRING, int64(len("dwtypes")), "dwtypes") - mkindex(&dwroot) - mkindex(&dwtypes) - mkindex(&dwglobals) - // Some types that must exist to define other ones. - newdie(&dwtypes, DW_ABRV_NULLTYPE, "") + newdie(&dwtypes, DW_ABRV_NULLTYPE, "", 0) - newdie(&dwtypes, DW_ABRV_NULLTYPE, "void") - newdie(&dwtypes, DW_ABRV_BARE_PTRTYPE, "unsafe.Pointer") + newdie(&dwtypes, DW_ABRV_NULLTYPE, "void", 0) + newdie(&dwtypes, DW_ABRV_BARE_PTRTYPE, "unsafe.Pointer", 0) - die := newdie(&dwtypes, DW_ABRV_BASETYPE, "uintptr") // needed for array size + die := newdie(&dwtypes, DW_ABRV_BASETYPE, "uintptr", 0) // needed for array size newattr(die, DW_AT_encoding, DW_CLS_CONSTANT, DW_ATE_unsigned, 0) newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, int64(Thearch.Ptrsize), 0) newattr(die, DW_AT_go_kind, DW_CLS_CONSTANT, obj.KindUintptr, 0) + // Prototypes needed for type synthesis. + prototypedies = map[string]*DWDie{ + "type.runtime.stringStructDWARF": nil, + "type.runtime.slice": nil, + "type.runtime.hmap": nil, + "type.runtime.bmap": nil, + "type.runtime.sudog": nil, + "type.runtime.waitq": nil, + "type.runtime.hchan": nil, + } + // Needed by the prettyprinter code for interface inspection. defgotype(lookup_or_diag("type.runtime._type")) @@ -2036,12 +1962,10 @@ func Dwarfemitdebugsections() { genasmsym(defdwsymb) - writeabbrev() - align(abbrevsize) - writelines() - align(linesize) - writeframes() - align(framesize) + dwarfp = writeabbrev() + last := dwarfp + last = writelines(last) + last = writeframes(last) synthesizestringtypes(dwtypes.child) synthesizeslicetypes(dwtypes.child) @@ -2055,403 +1979,61 @@ func Dwarfemitdebugsections() { movetomodule(&dwtypes) movetomodule(&dwglobals) - infoo = Cpos() - writeinfo() - infoe := Cpos() - pubnameso = infoe - pubtypeso = infoe - arangeso = infoe - gdbscripto = infoe - - if fwdcount > 0 { - if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f dwarf pass 2.\n", obj.Cputime()) - } - Cseek(infoo) - writeinfo() - if fwdcount > 0 { - Exitf("dwarf: unresolved references after first dwarf info pass") - } - - if infoe != Cpos() { - Exitf("dwarf: inconsistent second dwarf info pass") - } - } - - infosize = infoe - infoo - align(infosize) - - pubnameso = writepub(ispubname) - pubnamessize = Cpos() - pubnameso - align(pubnamessize) - - pubtypeso = writepub(ispubtype) - pubtypessize = Cpos() - pubtypeso - align(pubtypessize) + // Need to reorder symbols so SDWARFINFO is after all SDWARFSECT + // (but we need to generate dies before writepub) + writeinfo(last) + infosyms := last.Next - arangeso = writearanges() - arangessize = Cpos() - arangeso - align(arangessize) - - gdbscripto = writegdbscript() - gdbscriptsize = Cpos() - gdbscripto - align(gdbscriptsize) - - for Cpos()&7 != 0 { - Cput(0) - } - if HEADTYPE != obj.Hdarwin { - dwarfemitreloc() - } -} - -func dwarfemitreloc() { - if Debug['w'] != 0 { // disable dwarf - return - } - inforeloco = writedwarfreloc(infosec) - inforelocsize = Cpos() - inforeloco - align(inforelocsize) - - arangesreloco = writedwarfreloc(arangessec) - arangesrelocsize = Cpos() - arangesreloco - align(arangesrelocsize) - - linereloco = writedwarfreloc(linesec) - linerelocsize = Cpos() - linereloco - align(linerelocsize) - - framereloco = writedwarfreloc(framesec) - framerelocsize = Cpos() - framereloco - align(framerelocsize) + last = writepub(".debug_pubnames", ispubname, last) + last = writepub(".debug_pubtypes", ispubtype, last) + last = writearanges(last) + last = writegdbscript(last) + last.Next = infosyms } /* * Elf. */ -const ( - ElfStrDebugAbbrev = iota - ElfStrDebugAranges - ElfStrDebugFrame - ElfStrDebugInfo - ElfStrDebugLine - ElfStrDebugLoc - ElfStrDebugMacinfo - ElfStrDebugPubNames - ElfStrDebugPubTypes - ElfStrDebugRanges - ElfStrDebugStr - ElfStrGDBScripts - ElfStrRelDebugInfo - ElfStrRelDebugAranges - ElfStrRelDebugLine - ElfStrRelDebugFrame - NElfStrDbg -) - -var elfstrdbg [NElfStrDbg]int64 - func dwarfaddshstrings(shstrtab *LSym) { if Debug['w'] != 0 { // disable dwarf return } - elfstrdbg[ElfStrDebugAbbrev] = Addstring(shstrtab, ".debug_abbrev") - elfstrdbg[ElfStrDebugAranges] = Addstring(shstrtab, ".debug_aranges") - elfstrdbg[ElfStrDebugFrame] = Addstring(shstrtab, ".debug_frame") - elfstrdbg[ElfStrDebugInfo] = Addstring(shstrtab, ".debug_info") - elfstrdbg[ElfStrDebugLine] = Addstring(shstrtab, ".debug_line") - elfstrdbg[ElfStrDebugLoc] = Addstring(shstrtab, ".debug_loc") - elfstrdbg[ElfStrDebugMacinfo] = Addstring(shstrtab, ".debug_macinfo") - elfstrdbg[ElfStrDebugPubNames] = Addstring(shstrtab, ".debug_pubnames") - elfstrdbg[ElfStrDebugPubTypes] = Addstring(shstrtab, ".debug_pubtypes") - elfstrdbg[ElfStrDebugRanges] = Addstring(shstrtab, ".debug_ranges") - elfstrdbg[ElfStrDebugStr] = Addstring(shstrtab, ".debug_str") - elfstrdbg[ElfStrGDBScripts] = Addstring(shstrtab, ".debug_gdb_scripts") + Addstring(shstrtab, ".debug_abbrev") + Addstring(shstrtab, ".debug_aranges") + Addstring(shstrtab, ".debug_frame") + Addstring(shstrtab, ".debug_info") + Addstring(shstrtab, ".debug_line") + Addstring(shstrtab, ".debug_pubnames") + Addstring(shstrtab, ".debug_pubtypes") + Addstring(shstrtab, ".debug_gdb_scripts") if Linkmode == LinkExternal { - elfstrdbg[ElfStrRelDebugInfo] = Addstring(shstrtab, elfRelType+".debug_info") - elfstrdbg[ElfStrRelDebugAranges] = Addstring(shstrtab, elfRelType+".debug_aranges") - elfstrdbg[ElfStrRelDebugLine] = Addstring(shstrtab, elfRelType+".debug_line") - elfstrdbg[ElfStrRelDebugFrame] = Addstring(shstrtab, elfRelType+".debug_frame") - - infosym = Linklookup(Ctxt, ".debug_info", 0) - infosym.Attr |= AttrHidden - - abbrevsym = Linklookup(Ctxt, ".debug_abbrev", 0) - abbrevsym.Attr |= AttrHidden - - linesym = Linklookup(Ctxt, ".debug_line", 0) - linesym.Attr |= AttrHidden - - framesym = Linklookup(Ctxt, ".debug_frame", 0) - framesym.Attr |= AttrHidden + Addstring(shstrtab, elfRelType+".debug_info") + Addstring(shstrtab, elfRelType+".debug_aranges") + Addstring(shstrtab, elfRelType+".debug_line") + Addstring(shstrtab, elfRelType+".debug_frame") + Addstring(shstrtab, elfRelType+".debug_pubnames") + Addstring(shstrtab, elfRelType+".debug_pubtypes") } } -// Add section symbols for DWARF debug info. This is called before +// Add section symbols for DWARF debug info. This is called before // dwarfaddelfheaders. func dwarfaddelfsectionsyms() { - if infosym != nil { - infosympos = Cpos() - putelfsectionsym(infosym, 0) - } - - if abbrevsym != nil { - abbrevsympos = Cpos() - putelfsectionsym(abbrevsym, 0) - } - - if linesym != nil { - linesympos = Cpos() - putelfsectionsym(linesym, 0) - } - - if framesym != nil { - framesympos = Cpos() - putelfsectionsym(framesym, 0) - } -} - -func dwarfaddelfrelocheader(elfstr int, shdata *ElfShdr, off int64, size int64) { - sh := newElfShdr(elfstrdbg[elfstr]) - if elfRelType == ".rela" { - sh.type_ = SHT_RELA - } else { - sh.type_ = SHT_REL - } - - sh.entsize = uint64(Thearch.Ptrsize) * 2 - if sh.type_ == SHT_RELA { - sh.entsize += uint64(Thearch.Ptrsize) - } - sh.link = uint32(elfshname(".symtab").shnum) - sh.info = uint32(shdata.shnum) - sh.off = uint64(off) - sh.size = uint64(size) - sh.addralign = uint64(Thearch.Ptrsize) -} - -func dwarfaddelfheaders() { if Debug['w'] != 0 { // disable dwarf return } - - sh := newElfShdr(elfstrdbg[ElfStrDebugAbbrev]) - sh.type_ = SHT_PROGBITS - sh.off = uint64(abbrevo) - sh.size = uint64(abbrevsize) - sh.addralign = 1 - if abbrevsympos > 0 { - putelfsymshndx(abbrevsympos, sh.shnum) - } - - sh = newElfShdr(elfstrdbg[ElfStrDebugLine]) - sh.type_ = SHT_PROGBITS - sh.off = uint64(lineo) - sh.size = uint64(linesize) - sh.addralign = 1 - if linesympos > 0 { - putelfsymshndx(linesympos, sh.shnum) - } - shline := sh - - sh = newElfShdr(elfstrdbg[ElfStrDebugFrame]) - sh.type_ = SHT_PROGBITS - sh.off = uint64(frameo) - sh.size = uint64(framesize) - sh.addralign = 1 - if framesympos > 0 { - putelfsymshndx(framesympos, sh.shnum) - } - shframe := sh - - sh = newElfShdr(elfstrdbg[ElfStrDebugInfo]) - sh.type_ = SHT_PROGBITS - sh.off = uint64(infoo) - sh.size = uint64(infosize) - sh.addralign = 1 - if infosympos > 0 { - putelfsymshndx(infosympos, sh.shnum) - } - shinfo := sh - - if pubnamessize > 0 { - sh := newElfShdr(elfstrdbg[ElfStrDebugPubNames]) - sh.type_ = SHT_PROGBITS - sh.off = uint64(pubnameso) - sh.size = uint64(pubnamessize) - sh.addralign = 1 - } - - if pubtypessize > 0 { - sh := newElfShdr(elfstrdbg[ElfStrDebugPubTypes]) - sh.type_ = SHT_PROGBITS - sh.off = uint64(pubtypeso) - sh.size = uint64(pubtypessize) - sh.addralign = 1 - } - - var sharanges *ElfShdr - if arangessize != 0 { - sh := newElfShdr(elfstrdbg[ElfStrDebugAranges]) - sh.type_ = SHT_PROGBITS - sh.off = uint64(arangeso) - sh.size = uint64(arangessize) - sh.addralign = 1 - sharanges = sh - } - - if gdbscriptsize != 0 { - sh := newElfShdr(elfstrdbg[ElfStrGDBScripts]) - sh.type_ = SHT_PROGBITS - sh.off = uint64(gdbscripto) - sh.size = uint64(gdbscriptsize) - sh.addralign = 1 - } - - if inforelocsize != 0 { - dwarfaddelfrelocheader(ElfStrRelDebugInfo, shinfo, inforeloco, inforelocsize) - } - - if arangesrelocsize != 0 { - dwarfaddelfrelocheader(ElfStrRelDebugAranges, sharanges, arangesreloco, arangesrelocsize) - } - - if linerelocsize != 0 { - dwarfaddelfrelocheader(ElfStrRelDebugLine, shline, linereloco, linerelocsize) - } - - if framerelocsize != 0 { - dwarfaddelfrelocheader(ElfStrRelDebugFrame, shframe, framereloco, framerelocsize) - } -} - -/* - * Macho - */ -func dwarfaddmachoheaders(ms *MachoSeg) { - if Debug['w'] != 0 { // disable dwarf - return - } - - // Zero vsize segments won't be loaded in memory, even so they - // have to be page aligned in the file. - fakestart := Rnd(int64(Segdwarf.Fileoff), 0x1000) - addr := Segdata.Vaddr + Segdata.Length - - nsect := 4 - if pubnamessize > 0 { - nsect++ - } - if pubtypessize > 0 { - nsect++ - } - if arangessize > 0 { - nsect++ - } - if gdbscriptsize > 0 { - nsect++ - } - if Linkmode != LinkExternal { - ms = newMachoSeg("__DWARF", nsect) - ms.fileoffset = uint64(fakestart) - ms.filesize = Segdwarf.Filelen - ms.vaddr = addr - } - - msect := newMachoSect(ms, "__debug_abbrev", "__DWARF") - msect.off = uint32(abbrevo) - msect.size = uint64(abbrevsize) - msect.addr = addr - addr += msect.size - msect.flag = 0x02000000 - if abbrevsym != nil { - abbrevsym.Value = int64(msect.addr) - } - - msect = newMachoSect(ms, "__debug_line", "__DWARF") - msect.off = uint32(lineo) - msect.size = uint64(linesize) - msect.addr = addr - addr += msect.size - msect.flag = 0x02000000 - if linesym != nil { - linesym.Value = int64(msect.addr) - } - if linerelocsize > 0 { - msect.nreloc = uint32(len(linesec.R)) - msect.reloc = uint32(linereloco) - } - - msect = newMachoSect(ms, "__debug_frame", "__DWARF") - msect.off = uint32(frameo) - msect.size = uint64(framesize) - msect.addr = addr - addr += msect.size - msect.flag = 0x02000000 - if framesym != nil { - framesym.Value = int64(msect.addr) - } - if framerelocsize > 0 { - msect.nreloc = uint32(len(framesec.R)) - msect.reloc = uint32(framereloco) - } - - msect = newMachoSect(ms, "__debug_info", "__DWARF") - msect.off = uint32(infoo) - msect.size = uint64(infosize) - msect.addr = addr - addr += msect.size - msect.flag = 0x02000000 - if infosym != nil { - infosym.Value = int64(msect.addr) - } - if inforelocsize > 0 { - msect.nreloc = uint32(len(infosec.R)) - msect.reloc = uint32(inforeloco) - } - - if pubnamessize > 0 { - msect := newMachoSect(ms, "__debug_pubnames", "__DWARF") - msect.off = uint32(pubnameso) - msect.size = uint64(pubnamessize) - msect.addr = addr - addr += msect.size - msect.flag = 0x02000000 - } - - if pubtypessize > 0 { - msect := newMachoSect(ms, "__debug_pubtypes", "__DWARF") - msect.off = uint32(pubtypeso) - msect.size = uint64(pubtypessize) - msect.addr = addr - addr += msect.size - msect.flag = 0x02000000 - } - - if arangessize > 0 { - msect := newMachoSect(ms, "__debug_aranges", "__DWARF") - msect.off = uint32(arangeso) - msect.size = uint64(arangessize) - msect.addr = addr - addr += msect.size - msect.flag = 0x02000000 - if arangesrelocsize > 0 { - msect.nreloc = uint32(len(arangessec.R)) - msect.reloc = uint32(arangesreloco) - } - } - - // TODO(lvd) fix gdb/python to load MachO (16 char section name limit) - if gdbscriptsize > 0 { - msect := newMachoSect(ms, "__debug_gdb_scripts", "__DWARF") - msect.off = uint32(gdbscripto) - msect.size = uint64(gdbscriptsize) - msect.addr = addr - addr += msect.size - msect.flag = 0x02000000 + return } + sym := Linklookup(Ctxt, ".debug_info", 0) + putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + sym = Linklookup(Ctxt, ".debug_abbrev", 0) + putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + sym = Linklookup(Ctxt, ".debug_line", 0) + putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + sym = Linklookup(Ctxt, ".debug_frame", 0) + putelfsectionsym(sym, sym.Sect.Elfsect.shnum) } /* @@ -2461,13 +2043,12 @@ func dwarfaddpeheaders() { if Debug['w'] != 0 { // disable dwarf return } - - newPEDWARFSection(".debug_abbrev", abbrevsize) - newPEDWARFSection(".debug_line", linesize) - newPEDWARFSection(".debug_frame", framesize) - newPEDWARFSection(".debug_info", infosize) - newPEDWARFSection(".debug_pubnames", pubnamessize) - newPEDWARFSection(".debug_pubtypes", pubtypessize) - newPEDWARFSection(".debug_aranges", arangessize) - newPEDWARFSection(".debug_gdb_scripts", gdbscriptsize) + for sect := Segdwarf.Sect; sect != nil; sect = sect.Next { + h := newPEDWARFSection(sect.Name, int64(sect.Length)) + fileoff := sect.Vaddr - Segdwarf.Vaddr + Segdwarf.Fileoff + if uint64(h.PointerToRawData) != fileoff { + Diag("%s.PointerToRawData = %#x, want %#x", sect.Name, h.PointerToRawData, fileoff) + errorexit() + } + } } diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 3b40c66592..035826df7c 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -1622,6 +1622,9 @@ func elfshbits(sect *Section) *ElfShdr { sh.flags |= SHF_TLS sh.type_ = SHT_NOBITS } + if strings.HasPrefix(sect.Name, ".debug") { + sh.flags = 0 + } if Linkmode != LinkExternal { sh.addr = sect.Vaddr @@ -1739,6 +1742,9 @@ func Elfemitreloc() { for sect := Segdata.Sect; sect != nil; sect = sect.Next { elfrelocsect(sect, datap) } + for sect := Segdwarf.Sect; sect != nil; sect = sect.Next { + elfrelocsect(sect, dwarfp) + } } func addgonote(sectionName string, tag uint32, desc []byte) { @@ -2067,6 +2073,9 @@ func Asmbelfsetup() { for sect := Segdata.Sect; sect != nil; sect = sect.Next { elfshalloc(sect) } + for sect := Segdwarf.Sect; sect != nil; sect = sect.Next { + elfshalloc(sect) + } } func Asmbelf(symo int64) { @@ -2432,6 +2441,9 @@ elfobj: for sect := Segdata.Sect; sect != nil; sect = sect.Next { elfshbits(sect) } + for sect := Segdwarf.Sect; sect != nil; sect = sect.Next { + elfshbits(sect) + } if Linkmode == LinkExternal { for sect := Segtext.Sect; sect != nil; sect = sect.Next { @@ -2443,7 +2455,14 @@ elfobj: for sect := Segdata.Sect; sect != nil; sect = sect.Next { elfshreloc(sect) } - + for s := dwarfp; s != nil; s = s.Next { + if len(s.R) > 0 || s.Type == obj.SDWARFINFO { + elfshreloc(s.Sect) + } + if s.Type == obj.SDWARFINFO { + break + } + } // add a .note.GNU-stack section to mark the stack as non-executable sh := elfshname(".note.GNU-stack") @@ -2467,8 +2486,6 @@ elfobj: sh.off = uint64(symo) + uint64(Symsize) sh.size = uint64(len(Elfstrdat)) sh.addralign = 1 - - dwarfaddelfheaders() } /* Main header */ diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go index d60203fb91..cafc6b0382 100644 --- a/src/cmd/link/internal/ld/macho.go +++ b/src/cmd/link/internal/ld/macho.go @@ -356,7 +356,7 @@ func machoshbits(mseg *MachoSeg, sect *Section, segname string) { buf := "__" + strings.Replace(sect.Name[1:], ".", "_", -1) var msect *MachoSect - if sect.Rwx&1 == 0 && (Thearch.Thechar == '7' || // arm64 + if sect.Rwx&1 == 0 && segname != "__DWARF" && (Thearch.Thechar == '7' || // arm64 (Thearch.Thechar == '6' && (Buildmode == BuildmodeCShared || Buildmode == BuildmodeCArchive))) { // amd64 // Darwin external linker on arm64 and on amd64 in c-shared/c-archive buildmode // complains about absolute relocs in __TEXT, so if the section is not @@ -411,6 +411,10 @@ func machoshbits(mseg *MachoSeg, sect *Section, segname string) { msect.name = "__mod_init_func" msect.flag = 9 // S_MOD_INIT_FUNC_POINTERS } + + if segname == "__DWARF" { + msect.flag |= 0x02000000 + } } func Asmbmacho() { @@ -492,6 +496,20 @@ func Asmbmacho() { machoshbits(ms, sect, "__DATA") } + /* dwarf */ + if Debug['w'] == 0 { + if Linkmode != LinkExternal { + ms = newMachoSeg("__DWARF", 20) + ms.vaddr = Segdwarf.Vaddr + ms.vsize = 0 + ms.fileoffset = Segdwarf.Fileoff + ms.filesize = Segdwarf.Filelen + } + for sect := Segdwarf.Sect; sect != nil; sect = sect.Next { + machoshbits(ms, sect, "__DWARF") + } + } + if Linkmode != LinkExternal { switch Thearch.Thechar { default: @@ -586,11 +604,6 @@ func Asmbmacho() { ml.data[1] = 10<<16 | 7<<8 | 0<<0 // SDK 10.7.0 } - // TODO: dwarf headers go in ms too - if Debug['s'] == 0 { - dwarfaddmachoheaders(ms) - } - a := machowrite() if int32(a) > HEADR { Exitf("HEADR too small: %d > %d", a, HEADR) @@ -876,5 +889,7 @@ func Machoemitreloc() { for sect := Segdata.Sect; sect != nil; sect = sect.Next { machorelocsect(sect, datap) } - dwarfemitreloc() + for sect := Segdwarf.Sect; sect != nil; sect = sect.Next { + machorelocsect(sect, dwarfp) + } } diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index 0fe0a68c65..167176cc2d 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -195,18 +195,6 @@ func putelfsectionsym(s *LSym, shndx int) { numelfsym++ } -func putelfsymshndx(sympos int64, shndx int) { - here := Cpos() - if elf64 { - Cseek(sympos + 6) - } else { - Cseek(sympos + 14) - } - - Thearch.Wput(uint16(shndx)) - Cseek(here) -} - func Asmelfsym() { // the first symbol entry is reserved putelfsyment(0, 0, 0, STB_LOCAL<<4|STT_NOTYPE, 0, 0) diff --git a/src/cmd/link/internal/mips64/asm.go b/src/cmd/link/internal/mips64/asm.go index 8249c54e45..d0977e9b00 100644 --- a/src/cmd/link/internal/mips64/asm.go +++ b/src/cmd/link/internal/mips64/asm.go @@ -147,6 +147,9 @@ func asmb() { ld.Cseek(int64(ld.Segdata.Fileoff)) ld.Datblk(int64(ld.Segdata.Vaddr), int64(ld.Segdata.Filelen)) + ld.Cseek(int64(ld.Segdwarf.Fileoff)) + ld.Dwarfblk(int64(ld.Segdwarf.Vaddr), int64(ld.Segdwarf.Filelen)) + /* output symbol table */ ld.Symsize = 0 @@ -161,7 +164,7 @@ func asmb() { switch ld.HEADTYPE { default: if ld.Iself { - symo = uint32(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = uint32(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = uint32(ld.Rnd(int64(symo), int64(ld.INITRND))) } @@ -180,11 +183,6 @@ func asmb() { ld.Cflush() ld.Cwrite(ld.Elfstrdat) - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - ld.Dwarfemitdebugsections() - if ld.Linkmode == ld.LinkExternal { ld.Elfemitreloc() } diff --git a/src/cmd/link/internal/ppc64/asm.go b/src/cmd/link/internal/ppc64/asm.go index ae69799abf..4c2131dfc6 100644 --- a/src/cmd/link/internal/ppc64/asm.go +++ b/src/cmd/link/internal/ppc64/asm.go @@ -868,6 +868,9 @@ func asmb() { ld.Cseek(int64(ld.Segdata.Fileoff)) ld.Datblk(int64(ld.Segdata.Vaddr), int64(ld.Segdata.Filelen)) + ld.Cseek(int64(ld.Segdwarf.Fileoff)) + ld.Dwarfblk(int64(ld.Segdwarf.Vaddr), int64(ld.Segdwarf.Filelen)) + /* output symbol table */ ld.Symsize = 0 @@ -882,7 +885,7 @@ func asmb() { switch ld.HEADTYPE { default: if ld.Iself { - symo = uint32(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = uint32(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = uint32(ld.Rnd(int64(symo), int64(ld.INITRND))) } @@ -901,11 +904,6 @@ func asmb() { ld.Cflush() ld.Cwrite(ld.Elfstrdat) - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - ld.Dwarfemitdebugsections() - if ld.Linkmode == ld.LinkExternal { ld.Elfemitreloc() } diff --git a/src/cmd/link/internal/s390x/asm.go b/src/cmd/link/internal/s390x/asm.go index 75a8206174..c5e2d187b8 100644 --- a/src/cmd/link/internal/s390x/asm.go +++ b/src/cmd/link/internal/s390x/asm.go @@ -539,6 +539,9 @@ func asmb() { ld.Cseek(int64(ld.Segdata.Fileoff)) ld.Datblk(int64(ld.Segdata.Vaddr), int64(ld.Segdata.Filelen)) + ld.Cseek(int64(ld.Segdwarf.Fileoff)) + ld.Dwarfblk(int64(ld.Segdwarf.Vaddr), int64(ld.Segdwarf.Filelen)) + /* output symbol table */ ld.Symsize = 0 @@ -552,7 +555,7 @@ func asmb() { fmt.Fprintf(&ld.Bso, "%5.2f sym\n", obj.Cputime()) } ld.Bso.Flush() - symo = uint32(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = uint32(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = uint32(ld.Rnd(int64(symo), int64(ld.INITRND))) ld.Cseek(int64(symo)) @@ -566,7 +569,6 @@ func asmb() { if ld.Debug['v'] != 0 { fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) } - ld.Dwarfemitdebugsections() if ld.Linkmode == ld.LinkExternal { ld.Elfemitreloc() diff --git a/src/cmd/link/internal/x86/asm.go b/src/cmd/link/internal/x86/asm.go index 97fccf3ee6..7da5dd02be 100644 --- a/src/cmd/link/internal/x86/asm.go +++ b/src/cmd/link/internal/x86/asm.go @@ -643,19 +643,11 @@ func asmb() { ld.Cseek(int64(ld.Segdata.Fileoff)) ld.Datblk(int64(ld.Segdata.Vaddr), int64(ld.Segdata.Filelen)) + ld.Cseek(int64(ld.Segdwarf.Fileoff)) + ld.Dwarfblk(int64(ld.Segdwarf.Vaddr), int64(ld.Segdwarf.Filelen)) + machlink := uint32(0) if ld.HEADTYPE == obj.Hdarwin { - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - - dwarfoff := uint32(ld.Rnd(int64(uint64(ld.HEADR)+ld.Segtext.Length), int64(ld.INITRND)) + ld.Rnd(int64(ld.Segdata.Filelen), int64(ld.INITRND))) - ld.Cseek(int64(dwarfoff)) - - ld.Segdwarf.Fileoff = uint64(ld.Cpos()) - ld.Dwarfemitdebugsections() - ld.Segdwarf.Filelen = uint64(ld.Cpos()) - ld.Segdwarf.Fileoff - machlink = uint32(ld.Domacholink()) } @@ -672,7 +664,7 @@ func asmb() { switch ld.HEADTYPE { default: if ld.Iself { - symo = uint32(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = uint32(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = uint32(ld.Rnd(int64(symo), int64(ld.INITRND))) } @@ -683,7 +675,7 @@ func asmb() { symo = uint32(ld.Segdwarf.Fileoff + uint64(ld.Rnd(int64(ld.Segdwarf.Filelen), int64(ld.INITRND))) + uint64(machlink)) case obj.Hwindows: - symo = uint32(ld.Segdata.Fileoff + ld.Segdata.Filelen) + symo = uint32(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) symo = uint32(ld.Rnd(int64(symo), ld.PEFILEALIGN)) } @@ -698,11 +690,6 @@ func asmb() { ld.Cflush() ld.Cwrite(ld.Elfstrdat) - if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) - } - ld.Dwarfemitdebugsections() - if ld.Linkmode == ld.LinkExternal { ld.Elfemitreloc() } @@ -726,7 +713,6 @@ func asmb() { if ld.Debug['v'] != 0 { fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) } - ld.Dwarfemitdebugsections() case obj.Hdarwin: if ld.Linkmode == ld.LinkExternal { -- cgit v1.3 From c6e11fe03765e3fe1fc68bd794625ca0ecd833be Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 6 Apr 2016 12:01:40 -0700 Subject: cmd: add new common architecture representation Information about CPU architectures (e.g., name, family, byte ordering, pointer and register size) is currently redundantly scattered around the source tree. Instead consolidate the basic information into a single new package cmd/internal/sys. Also, introduce new sys.I386, sys.AMD64, etc. names for the constants '8', '6', etc. and replace most uses of the latter. The notable exceptions are a couple of error messages that still refer to the old char-based toolchain names and function reltype in cmd/link. Passes toolstash/buildall. Change-Id: I8a6f0cbd49577ec1672a98addebc45f767e36461 Reviewed-on: https://go-review.googlesource.com/21623 Reviewed-by: Michael Hudson-Doyle Reviewed-by: Brad Fitzpatrick Run-TryBot: Matthew Dempsky TryBot-Result: Gobot Gobot --- src/cmd/asm/internal/asm/asm.go | 49 +++++----- src/cmd/asm/internal/asm/parse.go | 16 ++-- src/cmd/compile/internal/amd64/galign.go | 12 +-- src/cmd/compile/internal/arm/galign.go | 7 +- src/cmd/compile/internal/arm64/galign.go | 7 +- src/cmd/compile/internal/gc/cgen.go | 88 +++++++++--------- src/cmd/compile/internal/gc/gen.go | 3 +- src/cmd/compile/internal/gc/go.go | 5 +- src/cmd/compile/internal/gc/gsubr.go | 19 ++-- src/cmd/compile/internal/gc/main.go | 21 +++-- src/cmd/compile/internal/gc/pgen.go | 5 +- src/cmd/compile/internal/gc/plive.go | 3 +- src/cmd/compile/internal/gc/reg.go | 7 +- src/cmd/compile/internal/gc/ssa.go | 7 +- src/cmd/compile/internal/gc/walk.go | 10 +-- src/cmd/compile/internal/mips64/galign.go | 10 +-- src/cmd/compile/internal/ppc64/galign.go | 11 +-- src/cmd/compile/internal/x86/galign.go | 7 +- src/cmd/dist/buildtool.go | 1 + src/cmd/internal/obj/arm/obj5.go | 15 ++-- src/cmd/internal/obj/arm64/obj7.go | 15 ++-- src/cmd/internal/obj/data.go | 2 +- src/cmd/internal/obj/link.go | 19 ++-- src/cmd/internal/obj/mips/obj0.go | 22 ++--- src/cmd/internal/obj/objfile.go | 3 +- src/cmd/internal/obj/pcln.go | 4 +- src/cmd/internal/obj/ppc64/obj9.go | 22 ++--- src/cmd/internal/obj/s390x/objz.go | 15 ++-- src/cmd/internal/obj/sym.go | 3 +- src/cmd/internal/obj/x86/asm6.go | 2 +- src/cmd/internal/obj/x86/obj6.go | 55 +++++------- src/cmd/internal/sys/arch.go | 145 ++++++++++++++++++++++++++++++ src/cmd/link/internal/amd64/asm.go | 2 +- src/cmd/link/internal/amd64/l.go | 5 -- src/cmd/link/internal/amd64/obj.go | 11 +-- src/cmd/link/internal/arm/l.go | 2 - src/cmd/link/internal/arm/obj.go | 9 +- src/cmd/link/internal/arm64/asm.go | 2 +- src/cmd/link/internal/arm64/l.go | 2 - src/cmd/link/internal/arm64/obj.go | 9 +- src/cmd/link/internal/ld/arch.go | 97 -------------------- src/cmd/link/internal/ld/data.go | 57 ++++++------ src/cmd/link/internal/ld/deadcode.go | 3 +- src/cmd/link/internal/ld/decodesym.go | 77 ++++++++-------- src/cmd/link/internal/ld/dwarf.go | 44 ++++----- src/cmd/link/internal/ld/elf.go | 92 ++++++++++--------- src/cmd/link/internal/ld/ldelf.go | 26 +++--- src/cmd/link/internal/ld/ldmacho.go | 18 ++-- src/cmd/link/internal/ld/ldpe.go | 3 +- src/cmd/link/internal/ld/lib.go | 53 +++++------ src/cmd/link/internal/ld/link.go | 25 ++---- src/cmd/link/internal/ld/macho.go | 46 ++++------ src/cmd/link/internal/ld/pcln.go | 38 ++++---- src/cmd/link/internal/ld/pe.go | 29 +++--- src/cmd/link/internal/ld/pobj.go | 9 +- src/cmd/link/internal/ld/sym.go | 33 +++---- src/cmd/link/internal/ld/symtab.go | 5 +- src/cmd/link/internal/mips64/asm.go | 9 +- src/cmd/link/internal/mips64/l.go | 2 - src/cmd/link/internal/mips64/obj.go | 15 ++-- src/cmd/link/internal/ppc64/l.go | 2 - src/cmd/link/internal/ppc64/obj.go | 17 ++-- src/cmd/link/internal/s390x/l.go | 5 -- src/cmd/link/internal/s390x/obj.go | 9 +- src/cmd/link/internal/x86/asm.go | 4 +- src/cmd/link/internal/x86/l.go | 3 - src/cmd/link/internal/x86/obj.go | 9 +- 67 files changed, 639 insertions(+), 743 deletions(-) create mode 100644 src/cmd/internal/sys/arch.go delete mode 100644 src/cmd/link/internal/ld/arch.go (limited to 'src/cmd') diff --git a/src/cmd/asm/internal/asm/asm.go b/src/cmd/asm/internal/asm/asm.go index 950fd735c9..d674914c67 100644 --- a/src/cmd/asm/internal/asm/asm.go +++ b/src/cmd/asm/internal/asm/asm.go @@ -13,6 +13,7 @@ import ( "cmd/asm/internal/flags" "cmd/asm/internal/lex" "cmd/internal/obj" + "cmd/internal/sys" ) // TODO: configure the architecture @@ -23,14 +24,14 @@ var testOut *bytes.Buffer // Gathers output when testing. // If doLabel is set, it also defines the labels collect for this Prog. func (p *Parser) append(prog *obj.Prog, cond string, doLabel bool) { if cond != "" { - switch p.arch.Thechar { - case '5': + switch p.arch.Family { + case sys.ARM: if !arch.ARMConditionCodes(prog, cond) { p.errorf("unrecognized condition code .%q", cond) return } - case '7': + case sys.ARM64: if !arch.ARM64Suffix(prog, cond) { p.errorf("unrecognized suffix .%q", cond) return @@ -361,7 +362,7 @@ func (p *Parser) asmJump(op obj.As, cond string, a []obj.Addr) { target = &a[1] prog.From = a[0] case 3: - if p.arch.Thechar == '9' { + if p.arch.Family == sys.PPC64 { // Special 3-operand jumps. // First two must be constants; a[1] is a register number. target = &a[2] @@ -378,7 +379,7 @@ func (p *Parser) asmJump(op obj.As, cond string, a []obj.Addr) { prog.Reg = reg break } - if p.arch.Thechar == '0' { + if p.arch.Family == sys.MIPS64 { // 3-operand jumps. // First two must be registers target = &a[2] @@ -386,7 +387,7 @@ func (p *Parser) asmJump(op obj.As, cond string, a []obj.Addr) { prog.Reg = p.getRegister(prog, op, &a[1]) break } - if p.arch.Thechar == 'z' { + if p.arch.Family == sys.S390X { // 3-operand jumps. target = &a[2] prog.From = a[0] @@ -438,7 +439,7 @@ func (p *Parser) asmJump(op obj.As, cond string, a []obj.Addr) { // JMP 4(R0) prog.To = *target // On the ppc64, 9a encodes BR (CTR) as BR CTR. We do the same. - if p.arch.Thechar == '9' && target.Offset == 0 { + if p.arch.Family == sys.PPC64 && target.Offset == 0 { prog.To.Type = obj.TYPE_REG } case target.Type == obj.TYPE_CONST: @@ -492,14 +493,14 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { prog.From = a[0] // prog.To is no address. } - if p.arch.Thechar == '9' && arch.IsPPC64NEG(op) { + if p.arch.Family == sys.PPC64 && arch.IsPPC64NEG(op) { // NEG: From and To are both a[0]. prog.To = a[0] prog.From = a[0] break } case 2: - if p.arch.Thechar == '5' { + if p.arch.Family == sys.ARM { if arch.IsARMCMP(op) { prog.From = a[0] prog.Reg = p.getRegister(prog, op, &a[1]) @@ -532,11 +533,11 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { prog.Reg = p.getRegister(prog, op, &a[1]) break } - } else if p.arch.Thechar == '7' && arch.IsARM64CMP(op) { + } else if p.arch.Family == sys.ARM64 && arch.IsARM64CMP(op) { prog.From = a[0] prog.Reg = p.getRegister(prog, op, &a[1]) break - } else if p.arch.Thechar == '0' { + } else if p.arch.Family == sys.MIPS64 { if arch.IsMIPS64CMP(op) || arch.IsMIPS64MUL(op) { prog.From = a[0] prog.Reg = p.getRegister(prog, op, &a[1]) @@ -546,12 +547,12 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { prog.From = a[0] prog.To = a[1] case 3: - switch p.arch.Thechar { - case '0': + switch p.arch.Family { + case sys.MIPS64: prog.From = a[0] prog.Reg = p.getRegister(prog, op, &a[1]) prog.To = a[2] - case '5': + case sys.ARM: // Special cases. if arch.IsARMSTREX(op) { /* @@ -567,7 +568,7 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { prog.From = a[0] prog.Reg = p.getRegister(prog, op, &a[1]) prog.To = a[2] - case '7': + case sys.ARM64: // ARM64 instructions with one input and two outputs. if arch.IsARM64STLXR(op) { prog.From = a[0] @@ -582,11 +583,11 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { prog.From = a[0] prog.Reg = p.getRegister(prog, op, &a[1]) prog.To = a[2] - case '6', '8': + case sys.AMD64, sys.I386: prog.From = a[0] prog.From3 = newAddr(a[1]) prog.To = a[2] - case '9': + case sys.PPC64: if arch.IsPPC64CMP(op) { // CMPW etc.; third argument is a CR register that goes into prog.Reg. prog.From = a[0] @@ -612,7 +613,7 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { p.errorf("invalid addressing modes for %s instruction", obj.Aconv(op)) return } - case 'z': + case sys.S390X: if arch.IsS390xWithLength(op) || arch.IsS390xWithIndex(op) { prog.From = a[1] prog.From3 = newAddr(a[0]) @@ -626,7 +627,7 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { return } case 4: - if p.arch.Thechar == '5' && arch.IsARMMULA(op) { + if p.arch.Family == sys.ARM && arch.IsARMMULA(op) { // All must be registers. p.getRegister(prog, op, &a[0]) r1 := p.getRegister(prog, op, &a[1]) @@ -639,14 +640,14 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { prog.Reg = r1 break } - if p.arch.Thechar == '7' { + if p.arch.Family == sys.ARM64 { prog.From = a[0] prog.Reg = p.getRegister(prog, op, &a[1]) prog.From3 = newAddr(a[2]) prog.To = a[3] break } - if p.arch.Thechar == '9' && arch.IsPPC64RLD(op) { + if p.arch.Family == sys.PPC64 && arch.IsPPC64RLD(op) { // 2nd operand must always be a register. // TODO: Do we need to guard this with the instruction type? // That is, are there 4-operand instructions without this property? @@ -656,7 +657,7 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { prog.To = a[3] break } - if p.arch.Thechar == 'z' { + if p.arch.Family == sys.S390X { prog.From = a[1] prog.Reg = p.getRegister(prog, op, &a[2]) prog.From3 = newAddr(a[0]) @@ -666,7 +667,7 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { p.errorf("can't handle %s instruction with 4 operands", obj.Aconv(op)) return case 5: - if p.arch.Thechar == '9' && arch.IsPPC64RLD(op) { + if p.arch.Family == sys.PPC64 && arch.IsPPC64RLD(op) { // Always reg, reg, con, con, reg. (con, con is a 'mask'). prog.From = a[0] prog.Reg = p.getRegister(prog, op, &a[1]) @@ -688,7 +689,7 @@ func (p *Parser) asmInstruction(op obj.As, cond string, a []obj.Addr) { p.errorf("can't handle %s instruction with 5 operands", obj.Aconv(op)) return case 6: - if p.arch.Thechar == '5' && arch.IsARMMRC(op) { + if p.arch.Family == sys.ARM && arch.IsARMMRC(op) { // Strange special case: MCR, MRC. prog.To.Type = obj.TYPE_CONST x0 := p.getConstant(prog, op, &a[0]) diff --git a/src/cmd/asm/internal/asm/parse.go b/src/cmd/asm/internal/asm/parse.go index ee37439962..40206e6dc1 100644 --- a/src/cmd/asm/internal/asm/parse.go +++ b/src/cmd/asm/internal/asm/parse.go @@ -19,6 +19,7 @@ import ( "cmd/asm/internal/flags" "cmd/asm/internal/lex" "cmd/internal/obj" + "cmd/internal/sys" ) type Parser struct { @@ -130,7 +131,7 @@ func (p *Parser) line() bool { for { tok = p.lex.Next() if len(operands) == 0 && len(items) == 0 { - if (p.arch.Thechar == '5' || p.arch.Thechar == '7') && tok == '.' { + if p.arch.InFamily(sys.ARM, sys.ARM64) && tok == '.' { // ARM conditionals. tok = p.lex.Next() str := p.lex.Text() @@ -420,7 +421,7 @@ func (p *Parser) atStartOfRegister(name string) bool { // We have consumed the register or R prefix. func (p *Parser) atRegisterShift() bool { // ARM only. - if p.arch.Thechar != '5' { + if p.arch.Family != sys.ARM { return false } // R1<<... @@ -476,15 +477,14 @@ func (p *Parser) register(name string, prefix rune) (r1, r2 int16, scale int8, o if c == ':' || c == ',' || c == '+' { // 2nd register; syntax (R1+R2) etc. No two architectures agree. // Check the architectures match the syntax. - char := p.arch.Thechar switch p.next().ScanToken { case ',': - if char != '5' && char != '7' { + if !p.arch.InFamily(sys.ARM, sys.ARM64) { p.errorf("(register,register) not supported on this architecture") return } case '+': - if char != '9' { + if p.arch.Family != sys.PPC64 { p.errorf("(register+register) not supported on this architecture") return } @@ -649,7 +649,7 @@ func (p *Parser) registerIndirect(a *obj.Addr, prefix rune) { a.Reg = r1 if r2 != 0 { // TODO: Consistency in the encoding would be nice here. - if p.arch.Thechar == '5' || p.arch.Thechar == '7' { + if p.arch.InFamily(sys.ARM, sys.ARM64) { // Special form // ARM: destination register pair (R1, R2). // ARM64: register pair (R1, R2) for LDP/STP. @@ -662,7 +662,7 @@ func (p *Parser) registerIndirect(a *obj.Addr, prefix rune) { // Nothing may follow return } - if p.arch.Thechar == '9' { + if p.arch.Family == sys.PPC64 { // Special form for PPC64: (R1+R2); alias for (R1)(R2*1). if prefix != 0 || scale != 0 { p.errorf("illegal address mode for register+register") @@ -752,7 +752,7 @@ ListLoop: // register number is ARM-specific. It returns the number of the specified register. func (p *Parser) registerNumber(name string) uint16 { - if p.arch.Thechar == '5' && name == "g" { + if p.arch.Family == sys.ARM && name == "g" { return 10 } if name[0] != 'R' { diff --git a/src/cmd/compile/internal/amd64/galign.go b/src/cmd/compile/internal/amd64/galign.go index 14721ea35b..461ef2ada1 100644 --- a/src/cmd/compile/internal/amd64/galign.go +++ b/src/cmd/compile/internal/amd64/galign.go @@ -18,12 +18,7 @@ var ( ) func betypeinit() { - gc.Widthptr = 8 - gc.Widthint = 8 - gc.Widthreg = 8 if obj.Getgoarch() == "amd64p32" { - gc.Widthptr = 4 - gc.Widthint = 4 addptr = x86.AADDL movptr = x86.AMOVL leaptr = x86.ALEAL @@ -42,12 +37,9 @@ func Main() { resvd = append(resvd, x86.REG_BP) } - gc.Thearch.Thechar = '6' - gc.Thearch.Thestring = "amd64" - gc.Thearch.Thelinkarch = &x86.Linkamd64 + gc.Thearch.LinkArch = &x86.Linkamd64 if obj.Getgoarch() == "amd64p32" { - gc.Thearch.Thestring = "amd64p32" - gc.Thearch.Thelinkarch = &x86.Linkamd64p32 + gc.Thearch.LinkArch = &x86.Linkamd64p32 } gc.Thearch.REGSP = x86.REGSP gc.Thearch.REGCTXT = x86.REGCTXT diff --git a/src/cmd/compile/internal/arm/galign.go b/src/cmd/compile/internal/arm/galign.go index e05f4d06bb..afd86e44c8 100644 --- a/src/cmd/compile/internal/arm/galign.go +++ b/src/cmd/compile/internal/arm/galign.go @@ -11,15 +11,10 @@ import ( ) func betypeinit() { - gc.Widthptr = 4 - gc.Widthint = 4 - gc.Widthreg = 4 } func Main() { - gc.Thearch.Thechar = '5' - gc.Thearch.Thestring = "arm" - gc.Thearch.Thelinkarch = &arm.Linkarm + gc.Thearch.LinkArch = &arm.Linkarm gc.Thearch.REGSP = arm.REGSP gc.Thearch.REGCTXT = arm.REGCTXT gc.Thearch.REGCALLX = arm.REG_R1 diff --git a/src/cmd/compile/internal/arm64/galign.go b/src/cmd/compile/internal/arm64/galign.go index 7e1226fee1..17c851cb14 100644 --- a/src/cmd/compile/internal/arm64/galign.go +++ b/src/cmd/compile/internal/arm64/galign.go @@ -10,15 +10,10 @@ import ( ) func betypeinit() { - gc.Widthptr = 8 - gc.Widthint = 8 - gc.Widthreg = 8 } func Main() { - gc.Thearch.Thechar = '7' - gc.Thearch.Thestring = "arm64" - gc.Thearch.Thelinkarch = &arm64.Linkarm64 + gc.Thearch.LinkArch = &arm64.Linkarm64 gc.Thearch.REGSP = arm64.REGSP gc.Thearch.REGCTXT = arm64.REGCTXT gc.Thearch.REGCALLX = arm64.REGRT1 diff --git a/src/cmd/compile/internal/gc/cgen.go b/src/cmd/compile/internal/gc/cgen.go index c594ad4c11..a9cedf7cfc 100644 --- a/src/cmd/compile/internal/gc/cgen.go +++ b/src/cmd/compile/internal/gc/cgen.go @@ -7,6 +7,7 @@ package gc import ( "cmd/internal/obj" "cmd/internal/obj/ppc64" + "cmd/internal/sys" "fmt" ) @@ -88,7 +89,7 @@ func cgen_wb(n, res *Node, wb bool) { if !res.Addable { if n.Ullman > res.Ullman { - if Ctxt.Arch.Regsize == 4 && Is64(n.Type) { + if Ctxt.Arch.RegSize == 4 && Is64(n.Type) { var n1 Node Tempname(&n1, n.Type) Cgen(n, &n1) @@ -127,7 +128,7 @@ func cgen_wb(n, res *Node, wb bool) { f = false } - if !n.Type.IsComplex() && Ctxt.Arch.Regsize == 8 && !wb { + if !n.Type.IsComplex() && Ctxt.Arch.RegSize == 8 && !wb { a := Thearch.Optoas(OAS, res.Type) var addr obj.Addr if Thearch.Sudoaddable(a, res, &addr) { @@ -151,7 +152,7 @@ func cgen_wb(n, res *Node, wb bool) { } } - if Ctxt.Arch.Thechar == '8' { + if Ctxt.Arch.Family == sys.I386 { // no registers to speak of var n1, n2 Node Tempname(&n1, n.Type) @@ -203,7 +204,7 @@ func cgen_wb(n, res *Node, wb bool) { // Write barrier now handled. Code below this line can ignore wb. - if Ctxt.Arch.Thechar == '5' { // TODO(rsc): Maybe more often? + if Ctxt.Arch.Family == sys.ARM { // TODO(rsc): Maybe more often? // if both are addressable, move if n.Addable && res.Addable { if Is64(n.Type) || Is64(res.Type) || n.Op == OREGISTER || res.Op == OREGISTER || n.Type.IsComplex() || res.Type.IsComplex() { @@ -246,12 +247,12 @@ func cgen_wb(n, res *Node, wb bool) { return } - if (Ctxt.Arch.Thechar == '6' || Ctxt.Arch.Thechar == '8') && n.Addable { + if Ctxt.Arch.InFamily(sys.AMD64, sys.I386) && n.Addable { Thearch.Gmove(n, res) return } - if Ctxt.Arch.Thechar == '0' || Ctxt.Arch.Thechar == '7' || Ctxt.Arch.Thechar == '9' { + if Ctxt.Arch.InFamily(sys.ARM64, sys.MIPS64, sys.PPC64) { // if both are addressable, move if n.Addable { if n.Op == OREGISTER || res.Op == OREGISTER { @@ -268,7 +269,7 @@ func cgen_wb(n, res *Node, wb bool) { } // if n is sudoaddable generate addr and move - if Ctxt.Arch.Thechar == '5' && !Is64(n.Type) && !Is64(res.Type) && !n.Type.IsComplex() && !res.Type.IsComplex() { + if Ctxt.Arch.Family == sys.ARM && !Is64(n.Type) && !Is64(res.Type) && !n.Type.IsComplex() && !res.Type.IsComplex() { a := Thearch.Optoas(OAS, n.Type) var addr obj.Addr if Thearch.Sudoaddable(a, n, &addr) { @@ -310,7 +311,7 @@ func cgen_wb(n, res *Node, wb bool) { } // 64-bit ops are hard on 32-bit machine. - if Ctxt.Arch.Regsize == 4 && (Is64(n.Type) || Is64(res.Type) || n.Left != nil && Is64(n.Left.Type)) { + if Ctxt.Arch.RegSize == 4 && (Is64(n.Type) || Is64(res.Type) || n.Left != nil && Is64(n.Left.Type)) { switch n.Op { // math goes to cgen64. case OMINUS, @@ -334,7 +335,7 @@ func cgen_wb(n, res *Node, wb bool) { return } - if !n.Type.IsComplex() && Ctxt.Arch.Regsize == 8 { + if !n.Type.IsComplex() && Ctxt.Arch.RegSize == 8 { a := Thearch.Optoas(OAS, n.Type) var addr obj.Addr if Thearch.Sudoaddable(a, n, &addr) { @@ -401,11 +402,11 @@ func cgen_wb(n, res *Node, wb bool) { Regalloc(&n1, nl.Type, res) Cgen(nl, &n1) - if Ctxt.Arch.Thechar == '5' { + if Ctxt.Arch.Family == sys.ARM { var n2 Node Nodconst(&n2, nl.Type, 0) Thearch.Gins(a, &n2, &n1) - } else if Ctxt.Arch.Thechar == '7' { + } else if Ctxt.Arch.Family == sys.ARM64 { Thearch.Gins(a, &n1, &n1) } else { Thearch.Gins(a, nil, &n1) @@ -452,7 +453,7 @@ func cgen_wb(n, res *Node, wb bool) { return } - if Ctxt.Arch.Thechar == '8' { + if Ctxt.Arch.Family == sys.I386 { var n1 Node var n2 Node Tempname(&n2, n.Type) @@ -465,7 +466,7 @@ func cgen_wb(n, res *Node, wb bool) { var n1 Node var n2 Node - if Ctxt.Arch.Thechar == '5' { + if Ctxt.Arch.Family == sys.ARM { if nl.Addable && !Is64(nl.Type) { Regalloc(&n1, nl.Type, res) Thearch.Gmove(nl, &n1) @@ -707,7 +708,7 @@ sbop: // symmetric binary abop: // asymmetric binary var n1 Node var n2 Node - if Ctxt.Arch.Thechar == '8' { + if Ctxt.Arch.Family == sys.I386 { // no registers, sigh if Smallintconst(nr) { var n1 Node @@ -751,14 +752,14 @@ abop: // asymmetric binary Regalloc(&n1, nl.Type, res) Cgen(nl, &n1) - if Smallintconst(nr) && Ctxt.Arch.Thechar != '0' && Ctxt.Arch.Thechar != '5' && Ctxt.Arch.Thechar != '7' && Ctxt.Arch.Thechar != '9' { // TODO(rsc): Check opcode for arm + if Smallintconst(nr) && Ctxt.Arch.Family != sys.MIPS64 && Ctxt.Arch.Family != sys.ARM && Ctxt.Arch.Family != sys.ARM64 && Ctxt.Arch.Family != sys.PPC64 { // TODO(rsc): Check opcode for arm n2 = *nr } else { Regalloc(&n2, nr.Type, nil) Cgen(nr, &n2) } } else { - if Smallintconst(nr) && Ctxt.Arch.Thechar != '0' && Ctxt.Arch.Thechar != '5' && Ctxt.Arch.Thechar != '7' && Ctxt.Arch.Thechar != '9' { // TODO(rsc): Check opcode for arm + if Smallintconst(nr) && Ctxt.Arch.Family != sys.MIPS64 && Ctxt.Arch.Family != sys.ARM && Ctxt.Arch.Family != sys.ARM64 && Ctxt.Arch.Family != sys.PPC64 { // TODO(rsc): Check opcode for arm n2 = *nr } else { Regalloc(&n2, nr.Type, res) @@ -876,8 +877,8 @@ func cgen_wbfat(n, res *Node) { // cgen_norm moves n1 to res, truncating to expected type if necessary. // n1 is a register, and cgen_norm frees it. func cgen_norm(n, n1, res *Node) { - switch Ctxt.Arch.Thechar { - case '6', '8': + switch Ctxt.Arch.Family { + case sys.AMD64, sys.I386: // We use sized math, so the result is already truncated. default: switch n.Op { @@ -980,7 +981,7 @@ func Agenr(n *Node, a *Node, res *Node) { Cgen_checknil(a) case OINDEX: - if Ctxt.Arch.Thechar == '5' { + if Ctxt.Arch.Family == sys.ARM { var p2 *obj.Prog // to be patched to panicindex. w := uint32(n.Type.Width) bounded := Debug['B'] != 0 || n.Bounded @@ -1127,7 +1128,7 @@ func Agenr(n *Node, a *Node, res *Node) { Regfree(&n2) break } - if Ctxt.Arch.Thechar == '8' { + if Ctxt.Arch.Family == sys.I386 { var p2 *obj.Prog // to be patched to panicindex. w := uint32(n.Type.Width) bounded := Debug['B'] != 0 || n.Bounded @@ -1604,7 +1605,7 @@ func Agen(n *Node, res *Node) { } func addOffset(res *Node, offset int64) { - if Ctxt.Arch.Thechar == '6' || Ctxt.Arch.Thechar == '8' { + if Ctxt.Arch.InFamily(sys.AMD64, sys.I386) { Thearch.Gins(Thearch.Optoas(OADD, Types[Tptr]), Nodintconst(offset), res) return } @@ -1825,13 +1826,14 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { return case ONAME: + // Some architectures might need a temporary or other help here, + // but they don't support direct generation of a bool value yet. + // We can fix that as we go. + mayNeedTemp := Ctxt.Arch.InFamily(sys.ARM, sys.ARM64, sys.MIPS64, sys.PPC64) + if genval { - // 5g, 7g, and 9g might need a temporary or other help here, - // but they don't support direct generation of a bool value yet. - // We can fix that as we go. - switch Ctxt.Arch.Thechar { - case '0', '5', '7', '9': - Fatalf("genval 0g, 5g, 7g, 9g ONAMES not fully implemented") + if mayNeedTemp { + Fatalf("genval ONAMES not fully implemented") } Cgen(n, res) if !wantTrue { @@ -1840,7 +1842,7 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { return } - if n.Addable && Ctxt.Arch.Thechar != '0' && Ctxt.Arch.Thechar != '5' && Ctxt.Arch.Thechar != '7' && Ctxt.Arch.Thechar != '9' { + if n.Addable && !mayNeedTemp { // no need for a temporary bgenNonZero(n, nil, wantTrue, likely, to) return @@ -1977,7 +1979,7 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { return } - if Ctxt.Arch.Regsize == 4 && Is64(nr.Type) { + if Ctxt.Arch.RegSize == 4 && Is64(nr.Type) { if genval { // TODO: Teach Cmp64 to generate boolean values and remove this. bvgenjump(n, res, wantTrue, false) @@ -2015,7 +2017,7 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { Regfree(&n2) } else { var n1 Node - if !nl.Addable && Ctxt.Arch.Thechar == '8' { + if !nl.Addable && Ctxt.Arch.Family == sys.I386 { Tempname(&n1, nl.Type) } else { Regalloc(&n1, nl.Type, nil) @@ -2024,13 +2026,13 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { Cgen(nl, &n1) nl = &n1 - if Smallintconst(nr) && Ctxt.Arch.Thechar != '0' && Ctxt.Arch.Thechar != '9' { + if Smallintconst(nr) && Ctxt.Arch.Family != sys.MIPS64 && Ctxt.Arch.Family != sys.PPC64 { Thearch.Gins(Thearch.Optoas(OCMP, nr.Type), nl, nr) bins(nr.Type, res, op, likely, to) return } - if !nr.Addable && Ctxt.Arch.Thechar == '8' { + if !nr.Addable && Ctxt.Arch.Family == sys.I386 { nr = CgenTemp(nr) } @@ -2044,13 +2046,13 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { l, r := nl, nr // On x86, only < and <= work right with NaN; reverse if needed - if Ctxt.Arch.Thechar == '6' && nl.Type.IsFloat() && (op == OGT || op == OGE) { + if Ctxt.Arch.Family == sys.AMD64 && nl.Type.IsFloat() && (op == OGT || op == OGE) { l, r = r, l op = Brrev(op) } // MIPS does not have CMP instruction - if Ctxt.Arch.Thechar == '0' { + if Ctxt.Arch.Family == sys.MIPS64 { p := Thearch.Ginscmp(op, nr.Type, l, r, likely) Patch(p, to) return @@ -2062,8 +2064,8 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { // Handle floating point special cases. // Note that 8g has Bgen_float and is handled above. if nl.Type.IsFloat() { - switch Ctxt.Arch.Thechar { - case '5': + switch Ctxt.Arch.Family { + case sys.ARM: if genval { Fatalf("genval 5g Isfloat special cases not implemented") } @@ -2077,7 +2079,7 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { Patch(p, Pc) } return - case '6': + case sys.AMD64: switch n.Op { case OEQ: // neither NE nor P @@ -2111,7 +2113,7 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { } return } - case '7', '9': + case sys.ARM64, sys.PPC64: if genval { Fatalf("genval 7g, 9g Isfloat special cases not implemented") } @@ -2143,7 +2145,7 @@ func bgenNonZero(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { } // MIPS does not have CMP instruction - if Thearch.Thechar == '0' { + if Thearch.LinkArch.Family == sys.MIPS64 { p := Gbranch(Thearch.Optoas(op, n.Type), n.Type, likely) Naddr(&p.From, n) Patch(p, to) @@ -2352,7 +2354,7 @@ func Ginscall(f *Node, proc int) { // into the instruction stream. Thearch.Ginsnop() - if Thearch.Thechar == '9' { + if Thearch.LinkArch.Family == sys.PPC64 { // On ppc64, when compiling Go into position // independent code on ppc64le we insert an // instruction to reload the TOC pointer from the @@ -2630,7 +2632,7 @@ func cgen_div(op Op, nl *Node, nr *Node, res *Node) { // in peep and optoas in order to enable this. // TODO(rsc): ppc64 needs to support the relevant instructions // in peep and optoas in order to enable this. - if nr.Op != OLITERAL || Ctxt.Arch.Thechar == '0' || Ctxt.Arch.Thechar == '7' || Ctxt.Arch.Thechar == '9' { + if nr.Op != OLITERAL || Ctxt.Arch.Family == sys.MIPS64 || Ctxt.Arch.Family == sys.ARM64 || Ctxt.Arch.Family == sys.PPC64 { goto longdiv } w = int(nl.Type.Width * 8) @@ -2995,7 +2997,7 @@ func cgen_slice(n, res *Node, wb bool) { regalloc := Regalloc ginscon := Thearch.Ginscon gins := Thearch.Gins - if Thearch.Thechar == '8' { + if Thearch.LinkArch.Family == sys.I386 { regalloc = func(n *Node, t *Type, reuse *Node) { Tempname(n, t) } @@ -3238,7 +3240,7 @@ func cgen_slice(n, res *Node, wb bool) { compare := func(n1, n2 *Node) { // n1 might be a 64-bit constant, even on 32-bit architectures, // but it will be represented in 32 bits. - if Ctxt.Arch.Regsize == 4 && Is64(n1.Type) { + if Ctxt.Arch.RegSize == 4 && Is64(n1.Type) { if n1.Val().U.(*Mpint).CmpInt64(1<<31) >= 0 { Fatalf("missed slice out of bounds check") } diff --git a/src/cmd/compile/internal/gc/gen.go b/src/cmd/compile/internal/gc/gen.go index 4a98f41bcb..7527452c93 100644 --- a/src/cmd/compile/internal/gc/gen.go +++ b/src/cmd/compile/internal/gc/gen.go @@ -8,6 +8,7 @@ package gc import ( "cmd/internal/obj" + "cmd/internal/sys" "fmt" ) @@ -1174,7 +1175,7 @@ func visitComponents(t *Type, startOffset int64, f func(elem *Type, elemOffset i } // NOTE: Assuming little endian (signed top half at offset 4). // We don't have any 32-bit big-endian systems. - if Thearch.Thechar != '5' && Thearch.Thechar != '8' { + if !Thearch.LinkArch.InFamily(sys.ARM, sys.I386) { Fatalf("unknown 32-bit architecture") } return f(Types[TUINT32], startOffset) && diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index 4cb985b1be..ef8b516ea5 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -360,9 +360,8 @@ const ( ) type Arch struct { - Thechar int - Thestring string - Thelinkarch *obj.LinkArch + LinkArch *obj.LinkArch + REGSP int REGCTXT int REGCALLX int // BX diff --git a/src/cmd/compile/internal/gc/gsubr.go b/src/cmd/compile/internal/gc/gsubr.go index a2fa5f8b31..63a8e969c3 100644 --- a/src/cmd/compile/internal/gc/gsubr.go +++ b/src/cmd/compile/internal/gc/gsubr.go @@ -32,6 +32,7 @@ package gc import ( "cmd/internal/obj" + "cmd/internal/sys" "fmt" "runtime" "strings" @@ -57,7 +58,7 @@ func Ismem(n *Node) bool { return true case OADDR: - return Thearch.Thechar == '6' || Thearch.Thechar == '9' // because 6g uses PC-relative addressing; TODO(rsc): not sure why 9g too + return Thearch.LinkArch.InFamily(sys.AMD64, sys.PPC64) // because 6g uses PC-relative addressing; TODO(rsc): not sure why 9g too } return false @@ -83,7 +84,7 @@ func Gbranch(as obj.As, t *Type, likely int) *obj.Prog { p := Prog(as) p.To.Type = obj.TYPE_BRANCH p.To.Val = nil - if as != obj.AJMP && likely != 0 && Thearch.Thechar != '9' && Thearch.Thechar != '7' && Thearch.Thechar != '0' { + if as != obj.AJMP && likely != 0 && Thearch.LinkArch.Family != sys.PPC64 && Thearch.LinkArch.Family != sys.ARM64 && Thearch.LinkArch.Family != sys.MIPS64 { p.From.Type = obj.TYPE_CONST if likely > 0 { p.From.Offset = 1 @@ -330,7 +331,7 @@ func Naddr(a *obj.Addr, n *Node) { a.Type = obj.TYPE_REG a.Reg = n.Reg a.Sym = nil - if Thearch.Thechar == '8' { // TODO(rsc): Never clear a->width. + if Thearch.LinkArch.Family == sys.I386 { // TODO(rsc): Never clear a->width. a.Width = 0 } @@ -342,7 +343,7 @@ func Naddr(a *obj.Addr, n *Node) { if a.Offset != int64(int32(a.Offset)) { Yyerror("offset %d too large for OINDREG", a.Offset) } - if Thearch.Thechar == '8' { // TODO(rsc): Never clear a->width. + if Thearch.LinkArch.Family == sys.I386 { // TODO(rsc): Never clear a->width. a.Width = 0 } @@ -424,7 +425,7 @@ func Naddr(a *obj.Addr, n *Node) { Naddr(a, n.Left) case OLITERAL: - if Thearch.Thechar == '8' { + if Thearch.LinkArch.Family == sys.I386 { a.Width = 0 } switch n.Val().Ctype() { @@ -457,7 +458,7 @@ func Naddr(a *obj.Addr, n *Node) { case OADDR: Naddr(a, n.Left) a.Etype = uint8(Tptr) - if Thearch.Thechar != '0' && Thearch.Thechar != '5' && Thearch.Thechar != '7' && Thearch.Thechar != '9' { // TODO(rsc): Do this even for arm, ppc64. + if !Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64) { // TODO(rsc): Do this even for arm, ppc64. a.Width = int64(Widthptr) } if a.Type != obj.TYPE_MEM { @@ -496,7 +497,7 @@ func Naddr(a *obj.Addr, n *Node) { } a.Etype = uint8(Simtype[TUINT]) a.Offset += int64(Array_nel) - if Thearch.Thechar != '5' { // TODO(rsc): Do this even on arm. + if Thearch.LinkArch.Family != sys.ARM { // TODO(rsc): Do this even on arm. a.Width = int64(Widthint) } @@ -509,7 +510,7 @@ func Naddr(a *obj.Addr, n *Node) { } a.Etype = uint8(Simtype[TUINT]) a.Offset += int64(Array_cap) - if Thearch.Thechar != '5' { // TODO(rsc): Do this even on arm. + if Thearch.LinkArch.Family != sys.ARM { // TODO(rsc): Do this even on arm. a.Width = int64(Widthint) } } @@ -695,7 +696,7 @@ func Regalloc(n *Node, t *Type, o *Node) { Fatalf("regalloc: t nil") } et := Simtype[t.Etype] - if Ctxt.Arch.Regsize == 4 && (et == TINT64 || et == TUINT64) { + if Ctxt.Arch.RegSize == 4 && (et == TINT64 || et == TUINT64) { Fatalf("regalloc 64bit") } diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 73ecb09fa5..72e6478afe 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -10,6 +10,7 @@ import ( "bufio" "cmd/compile/internal/ssa" "cmd/internal/obj" + "cmd/internal/sys" "flag" "fmt" "io" @@ -96,12 +97,12 @@ func Main() { // but not other values. p := obj.Getgoarch() - if !strings.HasPrefix(p, Thearch.Thestring) { - log.Fatalf("cannot use %cg with GOARCH=%s", Thearch.Thechar, p) + if !strings.HasPrefix(p, Thearch.LinkArch.Name) { + log.Fatalf("cannot use %cg with GOARCH=%s", Thearch.LinkArch.Family, p) } goarch = p - Ctxt = obj.Linknew(Thearch.Thelinkarch) + Ctxt = obj.Linknew(Thearch.LinkArch) Ctxt.DiagFunc = Yyerror Ctxt.Bso = &bstdout bstdout = *obj.Binitw(os.Stdout) @@ -200,15 +201,13 @@ func Main() { obj.Flagcount("y", "debug declarations in canned imports (with -d)", &Debug['y']) var flag_shared int var flag_dynlink bool - switch Thearch.Thechar { - case '5', '6', '7', '8', '9': + if Thearch.LinkArch.InFamily(sys.ARM, sys.AMD64, sys.ARM64, sys.I386, sys.PPC64) { obj.Flagcount("shared", "generate code that can be linked into a shared library", &flag_shared) } - if Thearch.Thechar == '6' { + if Thearch.LinkArch.Family == sys.AMD64 { obj.Flagcount("largemodel", "generate code that assumes a large memory model", &flag_largemodel) } - switch Thearch.Thechar { - case '5', '6', '7', '8', '9': + if Thearch.LinkArch.InFamily(sys.ARM, sys.AMD64, sys.ARM64, sys.I386, sys.PPC64) { flag.BoolVar(&flag_dynlink, "dynlink", false, "support references to Go symbols defined in other shared libraries") } obj.Flagstr("cpuprofile", "write cpu profile to `file`", &cpuprofile) @@ -301,9 +300,9 @@ func Main() { } Thearch.Betypeinit() - if Widthptr == 0 { - Fatalf("betypeinit failed") - } + Widthint = Thearch.LinkArch.IntSize + Widthptr = Thearch.LinkArch.PtrSize + Widthreg = Thearch.LinkArch.RegSize initUniverse() diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index 63f7bf825e..bfb65ade38 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -7,6 +7,7 @@ package gc import ( "cmd/compile/internal/ssa" "cmd/internal/obj" + "cmd/internal/sys" "crypto/md5" "fmt" "sort" @@ -286,7 +287,7 @@ func allocauto(ptxt *obj.Prog) { if haspointers(n.Type) { stkptrsize = Stksize } - if Thearch.Thechar == '0' || Thearch.Thechar == '5' || Thearch.Thechar == '7' || Thearch.Thechar == '9' { + if Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64) { Stksize = Rnd(Stksize, int64(Widthptr)) } if Stksize >= 1<<31 { @@ -323,7 +324,7 @@ func Cgen_checknil(n *Node) { Fatalf("bad checknil") } - if ((Thearch.Thechar == '0' || Thearch.Thechar == '5' || Thearch.Thechar == '7' || Thearch.Thechar == '9') && n.Op != OREGISTER) || !n.Addable || n.Op == OLITERAL { + if (Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64) && n.Op != OREGISTER) || !n.Addable || n.Op == OLITERAL { var reg Node Regalloc(®, Types[Tptr], n) Cgen(n, ®) diff --git a/src/cmd/compile/internal/gc/plive.go b/src/cmd/compile/internal/gc/plive.go index 43f594e2ea..6e43d3133f 100644 --- a/src/cmd/compile/internal/gc/plive.go +++ b/src/cmd/compile/internal/gc/plive.go @@ -17,6 +17,7 @@ package gc import ( "cmd/internal/obj" + "cmd/internal/sys" "fmt" "sort" "strings" @@ -1396,7 +1397,7 @@ func livenessepilogue(lv *Liveness) { // The instruction before a call to deferreturn is always a // no-op, to keep PC-specific data unambiguous. prev := p.Opt.(*obj.Prog) - if Ctxt.Arch.Thechar == '9' { + if Ctxt.Arch.Family == sys.PPC64 { // On ppc64 there is an additional instruction // (another no-op or reload of toc pointer) before // the call. diff --git a/src/cmd/compile/internal/gc/reg.go b/src/cmd/compile/internal/gc/reg.go index 26746a5bcf..8705d6dfa4 100644 --- a/src/cmd/compile/internal/gc/reg.go +++ b/src/cmd/compile/internal/gc/reg.go @@ -33,6 +33,7 @@ package gc import ( "bytes" "cmd/internal/obj" + "cmd/internal/sys" "fmt" "sort" "strings" @@ -249,7 +250,7 @@ func addmove(r *Flow, bn int, rn int, f int) { p1.As = Thearch.Optoas(OAS, Types[uint8(v.etype)]) // TODO(rsc): Remove special case here. - if (Thearch.Thechar == '0' || Thearch.Thechar == '5' || Thearch.Thechar == '7' || Thearch.Thechar == '9') && v.etype == TBOOL { + if Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64) && v.etype == TBOOL { p1.As = Thearch.Optoas(OAS, Types[TUINT8]) } p1.From.Type = obj.TYPE_REG @@ -302,7 +303,7 @@ func mkvar(f *Flow, a *obj.Addr) Bits { // TODO(rsc): Remove special case here. case obj.TYPE_ADDR: var bit Bits - if Thearch.Thechar == '0' || Thearch.Thechar == '5' || Thearch.Thechar == '7' || Thearch.Thechar == '9' { + if Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64) { goto memcase } a.Type = obj.TYPE_MEM @@ -368,7 +369,7 @@ func mkvar(f *Flow, a *obj.Addr) Bits { if v.etype == et { if int64(v.width) == w { // TODO(rsc): Remove special case for arm here. - if flag == 0 || Thearch.Thechar != '5' { + if flag == 0 || Thearch.LinkArch.Family != sys.ARM { return blsh(uint(i)) } } diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 127a7c4698..90c4d4e95e 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -13,6 +13,7 @@ import ( "cmd/compile/internal/ssa" "cmd/internal/obj" + "cmd/internal/sys" ) var ssaEnabled = true @@ -24,13 +25,13 @@ func initssa() *ssa.Config { ssaExp.unimplemented = false ssaExp.mustImplement = true if ssaConfig == nil { - ssaConfig = ssa.NewConfig(Thearch.Thestring, &ssaExp, Ctxt, Debug['N'] == 0) + ssaConfig = ssa.NewConfig(Thearch.LinkArch.Name, &ssaExp, Ctxt, Debug['N'] == 0) } return ssaConfig } func shouldssa(fn *Node) bool { - switch Thearch.Thestring { + switch Thearch.LinkArch.Name { default: // Only available for testing. if os.Getenv("SSATEST") == "" { @@ -2409,7 +2410,7 @@ func isSSAIntrinsic1(s *Sym) bool { // so far has only been noticed for Bswap32 and the 16-bit count // leading/trailing instructions, but heuristics might change // in the future or on different architectures). - if !ssaEnabled || ssa.IntrinsicsDisable || Thearch.Thechar != '6' { + if !ssaEnabled || ssa.IntrinsicsDisable || Thearch.LinkArch.Family != sys.AMD64 { return false } if s != nil && s.Pkg != nil && s.Pkg.Path == "runtime/internal/sys" { diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index ff8ddea7f6..586a8e9c4f 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -6,6 +6,7 @@ package gc import ( "cmd/internal/obj" + "cmd/internal/sys" "fmt" "strings" ) @@ -672,8 +673,7 @@ opswitch: walkexprlist(n.List.Slice(), init) if n.Left.Op == ONAME && n.Left.Sym.Name == "Sqrt" && n.Left.Sym.Pkg.Path == "math" { - switch Thearch.Thechar { - case '5', '6', '7', '9': + if Thearch.LinkArch.InFamily(sys.AMD64, sys.ARM, sys.ARM64, sys.PPC64) { n.Op = OSQRT n.Left = n.List.First() n.List.Set(nil) @@ -1056,7 +1056,7 @@ opswitch: n = walkexpr(n, init) case OCONV, OCONVNOP: - if Thearch.Thechar == '5' { + if Thearch.LinkArch.Family == sys.ARM { if n.Left.Type.IsFloat() { if n.Type.Etype == TINT64 { n = mkcall("float64toint64", n.Type, init, conv(n.Left, Types[TFLOAT64])) @@ -3274,7 +3274,7 @@ func samecheap(a *Node, b *Node) bool { // The result of walkrotate MUST be assigned back to n, e.g. // n.Left = walkrotate(n.Left) func walkrotate(n *Node) *Node { - if Thearch.Thechar == '0' || Thearch.Thechar == '7' || Thearch.Thechar == '9' { + if Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM64, sys.PPC64) { return n } @@ -3401,7 +3401,7 @@ func walkdiv(n *Node, init *Nodes) *Node { // if >= 0, nr is 1<= 32678 { ld.Diag("TLS offset out of range %d", v) } diff --git a/src/cmd/link/internal/arm64/l.go b/src/cmd/link/internal/arm64/l.go index b9b7ea50e3..67ad5c977f 100644 --- a/src/cmd/link/internal/arm64/l.go +++ b/src/cmd/link/internal/arm64/l.go @@ -62,11 +62,9 @@ package arm64 // THE SOFTWARE. const ( - thechar = '7' MaxAlign = 32 // max data alignment MinAlign = 1 // min data alignment FuncAlign = 8 - MINLC = 4 ) /* Used by ../internal/ld/dwarf.go */ diff --git a/src/cmd/link/internal/arm64/obj.go b/src/cmd/link/internal/arm64/obj.go index 693e106ff1..1169e79a58 100644 --- a/src/cmd/link/internal/arm64/obj.go +++ b/src/cmd/link/internal/arm64/obj.go @@ -32,6 +32,7 @@ package arm64 import ( "cmd/internal/obj" + "cmd/internal/sys" "cmd/link/internal/ld" "fmt" "log" @@ -45,17 +46,11 @@ func Main() { } func linkarchinit() { - ld.Thestring = obj.Getgoarch() - ld.Thelinkarch = &ld.Linkarm64 + ld.SysArch = sys.ArchARM64 - ld.Thearch.Thechar = thechar - ld.Thearch.Ptrsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Intsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Regsize = ld.Thelinkarch.Regsize ld.Thearch.Funcalign = FuncAlign ld.Thearch.Maxalign = MaxAlign ld.Thearch.Minalign = MinAlign - ld.Thearch.Minlc = MINLC ld.Thearch.Dwarfregsp = DWARFREGSP ld.Thearch.Dwarfreglr = DWARFREGLR diff --git a/src/cmd/link/internal/ld/arch.go b/src/cmd/link/internal/ld/arch.go deleted file mode 100644 index d28f37fa02..0000000000 --- a/src/cmd/link/internal/ld/arch.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2015 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 ld - -import "encoding/binary" - -var Linkarm = LinkArch{ - ByteOrder: binary.LittleEndian, - Name: "arm", - Thechar: '5', - Minlc: 4, - Ptrsize: 4, - Regsize: 4, -} - -var Linkarm64 = LinkArch{ - ByteOrder: binary.LittleEndian, - Name: "arm64", - Thechar: '7', - Minlc: 4, - Ptrsize: 8, - Regsize: 8, -} - -var Linkamd64 = LinkArch{ - ByteOrder: binary.LittleEndian, - Name: "amd64", - Thechar: '6', - Minlc: 1, - Ptrsize: 8, - Regsize: 8, -} - -var Linkamd64p32 = LinkArch{ - ByteOrder: binary.LittleEndian, - Name: "amd64p32", - Thechar: '6', - Minlc: 1, - Ptrsize: 4, - Regsize: 8, -} - -var Link386 = LinkArch{ - ByteOrder: binary.LittleEndian, - Name: "386", - Thechar: '8', - Minlc: 1, - Ptrsize: 4, - Regsize: 4, -} - -var Linkppc64 = LinkArch{ - ByteOrder: binary.BigEndian, - Name: "ppc64", - Thechar: '9', - Minlc: 4, - Ptrsize: 8, - Regsize: 8, -} - -var Linkppc64le = LinkArch{ - ByteOrder: binary.LittleEndian, - Name: "ppc64le", - Thechar: '9', - Minlc: 4, - Ptrsize: 8, - Regsize: 8, -} - -var Linkmips64 = LinkArch{ - ByteOrder: binary.BigEndian, - Name: "mips64", - Thechar: '0', - Minlc: 4, - Ptrsize: 8, - Regsize: 8, -} - -var Linkmips64le = LinkArch{ - ByteOrder: binary.LittleEndian, - Name: "mips64le", - Thechar: '0', - Minlc: 4, - Ptrsize: 8, - Regsize: 8, -} - -var Links390x = LinkArch{ - ByteOrder: binary.BigEndian, - Name: "s390x", - Thechar: 'z', - Minlc: 2, - Ptrsize: 8, - Regsize: 8, -} diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 6bbd6c7d5c..ae430b4e45 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -34,6 +34,7 @@ package ld import ( "cmd/internal/gcprog" "cmd/internal/obj" + "cmd/internal/sys" "fmt" "log" "os" @@ -121,7 +122,7 @@ func Adduint64(ctxt *Link, s *LSym, v uint64) int64 { } func adduint(ctxt *Link, s *LSym, v uint64) int64 { - return adduintxx(ctxt, s, v, Thearch.Intsize) + return adduintxx(ctxt, s, v, SysArch.IntSize) } func setuint8(ctxt *Link, s *LSym, r int64, v uint8) int64 { @@ -138,12 +139,12 @@ func Addaddrplus(ctxt *Link, s *LSym, t *LSym, add int64) int64 { } s.Attr |= AttrReachable i := s.Size - s.Size += int64(ctxt.Arch.Ptrsize) + s.Size += int64(ctxt.Arch.PtrSize) Symgrow(ctxt, s, s.Size) r := Addrel(s) r.Sym = t r.Off = int32(i) - r.Siz = uint8(ctxt.Arch.Ptrsize) + r.Siz = uint8(ctxt.Arch.PtrSize) r.Type = obj.R_ADDR r.Add = add return i + int64(r.Siz) @@ -163,7 +164,7 @@ func Addpcrelplus(ctxt *Link, s *LSym, t *LSym, add int64) int64 { r.Add = add r.Type = obj.R_PCREL r.Siz = 4 - if Thearch.Thechar == 'z' { + if SysArch.Family == sys.S390X { r.Variant = RV_390_DBL } return i + int64(r.Siz) @@ -178,15 +179,15 @@ func setaddrplus(ctxt *Link, s *LSym, off int64, t *LSym, add int64) int64 { s.Type = obj.SDATA } s.Attr |= AttrReachable - if off+int64(ctxt.Arch.Ptrsize) > s.Size { - s.Size = off + int64(ctxt.Arch.Ptrsize) + if off+int64(ctxt.Arch.PtrSize) > s.Size { + s.Size = off + int64(ctxt.Arch.PtrSize) Symgrow(ctxt, s, s.Size) } r := Addrel(s) r.Sym = t r.Off = int32(off) - r.Siz = uint8(ctxt.Arch.Ptrsize) + r.Siz = uint8(ctxt.Arch.PtrSize) r.Type = obj.R_ADDR r.Add = add return off + int64(r.Siz) @@ -202,12 +203,12 @@ func addsize(ctxt *Link, s *LSym, t *LSym) int64 { } s.Attr |= AttrReachable i := s.Size - s.Size += int64(ctxt.Arch.Ptrsize) + s.Size += int64(ctxt.Arch.PtrSize) Symgrow(ctxt, s, s.Size) r := Addrel(s) r.Sym = t r.Off = int32(i) - r.Siz = uint8(ctxt.Arch.Ptrsize) + r.Siz = uint8(ctxt.Arch.PtrSize) r.Type = obj.R_SIZE return i + int64(r.Siz) } @@ -356,7 +357,7 @@ func relocsym(s *LSym) { // We need to be able to reference dynimport symbols when linking against // shared libraries, and Solaris needs it always if HEADTYPE != obj.Hsolaris && r.Sym != nil && r.Sym.Type == obj.SDYNIMPORT && !DynlinkingGo() { - if !(Thearch.Thechar == '9' && Linkmode == LinkExternal && r.Sym.Name == ".TOC.") { + if !(SysArch.Family == sys.PPC64 && Linkmode == LinkExternal && r.Sym.Name == ".TOC.") { Diag("unhandled relocation for %s (type %d rtype %d)", r.Sym.Name, r.Sym.Type, r.Type) } } @@ -365,7 +366,7 @@ func relocsym(s *LSym) { } // TODO(mundaym): remove this special case - see issue 14218. - if Thearch.Thechar == 'z' { + if SysArch.Family == sys.S390X { switch r.Type { case obj.R_PCRELDBL: r.Type = obj.R_PCREL @@ -394,7 +395,7 @@ func relocsym(s *LSym) { } case obj.R_TLS_LE: - isAndroidX86 := goos == "android" && (Thearch.Thechar == '6' || Thearch.Thechar == '8') + isAndroidX86 := goos == "android" && (SysArch.InFamily(sys.AMD64, sys.I386)) if Linkmode == LinkExternal && Iself && HEADTYPE != obj.Hopenbsd && !isAndroidX86 { r.Done = 0 @@ -404,13 +405,13 @@ func relocsym(s *LSym) { r.Xsym = r.Sym r.Xadd = r.Add o = 0 - if Thearch.Thechar != '6' { + if SysArch.Family != sys.AMD64 { o = r.Add } break } - if Iself && Thearch.Thechar == '5' { + if Iself && SysArch.Family == sys.ARM { // On ELF ARM, the thread pointer is 8 bytes before // the start of the thread-local data block, so add 8 // to the actual TLS offset (r->sym->value). @@ -428,7 +429,7 @@ func relocsym(s *LSym) { } case obj.R_TLS_IE: - isAndroidX86 := goos == "android" && (Thearch.Thechar == '6' || Thearch.Thechar == '8') + isAndroidX86 := goos == "android" && (SysArch.InFamily(sys.AMD64, sys.I386)) if Linkmode == LinkExternal && Iself && HEADTYPE != obj.Hopenbsd && !isAndroidX86 { r.Done = 0 @@ -438,7 +439,7 @@ func relocsym(s *LSym) { r.Xsym = r.Sym r.Xadd = r.Add o = 0 - if Thearch.Thechar != '6' { + if SysArch.Family != sys.AMD64 { o = r.Add } break @@ -465,7 +466,7 @@ func relocsym(s *LSym) { o = r.Xadd if Iself { - if Thearch.Thechar == '6' { + if SysArch.Family == sys.AMD64 { o = 0 } } else if HEADTYPE == obj.Hdarwin { @@ -475,10 +476,10 @@ func relocsym(s *LSym) { // The workaround is that on arm64 don't ever add symaddr to o and always use // extern relocation by requiring rs->dynid >= 0. if rs.Type != obj.SHOSTOBJ { - if Thearch.Thechar == '7' && rs.Dynid < 0 { + if SysArch.Family == sys.ARM64 && rs.Dynid < 0 { Diag("R_ADDR reloc to %s+%d is not supported on darwin/arm64", rs.Name, o) } - if Thearch.Thechar != '7' { + if SysArch.Family != sys.ARM64 { o += Symaddr(rs) } } @@ -498,7 +499,7 @@ func relocsym(s *LSym) { // fail at runtime. See https://golang.org/issue/7980. // Instead of special casing only amd64, we treat this as an error on all // 64-bit architectures so as to be future-proof. - if int32(o) < 0 && Thearch.Ptrsize > 4 && siz == 4 { + if int32(o) < 0 && SysArch.PtrSize > 4 && siz == 4 { Diag("non-pc-relative relocation address is too big: %#x (%#x + %#x)", uint64(o), Symaddr(r.Sym), r.Add) errorexit() } @@ -515,7 +516,7 @@ func relocsym(s *LSym) { r.Xadd = r.Add + Symaddr(r.Sym) - int64(r.Sym.Sect.Vaddr) o = r.Xadd rs = r.Xsym - if Iself && Thearch.Thechar == '6' { + if Iself && SysArch.Family == sys.AMD64 { o = 0 } break @@ -544,7 +545,7 @@ func relocsym(s *LSym) { o = r.Xadd if Iself { - if Thearch.Thechar == '6' { + if SysArch.Family == sys.AMD64 { o = 0 } } else if HEADTYPE == obj.Hdarwin { @@ -556,7 +557,7 @@ func relocsym(s *LSym) { } else { o += int64(r.Siz) } - } else if HEADTYPE == obj.Hwindows && Thearch.Thechar == '6' { // only amd64 needs PCREL + } else if HEADTYPE == obj.Hwindows && SysArch.Family == sys.AMD64 { // only amd64 needs PCREL // PE/COFF's PC32 relocation uses the address after the relocated // bytes as the base. Compensate by skewing the addend. o += int64(r.Siz) @@ -675,7 +676,7 @@ func dynrelocsym(s *LSym) { r.Add = int64(targ.Plt) // jmp *addr - if Thearch.Thechar == '8' { + if SysArch.Family == sys.I386 { Adduint8(Ctxt, rel, 0xff) Adduint8(Ctxt, rel, 0x25) Addaddr(Ctxt, rel, targ) @@ -982,7 +983,7 @@ func addstrdata(name string, value string) { s.Attr |= AttrDuplicateOK reachable := s.Attr.Reachable() Addaddr(Ctxt, s, sp) - adduintxx(Ctxt, s, uint64(len(value)), Thearch.Ptrsize) + adduintxx(Ctxt, s, uint64(len(value)), SysArch.PtrSize) // addstring, addaddr, etc., mark the symbols as reachable. // In this case that is not necessarily true, so stick to what @@ -1128,7 +1129,7 @@ func (p *GCProg) writeByte(x byte) { } func (p *GCProg) End(size int64) { - p.w.ZeroUntil(size / int64(Thearch.Ptrsize)) + p.w.ZeroUntil(size / int64(SysArch.PtrSize)) p.w.End() if debugGCProg { fmt.Fprintf(os.Stderr, "ld: end GCProg\n") @@ -1144,7 +1145,7 @@ func (p *GCProg) AddSym(s *LSym) { return } - ptrsize := int64(Thearch.Ptrsize) + ptrsize := int64(SysArch.PtrSize) nptr := decodetype_ptrdata(typ) / ptrsize if debugGCProg { @@ -1532,7 +1533,7 @@ func dodata() { if s != nil && s.Type == obj.STLSBSS { if Iself && (Linkmode == LinkExternal || Debug['d'] == 0) && HEADTYPE != obj.Hopenbsd { sect = addsection(&Segdata, ".tbss", 06) - sect.Align = int32(Thearch.Ptrsize) + sect.Align = int32(SysArch.PtrSize) sect.Vaddr = 0 } else { sect = nil diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go index 56c4370bcc..b17b96001e 100644 --- a/src/cmd/link/internal/ld/deadcode.go +++ b/src/cmd/link/internal/ld/deadcode.go @@ -6,6 +6,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "fmt" "strings" "unicode" @@ -227,7 +228,7 @@ func (d *deadcodepass) markMethod(m methodref) { func (d *deadcodepass) init() { var names []string - if Thearch.Thechar == '5' { + if SysArch.Family == sys.ARM { // mark some functions that are only referenced after linker code editing if d.ctxt.Goarm == 5 { names = append(names, "_sfloat") diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go index 0a6bf094aa..bc29938590 100644 --- a/src/cmd/link/internal/ld/decodesym.go +++ b/src/cmd/link/internal/ld/decodesym.go @@ -7,6 +7,7 @@ package ld import ( "bytes" "cmd/internal/obj" + "cmd/internal/sys" "debug/elf" "fmt" ) @@ -46,39 +47,39 @@ func decode_inuxi(p []byte, sz int) uint64 { } } -func commonsize() int { return 6*Thearch.Ptrsize + 8 } // runtime._type -func structfieldSize() int { return 3 * Thearch.Ptrsize } // runtime.structfield -func uncommonSize() int { return 2*Thearch.Ptrsize + 2*Thearch.Intsize } // runtime.uncommontype +func commonsize() int { return 6*SysArch.PtrSize + 8 } // runtime._type +func structfieldSize() int { return 3 * SysArch.PtrSize } // runtime.structfield +func uncommonSize() int { return 2*SysArch.PtrSize + 2*SysArch.IntSize } // runtime.uncommontype // Type.commonType.kind func decodetype_kind(s *LSym) uint8 { - return uint8(s.P[2*Thearch.Ptrsize+7] & obj.KindMask) // 0x13 / 0x1f + return uint8(s.P[2*SysArch.PtrSize+7] & obj.KindMask) // 0x13 / 0x1f } // Type.commonType.kind func decodetype_noptr(s *LSym) uint8 { - return uint8(s.P[2*Thearch.Ptrsize+7] & obj.KindNoPointers) // 0x13 / 0x1f + return uint8(s.P[2*SysArch.PtrSize+7] & obj.KindNoPointers) // 0x13 / 0x1f } // Type.commonType.kind func decodetype_usegcprog(s *LSym) uint8 { - return uint8(s.P[2*Thearch.Ptrsize+7] & obj.KindGCProg) // 0x13 / 0x1f + return uint8(s.P[2*SysArch.PtrSize+7] & obj.KindGCProg) // 0x13 / 0x1f } // Type.commonType.size func decodetype_size(s *LSym) int64 { - return int64(decode_inuxi(s.P, Thearch.Ptrsize)) // 0x8 / 0x10 + return int64(decode_inuxi(s.P, SysArch.PtrSize)) // 0x8 / 0x10 } // Type.commonType.ptrdata func decodetype_ptrdata(s *LSym) int64 { - return int64(decode_inuxi(s.P[Thearch.Ptrsize:], Thearch.Ptrsize)) // 0x8 / 0x10 + return int64(decode_inuxi(s.P[SysArch.PtrSize:], SysArch.PtrSize)) // 0x8 / 0x10 } // Type.commonType.tflag func decodetype_hasUncommon(s *LSym) bool { const tflagUncommon = 1 // see ../../../../reflect/type.go:/^type.tflag - return s.P[2*Thearch.Ptrsize+4]&tflagUncommon != 0 + return s.P[2*SysArch.PtrSize+4]&tflagUncommon != 0 } // Find the elf.Section of a given shared library that contains a given address. @@ -112,11 +113,11 @@ func decodetype_gcprog(s *LSym) []byte { Exitf("cannot find gcprog for %s", s.Name) return nil } - return decode_reloc_sym(s, 2*int32(Thearch.Ptrsize)+8+1*int32(Thearch.Ptrsize)).P + return decode_reloc_sym(s, 2*int32(SysArch.PtrSize)+8+1*int32(SysArch.PtrSize)).P } func decodetype_gcprog_shlib(s *LSym) uint64 { - if Thearch.Thechar == '7' { + if SysArch.Family == sys.ARM64 { for _, shlib := range Ctxt.Shlibs { if shlib.Path == s.File { return shlib.gcdata_addresses[s] @@ -124,7 +125,7 @@ func decodetype_gcprog_shlib(s *LSym) uint64 { } return 0 } - return decode_inuxi(s.P[2*int32(Thearch.Ptrsize)+8+1*int32(Thearch.Ptrsize):], Thearch.Ptrsize) + return decode_inuxi(s.P[2*int32(SysArch.PtrSize)+8+1*int32(SysArch.PtrSize):], SysArch.PtrSize) } func decodetype_gcmask(s *LSym) []byte { @@ -133,14 +134,14 @@ func decodetype_gcmask(s *LSym) []byte { ptrdata := decodetype_ptrdata(s) sect := findShlibSection(s.File, addr) if sect != nil { - r := make([]byte, ptrdata/int64(Thearch.Ptrsize)) + r := make([]byte, ptrdata/int64(SysArch.PtrSize)) sect.ReadAt(r, int64(addr-sect.Addr)) return r } Exitf("cannot find gcmask for %s", s.Name) return nil } - mask := decode_reloc_sym(s, 2*int32(Thearch.Ptrsize)+8+1*int32(Thearch.Ptrsize)) + mask := decode_reloc_sym(s, 2*int32(SysArch.PtrSize)+8+1*int32(SysArch.PtrSize)) return mask.P } @@ -150,7 +151,7 @@ func decodetype_arrayelem(s *LSym) *LSym { } func decodetype_arraylen(s *LSym) int64 { - return int64(decode_inuxi(s.P[commonsize()+2*Thearch.Ptrsize:], Thearch.Ptrsize)) + return int64(decode_inuxi(s.P[commonsize()+2*SysArch.PtrSize:], SysArch.PtrSize)) } // Type.PtrType.elem @@ -164,7 +165,7 @@ func decodetype_mapkey(s *LSym) *LSym { } func decodetype_mapvalue(s *LSym) *LSym { - return decode_reloc_sym(s, int32(commonsize())+int32(Thearch.Ptrsize)) // 0x20 / 0x38 + return decode_reloc_sym(s, int32(commonsize())+int32(SysArch.PtrSize)) // 0x20 / 0x38 } // Type.ChanType.elem @@ -188,13 +189,13 @@ func decodetype_funcoutcount(s *LSym) int { func decodetype_funcintype(s *LSym, i int) *LSym { uadd := commonsize() + 4 - if Thearch.Ptrsize == 8 { + if SysArch.PtrSize == 8 { uadd += 4 } if decodetype_hasUncommon(s) { uadd += uncommonSize() } - return decode_reloc_sym(s, int32(uadd+i*Thearch.Ptrsize)) + return decode_reloc_sym(s, int32(uadd+i*SysArch.PtrSize)) } func decodetype_funcouttype(s *LSym, i int) *LSym { @@ -203,11 +204,11 @@ func decodetype_funcouttype(s *LSym, i int) *LSym { // Type.StructType.fields.Slice::length func decodetype_structfieldcount(s *LSym) int { - return int(decode_inuxi(s.P[commonsize()+2*Thearch.Ptrsize:], Thearch.Intsize)) + return int(decode_inuxi(s.P[commonsize()+2*SysArch.PtrSize:], SysArch.IntSize)) } func decodetype_structfieldarrayoff(s *LSym, i int) int { - off := commonsize() + 2*Thearch.Ptrsize + 2*Thearch.Intsize + off := commonsize() + 2*SysArch.PtrSize + 2*SysArch.IntSize if decodetype_hasUncommon(s) { off += uncommonSize() } @@ -224,7 +225,7 @@ func decodetype_stringptr(s *LSym, off int) string { if r == nil { // shouldn't happen. return "" } - strlen := int64(decode_inuxi(s.P[Thearch.Ptrsize:], Thearch.Intsize)) + strlen := int64(decode_inuxi(s.P[SysArch.PtrSize:], SysArch.IntSize)) return string(r.Sym.P[r.Add : r.Add+strlen]) } @@ -248,17 +249,17 @@ func decodetype_structfieldname(s *LSym, i int) string { func decodetype_structfieldtype(s *LSym, i int) *LSym { off := decodetype_structfieldarrayoff(s, i) - return decode_reloc_sym(s, int32(off+Thearch.Ptrsize)) + return decode_reloc_sym(s, int32(off+SysArch.PtrSize)) } func decodetype_structfieldoffs(s *LSym, i int) int64 { off := decodetype_structfieldarrayoff(s, i) - return int64(decode_inuxi(s.P[off+2*Thearch.Ptrsize:], Thearch.Intsize)) + return int64(decode_inuxi(s.P[off+2*SysArch.PtrSize:], SysArch.IntSize)) } // InterfaceType.methods.length func decodetype_ifacemethodcount(s *LSym) int64 { - return int64(decode_inuxi(s.P[commonsize()+2*Thearch.Ptrsize:], Thearch.Intsize)) + return int64(decode_inuxi(s.P[commonsize()+2*SysArch.PtrSize:], SysArch.IntSize)) } // methodsig is a fully qualified typed method signature, like @@ -288,7 +289,7 @@ func decode_methodsig(s *LSym, off, size, count int) []methodsig { var methods []methodsig for i := 0; i < count; i++ { buf.WriteString(decodetype_name(s, off)) - mtypSym := decode_reloc_sym(s, int32(off+Thearch.Ptrsize)) + mtypSym := decode_reloc_sym(s, int32(off+SysArch.PtrSize)) buf.WriteRune('(') inCount := decodetype_funcincount(mtypSym) @@ -319,7 +320,7 @@ func decodetype_ifacemethods(s *LSym) []methodsig { if decodetype_kind(s)&kindMask != kindInterface { panic(fmt.Sprintf("symbol %q is not an interface", s.Name)) } - r := decode_reloc(s, int32(commonsize()+Thearch.Ptrsize)) + r := decode_reloc(s, int32(commonsize()+SysArch.PtrSize)) if r == nil { return nil } @@ -328,7 +329,7 @@ func decodetype_ifacemethods(s *LSym) []methodsig { } off := int(r.Add) // array of reflect.imethod values numMethods := int(decodetype_ifacemethodcount(s)) - sizeofIMethod := 2 * Thearch.Ptrsize + sizeofIMethod := 2 * SysArch.PtrSize return decode_methodsig(s, off, sizeofIMethod, numMethods) } @@ -339,31 +340,31 @@ func decodetype_methods(s *LSym) []methodsig { off := commonsize() // reflect.rtype switch decodetype_kind(s) & kindMask { case kindStruct: // reflect.structType - off += 2*Thearch.Ptrsize + 2*Thearch.Intsize + off += 2*SysArch.PtrSize + 2*SysArch.IntSize case kindPtr: // reflect.ptrType - off += Thearch.Ptrsize + off += SysArch.PtrSize case kindFunc: // reflect.funcType - off += Thearch.Ptrsize // 4 bytes, pointer aligned + off += SysArch.PtrSize // 4 bytes, pointer aligned case kindSlice: // reflect.sliceType - off += Thearch.Ptrsize + off += SysArch.PtrSize case kindArray: // reflect.arrayType - off += 3 * Thearch.Ptrsize + off += 3 * SysArch.PtrSize case kindChan: // reflect.chanType - off += 2 * Thearch.Ptrsize + off += 2 * SysArch.PtrSize case kindMap: // reflect.mapType - off += 4*Thearch.Ptrsize + 8 + off += 4*SysArch.PtrSize + 8 case kindInterface: // reflect.interfaceType - off += Thearch.Ptrsize + 2*Thearch.Intsize + off += SysArch.PtrSize + 2*SysArch.IntSize default: // just Sizeof(rtype) } - numMethods := int(decode_inuxi(s.P[off+2*Thearch.Ptrsize:], Thearch.Intsize)) - r := decode_reloc(s, int32(off+Thearch.Ptrsize)) + numMethods := int(decode_inuxi(s.P[off+2*SysArch.PtrSize:], SysArch.IntSize)) + r := decode_reloc(s, int32(off+SysArch.PtrSize)) if r.Sym != s { panic(fmt.Sprintf("method slice pointer in %s leads to a different symbol %s", s, r.Sym)) } off = int(r.Add) // array of reflect.method values - sizeofMethod := 4 * Thearch.Ptrsize // sizeof reflect.method in program + sizeofMethod := 4 * SysArch.PtrSize // sizeof reflect.method in program return decode_methodsig(s, off, sizeofMethod, numMethods) } diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index eaa0bdbb41..230d146877 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -39,7 +39,7 @@ var gdbscript string * Basic I/O */ func addrput(s *LSym, addr int64) { - switch Thearch.Ptrsize { + switch SysArch.PtrSize { case 4: Adduint32(Ctxt, s, uint32(addr)) @@ -569,7 +569,7 @@ func adddwarfref(ctxt *Link, s *LSym, t *LSym, size int) int64 { default: Diag("invalid size %d in adddwarfref\n", size) fallthrough - case Thearch.Ptrsize: + case SysArch.PtrSize: result = Addaddr(ctxt, s, t) case 4: result = addaddrplus4(ctxt, s, t, 0) @@ -599,7 +599,7 @@ func putattr(s *LSym, abbrev int, form int, cls int, value int64, data interface case DW_FORM_block1: // block if cls == DW_CLS_ADDRESS { - Adduint8(Ctxt, s, uint8(1+Thearch.Ptrsize)) + Adduint8(Ctxt, s, uint8(1+SysArch.PtrSize)) Adduint8(Ctxt, s, DW_OP_addr) Addaddr(Ctxt, s, data.(*LSym)) break @@ -682,14 +682,14 @@ func putattr(s *LSym, abbrev int, form int, cls int, value int64, data interface case DW_FORM_ref_addr: // reference to a DIE in the .info section if data == nil { Diag("dwarf: null reference in %d", abbrev) - if Thearch.Ptrsize == 8 { + if SysArch.PtrSize == 8 { Adduint64(Ctxt, s, 0) // invalid dwarf, gdb will complain. } else { Adduint32(Ctxt, s, 0) // invalid dwarf, gdb will complain. } } else { dsym := data.(*LSym) - adddwarfref(Ctxt, s, dsym, Thearch.Ptrsize) + adddwarfref(Ctxt, s, dsym, SysArch.PtrSize) } case DW_FORM_ref1, // reference within the compilation unit @@ -1161,11 +1161,11 @@ func synthesizemaptypes(die *DWDie) { // compute size info like hashmap.c does. indirect_key, indirect_val := false, false if keysize > MaxKeySize { - keysize = int64(Thearch.Ptrsize) + keysize = int64(SysArch.PtrSize) indirect_key = true } if valsize > MaxValSize { - valsize = int64(Thearch.Ptrsize) + valsize = int64(SysArch.PtrSize) indirect_val = true } @@ -1212,13 +1212,13 @@ func synthesizemaptypes(die *DWDie) { fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "overflow", 0) newrefattr(fld, DW_AT_type, defptrto(dwhb.sym)) newmemberoffsetattr(fld, BucketSize+BucketSize*(int32(keysize)+int32(valsize))) - if Thearch.Regsize > Thearch.Ptrsize { + if SysArch.RegSize > SysArch.PtrSize { fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "pad", 0) newrefattr(fld, DW_AT_type, mustFind("uintptr")) - newmemberoffsetattr(fld, BucketSize+BucketSize*(int32(keysize)+int32(valsize))+int32(Thearch.Ptrsize)) + newmemberoffsetattr(fld, BucketSize+BucketSize*(int32(keysize)+int32(valsize))+int32(SysArch.PtrSize)) } - newattr(dwhb, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize+BucketSize*int64(keysize)+BucketSize*int64(valsize)+int64(Thearch.Regsize), 0) + newattr(dwhb, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize+BucketSize*int64(keysize)+BucketSize*int64(valsize)+int64(SysArch.RegSize), 0) }) // Construct hash @@ -1481,7 +1481,7 @@ func writelines(prev *LSym) *LSym { headerend = ls.Size Adduint8(Ctxt, ls, 0) // start extended opcode - uleb128put(ls, 1+int64(Thearch.Ptrsize)) + uleb128put(ls, 1+int64(SysArch.PtrSize)) Adduint8(Ctxt, ls, DW_LNE_set_address) pc := s.Value @@ -1555,7 +1555,7 @@ func writelines(prev *LSym) *LSym { dt = DW_ABRV_AUTO offs = int64(a.Aoffset) if !haslinkregister() { - offs -= int64(Thearch.Ptrsize) + offs -= int64(SysArch.PtrSize) } case obj.A_PARAM: @@ -1667,7 +1667,7 @@ func writeframes(prev *LSym) *LSym { if haslinkregister() { uleb128put(fs, int64(0)) // offset } else { - uleb128put(fs, int64(Thearch.Ptrsize)) // offset + uleb128put(fs, int64(SysArch.PtrSize)) // offset } Adduint8(Ctxt, fs, DW_CFA_offset_extended) @@ -1675,7 +1675,7 @@ func writeframes(prev *LSym) *LSym { if haslinkregister() { uleb128put(fs, int64(0)/DATAALIGNMENTFACTOR) // at cfa - 0 } else { - uleb128put(fs, int64(-Thearch.Ptrsize)/DATAALIGNMENTFACTOR) // at cfa - x*4 + uleb128put(fs, int64(-SysArch.PtrSize)/DATAALIGNMENTFACTOR) // at cfa - x*4 } // 4 is to exclude the length field. @@ -1713,10 +1713,10 @@ func writeframes(prev *LSym) *LSym { if haslinkregister() { deltaBuf = appendPCDeltaCFA(deltaBuf, int64(nextpc)-int64(pcsp.pc), int64(pcsp.value)) } else { - deltaBuf = appendPCDeltaCFA(deltaBuf, int64(nextpc)-int64(pcsp.pc), int64(Thearch.Ptrsize)+int64(pcsp.value)) + deltaBuf = appendPCDeltaCFA(deltaBuf, int64(nextpc)-int64(pcsp.pc), int64(SysArch.PtrSize)+int64(pcsp.value)) } } - pad := int(Rnd(int64(len(deltaBuf)), int64(Thearch.Ptrsize))) - len(deltaBuf) + pad := int(Rnd(int64(len(deltaBuf)), int64(SysArch.PtrSize))) - len(deltaBuf) deltaBuf = append(deltaBuf, zeros[:pad]...) // Emit the FDE header, Section 6.4.1. @@ -1724,7 +1724,7 @@ func writeframes(prev *LSym) *LSym { // 4 bytes: Pointer to the CIE above, at offset 0 // ptrsize: initial location // ptrsize: address range - Adduint32(Ctxt, fs, uint32(4+2*Thearch.Ptrsize+len(deltaBuf))) // length (excludes itself) + Adduint32(Ctxt, fs, uint32(4+2*SysArch.PtrSize+len(deltaBuf))) // length (excludes itself) if Linkmode == LinkExternal { adddwarfref(Ctxt, fs, framesec, 4) } else { @@ -1771,7 +1771,7 @@ func writeinfo(prev *LSym) *LSym { // debug_abbrev_offset (*) adddwarfref(Ctxt, s, abbrevsym, 4) - Adduint8(Ctxt, s, uint8(Thearch.Ptrsize)) // address_size + Adduint8(Ctxt, s, uint8(SysArch.PtrSize)) // address_size prev = putdie(prev, compunit) cusize := s.Size - 4 // exclude the length field. @@ -1848,7 +1848,7 @@ func writearanges(prev *LSym) *LSym { s.Type = obj.SDWARFSECT // The first tuple is aligned to a multiple of the size of a single tuple // (twice the size of an address) - headersize := int(Rnd(4+2+4+1+1, int64(Thearch.Ptrsize*2))) // don't count unit_length field itself + headersize := int(Rnd(4+2+4+1+1, int64(SysArch.PtrSize*2))) // don't count unit_length field itself for compunit := dwroot.child; compunit != nil; compunit = compunit.link { b := getattr(compunit, DW_AT_low_pc) @@ -1861,13 +1861,13 @@ func writearanges(prev *LSym) *LSym { } // Write .debug_aranges Header + entry (sec 6.1.2) - unitlength := uint32(headersize) + 4*uint32(Thearch.Ptrsize) - 4 + unitlength := uint32(headersize) + 4*uint32(SysArch.PtrSize) - 4 Adduint32(Ctxt, s, unitlength) // unit_length (*) Adduint16(Ctxt, s, 2) // dwarf version (appendix F) adddwarfref(Ctxt, s, compunit.sym, 4) - Adduint8(Ctxt, s, uint8(Thearch.Ptrsize)) // address_size + Adduint8(Ctxt, s, uint8(SysArch.PtrSize)) // address_size Adduint8(Ctxt, s, 0) // segment_size padding := headersize - (4 + 2 + 4 + 1 + 1) for i := 0; i < padding; i++ { @@ -1940,7 +1940,7 @@ func dwarfgeneratedebugsyms() { die := newdie(&dwtypes, DW_ABRV_BASETYPE, "uintptr", 0) // needed for array size newattr(die, DW_AT_encoding, DW_CLS_CONSTANT, DW_ATE_unsigned, 0) - newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, int64(Thearch.Ptrsize), 0) + newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, int64(SysArch.PtrSize), 0) newattr(die, DW_AT_go_kind, DW_CLS_CONSTANT, obj.KindUintptr, 0) // Prototypes needed for type synthesis. diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 035826df7c..7c760775b5 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -6,6 +6,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "crypto/sha1" "encoding/binary" "encoding/hex" @@ -866,25 +867,23 @@ var buildinfo []byte func Elfinit() { Iself = true - switch Thearch.Thechar { - case '0', '6', '7', '9', 'z': + if SysArch.InFamily(sys.AMD64, sys.ARM64, sys.MIPS64, sys.PPC64, sys.S390X) { elfRelType = ".rela" - default: + } else { elfRelType = ".rel" } - switch Thearch.Thechar { + switch SysArch.Family { // 64-bit architectures - case '9', 'z': + case sys.PPC64, sys.S390X: if Ctxt.Arch.ByteOrder == binary.BigEndian { ehdr.flags = 1 /* Version 1 ABI */ } else { ehdr.flags = 2 /* Version 2 ABI */ } fallthrough - - case '0', '6', '7': - if Thearch.Thechar == '0' { + case sys.AMD64, sys.ARM64, sys.MIPS64: + if SysArch.Family == sys.MIPS64 { ehdr.flags = 0x20000000 /* MIPS 3 */ } elf64 = true @@ -897,7 +896,7 @@ func Elfinit() { // we use EABI on both linux/arm and freebsd/arm. // 32-bit architectures - case '5': + case sys.ARM: // we use EABI on both linux/arm and freebsd/arm. if HEADTYPE == obj.Hlinux || HEADTYPE == obj.Hfreebsd { // We set a value here that makes no indication of which @@ -911,7 +910,6 @@ func Elfinit() { ehdr.flags = 0x5000002 // has entry point, Version5 EABI } fallthrough - default: ehdr.phoff = ELF32HDRSIZE /* Must be be ELF32HDRSIZE: first PHdr must follow ELF header */ @@ -1432,7 +1430,7 @@ func elfdynhash() { } // s390x (ELF64) hash table entries are 8 bytes - if Thearch.Thechar == 'z' { + if SysArch.Family == sys.S390X { Adduint64(Ctxt, s, uint64(nbucket)) Adduint64(Ctxt, s, uint64(nsym)) for i := 0; i < nbucket; i++ { @@ -1660,15 +1658,15 @@ func elfshreloc(sect *Section) *ElfShdr { sh := elfshname(elfRelType + sect.Name) sh.type_ = uint32(typ) - sh.entsize = uint64(Thearch.Regsize) * 2 + sh.entsize = uint64(SysArch.RegSize) * 2 if typ == SHT_RELA { - sh.entsize += uint64(Thearch.Regsize) + sh.entsize += uint64(SysArch.RegSize) } sh.link = uint32(elfshname(".symtab").shnum) sh.info = uint32(sect.Elfsect.shnum) sh.off = sect.Reloff sh.size = sect.Rellen - sh.addralign = uint64(Thearch.Regsize) + sh.addralign = uint64(SysArch.RegSize) return sh } @@ -1872,7 +1870,7 @@ func doelf() { Addstring(shstrtab, ".interp") Addstring(shstrtab, ".hash") Addstring(shstrtab, ".got") - if Thearch.Thechar == '9' { + if SysArch.Family == sys.PPC64 { Addstring(shstrtab, ".glink") } Addstring(shstrtab, ".got.plt") @@ -1919,7 +1917,7 @@ func doelf() { s.Type = obj.SELFGOT // writable /* ppc64 glink resolver */ - if Thearch.Thechar == '9' { + if SysArch.Family == sys.PPC64 { s := Linklookup(Ctxt, ".glink", 0) s.Attr |= AttrReachable s.Type = obj.SELFRXSECT @@ -1938,7 +1936,7 @@ func doelf() { s = Linklookup(Ctxt, ".plt", 0) s.Attr |= AttrReachable - if Thearch.Thechar == '9' { + if SysArch.Family == sys.PPC64 { // In the ppc64 ABI, .plt is a data section // written by the dynamic linker. s.Type = obj.SELFSECT @@ -1993,15 +1991,15 @@ func doelf() { Elfwritedynent(s, DT_RUNPATH, uint64(Addstring(dynstr, rpath.val))) } - if Thearch.Thechar == '9' { + if SysArch.Family == sys.PPC64 { elfwritedynentsym(s, DT_PLTGOT, Linklookup(Ctxt, ".plt", 0)) - } else if Thearch.Thechar == 'z' { + } else if SysArch.Family == sys.S390X { elfwritedynentsym(s, DT_PLTGOT, Linklookup(Ctxt, ".got", 0)) } else { elfwritedynentsym(s, DT_PLTGOT, Linklookup(Ctxt, ".got.plt", 0)) } - if Thearch.Thechar == '9' { + if SysArch.Family == sys.PPC64 { Elfwritedynent(s, DT_PPC64_OPT, 0) } @@ -2080,22 +2078,22 @@ func Asmbelfsetup() { func Asmbelf(symo int64) { eh := getElfEhdr() - switch Thearch.Thechar { + switch SysArch.Family { default: - Exitf("unknown architecture in asmbelf: %v", Thearch.Thechar) - case '0': + Exitf("unknown architecture in asmbelf: %v", SysArch.Family) + case sys.MIPS64: eh.machine = EM_MIPS - case '5': + case sys.ARM: eh.machine = EM_ARM - case '6': + case sys.AMD64: eh.machine = EM_X86_64 - case '7': + case sys.ARM64: eh.machine = EM_AARCH64 - case '8': + case sys.I386: eh.machine = EM_386 - case '9': + case sys.PPC64: eh.machine = EM_PPC64 - case 'z': + case sys.S390X: eh.machine = EM_S390 } @@ -2251,7 +2249,7 @@ func Asmbelf(symo int64) { } else { sh.entsize = ELF32SYMSIZE } - sh.addralign = uint64(Thearch.Regsize) + sh.addralign = uint64(SysArch.RegSize) sh.link = uint32(elfshname(".dynstr").shnum) // sh->info = index of first non-local symbol (number of local symbols) @@ -2275,7 +2273,7 @@ func Asmbelf(symo int64) { sh = elfshname(".gnu.version_r") sh.type_ = SHT_GNU_VERNEED sh.flags = SHF_ALLOC - sh.addralign = uint64(Thearch.Regsize) + sh.addralign = uint64(SysArch.RegSize) sh.info = uint32(elfverneed) sh.link = uint32(elfshname(".dynstr").shnum) shsym(sh, Linklookup(Ctxt, ".gnu.version_r", 0)) @@ -2286,7 +2284,7 @@ func Asmbelf(symo int64) { sh.type_ = SHT_RELA sh.flags = SHF_ALLOC sh.entsize = ELF64RELASIZE - sh.addralign = uint64(Thearch.Regsize) + sh.addralign = uint64(SysArch.RegSize) sh.link = uint32(elfshname(".dynsym").shnum) sh.info = uint32(elfshname(".plt").shnum) shsym(sh, Linklookup(Ctxt, ".rela.plt", 0)) @@ -2350,15 +2348,15 @@ func Asmbelf(symo int64) { sh := elfshname(".got") sh.type_ = SHT_PROGBITS sh.flags = SHF_ALLOC + SHF_WRITE - sh.entsize = uint64(Thearch.Regsize) - sh.addralign = uint64(Thearch.Regsize) + sh.entsize = uint64(SysArch.RegSize) + sh.addralign = uint64(SysArch.RegSize) shsym(sh, Linklookup(Ctxt, ".got", 0)) sh = elfshname(".got.plt") sh.type_ = SHT_PROGBITS sh.flags = SHF_ALLOC + SHF_WRITE - sh.entsize = uint64(Thearch.Regsize) - sh.addralign = uint64(Thearch.Regsize) + sh.entsize = uint64(SysArch.RegSize) + sh.addralign = uint64(SysArch.RegSize) shsym(sh, Linklookup(Ctxt, ".got.plt", 0)) } @@ -2366,7 +2364,7 @@ func Asmbelf(symo int64) { sh.type_ = SHT_HASH sh.flags = SHF_ALLOC sh.entsize = 4 - sh.addralign = uint64(Thearch.Regsize) + sh.addralign = uint64(SysArch.RegSize) sh.link = uint32(elfshname(".dynsym").shnum) shsym(sh, Linklookup(Ctxt, ".hash", 0)) @@ -2375,8 +2373,8 @@ func Asmbelf(symo int64) { sh.type_ = SHT_DYNAMIC sh.flags = SHF_ALLOC + SHF_WRITE - sh.entsize = 2 * uint64(Thearch.Regsize) - sh.addralign = uint64(Thearch.Regsize) + sh.entsize = 2 * uint64(SysArch.RegSize) + sh.addralign = uint64(SysArch.RegSize) sh.link = uint32(elfshname(".dynstr").shnum) shsym(sh, Linklookup(Ctxt, ".dynamic", 0)) ph := newElfPhdr() @@ -2402,7 +2400,7 @@ func Asmbelf(symo int64) { ph.type_ = PT_TLS ph.flags = PF_R ph.memsz = tlssize - ph.align = uint64(Thearch.Regsize) + ph.align = uint64(SysArch.RegSize) } } } @@ -2411,12 +2409,12 @@ func Asmbelf(symo int64) { ph := newElfPhdr() ph.type_ = PT_GNU_STACK ph.flags = PF_W + PF_R - ph.align = uint64(Thearch.Regsize) + ph.align = uint64(SysArch.RegSize) ph = newElfPhdr() ph.type_ = PT_PAX_FLAGS ph.flags = 0x2a00 // mprotect, randexec, emutramp disabled - ph.align = uint64(Thearch.Regsize) + ph.align = uint64(SysArch.RegSize) } elfobj: @@ -2476,8 +2474,8 @@ elfobj: sh.type_ = SHT_SYMTAB sh.off = uint64(symo) sh.size = uint64(Symsize) - sh.addralign = uint64(Thearch.Regsize) - sh.entsize = 8 + 2*uint64(Thearch.Regsize) + sh.addralign = uint64(SysArch.RegSize) + sh.entsize = 8 + 2*uint64(SysArch.RegSize) sh.link = uint32(elfshname(".strtab").shnum) sh.info = uint32(elfglobalsymndx) @@ -2600,7 +2598,7 @@ func Elfadddynsym(ctxt *Link, s *LSym) { /* size of object */ Adduint64(ctxt, d, uint64(s.Size)) - if Thearch.Thechar == '6' && !s.Attr.CgoExportDynamic() && s.Dynimplib != "" && !seenlib[s.Dynimplib] { + if SysArch.Family == sys.AMD64 && !s.Attr.CgoExportDynamic() && s.Dynimplib != "" && !seenlib[s.Dynimplib] { Elfwritedynent(Linklookup(ctxt, ".dynamic", 0), DT_NEEDED, uint64(Addstring(Linklookup(ctxt, ".dynstr", 0), s.Dynimplib))) } } else { @@ -2628,9 +2626,9 @@ func Elfadddynsym(ctxt *Link, s *LSym) { t := STB_GLOBAL << 4 // TODO(mwhudson): presumably the behaviour should actually be the same on both arm and 386. - if Thearch.Thechar == '8' && s.Attr.CgoExport() && s.Type&obj.SMASK == obj.STEXT { + if SysArch.Family == sys.I386 && s.Attr.CgoExport() && s.Type&obj.SMASK == obj.STEXT { t |= STT_FUNC - } else if Thearch.Thechar == '5' && s.Attr.CgoExportDynamic() && s.Type&obj.SMASK == obj.STEXT { + } else if SysArch.Family == sys.ARM && s.Attr.CgoExportDynamic() && s.Type&obj.SMASK == obj.STEXT { t |= STT_FUNC } else { t |= STT_OBJECT diff --git a/src/cmd/link/internal/ld/ldelf.go b/src/cmd/link/internal/ld/ldelf.go index 0255331ac6..3aee2d5ece 100644 --- a/src/cmd/link/internal/ld/ldelf.go +++ b/src/cmd/link/internal/ld/ldelf.go @@ -3,6 +3,7 @@ package ld import ( "bytes" "cmd/internal/obj" + "cmd/internal/sys" "encoding/binary" "fmt" "io" @@ -546,47 +547,48 @@ func ldelf(f *obj.Biobuf, pkg string, length int64, pn string) { return } - switch Thearch.Thechar { + switch SysArch.Family { default: - Diag("%s: elf %s unimplemented", pn, Thestring) + Diag("%s: elf %s unimplemented", pn, SysArch.Name) return - case '0': + case sys.MIPS64: if elfobj.machine != ElfMachMips || hdr.Ident[4] != ElfClass64 { Diag("%s: elf object but not mips64", pn) return } - case '5': + case sys.ARM: if e != binary.LittleEndian || elfobj.machine != ElfMachArm || hdr.Ident[4] != ElfClass32 { Diag("%s: elf object but not arm", pn) return } - case '6': + case sys.AMD64: if e != binary.LittleEndian || elfobj.machine != ElfMachAmd64 || hdr.Ident[4] != ElfClass64 { Diag("%s: elf object but not amd64", pn) return } - case '7': + case sys.ARM64: if e != binary.LittleEndian || elfobj.machine != ElfMachArm64 || hdr.Ident[4] != ElfClass64 { Diag("%s: elf object but not arm64", pn) return } - case '8': + case sys.I386: if e != binary.LittleEndian || elfobj.machine != ElfMach386 || hdr.Ident[4] != ElfClass32 { Diag("%s: elf object but not 386", pn) return } - case '9': + case sys.PPC64: if elfobj.machine != ElfMachPower64 || hdr.Ident[4] != ElfClass64 { Diag("%s: elf object but not ppc64", pn) return } - case 'z': + + case sys.S390X: if elfobj.machine != ElfMachS390 || hdr.Ident[4] != ElfClass64 { Diag("%s: elf object but not s390x", pn) return @@ -1056,7 +1058,7 @@ func readelfsym(elfobj *ElfObj, i int, sym *ElfSym, needSym int) (err error) { } case ElfSymBindLocal: - if Thearch.Thechar == '5' && (strings.HasPrefix(sym.name, "$a") || strings.HasPrefix(sym.name, "$d")) { + if SysArch.Family == sys.ARM && (strings.HasPrefix(sym.name, "$a") || strings.HasPrefix(sym.name, "$d")) { // binutils for arm generate these mapping // symbols, ignore these break @@ -1127,7 +1129,9 @@ func (x rbyoff) Less(i, j int) bool { } func reltype(pn string, elftype int, siz *uint8) int { - switch uint32(Thearch.Thechar) | uint32(elftype)<<24 { + // TODO(mdempsky): Remove dependency on ArchFamily char values. + + switch uint32(SysArch.Family) | uint32(elftype)<<24 { default: Diag("%s: unknown relocation type %d; compiled without -fpic?", pn, elftype) fallthrough diff --git a/src/cmd/link/internal/ld/ldmacho.go b/src/cmd/link/internal/ld/ldmacho.go index c4c13f13b9..9fbb2123af 100644 --- a/src/cmd/link/internal/ld/ldmacho.go +++ b/src/cmd/link/internal/ld/ldmacho.go @@ -2,6 +2,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "encoding/binary" "fmt" "log" @@ -471,18 +472,18 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { m.length = length m.name = pn - switch Thearch.Thechar { + switch SysArch.Family { default: - Diag("%s: mach-o %s unimplemented", pn, Thestring) + Diag("%s: mach-o %s unimplemented", pn, SysArch.Name) return - case '6': + case sys.AMD64: if e != binary.LittleEndian || m.cputype != LdMachoCpuAmd64 { Diag("%s: mach-o object but not amd64", pn) return } - case '8': + case sys.I386: if e != binary.LittleEndian || m.cputype != LdMachoCpu386 { Diag("%s: mach-o object but not 386", pn) return @@ -724,10 +725,9 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { rp = &r[rpi] rel = §.rel[j] if rel.scattered != 0 { - if Thearch.Thechar != '8' { + if SysArch.Family != sys.I386 { // mach-o only uses scattered relocation on 32-bit platforms Diag("unexpected scattered relocation") - continue } @@ -821,7 +821,7 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { rp.Off = int32(rel.addr) // Handle X86_64_RELOC_SIGNED referencing a section (rel->extrn == 0). - if Thearch.Thechar == '6' && rel.extrn == 0 && rel.type_ == 1 { + if SysArch.Family == sys.AMD64 && rel.extrn == 0 && rel.type_ == 1 { // Calculate the addend as the offset into the section. // // The rip-relative offset stored in the object file is encoded @@ -847,7 +847,7 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { // For i386 Mach-O PC-relative, the addend is written such that // it *is* the PC being subtracted. Use that to make // it match our version of PC-relative. - if rel.pcrel != 0 && Thearch.Thechar == '8' { + if rel.pcrel != 0 && SysArch.Family == sys.I386 { rp.Add += int64(rp.Off) + int64(rp.Siz) } if rel.extrn == 0 { @@ -866,7 +866,7 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { // include that information in the addend. // We only care about the delta from the // section base. - if Thearch.Thechar == '8' { + if SysArch.Family == sys.I386 { rp.Add -= int64(c.seg.sect[rel.symnum-1].addr) } } else { diff --git a/src/cmd/link/internal/ld/ldpe.go b/src/cmd/link/internal/ld/ldpe.go index 5c3e99c44f..ea0c482838 100644 --- a/src/cmd/link/internal/ld/ldpe.go +++ b/src/cmd/link/internal/ld/ldpe.go @@ -6,6 +6,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "encoding/binary" "fmt" "log" @@ -492,7 +493,7 @@ func readpesym(peobj *PeObj, i int, y **PeSym) (err error) { if strings.HasPrefix(name, "__imp_") { name = name[6:] // __imp_Name => Name } - if Thearch.Thechar == '8' && name[0] == '_' { + if SysArch.Family == sys.I386 && name[0] == '_' { name = name[1:] // _Name => Name } } diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 5616700445..3e0bd8ebc4 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -34,6 +34,7 @@ import ( "bufio" "bytes" "cmd/internal/obj" + "cmd/internal/sys" "crypto/sha1" "debug/elf" "encoding/binary" @@ -82,14 +83,9 @@ import ( // THE SOFTWARE. type Arch struct { - Thechar int - Ptrsize int - Intsize int - Regsize int Funcalign int Maxalign int Minalign int - Minlc int Dwarfregsp int Dwarfreglr int Linuxdynld string @@ -191,8 +187,7 @@ func UseRelro() bool { } var ( - Thestring string - Thelinkarch *LinkArch + SysArch *sys.Arch outfile string dynexp []*LSym dynlib []string @@ -509,7 +504,7 @@ func loadlib() { } loadinternal("runtime") - if Thearch.Thechar == '5' { + if SysArch.Family == sys.ARM { loadinternal("math") } if flag_race != 0 { @@ -562,7 +557,7 @@ func loadlib() { // dependency problems when compiling natively (external linking requires // runtime/cgo, runtime/cgo requires cmd/cgo, but cmd/cgo needs to be // compiled using external linking.) - if (Thearch.Thechar == '5' || Thearch.Thechar == '7') && HEADTYPE == obj.Hdarwin && iscgo { + if SysArch.InFamily(sys.ARM, sys.ARM64) && HEADTYPE == obj.Hdarwin && iscgo { Linkmode = LinkExternal } @@ -621,7 +616,7 @@ func loadlib() { // a variable to hold g in assembly (currently only intel). if tlsg.Type == 0 { tlsg.Type = obj.STLSBSS - tlsg.Size = int64(Thearch.Ptrsize) + tlsg.Size = int64(SysArch.PtrSize) } else if tlsg.Type != obj.SDYNIMPORT { Diag("internal error: runtime declared tlsg variable %d", tlsg.Type) } @@ -639,7 +634,7 @@ func loadlib() { // In addition, on ARM, the runtime depends on the linker // recording the value of GOARM. - if Thearch.Thechar == '5' { + if SysArch.Family == sys.ARM { s := Linklookup(Ctxt, "runtime.goarm", 0) s.Type = obj.SRODATA @@ -1226,7 +1221,7 @@ func hostlink() { if Debug['s'] == 0 && debug_s == 0 && HEADTYPE == obj.Hdarwin { // Skip combining dwarf on arm. - if Thearch.Thechar != '5' && Thearch.Thechar != '7' { + if !SysArch.InFamily(sys.ARM, sys.ARM64) { dsym := filepath.Join(tmpdir, "go.dwarf") if out, err := exec.Command("dsymutil", "-f", outfile, "-o", dsym).CombinedOutput(); err != nil { Ctxt.Cursym = nil @@ -1254,14 +1249,14 @@ func hostlink() { // hostlinkArchArgs returns arguments to pass to the external linker // based on the architecture. func hostlinkArchArgs() []string { - switch Thearch.Thechar { - case '8': + switch SysArch.Family { + case sys.I386: return []string{"-m32"} - case '6', '9', 'z': + case sys.AMD64, sys.PPC64, sys.S390X: return []string{"-m64"} - case '5': + case sys.ARM: return []string{"-marm"} - case '7': + case sys.ARM64: // nothing needed } return nil @@ -1306,10 +1301,10 @@ func ldobj(f *obj.Biobuf, pkg string, length int64, pn string, file string, when if !strings.HasPrefix(line, "go object ") { if strings.HasSuffix(pn, ".go") { - Exitf("%cl: input %s is not .%c file (use %cg to compile .go files)", Thearch.Thechar, pn, Thearch.Thechar, Thearch.Thechar) + Exitf("%cl: input %s is not .%c file (use %cg to compile .go files)", SysArch.Family, pn, SysArch.Family, SysArch.Family) } - if line == Thestring { + if line == SysArch.Name { // old header format: just $GOOS Diag("%s: stale object file", pn) return nil @@ -1500,12 +1495,12 @@ func ldshlibsyms(shlib string) { // the type data. if strings.HasPrefix(lsym.Name, "type.") && !strings.HasPrefix(lsym.Name, "type..") { lsym.P = readelfsymboldata(f, &elfsym) - gcdata_locations[elfsym.Value+2*uint64(Thearch.Ptrsize)+8+1*uint64(Thearch.Ptrsize)] = lsym + gcdata_locations[elfsym.Value+2*uint64(SysArch.PtrSize)+8+1*uint64(SysArch.PtrSize)] = lsym } } } gcdata_addresses := make(map[*LSym]uint64) - if Thearch.Thechar == '7' { + if SysArch.Family == sys.ARM64 { for _, sect := range f.Sections { if sect.Type == elf.SHT_RELA { var rela elf.Rela64 @@ -1565,8 +1560,8 @@ func mywhatsys() { goos = obj.Getgoos() goarch = obj.Getgoarch() - if !strings.HasPrefix(goarch, Thestring) { - log.Fatalf("cannot use %cc with GOARCH=%s", Thearch.Thechar, goarch) + if !strings.HasPrefix(goarch, SysArch.Name) { + log.Fatalf("cannot use %cc with GOARCH=%s", SysArch.Family, goarch) } } @@ -1608,7 +1603,7 @@ func addsection(seg *Segment, name string, rwx int) *Section { sect.Rwx = uint8(rwx) sect.Name = name sect.Seg = seg - sect.Align = int32(Thearch.Ptrsize) // everything is at least pointer-aligned + sect.Align = int32(SysArch.PtrSize) // everything is at least pointer-aligned *l = sect return sect } @@ -1652,7 +1647,7 @@ func callsize() int { if haslinkregister() { return 0 } - return Thearch.Regsize + return SysArch.RegSize } func dostkcheck() { @@ -1986,7 +1981,7 @@ func genasmsym(put func(*LSym, string, int, int64, int64, int, *LSym)) { put(s, s.Name, 'T', s.Value, s.Size, int(s.Version), s.Gotype) // NOTE(ality): acid can't produce a stack trace without .frame symbols - put(nil, ".frame", 'm', int64(s.Locals)+int64(Thearch.Ptrsize), 0, 0, nil) + put(nil, ".frame", 'm', int64(s.Locals)+int64(SysArch.PtrSize), 0, 0, nil) for _, a := range s.Autom { // Emit a or p according to actual offset, even if label is wrong. @@ -1999,7 +1994,7 @@ func genasmsym(put func(*LSym, string, int, int64, int64, int, *LSym)) { if a.Name == obj.A_PARAM { off = a.Aoffset } else { - off = a.Aoffset - int32(Thearch.Ptrsize) + off = a.Aoffset - int32(SysArch.PtrSize) } // FP @@ -2009,8 +2004,8 @@ func genasmsym(put func(*LSym, string, int, int64, int64, int, *LSym)) { } // SP - if off <= int32(-Thearch.Ptrsize) { - put(nil, a.Asym.Name, 'a', -(int64(off) + int64(Thearch.Ptrsize)), 0, 0, a.Gotype) + if off <= int32(-SysArch.PtrSize) { + put(nil, a.Asym.Name, 'a', -(int64(off) + int64(SysArch.PtrSize)), 0, 0, a.Gotype) continue } } diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index 67a855933e..f0811389d2 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -32,8 +32,8 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "debug/elf" - "encoding/binary" "fmt" ) @@ -161,11 +161,9 @@ type Shlib struct { } type Link struct { - Thechar int32 - Thestring string Goarm int32 Headtype int - Arch *LinkArch + Arch *sys.Arch Debugvlog int32 Bso *obj.Biobuf Windows int32 @@ -196,15 +194,15 @@ type Link struct { // on the stack in the function prologue and so always have a pointer between // the hardware stack pointer and the local variable area. func (ctxt *Link) FixedFrameSize() int64 { - switch ctxt.Arch.Thechar { - case '6', '8': + switch ctxt.Arch.Family { + case sys.AMD64, sys.I386: return 0 - case '9': + case sys.PPC64: // PIC code on ppc64le requires 32 bytes of stack, and it's easier to // just use that much stack always on ppc64x. - return int64(4 * ctxt.Arch.Ptrsize) + return int64(4 * ctxt.Arch.PtrSize) default: - return int64(ctxt.Arch.Ptrsize) + return int64(ctxt.Arch.PtrSize) } } @@ -213,15 +211,6 @@ func (l *Link) IncVersion() { l.Hash = append(l.Hash, make(map[string]*LSym)) } -type LinkArch struct { - ByteOrder binary.ByteOrder - Name string - Thechar int - Minlc int - Ptrsize int - Regsize int -} - type Library struct { Objref string Srcref string diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go index cafc6b0382..25d48fbf22 100644 --- a/src/cmd/link/internal/ld/macho.go +++ b/src/cmd/link/internal/ld/macho.go @@ -6,6 +6,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "sort" "strings" ) @@ -131,15 +132,7 @@ var nsortsym int var load_budget int = INITIAL_MACHO_HEADR - 2*1024 func Machoinit() { - switch Thearch.Thechar { - // 64-bit architectures - case '6', '7', '9': - macho64 = true - - // 32-bit architectures - default: - break - } + macho64 = SysArch.RegSize == 8 } func getMachoHdr() *MachoHdr { @@ -356,8 +349,8 @@ func machoshbits(mseg *MachoSeg, sect *Section, segname string) { buf := "__" + strings.Replace(sect.Name[1:], ".", "_", -1) var msect *MachoSect - if sect.Rwx&1 == 0 && segname != "__DWARF" && (Thearch.Thechar == '7' || // arm64 - (Thearch.Thechar == '6' && (Buildmode == BuildmodeCShared || Buildmode == BuildmodeCArchive))) { // amd64 + if sect.Rwx&1 == 0 && segname != "__DWARF" && (SysArch.Family == sys.ARM64 || + (SysArch.Family == sys.AMD64 && (Buildmode == BuildmodeCShared || Buildmode == BuildmodeCArchive))) { // Darwin external linker on arm64 and on amd64 in c-shared/c-archive buildmode // complains about absolute relocs in __TEXT, so if the section is not // executable, put it in __DATA segment. @@ -422,23 +415,23 @@ func Asmbmacho() { va := INITTEXT - int64(HEADR) mh := getMachoHdr() - switch Thearch.Thechar { + switch SysArch.Family { default: - Exitf("unknown macho architecture: %v", Thearch.Thechar) + Exitf("unknown macho architecture: %v", SysArch.Family) - case '5': + case sys.ARM: mh.cpu = MACHO_CPU_ARM mh.subcpu = MACHO_SUBCPU_ARMV7 - case '6': + case sys.AMD64: mh.cpu = MACHO_CPU_AMD64 mh.subcpu = MACHO_SUBCPU_X86 - case '7': + case sys.ARM64: mh.cpu = MACHO_CPU_ARM64 mh.subcpu = MACHO_SUBCPU_ARM64_ALL - case '8': + case sys.I386: mh.cpu = MACHO_CPU_386 mh.subcpu = MACHO_SUBCPU_X86 } @@ -449,7 +442,7 @@ func Asmbmacho() { ms = newMachoSeg("", 40) ms.fileoffset = Segtext.Fileoff - if Thearch.Thechar == '5' || Buildmode == BuildmodeCArchive { + if SysArch.Family == sys.ARM || Buildmode == BuildmodeCArchive { ms.filesize = Segdata.Fileoff + Segdata.Filelen - Segtext.Fileoff } else { ms.filesize = Segdwarf.Fileoff + Segdwarf.Filelen - Segtext.Fileoff @@ -511,31 +504,31 @@ func Asmbmacho() { } if Linkmode != LinkExternal { - switch Thearch.Thechar { + switch SysArch.Family { default: - Exitf("unknown macho architecture: %v", Thearch.Thechar) + Exitf("unknown macho architecture: %v", SysArch.Family) - case '5': + case sys.ARM: ml := newMachoLoad(5, 17+2) /* unix thread */ ml.data[0] = 1 /* thread type */ ml.data[1] = 17 /* word count */ ml.data[2+15] = uint32(Entryvalue()) /* start pc */ - case '6': + case sys.AMD64: ml := newMachoLoad(5, 42+2) /* unix thread */ ml.data[0] = 4 /* thread type */ ml.data[1] = 42 /* word count */ ml.data[2+32] = uint32(Entryvalue()) /* start pc */ ml.data[2+32+1] = uint32(Entryvalue() >> 32) - case '7': + case sys.ARM64: ml := newMachoLoad(5, 68+2) /* unix thread */ ml.data[0] = 6 /* thread type */ ml.data[1] = 68 /* word count */ ml.data[2+64] = uint32(Entryvalue()) /* start pc */ ml.data[2+64+1] = uint32(Entryvalue() >> 32) - case '8': + case sys.I386: ml := newMachoLoad(5, 16+2) /* unix thread */ ml.data[0] = 1 /* thread type */ ml.data[1] = 16 /* word count */ @@ -546,7 +539,6 @@ func Asmbmacho() { if Debug['d'] == 0 { // must match domacholink below s1 := Linklookup(Ctxt, ".machosymtab", 0) - s2 := Linklookup(Ctxt, ".linkedit.plt", 0) s3 := Linklookup(Ctxt, ".linkedit.got", 0) s4 := Linklookup(Ctxt, ".machosymstr", 0) @@ -729,7 +721,7 @@ func machosymtab() { Adduint8(Ctxt, symtab, 0x01) // type N_EXT, external symbol Adduint8(Ctxt, symtab, 0) // no section Adduint16(Ctxt, symtab, 0) // desc - adduintxx(Ctxt, symtab, 0, Thearch.Ptrsize) // no value + adduintxx(Ctxt, symtab, 0, SysArch.PtrSize) // no value } else { if s.Attr.CgoExport() { Adduint8(Ctxt, symtab, 0x0f) @@ -747,7 +739,7 @@ func machosymtab() { Adduint8(Ctxt, symtab, uint8(o.Sect.Extnum)) } Adduint16(Ctxt, symtab, 0) // desc - adduintxx(Ctxt, symtab, uint64(Symaddr(s)), Thearch.Ptrsize) + adduintxx(Ctxt, symtab, uint64(Symaddr(s)), SysArch.PtrSize) } } } diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index ff29ce2d70..471dda712f 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -93,7 +93,7 @@ func pciterinit(ctxt *Link, it *Pciter, d *Pcdata) { it.value = -1 it.start = 1 it.done = 0 - it.pcscale = uint32(ctxt.Arch.Minlc) + it.pcscale = uint32(ctxt.Arch.MinLC) pciternext(it) } @@ -242,12 +242,12 @@ func pclntab() { } pclntabNfunc = nfunc - Symgrow(Ctxt, ftab, 8+int64(Thearch.Ptrsize)+int64(nfunc)*2*int64(Thearch.Ptrsize)+int64(Thearch.Ptrsize)+4) + Symgrow(Ctxt, ftab, 8+int64(SysArch.PtrSize)+int64(nfunc)*2*int64(SysArch.PtrSize)+int64(SysArch.PtrSize)+4) setuint32(Ctxt, ftab, 0, 0xfffffffb) - setuint8(Ctxt, ftab, 6, uint8(Thearch.Minlc)) - setuint8(Ctxt, ftab, 7, uint8(Thearch.Ptrsize)) - setuintxx(Ctxt, ftab, 8, uint64(nfunc), int64(Thearch.Ptrsize)) - pclntabPclntabOffset = int32(8 + Thearch.Ptrsize) + setuint8(Ctxt, ftab, 6, uint8(SysArch.MinLC)) + setuint8(Ctxt, ftab, 7, uint8(SysArch.PtrSize)) + setuintxx(Ctxt, ftab, 8, uint64(nfunc), int64(SysArch.PtrSize)) + pclntabPclntabOffset = int32(8 + SysArch.PtrSize) nfunc = 0 var last *LSym @@ -272,16 +272,16 @@ func pclntab() { } funcstart = int32(len(ftab.P)) - funcstart += int32(-len(ftab.P)) & (int32(Thearch.Ptrsize) - 1) + funcstart += int32(-len(ftab.P)) & (int32(SysArch.PtrSize) - 1) - setaddr(Ctxt, ftab, 8+int64(Thearch.Ptrsize)+int64(nfunc)*2*int64(Thearch.Ptrsize), Ctxt.Cursym) - setuintxx(Ctxt, ftab, 8+int64(Thearch.Ptrsize)+int64(nfunc)*2*int64(Thearch.Ptrsize)+int64(Thearch.Ptrsize), uint64(funcstart), int64(Thearch.Ptrsize)) + setaddr(Ctxt, ftab, 8+int64(SysArch.PtrSize)+int64(nfunc)*2*int64(SysArch.PtrSize), Ctxt.Cursym) + setuintxx(Ctxt, ftab, 8+int64(SysArch.PtrSize)+int64(nfunc)*2*int64(SysArch.PtrSize)+int64(SysArch.PtrSize), uint64(funcstart), int64(SysArch.PtrSize)) // fixed size of struct, checked below off = funcstart - end = funcstart + int32(Thearch.Ptrsize) + 3*4 + 5*4 + int32(len(pcln.Pcdata))*4 + int32(len(pcln.Funcdata))*int32(Thearch.Ptrsize) - if len(pcln.Funcdata) > 0 && (end&int32(Thearch.Ptrsize-1) != 0) { + end = funcstart + int32(SysArch.PtrSize) + 3*4 + 5*4 + int32(len(pcln.Pcdata))*4 + int32(len(pcln.Funcdata))*int32(SysArch.PtrSize) + if len(pcln.Funcdata) > 0 && (end&int32(SysArch.PtrSize-1) != 0) { end += 4 } Symgrow(Ctxt, ftab, int64(end)) @@ -330,25 +330,25 @@ func pclntab() { // funcdata, must be pointer-aligned and we're only int32-aligned. // Missing funcdata will be 0 (nil pointer). if len(pcln.Funcdata) > 0 { - if off&int32(Thearch.Ptrsize-1) != 0 { + if off&int32(SysArch.PtrSize-1) != 0 { off += 4 } for i = 0; i < int32(len(pcln.Funcdata)); i++ { if pcln.Funcdata[i] == nil { - setuintxx(Ctxt, ftab, int64(off)+int64(Thearch.Ptrsize)*int64(i), uint64(pcln.Funcdataoff[i]), int64(Thearch.Ptrsize)) + setuintxx(Ctxt, ftab, int64(off)+int64(SysArch.PtrSize)*int64(i), uint64(pcln.Funcdataoff[i]), int64(SysArch.PtrSize)) } else { // TODO: Dedup. funcdata_bytes += pcln.Funcdata[i].Size - setaddrplus(Ctxt, ftab, int64(off)+int64(Thearch.Ptrsize)*int64(i), pcln.Funcdata[i], pcln.Funcdataoff[i]) + setaddrplus(Ctxt, ftab, int64(off)+int64(SysArch.PtrSize)*int64(i), pcln.Funcdata[i], pcln.Funcdataoff[i]) } } - off += int32(len(pcln.Funcdata)) * int32(Thearch.Ptrsize) + off += int32(len(pcln.Funcdata)) * int32(SysArch.PtrSize) } if off != end { - Diag("bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, len(pcln.Pcdata), len(pcln.Funcdata), Thearch.Ptrsize) + Diag("bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, len(pcln.Pcdata), len(pcln.Funcdata), SysArch.PtrSize) errorexit() } @@ -357,14 +357,14 @@ func pclntab() { pclntabLastFunc = last // Final entry of table is just end pc. - setaddrplus(Ctxt, ftab, 8+int64(Thearch.Ptrsize)+int64(nfunc)*2*int64(Thearch.Ptrsize), last, last.Size) + setaddrplus(Ctxt, ftab, 8+int64(SysArch.PtrSize)+int64(nfunc)*2*int64(SysArch.PtrSize), last, last.Size) // Start file table. start := int32(len(ftab.P)) - start += int32(-len(ftab.P)) & (int32(Thearch.Ptrsize) - 1) + start += int32(-len(ftab.P)) & (int32(SysArch.PtrSize) - 1) pclntabFiletabOffset = start - setuint32(Ctxt, ftab, 8+int64(Thearch.Ptrsize)+int64(nfunc)*2*int64(Thearch.Ptrsize)+int64(Thearch.Ptrsize), uint32(start)) + setuint32(Ctxt, ftab, 8+int64(SysArch.PtrSize)+int64(nfunc)*2*int64(SysArch.PtrSize)+int64(SysArch.PtrSize), uint32(start)) Symgrow(Ctxt, ftab, int64(start)+(int64(Ctxt.Nhistfile)+1)*4) setuint32(Ctxt, ftab, int64(start), uint32(Ctxt.Nhistfile)) diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index 56698361d0..0204b8c8c2 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -6,6 +6,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "encoding/binary" "fmt" "os" @@ -419,9 +420,9 @@ func chksectseg(h *IMAGE_SECTION_HEADER, s *Segment) { func Peinit() { var l int - switch Thearch.Thechar { + switch SysArch.Family { // 64-bit architectures - case '6': + case sys.AMD64: pe64 = 1 l = binary.Size(&oh64) @@ -506,7 +507,7 @@ func initdynimport() *Dll { if err != nil { Diag("failed to parse stdcall decoration: %v", err) } - m.argsize *= Thearch.Ptrsize + m.argsize *= SysArch.PtrSize s.Extname = s.Extname[:i] } @@ -520,10 +521,10 @@ func initdynimport() *Dll { for d := dr; d != nil; d = d.next { for m = d.ms; m != nil; m = m.next { m.s.Type = obj.SDATA - Symgrow(Ctxt, m.s, int64(Thearch.Ptrsize)) + Symgrow(Ctxt, m.s, int64(SysArch.PtrSize)) dynName := m.s.Extname // only windows/386 requires stdcall decoration - if Thearch.Thechar == '8' && m.argsize >= 0 { + if SysArch.Family == sys.I386 && m.argsize >= 0 { dynName += fmt.Sprintf("@%d", m.argsize) } dynSym := Linklookup(Ctxt, dynName, 0) @@ -532,7 +533,7 @@ func initdynimport() *Dll { r := Addrel(m.s) r.Sym = dynSym r.Off = 0 - r.Siz = uint8(Thearch.Ptrsize) + r.Siz = uint8(SysArch.PtrSize) r.Type = obj.R_ADDR } } @@ -546,10 +547,10 @@ func initdynimport() *Dll { m.s.Sub = dynamic.Sub dynamic.Sub = m.s m.s.Value = dynamic.Size - dynamic.Size += int64(Thearch.Ptrsize) + dynamic.Size += int64(SysArch.PtrSize) } - dynamic.Size += int64(Thearch.Ptrsize) + dynamic.Size += int64(SysArch.PtrSize) } } @@ -946,7 +947,7 @@ func writePESymTableRecords() int { } // only windows/386 requires underscore prefix on external symbols - if Thearch.Thechar == '8' && + if SysArch.Family == sys.I386 && Linkmode == LinkExternal && (s.Type != obj.SDYNIMPORT || s.Attr.CgoExport()) && s.Name == s.Extname && @@ -1002,7 +1003,7 @@ func writePESymTableRecords() int { for d := dr; d != nil; d = d.next { for m := d.ms; m != nil; m = m.next { s := m.s.R[0].Xsym - put(s, s.Name, 'U', 0, int64(Thearch.Ptrsize), 0, nil) + put(s, s.Name, 'U', 0, int64(SysArch.PtrSize), 0, nil) } } @@ -1129,12 +1130,12 @@ func addinitarray() (c *IMAGE_SECTION_HEADER) { } func Asmbpe() { - switch Thearch.Thechar { + switch SysArch.Family { default: - Exitf("unknown PE architecture: %v", Thearch.Thechar) - case '6': + Exitf("unknown PE architecture: %v", SysArch.Family) + case sys.AMD64: fh.Machine = IMAGE_FILE_MACHINE_AMD64 - case '8': + case sys.I386: fh.Machine = IMAGE_FILE_MACHINE_I386 } diff --git a/src/cmd/link/internal/ld/pobj.go b/src/cmd/link/internal/ld/pobj.go index f48b54efda..b9902a5e5e 100644 --- a/src/cmd/link/internal/ld/pobj.go +++ b/src/cmd/link/internal/ld/pobj.go @@ -32,6 +32,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "flag" "fmt" "os" @@ -44,9 +45,7 @@ var ( ) func Ldmain() { - Ctxt = linknew(Thelinkarch) - Ctxt.Thechar = int32(Thearch.Thechar) - Ctxt.Thestring = Thestring + Ctxt = linknew(SysArch) Ctxt.Diag = Diag Ctxt.Bso = &Bso @@ -70,7 +69,7 @@ func Ldmain() { } } - if Thearch.Thechar == '6' && obj.Getgoos() == "plan9" { + if SysArch.Family == sys.AMD64 && obj.Getgoos() == "plan9" { obj.Flagcount("8", "use 64-bit addresses in symbol table", &Debug['8']) } obj.Flagfn1("B", "add an ELF NT_GNU_BUILD_ID `note` when using ELF", addbuildinfo) @@ -107,7 +106,7 @@ func Ldmain() { obj.Flagcount("race", "enable race detector", &flag_race) obj.Flagcount("s", "disable symbol table", &Debug['s']) var flagShared int - if Thearch.Thechar == '5' || Thearch.Thechar == '6' { + if SysArch.InFamily(sys.ARM, sys.AMD64) { obj.Flagcount("shared", "generate shared object (implies -linkmode external)", &flagShared) } obj.Flagstr("tmpdir", "use `directory` for temporary files", &tmpdir) diff --git a/src/cmd/link/internal/ld/sym.go b/src/cmd/link/internal/ld/sym.go index 3deb94644e..76fe7dab79 100644 --- a/src/cmd/link/internal/ld/sym.go +++ b/src/cmd/link/internal/ld/sym.go @@ -33,6 +33,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "log" "strconv" ) @@ -55,7 +56,7 @@ var headers = []struct { {"windowsgui", obj.Hwindows}, } -func linknew(arch *LinkArch) *Link { +func linknew(arch *sys.Arch) *Link { ctxt := &Link{ Hash: []map[string]*LSym{ // preallocate about 2mb for hash of @@ -98,33 +99,33 @@ func linknew(arch *LinkArch) *Link { obj.Hdragonfly, obj.Hsolaris: if obj.Getgoos() == "android" { - switch ctxt.Arch.Thechar { - case '6': + switch ctxt.Arch.Family { + case sys.AMD64: // Android/amd64 constant - offset from 0(FS) to our TLS slot. // Explained in src/runtime/cgo/gcc_android_*.c ctxt.Tlsoffset = 0x1d0 - case '8': + case sys.I386: // Android/386 constant - offset from 0(GS) to our TLS slot. ctxt.Tlsoffset = 0xf8 default: - ctxt.Tlsoffset = -1 * ctxt.Arch.Ptrsize + ctxt.Tlsoffset = -1 * ctxt.Arch.PtrSize } } else { - ctxt.Tlsoffset = -1 * ctxt.Arch.Ptrsize + ctxt.Tlsoffset = -1 * ctxt.Arch.PtrSize } case obj.Hnacl: - switch ctxt.Arch.Thechar { + switch ctxt.Arch.Family { default: log.Fatalf("unknown thread-local storage offset for nacl/%s", ctxt.Arch.Name) - case '5': + case sys.ARM: ctxt.Tlsoffset = 0 - case '6': + case sys.AMD64: ctxt.Tlsoffset = 0 - case '8': + case sys.I386: ctxt.Tlsoffset = -8 } @@ -133,26 +134,26 @@ func linknew(arch *LinkArch) *Link { * Explained in src/runtime/cgo/gcc_darwin_*.c. */ case obj.Hdarwin: - switch ctxt.Arch.Thechar { + switch ctxt.Arch.Family { default: log.Fatalf("unknown thread-local storage offset for darwin/%s", ctxt.Arch.Name) - case '5': + case sys.ARM: ctxt.Tlsoffset = 0 // dummy value, not needed - case '6': + case sys.AMD64: ctxt.Tlsoffset = 0x8a0 - case '7': + case sys.ARM64: ctxt.Tlsoffset = 0 // dummy value, not needed - case '8': + case sys.I386: ctxt.Tlsoffset = 0x468 } } // On arm, record goarm. - if ctxt.Arch.Thechar == '5' { + if ctxt.Arch.Family == sys.ARM { ctxt.Goarm = obj.Getgoarm() } diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index 167176cc2d..ecd5c741bb 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -32,6 +32,7 @@ package ld import ( "cmd/internal/obj" + "cmd/internal/sys" "fmt" "path/filepath" "strings" @@ -160,7 +161,7 @@ func putelfsym(x *LSym, s string, t int, addr int64, size int64, ver int, go_ *L if x.Type&obj.SHIDDEN != 0 { other = STV_HIDDEN } - if (Buildmode == BuildmodePIE || DynlinkingGo()) && Thearch.Thechar == '9' && type_ == STT_FUNC && x.Name != "runtime.duffzero" && x.Name != "runtime.duffcopy" { + if (Buildmode == BuildmodePIE || DynlinkingGo()) && SysArch.Family == sys.PPC64 && type_ == STT_FUNC && x.Name != "runtime.duffzero" && x.Name != "runtime.duffcopy" { // On ppc64 the top three bits of the st_other field indicate how // many instructions separate the global and local entry points. In // our case it is two instructions, indicated by the value 3. @@ -229,7 +230,7 @@ func putplan9sym(x *LSym, s string, t int, addr int64, size int64, ver int, go_ 'Z', 'm': l := 4 - if HEADTYPE == obj.Hplan9 && Thearch.Thechar == '6' && Debug['8'] == 0 { + if HEADTYPE == obj.Hplan9 && SysArch.Family == sys.AMD64 && Debug['8'] == 0 { Lputb(uint32(addr >> 32)) l = 8 } diff --git a/src/cmd/link/internal/mips64/asm.go b/src/cmd/link/internal/mips64/asm.go index d0977e9b00..9a145e373a 100644 --- a/src/cmd/link/internal/mips64/asm.go +++ b/src/cmd/link/internal/mips64/asm.go @@ -32,6 +32,7 @@ package mips64 import ( "cmd/internal/obj" + "cmd/internal/sys" "cmd/link/internal/ld" "encoding/binary" "fmt" @@ -82,8 +83,8 @@ func archreloc(r *ld.Reloc, s *ld.LSym, val *int64) int { // the first instruction is always at the lower address, this is endian neutral; // but note that o1 and o2 should still use the target endian. - o1 := ld.Thelinkarch.ByteOrder.Uint32(s.P[r.Off:]) - o2 := ld.Thelinkarch.ByteOrder.Uint32(s.P[r.Off+4:]) + o1 := ld.SysArch.ByteOrder.Uint32(s.P[r.Off:]) + o2 := ld.SysArch.ByteOrder.Uint32(s.P[r.Off+4:]) o1 = o1&0xffff0000 | uint32(t>>16)&0xffff o2 = o2&0xffff0000 | uint32(t)&0xffff @@ -99,7 +100,7 @@ func archreloc(r *ld.Reloc, s *ld.LSym, val *int64) int { obj.R_JMPMIPS: // Low 26 bits = (S + A) >> 2 t := ld.Symaddr(r.Sym) + r.Add - o1 := ld.Thelinkarch.ByteOrder.Uint32(s.P[r.Off:]) + o1 := ld.SysArch.ByteOrder.Uint32(s.P[r.Off:]) *val = int64(o1&0xfc000000 | uint32(t>>2)&^0xfc000000) return 0 } @@ -214,7 +215,7 @@ func asmb() { default: case obj.Hplan9: /* plan 9 */ magic := uint32(4*18*18 + 7) - if ld.Thestring == "mips64le" { + if ld.SysArch == sys.ArchMIPS64LE { magic = uint32(4*26*26 + 7) } ld.Thearch.Lput(uint32(magic)) /* magic */ diff --git a/src/cmd/link/internal/mips64/l.go b/src/cmd/link/internal/mips64/l.go index 003ee5ce71..f4191e69ab 100644 --- a/src/cmd/link/internal/mips64/l.go +++ b/src/cmd/link/internal/mips64/l.go @@ -62,11 +62,9 @@ package mips64 // THE SOFTWARE. const ( - thechar = '0' MaxAlign = 32 // max data alignment MinAlign = 1 // min data alignment FuncAlign = 8 - MINLC = 4 ) /* Used by ../internal/ld/dwarf.go */ diff --git a/src/cmd/link/internal/mips64/obj.go b/src/cmd/link/internal/mips64/obj.go index 57a1b2ab14..87bb3a079b 100644 --- a/src/cmd/link/internal/mips64/obj.go +++ b/src/cmd/link/internal/mips64/obj.go @@ -32,6 +32,7 @@ package mips64 import ( "cmd/internal/obj" + "cmd/internal/sys" "cmd/link/internal/ld" "fmt" "log" @@ -45,21 +46,15 @@ func Main() { } func linkarchinit() { - ld.Thestring = obj.Getgoarch() - if ld.Thestring == "mips64le" { - ld.Thelinkarch = &ld.Linkmips64le + if obj.Getgoarch() == "mips64le" { + ld.SysArch = sys.ArchMIPS64LE } else { - ld.Thelinkarch = &ld.Linkmips64 + ld.SysArch = sys.ArchMIPS64 } - ld.Thearch.Thechar = thechar - ld.Thearch.Ptrsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Intsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Regsize = ld.Thelinkarch.Regsize ld.Thearch.Funcalign = FuncAlign ld.Thearch.Maxalign = MaxAlign ld.Thearch.Minalign = MinAlign - ld.Thearch.Minlc = MINLC ld.Thearch.Dwarfregsp = DWARFREGSP ld.Thearch.Dwarfreglr = DWARFREGLR @@ -72,7 +67,7 @@ func linkarchinit() { ld.Thearch.Elfsetupplt = elfsetupplt ld.Thearch.Gentext = gentext ld.Thearch.Machoreloc1 = machoreloc1 - if ld.Thelinkarch == &ld.Linkmips64le { + if ld.SysArch == sys.ArchMIPS64LE { ld.Thearch.Lput = ld.Lputl ld.Thearch.Wput = ld.Wputl ld.Thearch.Vput = ld.Vputl diff --git a/src/cmd/link/internal/ppc64/l.go b/src/cmd/link/internal/ppc64/l.go index 622d6bb12e..a720993fbc 100644 --- a/src/cmd/link/internal/ppc64/l.go +++ b/src/cmd/link/internal/ppc64/l.go @@ -62,11 +62,9 @@ package ppc64 // THE SOFTWARE. const ( - thechar = '9' MaxAlign = 32 // max data alignment MinAlign = 1 // min data alignment FuncAlign = 8 - MINLC = 4 ) /* Used by ../internal/ld/dwarf.go */ diff --git a/src/cmd/link/internal/ppc64/obj.go b/src/cmd/link/internal/ppc64/obj.go index 539ab1ac02..a540ab85b5 100644 --- a/src/cmd/link/internal/ppc64/obj.go +++ b/src/cmd/link/internal/ppc64/obj.go @@ -32,6 +32,7 @@ package ppc64 import ( "cmd/internal/obj" + "cmd/internal/sys" "cmd/link/internal/ld" "fmt" "log" @@ -45,21 +46,15 @@ func Main() { } func linkarchinit() { - ld.Thestring = obj.Getgoarch() - if ld.Thestring == "ppc64le" { - ld.Thelinkarch = &ld.Linkppc64le + if obj.Getgoarch() == "ppc64le" { + ld.SysArch = sys.ArchPPC64LE } else { - ld.Thelinkarch = &ld.Linkppc64 + ld.SysArch = sys.ArchPPC64 } - ld.Thearch.Thechar = thechar - ld.Thearch.Ptrsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Intsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Regsize = ld.Thelinkarch.Regsize ld.Thearch.Funcalign = FuncAlign ld.Thearch.Maxalign = MaxAlign ld.Thearch.Minalign = MinAlign - ld.Thearch.Minlc = MINLC ld.Thearch.Dwarfregsp = DWARFREGSP ld.Thearch.Dwarfreglr = DWARFREGLR @@ -72,7 +67,7 @@ func linkarchinit() { ld.Thearch.Elfsetupplt = elfsetupplt ld.Thearch.Gentext = gentext ld.Thearch.Machoreloc1 = machoreloc1 - if ld.Thelinkarch == &ld.Linkppc64le { + if ld.SysArch == sys.ArchPPC64LE { ld.Thearch.Lput = ld.Lputl ld.Thearch.Wput = ld.Wputl ld.Thearch.Vput = ld.Vputl @@ -150,7 +145,7 @@ func archinit() { } case obj.Hlinux: /* ppc64 elf */ - if ld.Thestring == "ppc64" { + if ld.SysArch == sys.ArchPPC64 { ld.Debug['d'] = 1 // TODO(austin): ELF ABI v1 not supported yet } ld.Elfinit() diff --git a/src/cmd/link/internal/s390x/l.go b/src/cmd/link/internal/s390x/l.go index 839a9849c8..42cf15ee85 100644 --- a/src/cmd/link/internal/s390x/l.go +++ b/src/cmd/link/internal/s390x/l.go @@ -62,14 +62,9 @@ package s390x // THE SOFTWARE. const ( - thechar = 'z' - PtrSize = 8 - IntSize = 8 - RegSize = 8 MaxAlign = 32 // max data alignment MinAlign = 2 // min data alignment FuncAlign = 16 - MINLC = 2 ) /* Used by ../internal/ld/dwarf.go */ diff --git a/src/cmd/link/internal/s390x/obj.go b/src/cmd/link/internal/s390x/obj.go index ef88d22bbd..fdb9898181 100644 --- a/src/cmd/link/internal/s390x/obj.go +++ b/src/cmd/link/internal/s390x/obj.go @@ -32,6 +32,7 @@ package s390x import ( "cmd/internal/obj" + "cmd/internal/sys" "cmd/link/internal/ld" "fmt" ) @@ -44,17 +45,11 @@ func Main() { } func linkarchinit() { - ld.Thestring = obj.Getgoarch() - ld.Thelinkarch = &ld.Links390x + ld.SysArch = sys.ArchS390X - ld.Thearch.Thechar = thechar - ld.Thearch.Ptrsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Intsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Regsize = ld.Thelinkarch.Regsize ld.Thearch.Funcalign = FuncAlign ld.Thearch.Maxalign = MaxAlign ld.Thearch.Minalign = MinAlign - ld.Thearch.Minlc = MINLC ld.Thearch.Dwarfregsp = DWARFREGSP ld.Thearch.Dwarfreglr = DWARFREGLR diff --git a/src/cmd/link/internal/x86/asm.go b/src/cmd/link/internal/x86/asm.go index 7da5dd02be..91251de15e 100644 --- a/src/cmd/link/internal/x86/asm.go +++ b/src/cmd/link/internal/x86/asm.go @@ -292,7 +292,7 @@ func adddynrel(s *ld.LSym, r *ld.Reloc) { return } - if ld.HEADTYPE == obj.Hdarwin && s.Size == PtrSize && r.Off == 0 { + if ld.HEADTYPE == obj.Hdarwin && s.Size == int64(ld.SysArch.PtrSize) && r.Off == 0 { // Mach-O relocations are a royal pain to lay out. // They use a compact stateful bytecode representation // that is too much bother to deal with. @@ -317,7 +317,7 @@ func adddynrel(s *ld.LSym, r *ld.Reloc) { return } - if ld.HEADTYPE == obj.Hwindows && s.Size == PtrSize { + if ld.HEADTYPE == obj.Hwindows && s.Size == int64(ld.SysArch.PtrSize) { // nothing to do, the relocation will be laid out in pereloc1 return } diff --git a/src/cmd/link/internal/x86/l.go b/src/cmd/link/internal/x86/l.go index 068fed9c8d..2043f9bb4e 100644 --- a/src/cmd/link/internal/x86/l.go +++ b/src/cmd/link/internal/x86/l.go @@ -31,12 +31,9 @@ package x86 const ( - thechar = '8' - PtrSize = 4 MaxAlign = 32 // max data alignment MinAlign = 1 // min data alignment FuncAlign = 16 - MINLC = 1 ) /* Used by ../internal/ld/dwarf.go */ diff --git a/src/cmd/link/internal/x86/obj.go b/src/cmd/link/internal/x86/obj.go index 4380c41ebb..574c0dad2d 100644 --- a/src/cmd/link/internal/x86/obj.go +++ b/src/cmd/link/internal/x86/obj.go @@ -32,6 +32,7 @@ package x86 import ( "cmd/internal/obj" + "cmd/internal/sys" "cmd/link/internal/ld" "fmt" "log" @@ -45,17 +46,11 @@ func Main() { } func linkarchinit() { - ld.Thestring = "386" - ld.Thelinkarch = &ld.Link386 + ld.SysArch = sys.Arch386 - ld.Thearch.Thechar = thechar - ld.Thearch.Ptrsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Intsize = ld.Thelinkarch.Ptrsize - ld.Thearch.Regsize = ld.Thelinkarch.Regsize ld.Thearch.Funcalign = FuncAlign ld.Thearch.Maxalign = MaxAlign ld.Thearch.Minalign = MinAlign - ld.Thearch.Minlc = MINLC ld.Thearch.Dwarfregsp = DWARFREGSP ld.Thearch.Dwarfreglr = DWARFREGLR -- cgit v1.3 From d481ffc1afeae8852caa3452a0e23b1cd90d1e10 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 6 Apr 2016 18:54:17 -0700 Subject: cmd/compile, cmd/link: eliminate uses of ArchFamily in error messages Two of these error messages are already dead code: cmd/compile.main and cmd/link.main already switch on $GOARCH, ensuring it must be a prefix of the sys.Arch.Family. The error message about uncompiled Go source files can be just be simplified: anyone who's manually constructing Go object file archives probably knows what tool to use to compile Go source files. Change-Id: Ia4a67c0a1d1158379c127c91e909226d3367f3c2 Reviewed-on: https://go-review.googlesource.com/21626 Run-TryBot: Matthew Dempsky TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/cmd/compile/internal/gc/main.go | 9 +-------- src/cmd/link/internal/ld/lib.go | 7 ++----- 2 files changed, 3 insertions(+), 13 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 72e6478afe..079f4916c7 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -93,14 +93,7 @@ func doversion() { func Main() { defer hidePanic() - // Allow GOARCH=thearch.thestring or GOARCH=thearch.thestringsuffix, - // but not other values. - p := obj.Getgoarch() - - if !strings.HasPrefix(p, Thearch.LinkArch.Name) { - log.Fatalf("cannot use %cg with GOARCH=%s", Thearch.LinkArch.Family, p) - } - goarch = p + goarch = obj.Getgoarch() Ctxt = obj.Linknew(Thearch.LinkArch) Ctxt.DiagFunc = Yyerror diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 3e0bd8ebc4..305a3bc0db 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1301,7 +1301,8 @@ func ldobj(f *obj.Biobuf, pkg string, length int64, pn string, file string, when if !strings.HasPrefix(line, "go object ") { if strings.HasSuffix(pn, ".go") { - Exitf("%cl: input %s is not .%c file (use %cg to compile .go files)", SysArch.Family, pn, SysArch.Family, SysArch.Family) + Exitf("%s: uncompiled .go source file", pn) + return nil } if line == SysArch.Name { @@ -1559,10 +1560,6 @@ func mywhatsys() { goroot = obj.Getgoroot() goos = obj.Getgoos() goarch = obj.Getgoarch() - - if !strings.HasPrefix(goarch, SysArch.Name) { - log.Fatalf("cannot use %cc with GOARCH=%s", SysArch.Family, goarch) - } } // Copied from ../gc/subr.c:/^pathtoprefix; must stay in sync. -- cgit v1.3 From 22ef687da815c4d651cef3c1b7d44f41100b6715 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 6 Apr 2016 20:06:12 -0700 Subject: cmd/link: remove dependency on sys.ArchFamily values Change-Id: I858054b72847f4f27a1ebbdaff82820a28c03743 Reviewed-on: https://go-review.googlesource.com/21627 Reviewed-by: Brad Fitzpatrick --- src/cmd/link/internal/ld/ldelf.go | 153 ++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 72 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/ldelf.go b/src/cmd/link/internal/ld/ldelf.go index 3aee2d5ece..485599be62 100644 --- a/src/cmd/link/internal/ld/ldelf.go +++ b/src/cmd/link/internal/ld/ldelf.go @@ -927,7 +927,8 @@ func ldelf(f *obj.Biobuf, pkg string, length int64, pn string) { rp.Sym = sym.sym } - rp.Type = int32(reltype(pn, int(uint32(info)), &rp.Siz)) + rp.Type = 256 + int32(info) + rp.Siz = relSize(pn, uint32(info)) if rela != 0 { rp.Add = int64(add) } else { @@ -1128,81 +1129,89 @@ func (x rbyoff) Less(i, j int) bool { return false } -func reltype(pn string, elftype int, siz *uint8) int { - // TODO(mdempsky): Remove dependency on ArchFamily char values. +func relSize(pn string, elftype uint32) uint8 { + // TODO(mdempsky): Replace this with a struct-valued switch statement + // once golang.org/issue/15164 is fixed or found to not impair cmd/link + // performance. - switch uint32(SysArch.Family) | uint32(elftype)<<24 { + const ( + AMD64 = uint32(sys.AMD64) + ARM = uint32(sys.ARM) + I386 = uint32(sys.I386) + PPC64 = uint32(sys.PPC64) + S390X = uint32(sys.S390X) + ) + + switch uint32(SysArch.Family) | elftype<<24 { default: Diag("%s: unknown relocation type %d; compiled without -fpic?", pn, elftype) fallthrough - case 'z' | R_390_8: - *siz = 1 - - case '9' | R_PPC64_TOC16<<24, - '9' | R_PPC64_TOC16_LO<<24, - '9' | R_PPC64_TOC16_HI<<24, - '9' | R_PPC64_TOC16_HA<<24, - '9' | R_PPC64_TOC16_DS<<24, - '9' | R_PPC64_TOC16_LO_DS<<24, - '9' | R_PPC64_REL16_LO<<24, - '9' | R_PPC64_REL16_HI<<24, - '9' | R_PPC64_REL16_HA<<24, - 'z' | R_390_16<<24, - 'z' | R_390_GOT16<<24, - 'z' | R_390_PC16<<24, - 'z' | R_390_PC16DBL<<24, - 'z' | R_390_PLT16DBL<<24: - *siz = 2 - - case '5' | R_ARM_ABS32<<24, - '5' | R_ARM_GOT32<<24, - '5' | R_ARM_PLT32<<24, - '5' | R_ARM_GOTOFF<<24, - '5' | R_ARM_GOTPC<<24, - '5' | R_ARM_THM_PC22<<24, - '5' | R_ARM_REL32<<24, - '5' | R_ARM_CALL<<24, - '5' | R_ARM_V4BX<<24, - '5' | R_ARM_GOT_PREL<<24, - '5' | R_ARM_PC24<<24, - '5' | R_ARM_JUMP24<<24, - '6' | R_X86_64_PC32<<24, - '6' | R_X86_64_PLT32<<24, - '6' | R_X86_64_GOTPCREL<<24, - '6' | R_X86_64_GOTPCRELX<<24, - '6' | R_X86_64_REX_GOTPCRELX<<24, - '8' | R_386_32<<24, - '8' | R_386_PC32<<24, - '8' | R_386_GOT32<<24, - '8' | R_386_PLT32<<24, - '8' | R_386_GOTOFF<<24, - '8' | R_386_GOTPC<<24, - '8' | R_386_GOT32X<<24, - '9' | R_PPC64_REL24<<24, - '9' | R_PPC_REL32<<24, - 'z' | R_390_32<<24, - 'z' | R_390_PC32<<24, - 'z' | R_390_GOT32<<24, - 'z' | R_390_PLT32<<24, - 'z' | R_390_PC32DBL<<24, - 'z' | R_390_PLT32DBL<<24, - 'z' | R_390_GOTPCDBL<<24, - 'z' | R_390_GOTENT<<24: - *siz = 4 - - case '6' | R_X86_64_64<<24, - '9' | R_PPC64_ADDR64<<24, - 'z' | R_390_GLOB_DAT<<24, - 'z' | R_390_RELATIVE<<24, - 'z' | R_390_GOTOFF<<24, - 'z' | R_390_GOTPC<<24, - 'z' | R_390_64<<24, - 'z' | R_390_PC64<<24, - 'z' | R_390_GOT64<<24, - 'z' | R_390_PLT64<<24: - *siz = 8 + case S390X | R_390_8<<24: + return 1 + + case PPC64 | R_PPC64_TOC16<<24, + PPC64 | R_PPC64_TOC16_LO<<24, + PPC64 | R_PPC64_TOC16_HI<<24, + PPC64 | R_PPC64_TOC16_HA<<24, + PPC64 | R_PPC64_TOC16_DS<<24, + PPC64 | R_PPC64_TOC16_LO_DS<<24, + PPC64 | R_PPC64_REL16_LO<<24, + PPC64 | R_PPC64_REL16_HI<<24, + PPC64 | R_PPC64_REL16_HA<<24, + S390X | R_390_16<<24, + S390X | R_390_GOT16<<24, + S390X | R_390_PC16<<24, + S390X | R_390_PC16DBL<<24, + S390X | R_390_PLT16DBL<<24: + return 2 + + case ARM | R_ARM_ABS32<<24, + ARM | R_ARM_GOT32<<24, + ARM | R_ARM_PLT32<<24, + ARM | R_ARM_GOTOFF<<24, + ARM | R_ARM_GOTPC<<24, + ARM | R_ARM_THM_PC22<<24, + ARM | R_ARM_REL32<<24, + ARM | R_ARM_CALL<<24, + ARM | R_ARM_V4BX<<24, + ARM | R_ARM_GOT_PREL<<24, + ARM | R_ARM_PC24<<24, + ARM | R_ARM_JUMP24<<24, + AMD64 | R_X86_64_PC32<<24, + AMD64 | R_X86_64_PLT32<<24, + AMD64 | R_X86_64_GOTPCREL<<24, + AMD64 | R_X86_64_GOTPCRELX<<24, + AMD64 | R_X86_64_REX_GOTPCRELX<<24, + I386 | R_386_32<<24, + I386 | R_386_PC32<<24, + I386 | R_386_GOT32<<24, + I386 | R_386_PLT32<<24, + I386 | R_386_GOTOFF<<24, + I386 | R_386_GOTPC<<24, + I386 | R_386_GOT32X<<24, + PPC64 | R_PPC64_REL24<<24, + PPC64 | R_PPC_REL32<<24, + S390X | R_390_32<<24, + S390X | R_390_PC32<<24, + S390X | R_390_GOT32<<24, + S390X | R_390_PLT32<<24, + S390X | R_390_PC32DBL<<24, + S390X | R_390_PLT32DBL<<24, + S390X | R_390_GOTPCDBL<<24, + S390X | R_390_GOTENT<<24: + return 4 + + case AMD64 | R_X86_64_64<<24, + PPC64 | R_PPC64_ADDR64<<24, + S390X | R_390_GLOB_DAT<<24, + S390X | R_390_RELATIVE<<24, + S390X | R_390_GOTOFF<<24, + S390X | R_390_GOTPC<<24, + S390X | R_390_64<<24, + S390X | R_390_PC64<<24, + S390X | R_390_GOT64<<24, + S390X | R_390_PLT64<<24: + return 8 } - - return 256 + elftype } -- cgit v1.3 From b17b95301a549d168d96e38310d06216f595e53b Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 6 Apr 2016 20:06:41 -0700 Subject: cmd/internal/sys: change ArchFamily constants to iotas RIP architecture characters. Change-Id: I36f53afdc311b14b9459ff3821bd6df54a057ded Reviewed-on: https://go-review.googlesource.com/21628 Reviewed-by: Brad Fitzpatrick Reviewed-by: Dave Cheney --- src/cmd/internal/sys/arch.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/internal/sys/arch.go b/src/cmd/internal/sys/arch.go index 0a7423aa9f..0dc7cb814b 100644 --- a/src/cmd/internal/sys/arch.go +++ b/src/cmd/internal/sys/arch.go @@ -10,13 +10,13 @@ import "encoding/binary" type ArchFamily byte const ( - AMD64 ArchFamily = '6' - ARM ArchFamily = '5' - ARM64 ArchFamily = '7' - I386 ArchFamily = '8' - MIPS64 ArchFamily = '0' - PPC64 ArchFamily = '9' - S390X ArchFamily = 'z' + AMD64 ArchFamily = iota + ARM + ARM64 + I386 + MIPS64 + PPC64 + S390X ) // Arch represents an individual architecture. @@ -30,6 +30,7 @@ type Arch struct { PtrSize int RegSize int + // MinLC is the minimum length of an instruction code. MinLC int } -- cgit v1.3 From 4b7e36cdfe8e0c3579a2503a81474fe43db4db69 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 6 Apr 2016 21:45:29 -0700 Subject: cmd: extract obj's Biobuf code into new bio package API could still be made more Go-ey. Updates #15165. Change-Id: I514ffceffa43c293ae5d7e5f1e9193fda0098865 Reviewed-on: https://go-review.googlesource.com/21644 Reviewed-by: Brad Fitzpatrick Run-TryBot: Matthew Dempsky TryBot-Result: Gobot Gobot --- src/cmd/asm/internal/asm/endtoend_test.go | 5 +- src/cmd/asm/main.go | 5 +- src/cmd/compile/internal/gc/bexport.go | 10 +- src/cmd/compile/internal/gc/export.go | 8 +- src/cmd/compile/internal/gc/go.go | 5 +- src/cmd/compile/internal/gc/main.go | 5 +- src/cmd/compile/internal/gc/obj.go | 31 +++--- src/cmd/dist/buildtool.go | 1 + src/cmd/internal/bio/buf.go | 150 ++++++++++++++++++++++++++++++ src/cmd/internal/obj/link.go | 7 +- src/cmd/internal/obj/objfile.go | 9 +- src/cmd/internal/obj/util.go | 140 ---------------------------- src/cmd/link/internal/ld/ar.go | 15 +-- src/cmd/link/internal/ld/go.go | 5 +- src/cmd/link/internal/ld/ldelf.go | 13 +-- src/cmd/link/internal/ld/ldmacho.go | 23 ++--- src/cmd/link/internal/ld/ldpe.go | 27 +++--- src/cmd/link/internal/ld/lib.go | 83 ++++++++--------- src/cmd/link/internal/ld/link.go | 4 +- src/cmd/link/internal/ld/objfile.go | 9 +- src/cmd/link/internal/ld/pobj.go | 3 +- 21 files changed, 291 insertions(+), 267 deletions(-) create mode 100644 src/cmd/internal/bio/buf.go (limited to 'src/cmd') diff --git a/src/cmd/asm/internal/asm/endtoend_test.go b/src/cmd/asm/internal/asm/endtoend_test.go index 1307c4243f..8986281f10 100644 --- a/src/cmd/asm/internal/asm/endtoend_test.go +++ b/src/cmd/asm/internal/asm/endtoend_test.go @@ -17,6 +17,7 @@ import ( "testing" "cmd/asm/internal/lex" + "cmd/internal/bio" "cmd/internal/obj" ) @@ -33,7 +34,7 @@ func testEndToEnd(t *testing.T, goarch, file string) { pList := obj.Linknewplist(ctxt) var ok bool testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. - ctxt.Bso = obj.Binitw(os.Stdout) + ctxt.Bso = bio.BufWriter(os.Stdout) defer ctxt.Bso.Flush() failed := false ctxt.DiagFunc = func(format string, args ...interface{}) { @@ -271,7 +272,7 @@ func testErrors(t *testing.T, goarch, file string) { pList := obj.Linknewplist(ctxt) var ok bool testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. - ctxt.Bso = obj.Binitw(os.Stdout) + ctxt.Bso = bio.BufWriter(os.Stdout) defer ctxt.Bso.Flush() failed := false var errBuf bytes.Buffer diff --git a/src/cmd/asm/main.go b/src/cmd/asm/main.go index 4e450bec98..75cb8f75d3 100644 --- a/src/cmd/asm/main.go +++ b/src/cmd/asm/main.go @@ -15,6 +15,7 @@ import ( "cmd/asm/internal/flags" "cmd/asm/internal/lex" + "cmd/internal/bio" "cmd/internal/obj" ) @@ -45,9 +46,9 @@ func main() { if *flags.Shared || *flags.Dynlink { ctxt.Flag_shared = 1 } - ctxt.Bso = obj.Binitw(os.Stdout) + ctxt.Bso = bio.BufWriter(os.Stdout) defer ctxt.Bso.Flush() - output := obj.Binitw(fd) + output := bio.BufWriter(fd) fmt.Fprintf(output, "go object %s %s %s\n", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion()) fmt.Fprintf(output, "!\n") diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index 092cdac2f6..702090280f 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -92,7 +92,7 @@ package gc import ( "bytes" "cmd/compile/internal/big" - "cmd/internal/obj" + "cmd/internal/bio" "encoding/binary" "fmt" "sort" @@ -124,7 +124,7 @@ const exportVersion = "v0" const exportInlined = true // default: true type exporter struct { - out *obj.Biobuf + out *bio.Buf pkgIndex map[*Pkg]int typIndex map[*Type]int inlined []*Func @@ -136,7 +136,7 @@ type exporter struct { } // Export writes the exportlist for localpkg to out and returns the number of bytes written. -func Export(out *obj.Biobuf, trace bool) int { +func Export(out *bio.Buf, trace bool) int { p := exporter{ out: out, pkgIndex: make(map[*Pkg]int), @@ -1531,10 +1531,10 @@ func (p *exporter) byte(b byte) { fallthrough case '|': // write '|' as '|' '|' - obj.Bputc(p.out, '|') + p.out.WriteByte('|') p.written++ } - obj.Bputc(p.out, b) + p.out.WriteByte(b) p.written++ } diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index 17311cf6af..5d4add8ff4 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -7,7 +7,7 @@ package gc import ( "bufio" "bytes" - "cmd/internal/obj" + "cmd/internal/bio" "fmt" "sort" "unicode" @@ -384,7 +384,7 @@ func dumpexport() { if debugFormat { // save a copy of the export data var copy bytes.Buffer - bcopy := obj.Binitw(©) + bcopy := bio.BufWriter(©) size = Export(bcopy, Debug_export != 0) bcopy.Flush() // flushing to bytes.Buffer cannot fail if n, err := bout.Write(copy.Bytes()); n != size || err != nil { @@ -577,7 +577,7 @@ func importtype(pt *Type, t *Type) { } func dumpasmhdr() { - b, err := obj.Bopenw(asmhdr) + b, err := bio.Create(asmhdr) if err != nil { Fatalf("%v", err) } @@ -604,5 +604,5 @@ func dumpasmhdr() { } } - obj.Bterm(b) + b.Close() } diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index ef8b516ea5..cd9db38fb4 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -6,6 +6,7 @@ package gc import ( "cmd/compile/internal/ssa" + "cmd/internal/bio" "cmd/internal/obj" ) @@ -132,7 +133,7 @@ var infile string var outfile string -var bout *obj.Biobuf +var bout *bio.Buf var nerrors int @@ -287,7 +288,7 @@ var Ctxt *obj.Link var writearchive int -var bstdout obj.Biobuf +var bstdout *bio.Buf var Nacl bool diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 079f4916c7..c8a778c34a 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -9,6 +9,7 @@ package gc import ( "bufio" "cmd/compile/internal/ssa" + "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/sys" "flag" @@ -97,8 +98,8 @@ func Main() { Ctxt = obj.Linknew(Thearch.LinkArch) Ctxt.DiagFunc = Yyerror - Ctxt.Bso = &bstdout - bstdout = *obj.Binitw(os.Stdout) + bstdout = bio.BufWriter(os.Stdout) + Ctxt.Bso = bstdout localpkg = mkpkg("") localpkg.Prefix = "\"\"" diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index 99eb73bd94..3920e25224 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -5,6 +5,7 @@ package gc import ( + "cmd/internal/bio" "cmd/internal/obj" "crypto/sha256" "fmt" @@ -23,7 +24,7 @@ func formathdr(arhdr []byte, name string, size int64) { func dumpobj() { var err error - bout, err = obj.Bopenw(outfile) + bout, err = bio.Create(outfile) if err != nil { Flusherrors() fmt.Printf("can't create %s: %v\n", outfile, err) @@ -33,10 +34,10 @@ func dumpobj() { startobj := int64(0) var arhdr [ArhdrSize]byte if writearchive != 0 { - obj.Bwritestring(bout, "!\n") + bout.WriteString("!\n") arhdr = [ArhdrSize]byte{} bout.Write(arhdr[:]) - startobj = obj.Boffset(bout) + startobj = bio.Boffset(bout) } fmt.Fprintf(bout, "go object %s %s %s %s\n", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion(), obj.Expstring()) @@ -44,19 +45,19 @@ func dumpobj() { if writearchive != 0 { bout.Flush() - size := obj.Boffset(bout) - startobj + size := bio.Boffset(bout) - startobj if size&1 != 0 { - obj.Bputc(bout, 0) + bout.WriteByte(0) } - obj.Bseek(bout, startobj-ArhdrSize, 0) + bio.Bseek(bout, startobj-ArhdrSize, 0) formathdr(arhdr[:], "__.PKGDEF", size) bout.Write(arhdr[:]) bout.Flush() - obj.Bseek(bout, startobj+size+(size&1), 0) + bio.Bseek(bout, startobj+size+(size&1), 0) arhdr = [ArhdrSize]byte{} bout.Write(arhdr[:]) - startobj = obj.Boffset(bout) + startobj = bio.Boffset(bout) fmt.Fprintf(bout, "go object %s %s %s %s\n", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion(), obj.Expstring()) } @@ -91,16 +92,16 @@ func dumpobj() { if writearchive != 0 { bout.Flush() - size := obj.Boffset(bout) - startobj + size := bio.Boffset(bout) - startobj if size&1 != 0 { - obj.Bputc(bout, 0) + bout.WriteByte(0) } - obj.Bseek(bout, startobj-ArhdrSize, 0) + bio.Bseek(bout, startobj-ArhdrSize, 0) formathdr(arhdr[:], "_go_.o", size) bout.Write(arhdr[:]) } - obj.Bterm(bout) + bout.Close() } func dumpglobls() { @@ -132,9 +133,9 @@ func dumpglobls() { funcsyms = nil } -func Bputname(b *obj.Biobuf, s *obj.LSym) { - obj.Bwritestring(b, s.Name) - obj.Bputc(b, 0) +func Bputname(b *bio.Buf, s *obj.LSym) { + b.WriteString(s.Name) + b.WriteByte(0) } func Linksym(s *Sym) *obj.LSym { diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 123d5ccf82..777c92c726 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -38,6 +38,7 @@ var bootstrapDirs = []string{ "compile/internal/ppc64", "compile/internal/ssa", "compile/internal/x86", + "internal/bio", "internal/gcprog", "internal/obj", "internal/obj/arm", diff --git a/src/cmd/internal/bio/buf.go b/src/cmd/internal/bio/buf.go new file mode 100644 index 0000000000..a1df26ca9c --- /dev/null +++ b/src/cmd/internal/bio/buf.go @@ -0,0 +1,150 @@ +// Copyright 2015 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 bio implements seekable buffered I/O. +package bio + +import ( + "bufio" + "io" + "log" + "os" +) + +const EOF = -1 + +// Buf implements a seekable buffered I/O abstraction. +type Buf struct { + f *os.File + r *bufio.Reader + w *bufio.Writer +} + +func (b *Buf) Reader() *bufio.Reader { return b.r } +func (b *Buf) Writer() *bufio.Writer { return b.w } + +func Create(name string) (*Buf, error) { + f, err := os.Create(name) + if err != nil { + return nil, err + } + return &Buf{f: f, w: bufio.NewWriter(f)}, nil +} + +func Open(name string) (*Buf, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + return &Buf{f: f, r: bufio.NewReader(f)}, nil +} + +func BufWriter(w io.Writer) *Buf { + return &Buf{w: bufio.NewWriter(w)} +} + +func BufReader(r io.Reader) *Buf { + return &Buf{r: bufio.NewReader(r)} +} + +func (b *Buf) Write(p []byte) (int, error) { + return b.w.Write(p) +} + +func (b *Buf) WriteString(p string) (int, error) { + return b.w.WriteString(p) +} + +func Bseek(b *Buf, offset int64, whence int) int64 { + if b.w != nil { + if err := b.w.Flush(); err != nil { + log.Fatalf("writing output: %v", err) + } + } else if b.r != nil { + if whence == 1 { + offset -= int64(b.r.Buffered()) + } + } + off, err := b.f.Seek(offset, whence) + if err != nil { + log.Fatalf("seeking in output: %v", err) + } + if b.r != nil { + b.r.Reset(b.f) + } + return off +} + +func Boffset(b *Buf) int64 { + if b.w != nil { + if err := b.w.Flush(); err != nil { + log.Fatalf("writing output: %v", err) + } + } + off, err := b.f.Seek(0, 1) + if err != nil { + log.Fatalf("seeking in output [0, 1]: %v", err) + } + if b.r != nil { + off -= int64(b.r.Buffered()) + } + return off +} + +func (b *Buf) Flush() error { + return b.w.Flush() +} + +func (b *Buf) WriteByte(c byte) error { + return b.w.WriteByte(c) +} + +func Bread(b *Buf, p []byte) int { + n, err := io.ReadFull(b.r, p) + if n == 0 { + if err != nil && err != io.EOF { + n = -1 + } + } + return n +} + +func Bgetc(b *Buf) int { + c, err := b.r.ReadByte() + if err != nil { + if err != io.EOF { + log.Fatalf("reading input: %v", err) + } + return EOF + } + return int(c) +} + +func (b *Buf) Read(p []byte) (int, error) { + return b.r.Read(p) +} + +func (b *Buf) Peek(n int) ([]byte, error) { + return b.r.Peek(n) +} + +func Brdline(b *Buf, delim int) string { + s, err := b.r.ReadBytes(byte(delim)) + if err != nil { + log.Fatalf("reading input: %v", err) + } + return string(s) +} + +func (b *Buf) Close() error { + var err error + if b.w != nil { + err = b.w.Flush() + } + err1 := b.f.Close() + if err == nil { + err = err1 + } + return err +} diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index 81a5689aef..2c81ca2f08 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -30,7 +30,10 @@ package obj -import "cmd/internal/sys" +import ( + "cmd/internal/bio" + "cmd/internal/sys" +) // An Addr is an argument to an instruction. // The general forms and their encodings are: @@ -626,7 +629,7 @@ type Link struct { Flag_shared int32 Flag_dynlink bool Flag_optimize bool - Bso *Biobuf + Bso *bio.Buf Pathname string Goroot string Goroot_final string diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index bdd3bfc826..405cbf446a 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -109,6 +109,7 @@ package obj import ( "bufio" + "cmd/internal/bio" "cmd/internal/sys" "fmt" "log" @@ -120,7 +121,7 @@ import ( // The Go and C compilers, and the assembler, call writeobj to write // out a Go object file. The linker does not call this; the linker // does not write out object files. -func Writeobjdirect(ctxt *Link, b *Biobuf) { +func Writeobjdirect(ctxt *Link, b *bio.Buf) { Flushplist(ctxt) WriteObjFile(ctxt, b) } @@ -373,16 +374,16 @@ func (w *objWriter) writeLengths() { w.writeInt(int64(w.nFile)) } -func newObjWriter(ctxt *Link, b *Biobuf) *objWriter { +func newObjWriter(ctxt *Link, b *bio.Buf) *objWriter { return &objWriter{ ctxt: ctxt, - wr: b.w, + wr: b.Writer(), vrefIdx: make(map[string]int), refIdx: make(map[string]int), } } -func WriteObjFile(ctxt *Link, b *Biobuf) { +func WriteObjFile(ctxt *Link, b *bio.Buf) { w := newObjWriter(ctxt, b) // Magic header diff --git a/src/cmd/internal/obj/util.go b/src/cmd/internal/obj/util.go index 245fab9690..04e6a76e1a 100644 --- a/src/cmd/internal/obj/util.go +++ b/src/cmd/internal/obj/util.go @@ -5,10 +5,8 @@ package obj import ( - "bufio" "bytes" "fmt" - "io" "log" "os" "strings" @@ -26,144 +24,6 @@ func Cputime() float64 { return time.Since(start).Seconds() } -type Biobuf struct { - f *os.File - r *bufio.Reader - w *bufio.Writer - linelen int -} - -func (b *Biobuf) Reader() *bufio.Reader { return b.r } - -func Bopenw(name string) (*Biobuf, error) { - f, err := os.Create(name) - if err != nil { - return nil, err - } - return &Biobuf{f: f, w: bufio.NewWriter(f)}, nil -} - -func Bopenr(name string) (*Biobuf, error) { - f, err := os.Open(name) - if err != nil { - return nil, err - } - return &Biobuf{f: f, r: bufio.NewReader(f)}, nil -} - -func Binitw(w io.Writer) *Biobuf { - return &Biobuf{w: bufio.NewWriter(w)} -} - -func Binitr(r io.Reader) *Biobuf { - return &Biobuf{r: bufio.NewReader(r)} -} - -func (b *Biobuf) Write(p []byte) (int, error) { - return b.w.Write(p) -} - -func Bwritestring(b *Biobuf, p string) (int, error) { - return b.w.WriteString(p) -} - -func Bseek(b *Biobuf, offset int64, whence int) int64 { - if b.w != nil { - if err := b.w.Flush(); err != nil { - log.Fatalf("writing output: %v", err) - } - } else if b.r != nil { - if whence == 1 { - offset -= int64(b.r.Buffered()) - } - } - off, err := b.f.Seek(offset, whence) - if err != nil { - log.Fatalf("seeking in output: %v", err) - } - if b.r != nil { - b.r.Reset(b.f) - } - return off -} - -func Boffset(b *Biobuf) int64 { - if b.w != nil { - if err := b.w.Flush(); err != nil { - log.Fatalf("writing output: %v", err) - } - } - off, err := b.f.Seek(0, 1) - if err != nil { - log.Fatalf("seeking in output [0, 1]: %v", err) - } - if b.r != nil { - off -= int64(b.r.Buffered()) - } - return off -} - -func (b *Biobuf) Flush() error { - return b.w.Flush() -} - -func Bputc(b *Biobuf, c byte) { - b.w.WriteByte(c) -} - -const Beof = -1 - -func Bread(b *Biobuf, p []byte) int { - n, err := io.ReadFull(b.r, p) - if n == 0 { - if err != nil && err != io.EOF { - n = -1 - } - } - return n -} - -func Bgetc(b *Biobuf) int { - c, err := b.r.ReadByte() - if err != nil { - return -1 - } - return int(c) -} - -func (b *Biobuf) Read(p []byte) (int, error) { - return b.r.Read(p) -} - -func (b *Biobuf) Peek(n int) ([]byte, error) { - return b.r.Peek(n) -} - -func Brdline(b *Biobuf, delim int) string { - s, err := b.r.ReadBytes(byte(delim)) - if err != nil { - log.Fatalf("reading input: %v", err) - } - b.linelen = len(s) - return string(s) -} - -func Blinelen(b *Biobuf) int { - return b.linelen -} - -func Bterm(b *Biobuf) error { - var err error - if b.w != nil { - err = b.w.Flush() - } - err1 := b.f.Close() - if err == nil { - err = err1 - } - return err -} - func envOr(key, value string) string { if x := os.Getenv(key); x != "" { return x diff --git a/src/cmd/link/internal/ld/ar.go b/src/cmd/link/internal/ld/ar.go index d07756071d..205773c7f8 100644 --- a/src/cmd/link/internal/ld/ar.go +++ b/src/cmd/link/internal/ld/ar.go @@ -31,6 +31,7 @@ package ld import ( + "cmd/internal/bio" "cmd/internal/obj" "encoding/binary" "fmt" @@ -62,7 +63,7 @@ type ArHdr struct { // define them. This is used for the compiler support library // libgcc.a. func hostArchive(name string) { - f, err := obj.Bopenr(name) + f, err := bio.Open(name) if err != nil { if os.IsNotExist(err) { // It's OK if we don't have a libgcc file at all. @@ -73,15 +74,15 @@ func hostArchive(name string) { } Exitf("cannot open file %s: %v", name, err) } - defer obj.Bterm(f) + defer f.Close() magbuf := make([]byte, len(ARMAG)) - if obj.Bread(f, magbuf) != len(magbuf) { + if bio.Bread(f, magbuf) != len(magbuf) { Exitf("file %s too short", name) } var arhdr ArHdr - l := nextar(f, obj.Boffset(f), &arhdr) + l := nextar(f, bio.Boffset(f), &arhdr) if l <= 0 { Exitf("%s missing armap", name) } @@ -117,7 +118,7 @@ func hostArchive(name string) { l = atolwhex(arhdr.size) h := ldobj(f, "libgcc", l, pname, name, ArchiveObj) - obj.Bseek(f, h.off, 0) + bio.Bseek(f, h.off, 0) h.ld(f, h.pkg, h.length, h.pn) } @@ -130,7 +131,7 @@ func hostArchive(name string) { type archiveMap map[string]uint64 // readArmap reads the archive symbol map. -func readArmap(filename string, f *obj.Biobuf, arhdr ArHdr) archiveMap { +func readArmap(filename string, f *bio.Buf, arhdr ArHdr) archiveMap { is64 := arhdr.name == "/SYM64/" wordSize := 4 if is64 { @@ -139,7 +140,7 @@ func readArmap(filename string, f *obj.Biobuf, arhdr ArHdr) archiveMap { l := atolwhex(arhdr.size) contents := make([]byte, l) - if obj.Bread(f, contents) != int(l) { + if bio.Bread(f, contents) != int(l) { Exitf("short read from %s", filename) } diff --git a/src/cmd/link/internal/ld/go.go b/src/cmd/link/internal/ld/go.go index 027e05d845..8bafaffd7c 100644 --- a/src/cmd/link/internal/ld/go.go +++ b/src/cmd/link/internal/ld/go.go @@ -8,6 +8,7 @@ package ld import ( "bytes" + "cmd/internal/bio" "cmd/internal/obj" "fmt" "os" @@ -26,7 +27,7 @@ func expandpkg(t0 string, pkg string) string { // once the dust settles, try to move some code to // libmach, so that other linkers and ar can share. -func ldpkg(f *obj.Biobuf, pkg string, length int64, filename string, whence int) { +func ldpkg(f *bio.Buf, pkg string, length int64, filename string, whence int) { var p0, p1 int if Debug['g'] != 0 { @@ -48,7 +49,7 @@ func ldpkg(f *obj.Biobuf, pkg string, length int64, filename string, whence int) } bdata := make([]byte, length) - if int64(obj.Bread(f, bdata)) != length { + if int64(bio.Bread(f, bdata)) != length { fmt.Fprintf(os.Stderr, "%s: short pkg read %s\n", os.Args[0], filename) if Debug['u'] != 0 { errorexit() diff --git a/src/cmd/link/internal/ld/ldelf.go b/src/cmd/link/internal/ld/ldelf.go index 485599be62..eafc6930d5 100644 --- a/src/cmd/link/internal/ld/ldelf.go +++ b/src/cmd/link/internal/ld/ldelf.go @@ -2,6 +2,7 @@ package ld import ( "bytes" + "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/sys" "encoding/binary" @@ -267,7 +268,7 @@ type ElfSect struct { } type ElfObj struct { - f *obj.Biobuf + f *bio.Buf base int64 // offset in f where ELF begins length int64 // length of ELF is64 int @@ -446,13 +447,13 @@ func parseArmAttributes(e binary.ByteOrder, data []byte) { } } -func ldelf(f *obj.Biobuf, pkg string, length int64, pn string) { +func ldelf(f *bio.Buf, pkg string, length int64, pn string) { if Debug['v'] != 0 { fmt.Fprintf(&Bso, "%5.2f ldelf %s\n", obj.Cputime(), pn) } Ctxt.IncVersion() - base := int32(obj.Boffset(f)) + base := int32(bio.Boffset(f)) var add uint64 var e binary.ByteOrder @@ -475,7 +476,7 @@ func ldelf(f *obj.Biobuf, pkg string, length int64, pn string) { var sect *ElfSect var sym ElfSym var symbols []*LSym - if obj.Bread(f, hdrbuf[:]) != len(hdrbuf) { + if bio.Bread(f, hdrbuf[:]) != len(hdrbuf) { goto bad } hdr = new(ElfHdrBytes) @@ -600,7 +601,7 @@ func ldelf(f *obj.Biobuf, pkg string, length int64, pn string) { elfobj.nsect = uint(elfobj.shnum) for i := 0; uint(i) < elfobj.nsect; i++ { - if obj.Bseek(f, int64(uint64(base)+elfobj.shoff+uint64(int64(i)*int64(elfobj.shentsize))), 0) < 0 { + if bio.Bseek(f, int64(uint64(base)+elfobj.shoff+uint64(int64(i)*int64(elfobj.shentsize))), 0) < 0 { goto bad } sect = &elfobj.sect[i] @@ -986,7 +987,7 @@ func elfmap(elfobj *ElfObj, sect *ElfSect) (err error) { sect.base = make([]byte, sect.size) err = fmt.Errorf("short read") - if obj.Bseek(elfobj.f, int64(uint64(elfobj.base)+sect.off), 0) < 0 || obj.Bread(elfobj.f, sect.base) != len(sect.base) { + if bio.Bseek(elfobj.f, int64(uint64(elfobj.base)+sect.off), 0) < 0 || bio.Bread(elfobj.f, sect.base) != len(sect.base) { return err } diff --git a/src/cmd/link/internal/ld/ldmacho.go b/src/cmd/link/internal/ld/ldmacho.go index 9fbb2123af..6376116d04 100644 --- a/src/cmd/link/internal/ld/ldmacho.go +++ b/src/cmd/link/internal/ld/ldmacho.go @@ -1,6 +1,7 @@ package ld import ( + "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/sys" "encoding/binary" @@ -42,7 +43,7 @@ const ( ) type LdMachoObj struct { - f *obj.Biobuf + f *bio.Buf base int64 // off in f where Mach-O begins length int64 // length of Mach-O is64 bool @@ -298,7 +299,7 @@ func macholoadrel(m *LdMachoObj, sect *LdMachoSect) int { rel := make([]LdMachoRel, sect.nreloc) n := int(sect.nreloc * 8) buf := make([]byte, n) - if obj.Bseek(m.f, m.base+int64(sect.reloff), 0) < 0 || obj.Bread(m.f, buf) != n { + if bio.Bseek(m.f, m.base+int64(sect.reloff), 0) < 0 || bio.Bread(m.f, buf) != n { return -1 } var p []byte @@ -344,7 +345,7 @@ func macholoaddsym(m *LdMachoObj, d *LdMachoDysymtab) int { n := int(d.nindirectsyms) p := make([]byte, n*4) - if obj.Bseek(m.f, m.base+int64(d.indirectsymoff), 0) < 0 || obj.Bread(m.f, p) != len(p) { + if bio.Bseek(m.f, m.base+int64(d.indirectsymoff), 0) < 0 || bio.Bread(m.f, p) != len(p) { return -1 } @@ -361,7 +362,7 @@ func macholoadsym(m *LdMachoObj, symtab *LdMachoSymtab) int { } strbuf := make([]byte, symtab.strsize) - if obj.Bseek(m.f, m.base+int64(symtab.stroff), 0) < 0 || obj.Bread(m.f, strbuf) != len(strbuf) { + if bio.Bseek(m.f, m.base+int64(symtab.stroff), 0) < 0 || bio.Bread(m.f, strbuf) != len(strbuf) { return -1 } @@ -371,7 +372,7 @@ func macholoadsym(m *LdMachoObj, symtab *LdMachoSymtab) int { } n := int(symtab.nsym * uint32(symsize)) symbuf := make([]byte, n) - if obj.Bseek(m.f, m.base+int64(symtab.symoff), 0) < 0 || obj.Bread(m.f, symbuf) != len(symbuf) { + if bio.Bseek(m.f, m.base+int64(symtab.symoff), 0) < 0 || bio.Bread(m.f, symbuf) != len(symbuf) { return -1 } sym := make([]LdMachoSym, symtab.nsym) @@ -401,7 +402,7 @@ func macholoadsym(m *LdMachoObj, symtab *LdMachoSymtab) int { return 0 } -func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { +func ldmacho(f *bio.Buf, pkg string, length int64, pn string) { var err error var j int var is64 bool @@ -431,8 +432,8 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { var name string Ctxt.IncVersion() - base := obj.Boffset(f) - if obj.Bread(f, hdr[:]) != len(hdr) { + base := bio.Boffset(f) + if bio.Bread(f, hdr[:]) != len(hdr) { goto bad } @@ -455,7 +456,7 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { if is64 { var tmp [4]uint8 - obj.Bread(f, tmp[:4]) // skip reserved word in header + bio.Bread(f, tmp[:4]) // skip reserved word in header } m = new(LdMachoObj) @@ -493,7 +494,7 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { m.cmd = make([]LdMachoCmd, ncmd) off = uint32(len(hdr)) cmdp = make([]byte, cmdsz) - if obj.Bread(f, cmdp) != len(cmdp) { + if bio.Bread(f, cmdp) != len(cmdp) { err = fmt.Errorf("reading cmds: %v", err) goto bad } @@ -556,7 +557,7 @@ func ldmacho(f *obj.Biobuf, pkg string, length int64, pn string) { } dat = make([]byte, c.seg.filesz) - if obj.Bseek(f, m.base+int64(c.seg.fileoff), 0) < 0 || obj.Bread(f, dat) != len(dat) { + if bio.Bseek(f, m.base+int64(c.seg.fileoff), 0) < 0 || bio.Bread(f, dat) != len(dat) { err = fmt.Errorf("cannot load object data: %v", err) goto bad } diff --git a/src/cmd/link/internal/ld/ldpe.go b/src/cmd/link/internal/ld/ldpe.go index ea0c482838..e97e842e7f 100644 --- a/src/cmd/link/internal/ld/ldpe.go +++ b/src/cmd/link/internal/ld/ldpe.go @@ -5,6 +5,7 @@ package ld import ( + "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/sys" "encoding/binary" @@ -117,7 +118,7 @@ type PeSect struct { } type PeObj struct { - f *obj.Biobuf + f *bio.Buf name string base uint32 sect []PeSect @@ -128,14 +129,14 @@ type PeObj struct { snames []byte } -func ldpe(f *obj.Biobuf, pkg string, length int64, pn string) { +func ldpe(f *bio.Buf, pkg string, length int64, pn string) { if Debug['v'] != 0 { fmt.Fprintf(&Bso, "%5.2f ldpe %s\n", obj.Cputime(), pn) } var sect *PeSect Ctxt.IncVersion() - base := int32(obj.Boffset(f)) + base := int32(bio.Boffset(f)) peobj := new(PeObj) peobj.f = f @@ -173,15 +174,15 @@ func ldpe(f *obj.Biobuf, pkg string, length int64, pn string) { // TODO return error if found .cormeta // load string table - obj.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) + bio.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) - if obj.Bread(f, symbuf[:4]) != 4 { + if bio.Bread(f, symbuf[:4]) != 4 { goto bad } l = Le32(symbuf[:]) peobj.snames = make([]byte, l) - obj.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) - if obj.Bread(f, peobj.snames) != len(peobj.snames) { + bio.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) + if bio.Bread(f, peobj.snames) != len(peobj.snames) { goto bad } @@ -201,10 +202,10 @@ func ldpe(f *obj.Biobuf, pkg string, length int64, pn string) { peobj.pesym = make([]PeSym, peobj.fh.NumberOfSymbols) peobj.npesym = uint(peobj.fh.NumberOfSymbols) - obj.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable), 0) + bio.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable), 0) for i := 0; uint32(i) < peobj.fh.NumberOfSymbols; i += numaux + 1 { - obj.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(i), 0) - if obj.Bread(f, symbuf[:]) != len(symbuf) { + bio.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(i), 0) + if bio.Bread(f, symbuf[:]) != len(symbuf) { goto bad } @@ -289,10 +290,10 @@ func ldpe(f *obj.Biobuf, pkg string, length int64, pn string) { } r = make([]Reloc, rsect.sh.NumberOfRelocations) - obj.Bseek(f, int64(peobj.base)+int64(rsect.sh.PointerToRelocations), 0) + bio.Bseek(f, int64(peobj.base)+int64(rsect.sh.PointerToRelocations), 0) for j = 0; j < int(rsect.sh.NumberOfRelocations); j++ { rp = &r[j] - if obj.Bread(f, symbuf[:10]) != 10 { + if bio.Bread(f, symbuf[:10]) != 10 { goto bad } rva := Le32(symbuf[0:]) @@ -465,7 +466,7 @@ func pemap(peobj *PeObj, sect *PeSect) int { if sect.sh.PointerToRawData == 0 { // .bss doesn't have data in object file return 0 } - if obj.Bseek(peobj.f, int64(peobj.base)+int64(sect.sh.PointerToRawData), 0) < 0 || obj.Bread(peobj.f, sect.base) != len(sect.base) { + if bio.Bseek(peobj.f, int64(peobj.base)+int64(sect.sh.PointerToRawData), 0) < 0 || bio.Bread(peobj.f, sect.base) != len(sect.base) { return -1 } diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 305a3bc0db..789eaef1a5 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -33,6 +33,7 @@ package ld import ( "bufio" "bytes" + "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/sys" "crypto/sha1" @@ -240,7 +241,7 @@ const ( var ( headstring string // buffered output - Bso obj.Biobuf + Bso bio.Buf ) type outBuf struct { @@ -738,13 +739,13 @@ func loadlib() { * look for the next file in an archive. * adapted from libmach. */ -func nextar(bp *obj.Biobuf, off int64, a *ArHdr) int64 { +func nextar(bp *bio.Buf, off int64, a *ArHdr) int64 { if off&1 != 0 { off++ } - obj.Bseek(bp, off, 0) + bio.Bseek(bp, off, 0) buf := make([]byte, SAR_HDR) - if n := obj.Bread(bp, buf); n < len(buf) { + if n := bio.Bread(bp, buf); n < len(buf) { if n >= 0 { return 0 } @@ -773,25 +774,25 @@ func objfile(lib *Library) { fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), lib.File, pkg) } Bso.Flush() - f, err := obj.Bopenr(lib.File) + f, err := bio.Open(lib.File) if err != nil { Exitf("cannot open file %s: %v", lib.File, err) } magbuf := make([]byte, len(ARMAG)) - if obj.Bread(f, magbuf) != len(magbuf) || !strings.HasPrefix(string(magbuf), ARMAG) { + if bio.Bread(f, magbuf) != len(magbuf) || !strings.HasPrefix(string(magbuf), ARMAG) { /* load it as a regular file */ - l := obj.Bseek(f, 0, 2) + l := bio.Bseek(f, 0, 2) - obj.Bseek(f, 0, 0) + bio.Bseek(f, 0, 0) ldobj(f, pkg, l, lib.File, lib.File, FileObj) - obj.Bterm(f) + f.Close() return } /* process __.PKGDEF */ - off := obj.Boffset(f) + off := bio.Boffset(f) var arhdr ArHdr l := nextar(f, off, &arhdr) @@ -807,12 +808,12 @@ func objfile(lib *Library) { } if Buildmode == BuildmodeShared { - before := obj.Boffset(f) + before := bio.Boffset(f) pkgdefBytes := make([]byte, atolwhex(arhdr.size)) - obj.Bread(f, pkgdefBytes) + bio.Bread(f, pkgdefBytes) hash := sha1.Sum(pkgdefBytes) lib.hash = hash[:] - obj.Bseek(f, before, 0) + bio.Bseek(f, before, 0) } off += l @@ -848,11 +849,11 @@ func objfile(lib *Library) { } out: - obj.Bterm(f) + f.Close() } type Hostobj struct { - ld func(*obj.Biobuf, string, int64, string) + ld func(*bio.Buf, string, int64, string) pkg string pn string file string @@ -873,7 +874,7 @@ var internalpkg = []string{ "runtime/msan", } -func ldhostobj(ld func(*obj.Biobuf, string, int64, string), f *obj.Biobuf, pkg string, length int64, pn string, file string) *Hostobj { +func ldhostobj(ld func(*bio.Buf, string, int64, string), f *bio.Buf, pkg string, length int64, pn string, file string) *Hostobj { isinternal := false for i := 0; i < len(internalpkg); i++ { if pkg == internalpkg[i] { @@ -904,26 +905,26 @@ func ldhostobj(ld func(*obj.Biobuf, string, int64, string), f *obj.Biobuf, pkg s h.pkg = pkg h.pn = pn h.file = file - h.off = obj.Boffset(f) + h.off = bio.Boffset(f) h.length = length return h } func hostobjs() { - var f *obj.Biobuf + var f *bio.Buf var h *Hostobj for i := 0; i < len(hostobj); i++ { h = &hostobj[i] var err error - f, err = obj.Bopenr(h.file) + f, err = bio.Open(h.file) if f == nil { Exitf("cannot reopen %s: %v", h.pn, err) } - obj.Bseek(f, h.off, 0) + bio.Bseek(f, h.off, 0) h.ld(f, h.pkg, h.length, h.pn) - obj.Bterm(f) + f.Close() } } @@ -1265,15 +1266,15 @@ func hostlinkArchArgs() []string { // ldobj loads an input object. If it is a host object (an object // compiled by a non-Go compiler) it returns the Hostobj pointer. If // it is a Go object, it returns nil. -func ldobj(f *obj.Biobuf, pkg string, length int64, pn string, file string, whence int) *Hostobj { - eof := obj.Boffset(f) + length +func ldobj(f *bio.Buf, pkg string, length int64, pn string, file string, whence int) *Hostobj { + eof := bio.Boffset(f) + length - start := obj.Boffset(f) - c1 := obj.Bgetc(f) - c2 := obj.Bgetc(f) - c3 := obj.Bgetc(f) - c4 := obj.Bgetc(f) - obj.Bseek(f, start, 0) + start := bio.Boffset(f) + c1 := bio.Bgetc(f) + c2 := bio.Bgetc(f) + c3 := bio.Bgetc(f) + c4 := bio.Bgetc(f) + bio.Bseek(f, start, 0) magic := uint32(c1)<<24 | uint32(c2)<<16 | uint32(c3)<<8 | uint32(c4) if magic == 0x7f454c46 { // \x7F E L F @@ -1289,12 +1290,8 @@ func ldobj(f *obj.Biobuf, pkg string, length int64, pn string, file string, when } /* check the header */ - line := obj.Brdline(f, '\n') + line := bio.Brdline(f, '\n') if line == "" { - if obj.Blinelen(f) > 0 { - Diag("%s: not an object file", pn) - return nil - } Diag("truncated object file: %s", pn) return nil } @@ -1337,28 +1334,28 @@ func ldobj(f *obj.Biobuf, pkg string, length int64, pn string, file string, when } /* skip over exports and other info -- ends with \n!\n */ - import0 := obj.Boffset(f) + import0 := bio.Boffset(f) c1 = '\n' // the last line ended in \n - c2 = obj.Bgetc(f) - c3 = obj.Bgetc(f) + c2 = bio.Bgetc(f) + c3 = bio.Bgetc(f) for c1 != '\n' || c2 != '!' || c3 != '\n' { c1 = c2 c2 = c3 - c3 = obj.Bgetc(f) - if c3 == obj.Beof { + c3 = bio.Bgetc(f) + if c3 == bio.EOF { Diag("truncated object file: %s", pn) return nil } } - import1 := obj.Boffset(f) + import1 := bio.Boffset(f) - obj.Bseek(f, import0, 0) + bio.Bseek(f, import0, 0) ldpkg(f, pkg, import1-import0-2, pn, whence) // -2 for !\n - obj.Bseek(f, import1, 0) + bio.Bseek(f, import1, 0) - LoadObjFile(Ctxt, f, pkg, eof-obj.Boffset(f), pn) + LoadObjFile(Ctxt, f, pkg, eof-bio.Boffset(f), pn) return nil } diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index f0811389d2..d3f9ed3703 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -31,7 +31,7 @@ package ld import ( - "cmd/internal/obj" + "cmd/internal/bio" "cmd/internal/sys" "debug/elf" "fmt" @@ -165,7 +165,7 @@ type Link struct { Headtype int Arch *sys.Arch Debugvlog int32 - Bso *obj.Biobuf + Bso *bio.Buf Windows int32 Goroot string diff --git a/src/cmd/link/internal/ld/objfile.go b/src/cmd/link/internal/ld/objfile.go index 8a406d17a6..6f177861f0 100644 --- a/src/cmd/link/internal/ld/objfile.go +++ b/src/cmd/link/internal/ld/objfile.go @@ -110,6 +110,7 @@ package ld import ( "bufio" "bytes" + "cmd/internal/bio" "cmd/internal/obj" "io" "log" @@ -146,8 +147,8 @@ type objReader struct { file []*LSym } -func LoadObjFile(ctxt *Link, f *obj.Biobuf, pkg string, length int64, pn string) { - start := obj.Boffset(f) +func LoadObjFile(ctxt *Link, f *bio.Buf, pkg string, length int64, pn string) { + start := bio.Boffset(f) r := &objReader{ rd: f.Reader(), pkg: pkg, @@ -156,8 +157,8 @@ func LoadObjFile(ctxt *Link, f *obj.Biobuf, pkg string, length int64, pn string) dupSym: &LSym{Name: ".dup"}, } r.loadObjFile() - if obj.Boffset(f) != start+length { - log.Fatalf("%s: unexpected end at %d, want %d", pn, int64(obj.Boffset(f)), int64(start+length)) + if bio.Boffset(f) != start+length { + log.Fatalf("%s: unexpected end at %d, want %d", pn, int64(bio.Boffset(f)), int64(start+length)) } } diff --git a/src/cmd/link/internal/ld/pobj.go b/src/cmd/link/internal/ld/pobj.go index b9902a5e5e..bb48f13185 100644 --- a/src/cmd/link/internal/ld/pobj.go +++ b/src/cmd/link/internal/ld/pobj.go @@ -31,6 +31,7 @@ package ld import ( + "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/sys" "flag" @@ -49,7 +50,7 @@ func Ldmain() { Ctxt.Diag = Diag Ctxt.Bso = &Bso - Bso = *obj.Binitw(os.Stdout) + Bso = *bio.BufWriter(os.Stdout) Debug = [128]int{} nerrors = 0 outfile = "" -- cgit v1.3 From 438ce713a1c8e4d24aea547b6fcf907b5dbf0bec Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 6 Apr 2016 23:34:32 -0700 Subject: cmd/link/internal/amd64: remove empty source file Change-Id: I2da012ed996c669db513a462f014c6f3ffa396ee Reviewed-on: https://go-review.googlesource.com/21646 Reviewed-by: Mikio Hara --- src/cmd/link/internal/amd64/z.go | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/cmd/link/internal/amd64/z.go (limited to 'src/cmd') diff --git a/src/cmd/link/internal/amd64/z.go b/src/cmd/link/internal/amd64/z.go deleted file mode 100644 index f70035b9e3..0000000000 --- a/src/cmd/link/internal/amd64/z.go +++ /dev/null @@ -1 +0,0 @@ -package amd64 -- cgit v1.3 From 8448d3aace7f26bd6eca14e8b89c5a981c2ab9d3 Mon Sep 17 00:00:00 2001 From: Alexandru Moșoi Date: Tue, 5 Apr 2016 23:32:49 +0200 Subject: cmd/compile: fold CMPconst and SHR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fold the comparison when the SHR result is small. Useful for: - murmur mix like hashing where higher bits are desirable, i.e. hash = uint32(i * C) >> 18 - integer log2 via DeBruijn sequence: http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn Change-Id: If70ae18cb86f4cc83ab6213f88ced03cc4986156 Reviewed-on: https://go-review.googlesource.com/21514 Run-TryBot: Alexandru Moșoi TryBot-Result: Gobot Gobot Reviewed-by: David Chase --- src/cmd/compile/internal/ssa/gen/AMD64.rules | 2 ++ src/cmd/compile/internal/ssa/rewriteAMD64.go | 32 ++++++++++++++++++++++++++++ test/checkbce.go | 12 +++++++++++ 3 files changed, 46 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index b37720eb39..d7f361dc2e 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -1101,6 +1101,8 @@ (CMPQconst (MOVBQZX _) [c]) && 0xFF < c -> (FlagLT_ULT) (CMPQconst (MOVWQZX _) [c]) && 0xFFFF < c -> (FlagLT_ULT) (CMPQconst (MOVLQZX _) [c]) && 0xFFFFFFFF < c -> (FlagLT_ULT) +(CMPLconst (SHRLconst _ [c]) [n]) && 0 <= n && 0 < c && c <= 32 && (1< (FlagLT_ULT) +(CMPQconst (SHRQconst _ [c]) [n]) && 0 <= n && 0 < c && c <= 64 && (1< (FlagLT_ULT) (CMPQconst (ANDQconst _ [m]) [n]) && 0 <= m && m < n -> (FlagLT_ULT) (CMPLconst (ANDLconst _ [m]) [n]) && 0 <= int32(m) && int32(m) < int32(n) -> (FlagLT_ULT) (CMPWconst (ANDWconst _ [m]) [n]) && 0 <= int16(m) && int16(m) < int16(n) -> (FlagLT_ULT) diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index a1d1e4edd9..34a393bbc5 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -2869,6 +2869,22 @@ func rewriteValueAMD64_OpAMD64CMPLconst(v *Value, config *Config) bool { v.reset(OpAMD64FlagGT_UGT) return true } + // match: (CMPLconst (SHRLconst _ [c]) [n]) + // cond: 0 <= n && 0 < c && c <= 32 && (1<>27]) + useInt(b[uint64(i*0x07C4ACDD)>>58]) + useInt(a[uint(i*0x07C4ACDD)>>59]) + + // The following bounds should removed as they can overflow. + useInt(a[uint32(i*0x106297f105d0cc86)>>26]) // ERROR "Found IsInBounds$" + useInt(b[uint64(i*0x106297f105d0cc86)>>57]) // ERROR "Found IsInBounds$" + useInt(a[int32(i*0x106297f105d0cc86)>>26]) // ERROR "Found IsInBounds$" + useInt(b[int64(i*0x106297f105d0cc86)>>57]) // ERROR "Found IsInBounds$" +} + func g1(a []int) { for i := range a { a[i] = i -- cgit v1.3 From e6181eb9e1dc4ab9e297a102ed192997582ac46c Mon Sep 17 00:00:00 2001 From: David Crawshaw Date: Thu, 7 Apr 2016 14:00:00 -0400 Subject: cmd/link: disable DWARF when not generating symtab Fixes #15166 Change-Id: I30284e3c0fb2c80b26a2572e2fb249b8018e85f9 Reviewed-on: https://go-review.googlesource.com/21587 Run-TryBot: David Crawshaw Reviewed-by: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/cmd/link/internal/ld/dwarf.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 230d146877..db8961676c 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -1915,6 +1915,9 @@ func dwarfgeneratedebugsyms() { if Debug['w'] != 0 { // disable dwarf return } + if Debug['s'] != 0 && HEADTYPE != obj.Hdarwin { + return + } if HEADTYPE == obj.Hplan9 { return } -- cgit v1.3 From 9658b7ef83ae9c34f4a52680e7102d958577d5bb Mon Sep 17 00:00:00 2001 From: David Crawshaw Date: Thu, 7 Apr 2016 14:27:15 -0400 Subject: cmd/link: hide go.dwarf symbols Fixes #15179 Change-Id: I0f70b7ae1682eafaece7f22d8e76f0aa806f3ec9 Reviewed-on: https://go-review.googlesource.com/21589 Run-TryBot: David Crawshaw Reviewed-by: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/cmd/link/internal/ld/dwarf.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index db8961676c..4741020a6d 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -509,6 +509,7 @@ func newdie(parent *DWDie, abbrev int, name string, version int) *DWDie { if name != "" && (abbrev <= DW_ABRV_VARIABLE || abbrev >= DW_ABRV_NULLTYPE) { if abbrev != DW_ABRV_VARIABLE || version == 0 { die.sym = Linklookup(Ctxt, infoprefix+name, version) + die.sym.Attr |= AttrHidden die.sym.Type = obj.SDWARFINFO } } @@ -814,6 +815,7 @@ func dotypedef(parent *DWDie, name string, def *DWDie) { } def.sym = Linklookup(Ctxt, def.sym.Name+".def", 0) + def.sym.Attr |= AttrHidden def.sym.Type = obj.SDWARFINFO // The typedef entry must be created after the def, -- cgit v1.3 From dfded57819dd9111afffc25360cd3e147859d354 Mon Sep 17 00:00:00 2001 From: Cheng-Lung Sung Date: Wed, 6 Apr 2016 23:05:20 +0800 Subject: cmd/go: revise importPath when ImportPath is 'command-line-arguments' Fixes #14613 Change-Id: I40d9696db3879549e78373ef17f6c92bd4b3470b Reviewed-on: https://go-review.googlesource.com/21596 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/cmd/go/pkg.go | 10 ++++++++-- src/cmd/go/vendor_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go index fa923c8873..30ef02beff 100644 --- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -434,6 +434,12 @@ func vendoredImportPath(parent *Package, path string) (found string) { } targ := filepath.Join(dir[:i], vpath) if isDir(targ) && hasGoFiles(targ) { + importPath := parent.ImportPath + if importPath == "command-line-arguments" { + // If parent.ImportPath is 'command-line-arguments'. + // set to relative directory to root (also chopped root directory) + importPath = dir[len(root)+1:] + } // We started with parent's dir c:\gopath\src\foo\bar\baz\quux\xyzzy. // We know the import path for parent's dir. // We chopped off some number of path elements and @@ -443,14 +449,14 @@ func vendoredImportPath(parent *Package, path string) (found string) { // (actually the same number of bytes) from parent's import path // and then append /vendor/path. chopped := len(dir) - i - if chopped == len(parent.ImportPath)+1 { + if chopped == len(importPath)+1 { // We walked up from c:\gopath\src\foo\bar // and found c:\gopath\src\vendor\path. // We chopped \foo\bar (length 8) but the import path is "foo/bar" (length 7). // Use "vendor/path" without any prefix. return vpath } - return parent.ImportPath[:len(parent.ImportPath)-chopped] + "/" + vpath + return importPath[:len(importPath)-chopped] + "/" + vpath } } return path diff --git a/src/cmd/go/vendor_test.go b/src/cmd/go/vendor_test.go index 199eab4471..bcb5082311 100644 --- a/src/cmd/go/vendor_test.go +++ b/src/cmd/go/vendor_test.go @@ -232,6 +232,32 @@ func TestVendorTest2(t *testing.T) { tg.run("test", "github.com/rsc/go-get-issue-11864/vendor/vendor.org/tx2") } +func TestVendorTest3(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + + tg := testgo(t) + defer tg.cleanup() + tg.makeTempdir() + tg.setenv("GOPATH", tg.path(".")) + tg.run("get", "github.com/clsung/go-vendor-issue-14613") + + tg.run("build", "-i", "github.com/clsung/go-vendor-issue-14613") + + // test folder should work + tg.run("test", "-i", "github.com/clsung/go-vendor-issue-14613") + tg.run("test", "github.com/clsung/go-vendor-issue-14613") + + // test with specified _test.go should work too + tg.cd(filepath.Join(tg.path("."), "src")) + tg.run("test", "-i", "github.com/clsung/go-vendor-issue-14613/vendor_test.go") + tg.run("test", "github.com/clsung/go-vendor-issue-14613/vendor_test.go") + + // test with imported and not used + tg.run("test", "-i", "github.com/clsung/go-vendor-issue-14613/vendor/mylibtesttest/myapp/myapp_test.go") + tg.runFail("test", "github.com/clsung/go-vendor-issue-14613/vendor/mylibtesttest/myapp/myapp_test.go") + tg.grepStderr("imported and not used:", `should say "imported and not used"`) +} + func TestVendorList(t *testing.T) { testenv.MustHaveExternalNetwork(t) -- cgit v1.3 From 68ac1f774624faf99e7f6ec6592acb50f23b7874 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 7 Apr 2016 10:21:35 -0700 Subject: cmd/compile: Fix constant-folding of unsigned shifts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure the results of unsigned constant-folded shifts are sign-extended into the AuxInt field. Fixes #15175 Change-Id: I3490d1bc3d9b2e1578ed30964645508577894f58 Reviewed-on: https://go-review.googlesource.com/21586 Reviewed-by: Alexandru Moșoi Run-TryBot: Keith Randall TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/ssa/gen/generic.rules | 6 +-- src/cmd/compile/internal/ssa/rewritegeneric.go | 12 ++--- test/fixedbugs/issue15175.go | 66 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 test/fixedbugs/issue15175.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/gen/generic.rules b/src/cmd/compile/internal/ssa/gen/generic.rules index b56e3f1b2d..dacc2007c8 100644 --- a/src/cmd/compile/internal/ssa/gen/generic.rules +++ b/src/cmd/compile/internal/ssa/gen/generic.rules @@ -81,13 +81,13 @@ (Rsh64Ux64 (Const64 [c]) (Const64 [d])) -> (Const64 [int64(uint64(c) >> uint64(d))]) (Lsh32x64 (Const32 [c]) (Const64 [d])) -> (Const32 [int64(int32(c) << uint64(d))]) (Rsh32x64 (Const32 [c]) (Const64 [d])) -> (Const32 [int64(int32(c) >> uint64(d))]) -(Rsh32Ux64 (Const32 [c]) (Const64 [d])) -> (Const32 [int64(uint32(c) >> uint64(d))]) +(Rsh32Ux64 (Const32 [c]) (Const64 [d])) -> (Const32 [int64(int32(uint32(c) >> uint64(d)))]) (Lsh16x64 (Const16 [c]) (Const64 [d])) -> (Const16 [int64(int16(c) << uint64(d))]) (Rsh16x64 (Const16 [c]) (Const64 [d])) -> (Const16 [int64(int16(c) >> uint64(d))]) -(Rsh16Ux64 (Const16 [c]) (Const64 [d])) -> (Const16 [int64(uint16(c) >> uint64(d))]) +(Rsh16Ux64 (Const16 [c]) (Const64 [d])) -> (Const16 [int64(int16(uint16(c) >> uint64(d)))]) (Lsh8x64 (Const8 [c]) (Const64 [d])) -> (Const8 [int64(int8(c) << uint64(d))]) (Rsh8x64 (Const8 [c]) (Const64 [d])) -> (Const8 [int64(int8(c) >> uint64(d))]) -(Rsh8Ux64 (Const8 [c]) (Const64 [d])) -> (Const8 [int64(uint8(c) >> uint64(d))]) +(Rsh8Ux64 (Const8 [c]) (Const64 [d])) -> (Const8 [int64(int8(uint8(c) >> uint64(d)))]) (Lsh64x64 (Const64 [0]) _) -> (Const64 [0]) (Rsh64x64 (Const64 [0]) _) -> (Const64 [0]) diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index 932cb42235..9b0f43c414 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -6185,7 +6185,7 @@ func rewriteValuegeneric_OpRsh16Ux64(v *Value, config *Config) bool { _ = b // match: (Rsh16Ux64 (Const16 [c]) (Const64 [d])) // cond: - // result: (Const16 [int64(uint16(c) >> uint64(d))]) + // result: (Const16 [int64(int16(uint16(c) >> uint64(d)))]) for { v_0 := v.Args[0] if v_0.Op != OpConst16 { @@ -6198,7 +6198,7 @@ func rewriteValuegeneric_OpRsh16Ux64(v *Value, config *Config) bool { } d := v_1.AuxInt v.reset(OpConst16) - v.AuxInt = int64(uint16(c) >> uint64(d)) + v.AuxInt = int64(int16(uint16(c) >> uint64(d))) return true } // match: (Rsh16Ux64 (Const16 [0]) _) @@ -6547,7 +6547,7 @@ func rewriteValuegeneric_OpRsh32Ux64(v *Value, config *Config) bool { _ = b // match: (Rsh32Ux64 (Const32 [c]) (Const64 [d])) // cond: - // result: (Const32 [int64(uint32(c) >> uint64(d))]) + // result: (Const32 [int64(int32(uint32(c) >> uint64(d)))]) for { v_0 := v.Args[0] if v_0.Op != OpConst32 { @@ -6560,7 +6560,7 @@ func rewriteValuegeneric_OpRsh32Ux64(v *Value, config *Config) bool { } d := v_1.AuxInt v.reset(OpConst32) - v.AuxInt = int64(uint32(c) >> uint64(d)) + v.AuxInt = int64(int32(uint32(c) >> uint64(d))) return true } // match: (Rsh32Ux64 (Const32 [0]) _) @@ -7353,7 +7353,7 @@ func rewriteValuegeneric_OpRsh8Ux64(v *Value, config *Config) bool { _ = b // match: (Rsh8Ux64 (Const8 [c]) (Const64 [d])) // cond: - // result: (Const8 [int64(uint8(c) >> uint64(d))]) + // result: (Const8 [int64(int8(uint8(c) >> uint64(d)))]) for { v_0 := v.Args[0] if v_0.Op != OpConst8 { @@ -7366,7 +7366,7 @@ func rewriteValuegeneric_OpRsh8Ux64(v *Value, config *Config) bool { } d := v_1.AuxInt v.reset(OpConst8) - v.AuxInt = int64(uint8(c) >> uint64(d)) + v.AuxInt = int64(int8(uint8(c) >> uint64(d))) return true } // match: (Rsh8Ux64 (Const8 [0]) _) diff --git a/test/fixedbugs/issue15175.go b/test/fixedbugs/issue15175.go new file mode 100644 index 0000000000..c6cab532f8 --- /dev/null +++ b/test/fixedbugs/issue15175.go @@ -0,0 +1,66 @@ +// run + +// 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. + +// Make sure unsigned shift results get sign-extended correctly. +package main + +import "fmt" + +func main() { + failed := false + a6 := uint8(253) + if got := a6 >> 0; got != 253 { + fmt.Printf("uint8(253)>>0 = %v, wanted 253\n", got) + failed = true + } + if got := f1(0, 2, 1, 0, 0, 1, true); got != 255 { + fmt.Printf("f1(...) = %v, wanted 255\n", got) + failed = true + } + if got := f2(1); got != 242 { + fmt.Printf("f2(...) = %v, wanted 242\n", got) + failed = true + } + if got := f3(false, 0, 0); got != 254 { + fmt.Printf("f3(...) = %v, wanted 254\n", got) + failed = true + } + if failed { + panic("bad") + } +} + +func f1(a1 uint, a2 int8, a3 int8, a4 int8, a5 uint8, a6 int, a7 bool) uint8 { + a5-- + a4 += (a2 << a1 << 2) | (a4 ^ a4<<(a1&a1)) - a3 // int8 + a6 -= a6 >> (2 + uint32(a2)>>3) // int + a1 += a1 // uint + a3 *= a4 << (a1 | a1) << (uint16(3) >> 2 & (1 - 0) & (uint16(1) << a5 << 3)) // int8 + a7 = a7 || ((a2 == a4) || (a7 && a7) || ((a5 == a5) || (a7 || a7))) // bool + return a5 >> a1 +} + +func f2(a1 uint8) uint8 { + a1-- + a1-- + a1 -= a1 + (a1 << 1) - (a1*a1*a1)<<(2-0+(3|3)-1) // uint8 + v1 := 0 * ((2 * 1) ^ 1) & ((uint(0) >> a1) + (2+0)*(uint(2)+0)) // uint + _ = v1 + return a1 >> (((2 ^ 2) >> (v1 | 2)) + 0) +} + +func f3(a1 bool, a2 uint, a3 int64) uint8 { + a3-- + v1 := 1 & (2 & 1 * (1 ^ 2) & (uint8(3*1) >> 0)) // uint8 + _ = v1 + v1 += v1 - (v1 >> a2) + (v1 << (a2 ^ a2) & v1) // uint8 + v1 *= v1 // uint8 + a3-- + v1 += v1 & v1 // uint8 + v1-- + v1 = ((v1 << 0) | v1>>0) + v1 // uint8 + return v1 >> 0 +} -- cgit v1.3 From 4dae828f77a37ed87401f7877998b241f0d2c33e Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Fri, 8 Apr 2016 15:09:45 +1000 Subject: cmd/go: fix failing tests since vet was moved from x/tools Change-Id: I3276a118ced78f3efd8f1bc5fb8b8fa2fde52496 Reviewed-on: https://go-review.googlesource.com/21704 Reviewed-by: Brad Fitzpatrick --- src/cmd/go/go_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 3e595d187f..8a0416089c 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -2320,8 +2320,7 @@ func TestGoVetWithExternalTests(t *testing.T) { tg := testgo(t) defer tg.cleanup() tg.makeTempdir() - tg.setenv("GOPATH", tg.path(".")) - tg.run("get", "golang.org/x/tools/cmd/vet") + tg.run("install", "cmd/vet") tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata")) tg.runFail("vet", "vetpkg") tg.grepBoth("missing argument for Printf", "go vet vetpkg did not find missing argument for Printf") @@ -2333,8 +2332,7 @@ func TestGoVetWithTags(t *testing.T) { tg := testgo(t) defer tg.cleanup() tg.makeTempdir() - tg.setenv("GOPATH", tg.path(".")) - tg.run("get", "golang.org/x/tools/cmd/vet") + tg.run("install", "cmd/vet") tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata")) tg.runFail("vet", "-tags", "tagtest", "vetpkg") tg.grepBoth(`c\.go.*wrong number of args for format`, "go get vetpkg did not run scan tagged file") -- cgit v1.3 From 49e07f2b7e25a1f7a050f73fbb7807185e09e46b Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Fri, 8 Apr 2016 20:09:10 +1000 Subject: cmd/compile/internal/gc: unexport Export Export does not need to be exported. Change-Id: I252f0c024732f1d056817cab13e8e3c589b54d13 Reviewed-on: https://go-review.googlesource.com/21721 Run-TryBot: Dave Cheney TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/cmd/compile/internal/gc/bexport.go | 4 ++-- src/cmd/compile/internal/gc/export.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index 702090280f..909ff14982 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -135,8 +135,8 @@ type exporter struct { trace bool } -// Export writes the exportlist for localpkg to out and returns the number of bytes written. -func Export(out *bio.Buf, trace bool) int { +// export writes the exportlist for localpkg to out and returns the number of bytes written. +func export(out *bio.Buf, trace bool) int { p := exporter{ out: out, pkgIndex: make(map[*Pkg]int), diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index 5d4add8ff4..2f94b9c62f 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -385,7 +385,7 @@ func dumpexport() { // save a copy of the export data var copy bytes.Buffer bcopy := bio.BufWriter(©) - size = Export(bcopy, Debug_export != 0) + size = export(bcopy, Debug_export != 0) bcopy.Flush() // flushing to bytes.Buffer cannot fail if n, err := bout.Write(copy.Bytes()); n != size || err != nil { Fatalf("error writing export data: got %d bytes, want %d bytes, err = %v", n, size, err) @@ -407,7 +407,7 @@ func dumpexport() { pkgs = savedPkgs pkgMap = savedPkgMap } else { - size = Export(bout, Debug_export != 0) + size = export(bout, Debug_export != 0) } exportf("\n$$\n") } else { -- cgit v1.3 From d22357ce9dc650a69e78b37a6b25be1ee0b8b26c Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Thu, 7 Apr 2016 15:31:49 -0400 Subject: cmd/compile: cleanup -dynlink/-shared support check Moves the list of architectures that support shared libraries into a function. Also adds s390x to that list. Change-Id: I99c8a9f6cd4816ce3d53abaabaf8d002e25e6b28 Reviewed-on: https://go-review.googlesource.com/21661 Reviewed-by: Matthew Dempsky Reviewed-by: Michael Hudson-Doyle Run-TryBot: Michael Munday --- src/cmd/compile/internal/gc/main.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index c8a778c34a..03143f5d0a 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -91,6 +91,12 @@ func doversion() { os.Exit(0) } +// supportsDynlink reports whether or not the code generator for the given +// architecture supports the -shared and -dynlink flags. +func supportsDynlink(arch *sys.Arch) bool { + return arch.InFamily(sys.AMD64, sys.ARM, sys.ARM64, sys.I386, sys.PPC64, sys.S390X) +} + func Main() { defer hidePanic() @@ -195,15 +201,13 @@ func Main() { obj.Flagcount("y", "debug declarations in canned imports (with -d)", &Debug['y']) var flag_shared int var flag_dynlink bool - if Thearch.LinkArch.InFamily(sys.ARM, sys.AMD64, sys.ARM64, sys.I386, sys.PPC64) { + if supportsDynlink(Thearch.LinkArch.Arch) { obj.Flagcount("shared", "generate code that can be linked into a shared library", &flag_shared) + flag.BoolVar(&flag_dynlink, "dynlink", false, "support references to Go symbols defined in other shared libraries") } if Thearch.LinkArch.Family == sys.AMD64 { obj.Flagcount("largemodel", "generate code that assumes a large memory model", &flag_largemodel) } - if Thearch.LinkArch.InFamily(sys.ARM, sys.AMD64, sys.ARM64, sys.I386, sys.PPC64) { - flag.BoolVar(&flag_dynlink, "dynlink", false, "support references to Go symbols defined in other shared libraries") - } obj.Flagstr("cpuprofile", "write cpu profile to `file`", &cpuprofile) obj.Flagstr("memprofile", "write memory profile to `file`", &memprofile) obj.Flagint64("memprofilerate", "set runtime.MemProfileRate to `rate`", &memprofilerate) -- cgit v1.3 From 8f2edf11998a30b497586ac0e9f75036a318280a Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Fri, 8 Apr 2016 19:14:03 +1000 Subject: cmd: replace bio.Buf with bio.Reader and bio.Writer Replace the bidirectional bio.Buf type with a pair of unidirectional buffered seekable Reader and Writers. Change-Id: I86664a06f93c94595dc67c2cbd21356feb6680ef Reviewed-on: https://go-review.googlesource.com/21720 Reviewed-by: Brad Fitzpatrick Run-TryBot: Dave Cheney TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/gc/bexport.go | 4 +- src/cmd/compile/internal/gc/go.go | 4 +- src/cmd/compile/internal/gc/obj.go | 16 ++-- src/cmd/internal/bio/buf.go | 137 +++++++++++++++++++-------------- src/cmd/internal/obj/link.go | 2 +- src/cmd/internal/obj/objfile.go | 6 +- src/cmd/link/internal/ld/ar.go | 6 +- src/cmd/link/internal/ld/go.go | 2 +- src/cmd/link/internal/ld/ldelf.go | 10 +-- src/cmd/link/internal/ld/ldmacho.go | 16 ++-- src/cmd/link/internal/ld/ldpe.go | 18 ++--- src/cmd/link/internal/ld/lib.go | 49 ++++++------ src/cmd/link/internal/ld/link.go | 7 +- src/cmd/link/internal/ld/objfile.go | 8 +- 14 files changed, 154 insertions(+), 131 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index 909ff14982..bb0a34e67b 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -124,7 +124,7 @@ const exportVersion = "v0" const exportInlined = true // default: true type exporter struct { - out *bio.Buf + out *bio.Writer pkgIndex map[*Pkg]int typIndex map[*Type]int inlined []*Func @@ -136,7 +136,7 @@ type exporter struct { } // export writes the exportlist for localpkg to out and returns the number of bytes written. -func export(out *bio.Buf, trace bool) int { +func export(out *bio.Writer, trace bool) int { p := exporter{ out: out, pkgIndex: make(map[*Pkg]int), diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index cd9db38fb4..ec7e219d95 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -133,7 +133,7 @@ var infile string var outfile string -var bout *bio.Buf +var bout *bio.Writer var nerrors int @@ -288,7 +288,7 @@ var Ctxt *obj.Link var writearchive int -var bstdout *bio.Buf +var bstdout *bio.Writer var Nacl bool diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index 3920e25224..23c8be645c 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -37,7 +37,7 @@ func dumpobj() { bout.WriteString("!\n") arhdr = [ArhdrSize]byte{} bout.Write(arhdr[:]) - startobj = bio.Boffset(bout) + startobj = bout.Offset() } fmt.Fprintf(bout, "go object %s %s %s %s\n", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion(), obj.Expstring()) @@ -45,19 +45,19 @@ func dumpobj() { if writearchive != 0 { bout.Flush() - size := bio.Boffset(bout) - startobj + size := bout.Offset() - startobj if size&1 != 0 { bout.WriteByte(0) } - bio.Bseek(bout, startobj-ArhdrSize, 0) + bout.Seek(startobj-ArhdrSize, 0) formathdr(arhdr[:], "__.PKGDEF", size) bout.Write(arhdr[:]) bout.Flush() - bio.Bseek(bout, startobj+size+(size&1), 0) + bout.Seek(startobj+size+(size&1), 0) arhdr = [ArhdrSize]byte{} bout.Write(arhdr[:]) - startobj = bio.Boffset(bout) + startobj = bout.Offset() fmt.Fprintf(bout, "go object %s %s %s %s\n", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion(), obj.Expstring()) } @@ -92,11 +92,11 @@ func dumpobj() { if writearchive != 0 { bout.Flush() - size := bio.Boffset(bout) - startobj + size := bout.Offset() - startobj if size&1 != 0 { bout.WriteByte(0) } - bio.Bseek(bout, startobj-ArhdrSize, 0) + bout.Seek(startobj-ArhdrSize, 0) formathdr(arhdr[:], "_go_.o", size) bout.Write(arhdr[:]) } @@ -133,7 +133,7 @@ func dumpglobls() { funcsyms = nil } -func Bputname(b *bio.Buf, s *obj.LSym) { +func Bputname(b *bio.Writer, s *obj.LSym) { b.WriteString(s.Name) b.WriteByte(0) } diff --git a/src/cmd/internal/bio/buf.go b/src/cmd/internal/bio/buf.go index a1df26ca9c..0bd4658cdd 100644 --- a/src/cmd/internal/bio/buf.go +++ b/src/cmd/internal/bio/buf.go @@ -14,94 +14,116 @@ import ( const EOF = -1 -// Buf implements a seekable buffered I/O abstraction. -type Buf struct { +// Reader implements a seekable buffered io.Reader. +type Reader struct { f *os.File r *bufio.Reader +} + +// Writer implements a seekable buffered io.Writer. +type Writer struct { + f *os.File w *bufio.Writer } -func (b *Buf) Reader() *bufio.Reader { return b.r } -func (b *Buf) Writer() *bufio.Writer { return b.w } +// Reader returns this Reader's underlying bufio.Reader. +func (r *Reader) Reader() *bufio.Reader { return r.r } -func Create(name string) (*Buf, error) { +// Writer returns this Writer's underlying bufio.Writer. +func (w *Writer) Writer() *bufio.Writer { return w.w } + +// Create creates the file named name and returns a Writer +// for that file. +func Create(name string) (*Writer, error) { f, err := os.Create(name) if err != nil { return nil, err } - return &Buf{f: f, w: bufio.NewWriter(f)}, nil + return &Writer{f: f, w: bufio.NewWriter(f)}, nil } -func Open(name string) (*Buf, error) { +// Open returns a Reader for the file named name. +func Open(name string) (*Reader, error) { f, err := os.Open(name) if err != nil { return nil, err } - return &Buf{f: f, r: bufio.NewReader(f)}, nil + return &Reader{f: f, r: bufio.NewReader(f)}, nil } -func BufWriter(w io.Writer) *Buf { - return &Buf{w: bufio.NewWriter(w)} +// BufWriter returns a Writer on top of w. +// TODO(dfc) remove this method and replace caller with bufio.Writer. +func BufWriter(w io.Writer) *Writer { + return &Writer{w: bufio.NewWriter(w)} } -func BufReader(r io.Reader) *Buf { - return &Buf{r: bufio.NewReader(r)} +// BufWriter returns a Reader on top of r. +// TODO(dfc) remove this method and replace caller with bufio.Reader. +func BufReader(r io.Reader) *Reader { + return &Reader{r: bufio.NewReader(r)} } -func (b *Buf) Write(p []byte) (int, error) { - return b.w.Write(p) +func (w *Writer) Write(p []byte) (int, error) { + return w.w.Write(p) } -func (b *Buf) WriteString(p string) (int, error) { - return b.w.WriteString(p) +func (w *Writer) WriteString(p string) (int, error) { + return w.w.WriteString(p) } -func Bseek(b *Buf, offset int64, whence int) int64 { - if b.w != nil { - if err := b.w.Flush(); err != nil { - log.Fatalf("writing output: %v", err) - } - } else if b.r != nil { - if whence == 1 { - offset -= int64(b.r.Buffered()) - } +func (r *Reader) Seek(offset int64, whence int) int64 { + if whence == 1 { + offset -= int64(r.r.Buffered()) } - off, err := b.f.Seek(offset, whence) + off, err := r.f.Seek(offset, whence) if err != nil { log.Fatalf("seeking in output: %v", err) } - if b.r != nil { - b.r.Reset(b.f) - } + r.r.Reset(r.f) return off } -func Boffset(b *Buf) int64 { - if b.w != nil { - if err := b.w.Flush(); err != nil { - log.Fatalf("writing output: %v", err) - } +func (w *Writer) Seek(offset int64, whence int) int64 { + if err := w.w.Flush(); err != nil { + log.Fatalf("writing output: %v", err) } - off, err := b.f.Seek(0, 1) + off, err := w.f.Seek(offset, whence) + if err != nil { + log.Fatalf("seeking in output: %v", err) + } + return off +} + +func (r *Reader) Offset() int64 { + off, err := r.f.Seek(0, 1) if err != nil { log.Fatalf("seeking in output [0, 1]: %v", err) } - if b.r != nil { - off -= int64(b.r.Buffered()) + off -= int64(r.r.Buffered()) + return off +} + +func (w *Writer) Offset() int64 { + if err := w.w.Flush(); err != nil { + log.Fatalf("writing output: %v", err) + } + off, err := w.f.Seek(0, 1) + if err != nil { + log.Fatalf("seeking in output [0, 1]: %v", err) } return off } -func (b *Buf) Flush() error { - return b.w.Flush() +func (w *Writer) Flush() error { + return w.w.Flush() } -func (b *Buf) WriteByte(c byte) error { - return b.w.WriteByte(c) +func (w *Writer) WriteByte(c byte) error { + return w.w.WriteByte(c) } -func Bread(b *Buf, p []byte) int { - n, err := io.ReadFull(b.r, p) +func Bread(r *Reader, p []byte) int { + n, err := io.ReadFull(r.r, p) if n == 0 { if err != nil && err != io.EOF { n = -1 @@ -110,8 +132,8 @@ func Bread(b *Buf, p []byte) int { return n } -func Bgetc(b *Buf) int { - c, err := b.r.ReadByte() +func Bgetc(r *Reader) int { + c, err := r.r.ReadByte() if err != nil { if err != io.EOF { log.Fatalf("reading input: %v", err) @@ -121,28 +143,29 @@ func Bgetc(b *Buf) int { return int(c) } -func (b *Buf) Read(p []byte) (int, error) { - return b.r.Read(p) +func (r *Reader) Read(p []byte) (int, error) { + return r.r.Read(p) } -func (b *Buf) Peek(n int) ([]byte, error) { - return b.r.Peek(n) +func (r *Reader) Peek(n int) ([]byte, error) { + return r.r.Peek(n) } -func Brdline(b *Buf, delim int) string { - s, err := b.r.ReadBytes(byte(delim)) +func Brdline(r *Reader, delim int) string { + s, err := r.r.ReadBytes(byte(delim)) if err != nil { log.Fatalf("reading input: %v", err) } return string(s) } -func (b *Buf) Close() error { - var err error - if b.w != nil { - err = b.w.Flush() - } - err1 := b.f.Close() +func (r *Reader) Close() error { + return r.f.Close() +} + +func (w *Writer) Close() error { + err := w.w.Flush() + err1 := w.f.Close() if err == nil { err = err1 } diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index 2c81ca2f08..c48c3d807f 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -629,7 +629,7 @@ type Link struct { Flag_shared int32 Flag_dynlink bool Flag_optimize bool - Bso *bio.Buf + Bso *bio.Writer Pathname string Goroot string Goroot_final string diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index 405cbf446a..ed6d75eba3 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -121,7 +121,7 @@ import ( // The Go and C compilers, and the assembler, call writeobj to write // out a Go object file. The linker does not call this; the linker // does not write out object files. -func Writeobjdirect(ctxt *Link, b *bio.Buf) { +func Writeobjdirect(ctxt *Link, b *bio.Writer) { Flushplist(ctxt) WriteObjFile(ctxt, b) } @@ -374,7 +374,7 @@ func (w *objWriter) writeLengths() { w.writeInt(int64(w.nFile)) } -func newObjWriter(ctxt *Link, b *bio.Buf) *objWriter { +func newObjWriter(ctxt *Link, b *bio.Writer) *objWriter { return &objWriter{ ctxt: ctxt, wr: b.Writer(), @@ -383,7 +383,7 @@ func newObjWriter(ctxt *Link, b *bio.Buf) *objWriter { } } -func WriteObjFile(ctxt *Link, b *bio.Buf) { +func WriteObjFile(ctxt *Link, b *bio.Writer) { w := newObjWriter(ctxt, b) // Magic header diff --git a/src/cmd/link/internal/ld/ar.go b/src/cmd/link/internal/ld/ar.go index 205773c7f8..6a0aeb121f 100644 --- a/src/cmd/link/internal/ld/ar.go +++ b/src/cmd/link/internal/ld/ar.go @@ -82,7 +82,7 @@ func hostArchive(name string) { } var arhdr ArHdr - l := nextar(f, bio.Boffset(f), &arhdr) + l := nextar(f, f.Offset(), &arhdr) if l <= 0 { Exitf("%s missing armap", name) } @@ -118,7 +118,7 @@ func hostArchive(name string) { l = atolwhex(arhdr.size) h := ldobj(f, "libgcc", l, pname, name, ArchiveObj) - bio.Bseek(f, h.off, 0) + f.Seek(h.off, 0) h.ld(f, h.pkg, h.length, h.pn) } @@ -131,7 +131,7 @@ func hostArchive(name string) { type archiveMap map[string]uint64 // readArmap reads the archive symbol map. -func readArmap(filename string, f *bio.Buf, arhdr ArHdr) archiveMap { +func readArmap(filename string, f *bio.Reader, arhdr ArHdr) archiveMap { is64 := arhdr.name == "/SYM64/" wordSize := 4 if is64 { diff --git a/src/cmd/link/internal/ld/go.go b/src/cmd/link/internal/ld/go.go index 8bafaffd7c..5dad90dae6 100644 --- a/src/cmd/link/internal/ld/go.go +++ b/src/cmd/link/internal/ld/go.go @@ -27,7 +27,7 @@ func expandpkg(t0 string, pkg string) string { // once the dust settles, try to move some code to // libmach, so that other linkers and ar can share. -func ldpkg(f *bio.Buf, pkg string, length int64, filename string, whence int) { +func ldpkg(f *bio.Reader, pkg string, length int64, filename string, whence int) { var p0, p1 int if Debug['g'] != 0 { diff --git a/src/cmd/link/internal/ld/ldelf.go b/src/cmd/link/internal/ld/ldelf.go index eafc6930d5..55884c07a2 100644 --- a/src/cmd/link/internal/ld/ldelf.go +++ b/src/cmd/link/internal/ld/ldelf.go @@ -268,7 +268,7 @@ type ElfSect struct { } type ElfObj struct { - f *bio.Buf + f *bio.Reader base int64 // offset in f where ELF begins length int64 // length of ELF is64 int @@ -447,13 +447,13 @@ func parseArmAttributes(e binary.ByteOrder, data []byte) { } } -func ldelf(f *bio.Buf, pkg string, length int64, pn string) { +func ldelf(f *bio.Reader, pkg string, length int64, pn string) { if Debug['v'] != 0 { fmt.Fprintf(&Bso, "%5.2f ldelf %s\n", obj.Cputime(), pn) } Ctxt.IncVersion() - base := int32(bio.Boffset(f)) + base := f.Offset() var add uint64 var e binary.ByteOrder @@ -601,7 +601,7 @@ func ldelf(f *bio.Buf, pkg string, length int64, pn string) { elfobj.nsect = uint(elfobj.shnum) for i := 0; uint(i) < elfobj.nsect; i++ { - if bio.Bseek(f, int64(uint64(base)+elfobj.shoff+uint64(int64(i)*int64(elfobj.shentsize))), 0) < 0 { + if f.Seek(int64(uint64(base)+elfobj.shoff+uint64(int64(i)*int64(elfobj.shentsize))), 0) < 0 { goto bad } sect = &elfobj.sect[i] @@ -987,7 +987,7 @@ func elfmap(elfobj *ElfObj, sect *ElfSect) (err error) { sect.base = make([]byte, sect.size) err = fmt.Errorf("short read") - if bio.Bseek(elfobj.f, int64(uint64(elfobj.base)+sect.off), 0) < 0 || bio.Bread(elfobj.f, sect.base) != len(sect.base) { + if elfobj.f.Seek(int64(uint64(elfobj.base)+sect.off), 0) < 0 || bio.Bread(elfobj.f, sect.base) != len(sect.base) { return err } diff --git a/src/cmd/link/internal/ld/ldmacho.go b/src/cmd/link/internal/ld/ldmacho.go index 6376116d04..dffe6f69ce 100644 --- a/src/cmd/link/internal/ld/ldmacho.go +++ b/src/cmd/link/internal/ld/ldmacho.go @@ -43,7 +43,7 @@ const ( ) type LdMachoObj struct { - f *bio.Buf + f *bio.Reader base int64 // off in f where Mach-O begins length int64 // length of Mach-O is64 bool @@ -299,7 +299,7 @@ func macholoadrel(m *LdMachoObj, sect *LdMachoSect) int { rel := make([]LdMachoRel, sect.nreloc) n := int(sect.nreloc * 8) buf := make([]byte, n) - if bio.Bseek(m.f, m.base+int64(sect.reloff), 0) < 0 || bio.Bread(m.f, buf) != n { + if m.f.Seek(m.base+int64(sect.reloff), 0) < 0 || bio.Bread(m.f, buf) != n { return -1 } var p []byte @@ -345,7 +345,7 @@ func macholoaddsym(m *LdMachoObj, d *LdMachoDysymtab) int { n := int(d.nindirectsyms) p := make([]byte, n*4) - if bio.Bseek(m.f, m.base+int64(d.indirectsymoff), 0) < 0 || bio.Bread(m.f, p) != len(p) { + if m.f.Seek(m.base+int64(d.indirectsymoff), 0) < 0 || bio.Bread(m.f, p) != len(p) { return -1 } @@ -362,7 +362,7 @@ func macholoadsym(m *LdMachoObj, symtab *LdMachoSymtab) int { } strbuf := make([]byte, symtab.strsize) - if bio.Bseek(m.f, m.base+int64(symtab.stroff), 0) < 0 || bio.Bread(m.f, strbuf) != len(strbuf) { + if m.f.Seek(m.base+int64(symtab.stroff), 0) < 0 || bio.Bread(m.f, strbuf) != len(strbuf) { return -1 } @@ -372,7 +372,7 @@ func macholoadsym(m *LdMachoObj, symtab *LdMachoSymtab) int { } n := int(symtab.nsym * uint32(symsize)) symbuf := make([]byte, n) - if bio.Bseek(m.f, m.base+int64(symtab.symoff), 0) < 0 || bio.Bread(m.f, symbuf) != len(symbuf) { + if m.f.Seek(m.base+int64(symtab.symoff), 0) < 0 || bio.Bread(m.f, symbuf) != len(symbuf) { return -1 } sym := make([]LdMachoSym, symtab.nsym) @@ -402,7 +402,7 @@ func macholoadsym(m *LdMachoObj, symtab *LdMachoSymtab) int { return 0 } -func ldmacho(f *bio.Buf, pkg string, length int64, pn string) { +func ldmacho(f *bio.Reader, pkg string, length int64, pn string) { var err error var j int var is64 bool @@ -432,7 +432,7 @@ func ldmacho(f *bio.Buf, pkg string, length int64, pn string) { var name string Ctxt.IncVersion() - base := bio.Boffset(f) + base := f.Offset() if bio.Bread(f, hdr[:]) != len(hdr) { goto bad } @@ -557,7 +557,7 @@ func ldmacho(f *bio.Buf, pkg string, length int64, pn string) { } dat = make([]byte, c.seg.filesz) - if bio.Bseek(f, m.base+int64(c.seg.fileoff), 0) < 0 || bio.Bread(f, dat) != len(dat) { + if f.Seek(m.base+int64(c.seg.fileoff), 0) < 0 || bio.Bread(f, dat) != len(dat) { err = fmt.Errorf("cannot load object data: %v", err) goto bad } diff --git a/src/cmd/link/internal/ld/ldpe.go b/src/cmd/link/internal/ld/ldpe.go index e97e842e7f..ba5b928ea0 100644 --- a/src/cmd/link/internal/ld/ldpe.go +++ b/src/cmd/link/internal/ld/ldpe.go @@ -118,7 +118,7 @@ type PeSect struct { } type PeObj struct { - f *bio.Buf + f *bio.Reader name string base uint32 sect []PeSect @@ -129,14 +129,14 @@ type PeObj struct { snames []byte } -func ldpe(f *bio.Buf, pkg string, length int64, pn string) { +func ldpe(f *bio.Reader, pkg string, length int64, pn string) { if Debug['v'] != 0 { fmt.Fprintf(&Bso, "%5.2f ldpe %s\n", obj.Cputime(), pn) } var sect *PeSect Ctxt.IncVersion() - base := int32(bio.Boffset(f)) + base := f.Offset() peobj := new(PeObj) peobj.f = f @@ -174,14 +174,14 @@ func ldpe(f *bio.Buf, pkg string, length int64, pn string) { // TODO return error if found .cormeta // load string table - bio.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) + f.Seek(int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) if bio.Bread(f, symbuf[:4]) != 4 { goto bad } l = Le32(symbuf[:]) peobj.snames = make([]byte, l) - bio.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) + f.Seek(int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) if bio.Bread(f, peobj.snames) != len(peobj.snames) { goto bad } @@ -202,9 +202,9 @@ func ldpe(f *bio.Buf, pkg string, length int64, pn string) { peobj.pesym = make([]PeSym, peobj.fh.NumberOfSymbols) peobj.npesym = uint(peobj.fh.NumberOfSymbols) - bio.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable), 0) + f.Seek(int64(base)+int64(peobj.fh.PointerToSymbolTable), 0) for i := 0; uint32(i) < peobj.fh.NumberOfSymbols; i += numaux + 1 { - bio.Bseek(f, int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(i), 0) + f.Seek(int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(i), 0) if bio.Bread(f, symbuf[:]) != len(symbuf) { goto bad } @@ -290,7 +290,7 @@ func ldpe(f *bio.Buf, pkg string, length int64, pn string) { } r = make([]Reloc, rsect.sh.NumberOfRelocations) - bio.Bseek(f, int64(peobj.base)+int64(rsect.sh.PointerToRelocations), 0) + f.Seek(int64(peobj.base)+int64(rsect.sh.PointerToRelocations), 0) for j = 0; j < int(rsect.sh.NumberOfRelocations); j++ { rp = &r[j] if bio.Bread(f, symbuf[:10]) != 10 { @@ -466,7 +466,7 @@ func pemap(peobj *PeObj, sect *PeSect) int { if sect.sh.PointerToRawData == 0 { // .bss doesn't have data in object file return 0 } - if bio.Bseek(peobj.f, int64(peobj.base)+int64(sect.sh.PointerToRawData), 0) < 0 || bio.Bread(peobj.f, sect.base) != len(sect.base) { + if peobj.f.Seek(int64(peobj.base)+int64(sect.sh.PointerToRawData), 0) < 0 || bio.Bread(peobj.f, sect.base) != len(sect.base) { return -1 } diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 789eaef1a5..f8cc995c30 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -241,9 +241,10 @@ const ( var ( headstring string // buffered output - Bso bio.Buf + Bso bio.Writer ) +// TODO(dfc) outBuf duplicates bio.Writer type outBuf struct { w *bufio.Writer f *os.File @@ -739,11 +740,11 @@ func loadlib() { * look for the next file in an archive. * adapted from libmach. */ -func nextar(bp *bio.Buf, off int64, a *ArHdr) int64 { +func nextar(bp *bio.Reader, off int64, a *ArHdr) int64 { if off&1 != 0 { off++ } - bio.Bseek(bp, off, 0) + bp.Seek(off, 0) buf := make([]byte, SAR_HDR) if n := bio.Bread(bp, buf); n < len(buf) { if n >= 0 { @@ -782,9 +783,9 @@ func objfile(lib *Library) { magbuf := make([]byte, len(ARMAG)) if bio.Bread(f, magbuf) != len(magbuf) || !strings.HasPrefix(string(magbuf), ARMAG) { /* load it as a regular file */ - l := bio.Bseek(f, 0, 2) + l := f.Seek(0, 2) - bio.Bseek(f, 0, 0) + f.Seek(0, 0) ldobj(f, pkg, l, lib.File, lib.File, FileObj) f.Close() @@ -792,7 +793,7 @@ func objfile(lib *Library) { } /* process __.PKGDEF */ - off := bio.Boffset(f) + off := f.Offset() var arhdr ArHdr l := nextar(f, off, &arhdr) @@ -808,12 +809,12 @@ func objfile(lib *Library) { } if Buildmode == BuildmodeShared { - before := bio.Boffset(f) + before := f.Offset() pkgdefBytes := make([]byte, atolwhex(arhdr.size)) bio.Bread(f, pkgdefBytes) hash := sha1.Sum(pkgdefBytes) lib.hash = hash[:] - bio.Bseek(f, before, 0) + f.Seek(before, 0) } off += l @@ -853,7 +854,7 @@ out: } type Hostobj struct { - ld func(*bio.Buf, string, int64, string) + ld func(*bio.Reader, string, int64, string) pkg string pn string file string @@ -874,7 +875,7 @@ var internalpkg = []string{ "runtime/msan", } -func ldhostobj(ld func(*bio.Buf, string, int64, string), f *bio.Buf, pkg string, length int64, pn string, file string) *Hostobj { +func ldhostobj(ld func(*bio.Reader, string, int64, string), f *bio.Reader, pkg string, length int64, pn string, file string) *Hostobj { isinternal := false for i := 0; i < len(internalpkg); i++ { if pkg == internalpkg[i] { @@ -905,24 +906,22 @@ func ldhostobj(ld func(*bio.Buf, string, int64, string), f *bio.Buf, pkg string, h.pkg = pkg h.pn = pn h.file = file - h.off = bio.Boffset(f) + h.off = f.Offset() h.length = length return h } func hostobjs() { - var f *bio.Buf var h *Hostobj for i := 0; i < len(hostobj); i++ { h = &hostobj[i] - var err error - f, err = bio.Open(h.file) - if f == nil { + f, err := bio.Open(h.file) + if err != nil { Exitf("cannot reopen %s: %v", h.pn, err) } - bio.Bseek(f, h.off, 0) + f.Seek(h.off, 0) h.ld(f, h.pkg, h.length, h.pn) f.Close() } @@ -1266,15 +1265,15 @@ func hostlinkArchArgs() []string { // ldobj loads an input object. If it is a host object (an object // compiled by a non-Go compiler) it returns the Hostobj pointer. If // it is a Go object, it returns nil. -func ldobj(f *bio.Buf, pkg string, length int64, pn string, file string, whence int) *Hostobj { - eof := bio.Boffset(f) + length +func ldobj(f *bio.Reader, pkg string, length int64, pn string, file string, whence int) *Hostobj { + eof := f.Offset() + length - start := bio.Boffset(f) + start := f.Offset() c1 := bio.Bgetc(f) c2 := bio.Bgetc(f) c3 := bio.Bgetc(f) c4 := bio.Bgetc(f) - bio.Bseek(f, start, 0) + f.Seek(start, 0) magic := uint32(c1)<<24 | uint32(c2)<<16 | uint32(c3)<<8 | uint32(c4) if magic == 0x7f454c46 { // \x7F E L F @@ -1334,7 +1333,7 @@ func ldobj(f *bio.Buf, pkg string, length int64, pn string, file string, whence } /* skip over exports and other info -- ends with \n!\n */ - import0 := bio.Boffset(f) + import0 := f.Offset() c1 = '\n' // the last line ended in \n c2 = bio.Bgetc(f) @@ -1349,13 +1348,13 @@ func ldobj(f *bio.Buf, pkg string, length int64, pn string, file string, whence } } - import1 := bio.Boffset(f) + import1 := f.Offset() - bio.Bseek(f, import0, 0) + f.Seek(import0, 0) ldpkg(f, pkg, import1-import0-2, pn, whence) // -2 for !\n - bio.Bseek(f, import1, 0) + f.Seek(import1, 0) - LoadObjFile(Ctxt, f, pkg, eof-bio.Boffset(f), pn) + LoadObjFile(Ctxt, f, pkg, eof-f.Offset(), pn) return nil } diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index d3f9ed3703..cbcc979c85 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -165,9 +165,10 @@ type Link struct { Headtype int Arch *sys.Arch Debugvlog int32 - Bso *bio.Buf - Windows int32 - Goroot string + + Bso *bio.Writer + Windows int32 + Goroot string // Symbol lookup based on name and indexed by version. Hash []map[string]*LSym diff --git a/src/cmd/link/internal/ld/objfile.go b/src/cmd/link/internal/ld/objfile.go index 6f177861f0..61a67cf94c 100644 --- a/src/cmd/link/internal/ld/objfile.go +++ b/src/cmd/link/internal/ld/objfile.go @@ -147,8 +147,8 @@ type objReader struct { file []*LSym } -func LoadObjFile(ctxt *Link, f *bio.Buf, pkg string, length int64, pn string) { - start := bio.Boffset(f) +func LoadObjFile(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) { + start := f.Offset() r := &objReader{ rd: f.Reader(), pkg: pkg, @@ -157,8 +157,8 @@ func LoadObjFile(ctxt *Link, f *bio.Buf, pkg string, length int64, pn string) { dupSym: &LSym{Name: ".dup"}, } r.loadObjFile() - if bio.Boffset(f) != start+length { - log.Fatalf("%s: unexpected end at %d, want %d", pn, int64(bio.Boffset(f)), int64(start+length)) + if f.Offset() != start+length { + log.Fatalf("%s: unexpected end at %d, want %d", pn, f.Offset(), start+length) } } -- cgit v1.3 From 78715cebcfcca3aaaaba4dd41ef6b82a46d7b93d Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Fri, 8 Apr 2016 11:55:05 -0400 Subject: cmd/link: add s390x to link tool main function Change-Id: I83bc2b4a00216b069f133113e4ae9ad76c98a708 Reviewed-on: https://go-review.googlesource.com/21741 Run-TryBot: Michael Munday Reviewed-by: Brad Fitzpatrick --- src/cmd/link/main.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/link/main.go b/src/cmd/link/main.go index e52b718699..f92e02eac3 100644 --- a/src/cmd/link/main.go +++ b/src/cmd/link/main.go @@ -11,6 +11,7 @@ import ( "cmd/link/internal/arm64" "cmd/link/internal/mips64" "cmd/link/internal/ppc64" + "cmd/link/internal/s390x" "cmd/link/internal/x86" "fmt" "os" @@ -33,5 +34,7 @@ func main() { mips64.Main() case "ppc64", "ppc64le": ppc64.Main() + case "s390x": + s390x.Main() } } -- cgit v1.3 From 9ada88aec271a2f08c998e9669331145803e7d5a Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Fri, 8 Apr 2016 13:02:49 -0400 Subject: cmd/cgo: increase s390x int type size to 8 bytes The size of the int type in Go on s390x is 8 bytes, not 4. Change-Id: I1a71ce8c9925f3499abb61c1aa4f6fa2d2ec0d7e Reviewed-on: https://go-review.googlesource.com/21760 Reviewed-by: Bill O'Farrell Reviewed-by: Brad Fitzpatrick --- src/cmd/cgo/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go index 5e863549d6..2dc36c20db 100644 --- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -156,7 +156,7 @@ var intSizeMap = map[string]int64{ "ppc64": 8, "ppc64le": 8, "s390": 4, - "s390x": 4, + "s390x": 8, } var cPrefix string -- cgit v1.3 From 0fb7b4cccd02df10f239ed77d6d85566b6388b83 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Fri, 8 Apr 2016 15:14:37 +0200 Subject: runtime: emit file:line info into traces This makes traces self-contained and simplifies trace workflow in modern cloud environments where it is simpler to reach a service via HTTP than to obtain the binary. Change-Id: I6ff3ca694dc698270f1e29da37d5efaf4e843a0d Reviewed-on: https://go-review.googlesource.com/21732 Run-TryBot: Dmitry Vyukov TryBot-Result: Gobot Gobot Reviewed-by: Hyang-Ah Hana Kim --- src/cmd/trace/main.go | 5 - src/internal/trace/parser.go | 174 +++++++++++++--------------------- src/runtime/trace.go | 98 ++++++++++++++++--- src/runtime/trace/trace_stack_test.go | 7 +- 4 files changed, 156 insertions(+), 128 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/trace/main.go b/src/cmd/trace/main.go index e493be91b7..12bf8c3c16 100644 --- a/src/cmd/trace/main.go +++ b/src/cmd/trace/main.go @@ -104,11 +104,6 @@ func parseEvents() ([]*trace.Event, error) { loader.err = fmt.Errorf("failed to parse trace: %v", err) return } - err = trace.Symbolize(events, programBinary) - if err != nil { - loader.err = fmt.Errorf("failed to symbolize trace: %v", err) - return - } loader.events = events }) return loader.events, loader.err diff --git a/src/internal/trace/parser.go b/src/internal/trace/parser.go index e325678733..5db3fc317e 100644 --- a/src/internal/trace/parser.go +++ b/src/internal/trace/parser.go @@ -5,15 +5,10 @@ package trace import ( - "bufio" "bytes" "fmt" "io" - "os" - "os/exec" "sort" - "strconv" - "strings" ) // Event describes one event in the trace. @@ -59,11 +54,11 @@ const ( // Parse parses, post-processes and verifies the trace. func Parse(r io.Reader) ([]*Event, error) { - rawEvents, err := readTrace(r) + rawEvents, strings, err := readTrace(r) if err != nil { return nil, err } - events, err := parseEvents(rawEvents) + events, stacks, err := parseEvents(rawEvents, strings) if err != nil { return nil, err } @@ -75,6 +70,12 @@ func Parse(r io.Reader) ([]*Event, error) { if err != nil { return nil, err } + // Attach stack traces. + for _, ev := range events { + if ev.StkID != 0 { + ev.Stk = stacks[ev.StkID] + } + } return events, nil } @@ -87,19 +88,20 @@ type rawEvent struct { // readTrace does wire-format parsing and verification. // It does not care about specific event types and argument meaning. -func readTrace(r io.Reader) ([]rawEvent, error) { +func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) { // Read and validate trace header. var buf [16]byte off, err := r.Read(buf[:]) if off != 16 || err != nil { - return nil, fmt.Errorf("failed to read header: read %v, err %v", off, err) + return nil, nil, fmt.Errorf("failed to read header: read %v, err %v", off, err) } if !bytes.Equal(buf[:], []byte("go 1.5 trace\x00\x00\x00\x00")) { - return nil, fmt.Errorf("not a trace file") + return nil, nil, fmt.Errorf("not a trace file") } // Read events. var events []rawEvent + strings := make(map[uint64]string) for { // Read event type and number of arguments (1 byte). off0 := off @@ -108,18 +110,51 @@ func readTrace(r io.Reader) ([]rawEvent, error) { break } if err != nil || n != 1 { - return nil, fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err) + return nil, nil, fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err) } off += n typ := buf[0] << 2 >> 2 narg := buf[0] >> 6 + if typ == EvString { + // String dictionary entry [ID, length, string]. + var id uint64 + id, off, err = readVal(r, off) + if err != nil { + return nil, nil, err + } + if id == 0 { + return nil, nil, fmt.Errorf("string at offset %d has invalid id 0", off) + } + if strings[id] != "" { + return nil, nil, fmt.Errorf("string at offset %d has duplicate id %v", off, id) + } + var ln uint64 + ln, off, err = readVal(r, off) + if err != nil { + return nil, nil, err + } + if ln == 0 { + return nil, nil, fmt.Errorf("string at offset %d has invalie length 0", off) + } + if ln > 1e6 { + return nil, nil, fmt.Errorf("string at offset %d has too large length %v", off, ln) + } + buf := make([]byte, ln) + n, err := io.ReadFull(r, buf) + if err != nil { + return nil, nil, fmt.Errorf("failed to read trace at offset %d: read %v, want %v, error %v", off, n, ln, err) + } + off += n + strings[id] = string(buf) + continue + } ev := rawEvent{typ: typ, off: off0} if narg < 3 { for i := 0; i < int(narg)+2; i++ { // sequence number and time stamp are present but not counted in narg var v uint64 v, off, err = readVal(r, off) if err != nil { - return nil, err + return nil, nil, err } ev.args = append(ev.args, v) } @@ -128,34 +163,34 @@ func readTrace(r io.Reader) ([]rawEvent, error) { var v uint64 v, off, err = readVal(r, off) if err != nil { - return nil, err + return nil, nil, err } evLen := v off1 := off for evLen > uint64(off-off1) { v, off, err = readVal(r, off) if err != nil { - return nil, err + return nil, nil, err } ev.args = append(ev.args, v) } if evLen != uint64(off-off1) { - return nil, fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1) + return nil, nil, fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1) } } events = append(events, ev) } - return events, nil + return events, strings, nil } // Parse events transforms raw events into events. // It does analyze and verify per-event-type arguments. -func parseEvents(rawEvents []rawEvent) (events []*Event, err error) { +func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Event, stacks map[uint64][]*Frame, err error) { var ticksPerSec, lastSeq, lastTs int64 var lastG, timerGoid uint64 var lastP int lastGs := make(map[int]uint64) // last goroutine running on P - stacks := make(map[uint64][]*Frame) + stacks = make(map[uint64][]*Frame) for _, raw := range rawEvents { if raw.typ == EvNone || raw.typ >= EvCount { err = fmt.Errorf("unknown event type %v at offset 0x%x", raw.typ, raw.off) @@ -211,16 +246,20 @@ func parseEvents(rawEvents []rawEvent) (events []*Event, err error) { raw.off, size) return } - if uint64(len(raw.args)) != size+2 { + if want := 2 + 4*size; uint64(len(raw.args)) != want { err = fmt.Errorf("EvStack has wrong number of arguments at offset 0x%x: want %v, got %v", - raw.off, size+2, len(raw.args)) + raw.off, want, len(raw.args)) return } id := raw.args[0] if id != 0 && size > 0 { stk := make([]*Frame, size) for i := 0; i < int(size); i++ { - stk[i] = &Frame{PC: raw.args[i+2]} + pc := raw.args[2+i*4+0] + fn := raw.args[2+i*4+1] + file := raw.args[2+i*4+2] + line := raw.args[2+i*4+3] + stk[i] = &Frame{PC: pc, Fn: strings[fn], File: strings[file], Line: int(line)} } stacks[id] = stk } @@ -263,13 +302,6 @@ func parseEvents(rawEvents []rawEvent) (events []*Event, err error) { return } - // Attach stack traces. - for _, ev := range events { - if ev.StkID != 0 { - ev.Stk = stacks[ev.StkID] - } - } - // Sort by sequence number and translate cpu ticks to real time. sort.Sort(eventList(events)) if ticksPerSec == 0 { @@ -478,8 +510,7 @@ func postProcessTrace(events []*Event) error { g.evStart = ev p.g = ev.G if g.evCreate != nil { - // +1 because symbolizer expects return pc. - ev.Stk = []*Frame{{PC: g.evCreate.Args[1] + 1}} + ev.StkID = g.evCreate.Args[1] g.evCreate = nil } @@ -580,79 +611,6 @@ func postProcessTrace(events []*Event) error { return nil } -// symbolizeTrace attaches func/file/line info to stack traces. -func Symbolize(events []*Event, bin string) error { - // First, collect and dedup all pcs. - pcs := make(map[uint64]*Frame) - for _, ev := range events { - for _, f := range ev.Stk { - pcs[f.PC] = nil - } - } - - // Start addr2line. - cmd := exec.Command("go", "tool", "addr2line", bin) - in, err := cmd.StdinPipe() - if err != nil { - return fmt.Errorf("failed to pipe addr2line stdin: %v", err) - } - cmd.Stderr = os.Stderr - out, err := cmd.StdoutPipe() - if err != nil { - return fmt.Errorf("failed to pipe addr2line stdout: %v", err) - } - err = cmd.Start() - if err != nil { - return fmt.Errorf("failed to start addr2line: %v", err) - } - outb := bufio.NewReader(out) - - // Write all pcs to addr2line. - // Need to copy pcs to an array, because map iteration order is non-deterministic. - var pcArray []uint64 - for pc := range pcs { - pcArray = append(pcArray, pc) - _, err := fmt.Fprintf(in, "0x%x\n", pc-1) - if err != nil { - return fmt.Errorf("failed to write to addr2line: %v", err) - } - } - in.Close() - - // Read in answers. - for _, pc := range pcArray { - fn, err := outb.ReadString('\n') - if err != nil { - return fmt.Errorf("failed to read from addr2line: %v", err) - } - file, err := outb.ReadString('\n') - if err != nil { - return fmt.Errorf("failed to read from addr2line: %v", err) - } - f := &Frame{PC: pc} - f.Fn = fn[:len(fn)-1] - f.File = file[:len(file)-1] - if colon := strings.LastIndex(f.File, ":"); colon != -1 { - ln, err := strconv.Atoi(f.File[colon+1:]) - if err == nil { - f.File = f.File[:colon] - f.Line = ln - } - } - pcs[pc] = f - } - cmd.Wait() - - // Replace frames in events array. - for _, ev := range events { - for i, f := range ev.Stk { - ev.Stk[i] = pcs[f.PC] - } - } - - return nil -} - // readVal reads unsigned base-128 value from r. func readVal(r io.Reader, off0 int) (v uint64, off int, err error) { off = off0 @@ -704,7 +662,7 @@ const ( EvNone = 0 // unused EvBatch = 1 // start of per-P batch of events [pid, timestamp] EvFrequency = 2 // contains tracer timer frequency [frequency (ticks per second)] - EvStack = 3 // stack [stack id, number of PCs, array of PCs] + EvStack = 3 // stack [stack id, number of PCs, array of {PC, func string ID, file string ID, line}] EvGomaxprocs = 4 // current value of GOMAXPROCS [timestamp, GOMAXPROCS, stack id] EvProcStart = 5 // start of P [timestamp, thread id] EvProcStop = 6 // stop of P [timestamp] @@ -714,7 +672,7 @@ const ( EvGCScanDone = 10 // GC scan done [timestamp] EvGCSweepStart = 11 // GC sweep start [timestamp, stack id] EvGCSweepDone = 12 // GC sweep done [timestamp] - EvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, start PC, stack id] + EvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, new start id, stack id] EvGoStart = 14 // goroutine starts running [timestamp, goroutine id] EvGoEnd = 15 // goroutine ends [timestamp] EvGoStop = 16 // goroutine stops (like in select{}) [timestamp, stack] @@ -738,7 +696,8 @@ const ( EvNextGC = 34 // memstats.next_gc change [timestamp, next_gc] EvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id] EvFutileWakeup = 36 // denotes that the previous wakeup of this goroutine was futile [timestamp] - EvCount = 37 + EvString = 37 // string dictionary entry [ID, length, string] + EvCount = 38 ) var EventDescriptions = [EvCount]struct { @@ -759,7 +718,7 @@ var EventDescriptions = [EvCount]struct { EvGCScanDone: {"GCScanDone", false, []string{}}, EvGCSweepStart: {"GCSweepStart", true, []string{}}, EvGCSweepDone: {"GCSweepDone", false, []string{}}, - EvGoCreate: {"GoCreate", true, []string{"g", "pc"}}, + EvGoCreate: {"GoCreate", true, []string{"g", "stack"}}, EvGoStart: {"GoStart", false, []string{"g"}}, EvGoEnd: {"GoEnd", false, []string{}}, EvGoStop: {"GoStop", true, []string{}}, @@ -783,4 +742,5 @@ var EventDescriptions = [EvCount]struct { EvNextGC: {"NextGC", false, []string{"mem"}}, EvTimerGoroutine: {"TimerGoroutine", false, []string{"g", "unused"}}, EvFutileWakeup: {"FutileWakeup", false, []string{}}, + EvString: {"String", false, []string{}}, } diff --git a/src/runtime/trace.go b/src/runtime/trace.go index 805c34f483..f54e5e0a7e 100644 --- a/src/runtime/trace.go +++ b/src/runtime/trace.go @@ -23,7 +23,7 @@ const ( traceEvNone = 0 // unused traceEvBatch = 1 // start of per-P batch of events [pid, timestamp] traceEvFrequency = 2 // contains tracer timer frequency [frequency (ticks per second)] - traceEvStack = 3 // stack [stack id, number of PCs, array of PCs] + traceEvStack = 3 // stack [stack id, number of PCs, array of {PC, func string ID, file string ID, line}] traceEvGomaxprocs = 4 // current value of GOMAXPROCS [timestamp, GOMAXPROCS, stack id] traceEvProcStart = 5 // start of P [timestamp, thread id] traceEvProcStop = 6 // stop of P [timestamp] @@ -33,7 +33,7 @@ const ( traceEvGCScanDone = 10 // GC scan done [timestamp] traceEvGCSweepStart = 11 // GC sweep start [timestamp, stack id] traceEvGCSweepDone = 12 // GC sweep done [timestamp] - traceEvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, start PC, stack id] + traceEvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, new start id, stack id] traceEvGoStart = 14 // goroutine starts running [timestamp, goroutine id] traceEvGoEnd = 15 // goroutine ends [timestamp] traceEvGoStop = 16 // goroutine stops (like in select{}) [timestamp, stack] @@ -57,7 +57,8 @@ const ( traceEvNextGC = 34 // memstats.next_gc change [timestamp, next_gc] traceEvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id] traceEvFutileWakeup = 36 // denotes that the previous wakeup of this goroutine was futile [timestamp] - traceEvCount = 37 + traceEvString = 37 // string dictionary entry [ID, length, string] + traceEvCount = 38 ) const ( @@ -111,6 +112,12 @@ var trace struct { reader *g // goroutine that called ReadTrace, or nil stackTab traceStackTable // maps stack traces to unique ids + // Dictionary for traceEvString. + // Currently this is used only for func/file:line info after tracing session, + // so we assume single-threaded access. + strings map[string]uint64 + stringSeq uint64 + bufLock mutex // protects buf buf traceBufPtr // global trace buffer, used when running without a p } @@ -191,6 +198,8 @@ func StartTrace() error { trace.timeStart = nanotime() trace.headerWritten = false trace.footerWritten = false + trace.strings = make(map[string]uint64) + trace.stringSeq = 0 // Can't set trace.enabled yet. While the world is stopped, exitsyscall could // already emit a delayed event (see exitTicks in exitsyscall) if we set trace.enabled here. @@ -272,8 +281,6 @@ func StopTrace() { trace.enabled = false trace.shutdown = true - trace.stackTab.dump() - unlock(&trace.bufLock) startTheWorld() @@ -309,6 +316,7 @@ func StopTrace() { trace.empty = buf.ptr().link sysFree(unsafe.Pointer(buf), unsafe.Sizeof(*buf.ptr()), &memstats.other_sys) } + trace.strings = nil trace.shutdown = false unlock(&trace.lock) } @@ -380,6 +388,9 @@ func ReadTrace() []byte { data = traceAppend(data, uint64(timers.gp.goid)) data = traceAppend(data, 0) } + // This will emit a bunch of full buffers, we will pick them up + // on the next iteration. + trace.stackTab.dump() return data } // Done. @@ -603,6 +614,29 @@ func traceFlush(buf traceBufPtr) traceBufPtr { return buf } +func traceString(buf *traceBuf, s string) (uint64, *traceBuf) { + if s == "" { + return 0, buf + } + if id, ok := trace.strings[s]; ok { + return id, buf + } + + trace.stringSeq++ + id := trace.stringSeq + trace.strings[s] = id + + size := 1 + 2*traceBytesPerNumber + len(s) + if len(buf.arr)-buf.pos < size { + buf = traceFlush(traceBufPtrOf(buf)).ptr() + } + buf.byte(traceEvString) + buf.varint(id) + buf.varint(uint64(len(s))) + buf.pos += copy(buf.arr[buf.pos:], s) + return id, buf +} + // traceAppend appends v to buf in little-endian-base-128 encoding. func traceAppend(buf []byte, v uint64) []byte { for ; v >= 0x80; v >>= 7 { @@ -716,23 +750,28 @@ func (tab *traceStackTable) newStack(n int) *traceStack { // dump writes all previously cached stacks to trace buffers, // releases all memory and resets state. func (tab *traceStackTable) dump() { - var tmp [(2 + traceStackSize) * traceBytesPerNumber]byte + frames := make(map[uintptr]traceFrame) + var tmp [(2 + 4*traceStackSize) * traceBytesPerNumber]byte buf := traceFlush(0).ptr() for _, stk := range tab.tab { stk := stk.ptr() for ; stk != nil; stk = stk.link.ptr() { - maxSize := 1 + (3+stk.n)*traceBytesPerNumber - if len(buf.arr)-buf.pos < maxSize { - buf = traceFlush(traceBufPtrOf(buf)).ptr() - } - // Form the event in the temp buffer, we need to know the actual length. tmpbuf := tmp[:0] tmpbuf = traceAppend(tmpbuf, uint64(stk.id)) tmpbuf = traceAppend(tmpbuf, uint64(stk.n)) for _, pc := range stk.stack() { + var frame traceFrame + frame, buf = traceFrameForPC(buf, frames, pc) tmpbuf = traceAppend(tmpbuf, uint64(pc)) + tmpbuf = traceAppend(tmpbuf, uint64(frame.funcID)) + tmpbuf = traceAppend(tmpbuf, uint64(frame.fileID)) + tmpbuf = traceAppend(tmpbuf, uint64(frame.line)) } // Now copy to the buffer. + size := 1 + traceBytesPerNumber + len(tmpbuf) + if len(buf.arr)-buf.pos < size { + buf = traceFlush(traceBufPtrOf(buf)).ptr() + } buf.byte(traceEvStack | 3< maxLen { + fn = fn[len(fn)-maxLen:] + } + frame.funcID, buf = traceString(buf, fn) + file, line := funcline(f, pc-sys.PCQuantum) + frame.line = uint64(line) + if len(file) > maxLen { + file = file[len(file)-maxLen:] + } + frame.fileID, buf = traceString(buf, file) + return frame, buf +} + // traceAlloc is a non-thread-safe region allocator. // It holds a linked list of traceAllocBlock. type traceAlloc struct { @@ -844,7 +916,9 @@ func traceGCSweepDone() { } func traceGoCreate(newg *g, pc uintptr) { - traceEvent(traceEvGoCreate, 2, uint64(newg.goid), uint64(pc)) + // +PCQuantum because traceFrameForPC expects return PCs and subtracts PCQuantum. + id := trace.stackTab.put([]uintptr{pc + sys.PCQuantum}) + traceEvent(traceEvGoCreate, 2, uint64(newg.goid), uint64(id)) } func traceGoStart() { diff --git a/src/runtime/trace/trace_stack_test.go b/src/runtime/trace/trace_stack_test.go index b99ec687d5..c3fb0f6fee 100644 --- a/src/runtime/trace/trace_stack_test.go +++ b/src/runtime/trace/trace_stack_test.go @@ -129,10 +129,6 @@ func TestTraceSymbolize(t *testing.T) { if err != nil { t.Fatalf("failed to parse trace: %v", err) } - err = trace.Symbolize(events, os.Args[0]) - if err != nil { - t.Fatalf("failed to symbolize trace: %v", err) - } // Now check that the stacks are correct. type frame struct { @@ -149,6 +145,9 @@ func TestTraceSymbolize(t *testing.T) { {"runtime/trace_test.TestTraceSymbolize", 106}, {"testing.tRunner", 0}, }}, + {trace.EvGoStart, []frame{ + {"runtime/trace_test.TestTraceSymbolize.func1", 37}, + }}, {trace.EvGoSched, []frame{ {"runtime/trace_test.TestTraceSymbolize", 107}, {"testing.tRunner", 0}, -- cgit v1.3 From c3b3e7b4ef9dff1fc0cc504f81465ded5663b4e4 Mon Sep 17 00:00:00 2001 From: David Chase Date: Fri, 8 Apr 2016 13:33:43 -0400 Subject: cmd/compile: insert instrumentation more carefully in racewalk Be more careful about inserting instrumentation in racewalk. If the node being instrumented is an OAS, and it has a non- empty Ninit, then append instrumentation to the Ninit list rather than letting it be inserted before the OAS (and the compilation of its init list). This deals with the case that the Ninit list defines a variable used in the RHS of the OAS. Fixes #15091. Change-Id: Iac91696d9104d07f0bf1bd3499bbf56b2e1ef073 Reviewed-on: https://go-review.googlesource.com/21771 Reviewed-by: Josh Bleecher Snyder Run-TryBot: David Chase --- src/cmd/compile/internal/gc/fmt.go | 3 +++ src/cmd/compile/internal/gc/racewalk.go | 8 +++++++- src/cmd/compile/internal/gc/ssa.go | 4 ++++ src/cmd/compile/internal/ssa/regalloc.go | 4 ++-- test/fixedbugs/issue15091.go | 25 +++++++++++++++++++++++++ 5 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 test/fixedbugs/issue15091.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/fmt.go b/src/cmd/compile/internal/gc/fmt.go index 5c5503619f..19f109055d 100644 --- a/src/cmd/compile/internal/gc/fmt.go +++ b/src/cmd/compile/internal/gc/fmt.go @@ -737,6 +737,9 @@ func typefmt(t *Type, flag FmtFlag) string { Fatalf("cannot use TDDDFIELD with old exporter") } return fmt.Sprintf("%v <%v> %v", Econv(t.Etype), t.Sym, t.DDDField()) + + case Txxx: + return "Txxx" } if fmtmode == FExp { diff --git a/src/cmd/compile/internal/gc/racewalk.go b/src/cmd/compile/internal/gc/racewalk.go index 09889a40f3..f6e65146d6 100644 --- a/src/cmd/compile/internal/gc/racewalk.go +++ b/src/cmd/compile/internal/gc/racewalk.go @@ -164,7 +164,13 @@ func instrumentnode(np **Node, init *Nodes, wr int, skip int) { var outn Nodes outn.Set(out) instrumentnode(&ls[i], &outn, 0, 0) - out = append(outn.Slice(), ls[i]) + if ls[i].Op != OAS || ls[i].Ninit.Len() == 0 { + out = append(outn.Slice(), ls[i]) + } else { + // Splice outn onto end of ls[i].Ninit + ls[i].Ninit.AppendNodes(&outn) + out = append(out, ls[i]) + } } } n.List.Set(out) diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 90c4d4e95e..7c5f906d76 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -3699,6 +3699,10 @@ func (s *state) resolveFwdRef(v *ssa.Value) { if b == s.f.Entry { // Live variable at start of function. if s.canSSA(name) { + if strings.HasPrefix(name.Sym.Name, "autotmp_") { + // It's likely that this is an uninitialized variable in the entry block. + s.Fatalf("Treating auto as if it were arg, func %s, node %v, value %v", b.Func.Name, name, v) + } v.Op = ssa.OpArg v.Aux = name return diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go index 22b9d12c19..aec23a1368 100644 --- a/src/cmd/compile/internal/ssa/regalloc.go +++ b/src/cmd/compile/internal/ssa/regalloc.go @@ -417,7 +417,7 @@ func (s *regAllocState) allocValToReg(v *Value, mask regMask, nospill bool, line // Load v from its spill location. case vi.spill != nil: if s.f.pass.debug > logSpills { - s.f.Config.Warnl(vi.spill.Line, "load spill") + s.f.Config.Warnl(vi.spill.Line, "load spill for %v from %v", v, vi.spill) } c = s.curBlock.NewValue1(line, OpLoadReg, v.Type, vi.spill) vi.spillUsed = true @@ -1078,7 +1078,7 @@ func (s *regAllocState) regalloc(f *Func) { vi := s.values[i] if vi.spillUsed { if s.f.pass.debug > logSpills { - s.f.Config.Warnl(vi.spill.Line, "spilled value") + s.f.Config.Warnl(vi.spill.Line, "spilled value at %v remains", vi.spill) } continue } diff --git a/test/fixedbugs/issue15091.go b/test/fixedbugs/issue15091.go new file mode 100644 index 0000000000..346e906171 --- /dev/null +++ b/test/fixedbugs/issue15091.go @@ -0,0 +1,25 @@ +// errorcheck -0 -race + +// 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 sample + +type Html struct { + headerIDs map[string]int +} + +// We don't want to see: +// internal error: (*Html).xyzzy autotmp_3 (type *int) recorded as live on entry, p.Pc=0 +// or (now, with the error caught earlier) +// Treating auto as if it were arg, func (*Html).xyzzy, node ... +// caused by racewalker inserting instrumentation before an OAS where the Ninit +// of the OAS defines part of its right-hand-side. (I.e., the race instrumentation +// references a variable before it is defined.) +func (options *Html) xyzzy(id string) string { + for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] { + _ = count + } + return "" +} -- cgit v1.3 From 092ef8a2ca60e1a7573442757b02ec1efc456c2c Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Fri, 8 Apr 2016 14:27:35 +1200 Subject: cmd/cgo: fix cgo with gccgo Change-Id: I1780899255e22c16d7f8e9947609a1c284d7c42e Reviewed-on: https://go-review.googlesource.com/21690 Reviewed-by: David Crawshaw Run-TryBot: Michael Hudson-Doyle TryBot-Result: Gobot Gobot --- src/cmd/cgo/out.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 621c41c6b2..88b0147364 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -1451,7 +1451,7 @@ const char *_cgoPREFIX_Cfunc_CString(struct __go_string s) { void *_cgoPREFIX_Cfunc_CBytes(struct __go_open_array b) { char *p = malloc(b.__count); - memmove(p, b.__data, b.__count); + memmove(p, b.__values, b.__count); return p; } -- cgit v1.3 From 6fee4aa5c76612d133d2b01441e608cfa696bae9 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 9 Apr 2016 12:54:45 +1000 Subject: cmd/link/internal: make ld.Bso a *bio.Writer This is a pre requesite of CL 21722 and removes a lot of unidiomatic boilerplate in the linker. Change-Id: If7491b88212b2be7b0c8c588e9c196839131f8ad Reviewed-on: https://go-review.googlesource.com/21780 Run-TryBot: Dave Cheney Reviewed-by: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/cmd/link/internal/amd64/asm.go | 16 ++++++------- src/cmd/link/internal/arm/asm.go | 12 +++++----- src/cmd/link/internal/arm64/asm.go | 12 +++++----- src/cmd/link/internal/ld/ar.go | 2 +- src/cmd/link/internal/ld/data.go | 46 ++++++++++++++++++------------------- src/cmd/link/internal/ld/dwarf.go | 2 +- src/cmd/link/internal/ld/ldelf.go | 8 +++---- src/cmd/link/internal/ld/ldpe.go | 2 +- src/cmd/link/internal/ld/lib.go | 34 +++++++++++++-------------- src/cmd/link/internal/ld/pcln.go | 2 +- src/cmd/link/internal/ld/pobj.go | 15 ++++++------ src/cmd/link/internal/mips64/asm.go | 12 +++++----- src/cmd/link/internal/ppc64/asm.go | 12 +++++----- src/cmd/link/internal/s390x/asm.go | 14 +++++------ src/cmd/link/internal/x86/asm.go | 14 +++++------ 15 files changed, 102 insertions(+), 101 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/amd64/asm.go b/src/cmd/link/internal/amd64/asm.go index 2b219da881..8cecd422e1 100644 --- a/src/cmd/link/internal/amd64/asm.go +++ b/src/cmd/link/internal/amd64/asm.go @@ -611,12 +611,12 @@ func addgotsym(s *ld.LSym) { func asmb() { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f asmb\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f asmb\n", obj.Cputime()) } ld.Bso.Flush() if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f codeblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f codeblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -634,7 +634,7 @@ func asmb() { if ld.Segrodata.Filelen > 0 { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -643,7 +643,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f datblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f datblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -688,7 +688,7 @@ func asmb() { symo := int64(0) if ld.Debug['s'] == 0 { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f sym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f sym\n", obj.Cputime()) } ld.Bso.Flush() switch ld.HEADTYPE { @@ -725,7 +725,7 @@ func asmb() { ld.Cwrite(ld.Elfstrdat) if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f dwarf\n", obj.Cputime()) } if ld.Linkmode == ld.LinkExternal { @@ -749,7 +749,7 @@ func asmb() { case obj.Hwindows: if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f dwarf\n", obj.Cputime()) } case obj.Hdarwin: @@ -760,7 +760,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f headr\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f headr\n", obj.Cputime()) } ld.Bso.Flush() ld.Cseek(0) diff --git a/src/cmd/link/internal/arm/asm.go b/src/cmd/link/internal/arm/asm.go index e2718bfac8..b89cb20bdf 100644 --- a/src/cmd/link/internal/arm/asm.go +++ b/src/cmd/link/internal/arm/asm.go @@ -563,7 +563,7 @@ func addgotsym(ctxt *ld.Link, s *ld.LSym) { func asmb() { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f asmb\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f asmb\n", obj.Cputime()) } ld.Bso.Flush() @@ -581,7 +581,7 @@ func asmb() { if ld.Segrodata.Filelen > 0 { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -590,7 +590,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f datblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f datblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -613,7 +613,7 @@ func asmb() { if ld.Debug['s'] == 0 { // TODO: rationalize if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f sym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f sym\n", obj.Cputime()) } ld.Bso.Flush() switch ld.HEADTYPE { @@ -635,7 +635,7 @@ func asmb() { default: if ld.Iself { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f elfsym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f elfsym\n", obj.Cputime()) } ld.Asmelfsym() ld.Cflush() @@ -669,7 +669,7 @@ func asmb() { ld.Ctxt.Cursym = nil if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f header\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f header\n", obj.Cputime()) } ld.Bso.Flush() ld.Cseek(0) diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index fd28e8693b..fd8929dd99 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -401,7 +401,7 @@ func archrelocvariant(r *ld.Reloc, s *ld.LSym, t int64) int64 { func asmb() { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f asmb\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f asmb\n", obj.Cputime()) } ld.Bso.Flush() @@ -419,7 +419,7 @@ func asmb() { if ld.Segrodata.Filelen > 0 { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -428,7 +428,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f datblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f datblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -451,7 +451,7 @@ func asmb() { if ld.Debug['s'] == 0 { // TODO: rationalize if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f sym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f sym\n", obj.Cputime()) } ld.Bso.Flush() switch ld.HEADTYPE { @@ -473,7 +473,7 @@ func asmb() { default: if ld.Iself { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f elfsym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f elfsym\n", obj.Cputime()) } ld.Asmelfsym() ld.Cflush() @@ -507,7 +507,7 @@ func asmb() { ld.Ctxt.Cursym = nil if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f header\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f header\n", obj.Cputime()) } ld.Bso.Flush() ld.Cseek(0) diff --git a/src/cmd/link/internal/ld/ar.go b/src/cmd/link/internal/ld/ar.go index 6a0aeb121f..f9357392d7 100644 --- a/src/cmd/link/internal/ld/ar.go +++ b/src/cmd/link/internal/ld/ar.go @@ -68,7 +68,7 @@ func hostArchive(name string) { if os.IsNotExist(err) { // It's OK if we don't have a libgcc file at all. if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "skipping libgcc file: %v\n", err) + fmt.Fprintf(Bso, "skipping libgcc file: %v\n", err) } return } diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index ae430b4e45..cd910b54c0 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -638,7 +638,7 @@ func relocsym(s *LSym) { func reloc() { if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f reloc\n", obj.Cputime()) + fmt.Fprintf(Bso, "%5.2f reloc\n", obj.Cputime()) } Bso.Flush() @@ -717,7 +717,7 @@ func dynreloc() { return } if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f reloc\n", obj.Cputime()) + fmt.Fprintf(Bso, "%5.2f reloc\n", obj.Cputime()) } Bso.Flush() @@ -785,7 +785,7 @@ func blk(start *LSym, addr int64, size int64) { func Codeblk(addr int64, size int64) { if Debug['a'] != 0 { - fmt.Fprintf(&Bso, "codeblk [%#x,%#x) at offset %#x\n", addr, addr+size, Cpos()) + fmt.Fprintf(Bso, "codeblk [%#x,%#x) at offset %#x\n", addr, addr+size, Cpos()) } blk(Ctxt.Textp, addr, size) @@ -816,32 +816,32 @@ func Codeblk(addr int64, size int64) { } if addr < sym.Value { - fmt.Fprintf(&Bso, "%-20s %.8x|", "_", uint64(int64(addr))) + fmt.Fprintf(Bso, "%-20s %.8x|", "_", uint64(int64(addr))) for ; addr < sym.Value; addr++ { - fmt.Fprintf(&Bso, " %.2x", 0) + fmt.Fprintf(Bso, " %.2x", 0) } - fmt.Fprintf(&Bso, "\n") + fmt.Fprintf(Bso, "\n") } - fmt.Fprintf(&Bso, "%.6x\t%-20s\n", uint64(int64(addr)), sym.Name) + fmt.Fprintf(Bso, "%.6x\t%-20s\n", uint64(int64(addr)), sym.Name) q = sym.P for len(q) >= 16 { - fmt.Fprintf(&Bso, "%.6x\t% x\n", uint64(addr), q[:16]) + fmt.Fprintf(Bso, "%.6x\t% x\n", uint64(addr), q[:16]) addr += 16 q = q[16:] } if len(q) > 0 { - fmt.Fprintf(&Bso, "%.6x\t% x\n", uint64(addr), q) + fmt.Fprintf(Bso, "%.6x\t% x\n", uint64(addr), q) addr += int64(len(q)) } } if addr < eaddr { - fmt.Fprintf(&Bso, "%-20s %.8x|", "_", uint64(int64(addr))) + fmt.Fprintf(Bso, "%-20s %.8x|", "_", uint64(int64(addr))) for ; addr < eaddr; addr++ { - fmt.Fprintf(&Bso, " %.2x", 0) + fmt.Fprintf(Bso, " %.2x", 0) } } @@ -850,7 +850,7 @@ func Codeblk(addr int64, size int64) { func Datblk(addr int64, size int64) { if Debug['a'] != 0 { - fmt.Fprintf(&Bso, "datblk [%#x,%#x) at offset %#x\n", addr, addr+size, Cpos()) + fmt.Fprintf(Bso, "datblk [%#x,%#x) at offset %#x\n", addr, addr+size, Cpos()) } blk(datap, addr, size) @@ -879,26 +879,26 @@ func Datblk(addr int64, size int64) { break } if addr < sym.Value { - fmt.Fprintf(&Bso, "\t%.8x| 00 ...\n", uint64(addr)) + fmt.Fprintf(Bso, "\t%.8x| 00 ...\n", uint64(addr)) addr = sym.Value } - fmt.Fprintf(&Bso, "%s\n\t%.8x|", sym.Name, uint(addr)) + fmt.Fprintf(Bso, "%s\n\t%.8x|", sym.Name, uint(addr)) p = sym.P ep = p[len(sym.P):] for -cap(p) < -cap(ep) { if -cap(p) > -cap(sym.P) && int(-cap(p)+cap(sym.P))%16 == 0 { - fmt.Fprintf(&Bso, "\n\t%.8x|", uint(addr+int64(-cap(p)+cap(sym.P)))) + fmt.Fprintf(Bso, "\n\t%.8x|", uint(addr+int64(-cap(p)+cap(sym.P)))) } - fmt.Fprintf(&Bso, " %.2x", p[0]) + fmt.Fprintf(Bso, " %.2x", p[0]) p = p[1:] } addr += int64(len(sym.P)) for ; addr < sym.Value+sym.Size; addr++ { - fmt.Fprintf(&Bso, " %.2x", 0) + fmt.Fprintf(Bso, " %.2x", 0) } - fmt.Fprintf(&Bso, "\n") + fmt.Fprintf(Bso, "\n") if Linkmode == LinkExternal { for i = 0; i < int64(len(sym.R)); i++ { @@ -919,20 +919,20 @@ func Datblk(addr int64, size int64) { typ = "call" } - fmt.Fprintf(&Bso, "\treloc %.8x/%d %s %s+%#x [%#x]\n", uint(sym.Value+int64(r.Off)), r.Siz, typ, rsname, int64(r.Add), int64(r.Sym.Value+r.Add)) + fmt.Fprintf(Bso, "\treloc %.8x/%d %s %s+%#x [%#x]\n", uint(sym.Value+int64(r.Off)), r.Siz, typ, rsname, int64(r.Add), int64(r.Sym.Value+r.Add)) } } } if addr < eaddr { - fmt.Fprintf(&Bso, "\t%.8x| 00 ...\n", uint(addr)) + fmt.Fprintf(Bso, "\t%.8x| 00 ...\n", uint(addr)) } - fmt.Fprintf(&Bso, "\t%.8x|\n", uint(eaddr)) + fmt.Fprintf(Bso, "\t%.8x|\n", uint(eaddr)) } func Dwarfblk(addr int64, size int64) { if Debug['a'] != 0 { - fmt.Fprintf(&Bso, "dwarfblk [%#x,%#x) at offset %#x\n", addr, addr+size, Cpos()) + fmt.Fprintf(Bso, "dwarfblk [%#x,%#x) at offset %#x\n", addr, addr+size, Cpos()) } blk(dwarfp, addr, size) @@ -1248,7 +1248,7 @@ func dataSort(head *LSym) *LSym { func dodata() { if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f dodata\n", obj.Cputime()) + fmt.Fprintf(Bso, "%5.2f dodata\n", obj.Cputime()) } Bso.Flush() diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 4741020a6d..de2d50a1a9 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -1931,7 +1931,7 @@ func dwarfgeneratedebugsyms() { } if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f dwarf\n", obj.Cputime()) + fmt.Fprintf(Bso, "%5.2f dwarf\n", obj.Cputime()) } // For diagnostic messages. diff --git a/src/cmd/link/internal/ld/ldelf.go b/src/cmd/link/internal/ld/ldelf.go index 55884c07a2..1c55daa392 100644 --- a/src/cmd/link/internal/ld/ldelf.go +++ b/src/cmd/link/internal/ld/ldelf.go @@ -405,7 +405,7 @@ func parseArmAttributes(e binary.ByteOrder, data []byte) { ehdr.flags = 0x5000202 } if data[0] != 'A' { - fmt.Fprintf(&Bso, ".ARM.attributes has unexpected format %c\n", data[0]) + fmt.Fprintf(Bso, ".ARM.attributes has unexpected format %c\n", data[0]) return } data = data[1:] @@ -416,7 +416,7 @@ func parseArmAttributes(e binary.ByteOrder, data []byte) { nulIndex := bytes.IndexByte(sectiondata, 0) if nulIndex < 0 { - fmt.Fprintf(&Bso, "corrupt .ARM.attributes (section name not NUL-terminated)\n") + fmt.Fprintf(Bso, "corrupt .ARM.attributes (section name not NUL-terminated)\n") return } name := string(sectiondata[:nulIndex]) @@ -440,7 +440,7 @@ func parseArmAttributes(e binary.ByteOrder, data []byte) { } } if attrList.err != nil { - fmt.Fprintf(&Bso, "could not parse .ARM.attributes\n") + fmt.Fprintf(Bso, "could not parse .ARM.attributes\n") } } } @@ -449,7 +449,7 @@ func parseArmAttributes(e binary.ByteOrder, data []byte) { func ldelf(f *bio.Reader, pkg string, length int64, pn string) { if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f ldelf %s\n", obj.Cputime(), pn) + fmt.Fprintf(Bso, "%5.2f ldelf %s\n", obj.Cputime(), pn) } Ctxt.IncVersion() diff --git a/src/cmd/link/internal/ld/ldpe.go b/src/cmd/link/internal/ld/ldpe.go index ba5b928ea0..37a550d5c9 100644 --- a/src/cmd/link/internal/ld/ldpe.go +++ b/src/cmd/link/internal/ld/ldpe.go @@ -131,7 +131,7 @@ type PeObj struct { func ldpe(f *bio.Reader, pkg string, length int64, pn string) { if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f ldpe %s\n", obj.Cputime(), pn) + fmt.Fprintf(Bso, "%5.2f ldpe %s\n", obj.Cputime(), pn) } var sect *PeSect diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index f8cc995c30..e35306dd0e 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -241,7 +241,7 @@ const ( var ( headstring string // buffered output - Bso bio.Writer + Bso *bio.Writer ) // TODO(dfc) outBuf duplicates bio.Writer @@ -469,7 +469,7 @@ func loadinternal(name string) { if Linkshared { shlibname := filepath.Join(Ctxt.Libdir[i], name+".shlibname") if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "searching for %s.a in %s\n", name, shlibname) + fmt.Fprintf(Bso, "searching for %s.a in %s\n", name, shlibname) } if _, err := os.Stat(shlibname); err == nil { addlibpath(Ctxt, "internal", "internal", "", name, shlibname) @@ -479,7 +479,7 @@ func loadinternal(name string) { } pname := filepath.Join(Ctxt.Libdir[i], name+".a") if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "searching for %s.a in %s\n", name, pname) + fmt.Fprintf(Bso, "searching for %s.a in %s\n", name, pname) } if _, err := os.Stat(pname); err == nil { addlibpath(Ctxt, "internal", "internal", pname, name, "") @@ -489,7 +489,7 @@ func loadinternal(name string) { } if found == 0 { - fmt.Fprintf(&Bso, "warning: unable to find %s.a\n", name) + fmt.Fprintf(Bso, "warning: unable to find %s.a\n", name) } } @@ -521,7 +521,7 @@ func loadlib() { iscgo = iscgo || Ctxt.Library[i].Pkg == "runtime/cgo" if Ctxt.Library[i].Shlib == "" { if Debug['v'] > 1 { - fmt.Fprintf(&Bso, "%5.2f autolib: %s (from %s)\n", obj.Cputime(), Ctxt.Library[i].File, Ctxt.Library[i].Objref) + fmt.Fprintf(Bso, "%5.2f autolib: %s (from %s)\n", obj.Cputime(), Ctxt.Library[i].File, Ctxt.Library[i].Objref) } objfile(Ctxt.Library[i]) } @@ -530,7 +530,7 @@ func loadlib() { for i = 0; i < len(Ctxt.Library); i++ { if Ctxt.Library[i].Shlib != "" { if Debug['v'] > 1 { - fmt.Fprintf(&Bso, "%5.2f autolib: %s (from %s)\n", obj.Cputime(), Ctxt.Library[i].Shlib, Ctxt.Library[i].Objref) + fmt.Fprintf(Bso, "%5.2f autolib: %s (from %s)\n", obj.Cputime(), Ctxt.Library[i].Shlib, Ctxt.Library[i].Objref) } ldshlibsyms(Ctxt.Library[i].Shlib) } @@ -693,13 +693,13 @@ func loadlib() { args := hostlinkArchArgs() args = append(args, "--print-libgcc-file-name") if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%s %v\n", extld, args) + fmt.Fprintf(Bso, "%s %v\n", extld, args) } out, err := exec.Command(extld, args...).Output() if err != nil { if Debug['v'] != 0 { - fmt.Fprintln(&Bso, "not using a libgcc file because compiler failed") - fmt.Fprintf(&Bso, "%v\n%s\n", err, out) + fmt.Fprintln(Bso, "not using a libgcc file because compiler failed") + fmt.Fprintf(Bso, "%v\n%s\n", err, out) } libgccfile = "none" } else { @@ -772,7 +772,7 @@ func objfile(lib *Library) { pkg := pathtoprefix(lib.Pkg) if Debug['v'] > 1 { - fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), lib.File, pkg) + fmt.Fprintf(Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), lib.File, pkg) } Bso.Flush() f, err := bio.Open(lib.File) @@ -1035,7 +1035,7 @@ func archive() { argv = append(argv, hostobjCopy()...) if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "archive: %s\n", strings.Join(argv, " ")) + fmt.Fprintf(Bso, "archive: %s\n", strings.Join(argv, " ")) Bso.Flush() } @@ -1204,18 +1204,18 @@ func hostlink() { } if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "host link:") + fmt.Fprintf(Bso, "host link:") for _, v := range argv { - fmt.Fprintf(&Bso, " %q", v) + fmt.Fprintf(Bso, " %q", v) } - fmt.Fprintf(&Bso, "\n") + fmt.Fprintf(Bso, "\n") Bso.Flush() } if out, err := exec.Command(argv[0], argv[1:]...).CombinedOutput(); err != nil { Exitf("running %s failed: %v\n%s", argv[0], err, out) } else if Debug['v'] != 0 && len(out) > 0 { - fmt.Fprintf(&Bso, "%s", out) + fmt.Fprintf(Bso, "%s", out) Bso.Flush() } @@ -2007,7 +2007,7 @@ func genasmsym(put func(*LSym, string, int, int64, int64, int, *LSym)) { // Otherwise, off is addressing the saved program counter. // Something underhanded is going on. Say nothing. if Debug['v'] != 0 || Debug['n'] != 0 { - fmt.Fprintf(&Bso, "%5.2f symsize = %d\n", obj.Cputime(), uint32(Symsize)) + fmt.Fprintf(Bso, "%5.2f symsize = %d\n", obj.Cputime(), uint32(Symsize)) } Bso.Flush() } @@ -2098,7 +2098,7 @@ func callgraph() { continue } if (r.Type == obj.R_CALL || r.Type == obj.R_CALLARM || r.Type == obj.R_CALLPOWER || r.Type == obj.R_CALLMIPS) && r.Sym.Type == obj.STEXT { - fmt.Fprintf(&Bso, "%s calls %s\n", s.Name, r.Sym.Name) + fmt.Fprintf(Bso, "%s calls %s\n", s.Name, r.Sym.Name) } } } diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index 471dda712f..a5fea3db76 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -375,7 +375,7 @@ func pclntab() { ftab.Size = int64(len(ftab.P)) if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f pclntab=%d bytes, funcdata total %d bytes\n", obj.Cputime(), int64(ftab.Size), int64(funcdata_bytes)) + fmt.Fprintf(Bso, "%5.2f pclntab=%d bytes, funcdata total %d bytes\n", obj.Cputime(), int64(ftab.Size), int64(funcdata_bytes)) } } diff --git a/src/cmd/link/internal/ld/pobj.go b/src/cmd/link/internal/ld/pobj.go index bb48f13185..50066d32d7 100644 --- a/src/cmd/link/internal/ld/pobj.go +++ b/src/cmd/link/internal/ld/pobj.go @@ -46,11 +46,12 @@ var ( ) func Ldmain() { + Bso = bio.BufWriter(os.Stdout) + Ctxt = linknew(SysArch) Ctxt.Diag = Diag - Ctxt.Bso = &Bso + Ctxt.Bso = Bso - Bso = *bio.BufWriter(os.Stdout) Debug = [128]int{} nerrors = 0 outfile = "" @@ -122,7 +123,7 @@ func Ldmain() { obj.Flagparse(usage) startProfile() - Ctxt.Bso = &Bso + Ctxt.Bso = Bso Ctxt.Debugvlog = int32(Debug['v']) if flagShared != 0 { if Buildmode == BuildmodeUnset { @@ -163,7 +164,7 @@ func Ldmain() { } if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "HEADER = -H%d -T0x%x -D0x%x -R0x%x\n", HEADTYPE, uint64(INITTEXT), uint64(INITDAT), uint32(INITRND)) + fmt.Fprintf(Bso, "HEADER = -H%d -T0x%x -D0x%x -R0x%x\n", HEADTYPE, uint64(INITTEXT), uint64(INITDAT), uint32(INITRND)) } Bso.Flush() @@ -214,9 +215,9 @@ func Ldmain() { hostlink() archive() if Debug['v'] != 0 { - fmt.Fprintf(&Bso, "%5.2f cpu time\n", obj.Cputime()) - fmt.Fprintf(&Bso, "%d symbols\n", len(Ctxt.Allsym)) - fmt.Fprintf(&Bso, "%d liveness data\n", liveness) + fmt.Fprintf(Bso, "%5.2f cpu time\n", obj.Cputime()) + fmt.Fprintf(Bso, "%d symbols\n", len(Ctxt.Allsym)) + fmt.Fprintf(Bso, "%d liveness data\n", liveness) } Bso.Flush() diff --git a/src/cmd/link/internal/mips64/asm.go b/src/cmd/link/internal/mips64/asm.go index 9a145e373a..027736cc11 100644 --- a/src/cmd/link/internal/mips64/asm.go +++ b/src/cmd/link/internal/mips64/asm.go @@ -114,7 +114,7 @@ func archrelocvariant(r *ld.Reloc, s *ld.LSym, t int64) int64 { func asmb() { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f asmb\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f asmb\n", obj.Cputime()) } ld.Bso.Flush() @@ -132,7 +132,7 @@ func asmb() { if ld.Segrodata.Filelen > 0 { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -141,7 +141,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f datblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f datblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -159,7 +159,7 @@ func asmb() { if ld.Debug['s'] == 0 { // TODO: rationalize if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f sym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f sym\n", obj.Cputime()) } ld.Bso.Flush() switch ld.HEADTYPE { @@ -178,7 +178,7 @@ func asmb() { default: if ld.Iself { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f elfsym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f elfsym\n", obj.Cputime()) } ld.Asmelfsym() ld.Cflush() @@ -207,7 +207,7 @@ func asmb() { ld.Ctxt.Cursym = nil if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f header\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f header\n", obj.Cputime()) } ld.Bso.Flush() ld.Cseek(0) diff --git a/src/cmd/link/internal/ppc64/asm.go b/src/cmd/link/internal/ppc64/asm.go index 4c2131dfc6..13d80545c7 100644 --- a/src/cmd/link/internal/ppc64/asm.go +++ b/src/cmd/link/internal/ppc64/asm.go @@ -834,7 +834,7 @@ func ensureglinkresolver() *ld.LSym { func asmb() { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f asmb\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f asmb\n", obj.Cputime()) } ld.Bso.Flush() @@ -852,7 +852,7 @@ func asmb() { if ld.Segrodata.Filelen > 0 { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -861,7 +861,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f datblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f datblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -879,7 +879,7 @@ func asmb() { if ld.Debug['s'] == 0 { // TODO: rationalize if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f sym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f sym\n", obj.Cputime()) } ld.Bso.Flush() switch ld.HEADTYPE { @@ -898,7 +898,7 @@ func asmb() { default: if ld.Iself { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f elfsym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f elfsym\n", obj.Cputime()) } ld.Asmelfsym() ld.Cflush() @@ -927,7 +927,7 @@ func asmb() { ld.Ctxt.Cursym = nil if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f header\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f header\n", obj.Cputime()) } ld.Bso.Flush() ld.Cseek(0) diff --git a/src/cmd/link/internal/s390x/asm.go b/src/cmd/link/internal/s390x/asm.go index c5e2d187b8..30b1e5a3e1 100644 --- a/src/cmd/link/internal/s390x/asm.go +++ b/src/cmd/link/internal/s390x/asm.go @@ -505,7 +505,7 @@ func addgotsym(s *ld.LSym) { func asmb() { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f asmb\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f asmb\n", obj.Cputime()) } ld.Bso.Flush() @@ -523,7 +523,7 @@ func asmb() { if ld.Segrodata.Filelen > 0 { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -532,7 +532,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f datblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f datblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -552,7 +552,7 @@ func asmb() { ld.Diag("unsupported executable format") } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f sym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f sym\n", obj.Cputime()) } ld.Bso.Flush() symo = uint32(ld.Segdwarf.Fileoff + ld.Segdwarf.Filelen) @@ -560,14 +560,14 @@ func asmb() { ld.Cseek(int64(symo)) if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f elfsym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f elfsym\n", obj.Cputime()) } ld.Asmelfsym() ld.Cflush() ld.Cwrite(ld.Elfstrdat) if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f dwarf\n", obj.Cputime()) } if ld.Linkmode == ld.LinkExternal { @@ -577,7 +577,7 @@ func asmb() { ld.Ctxt.Cursym = nil if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f header\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f header\n", obj.Cputime()) } ld.Bso.Flush() ld.Cseek(0) diff --git a/src/cmd/link/internal/x86/asm.go b/src/cmd/link/internal/x86/asm.go index 91251de15e..a786ba5a48 100644 --- a/src/cmd/link/internal/x86/asm.go +++ b/src/cmd/link/internal/x86/asm.go @@ -609,7 +609,7 @@ func addgotsym(ctxt *ld.Link, s *ld.LSym) { func asmb() { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f asmb\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f asmb\n", obj.Cputime()) } ld.Bso.Flush() @@ -627,7 +627,7 @@ func asmb() { if ld.Segrodata.Filelen > 0 { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f rodatblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -636,7 +636,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f datblk\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f datblk\n", obj.Cputime()) } ld.Bso.Flush() @@ -658,7 +658,7 @@ func asmb() { if ld.Debug['s'] == 0 { // TODO: rationalize if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f sym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f sym\n", obj.Cputime()) } ld.Bso.Flush() switch ld.HEADTYPE { @@ -684,7 +684,7 @@ func asmb() { default: if ld.Iself { if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f elfsym\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f elfsym\n", obj.Cputime()) } ld.Asmelfsym() ld.Cflush() @@ -711,7 +711,7 @@ func asmb() { case obj.Hwindows: if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f dwarf\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f dwarf\n", obj.Cputime()) } case obj.Hdarwin: @@ -722,7 +722,7 @@ func asmb() { } if ld.Debug['v'] != 0 { - fmt.Fprintf(&ld.Bso, "%5.2f headr\n", obj.Cputime()) + fmt.Fprintf(ld.Bso, "%5.2f headr\n", obj.Cputime()) } ld.Bso.Flush() ld.Cseek(0) -- cgit v1.3 From ca397bb68e4b548843d2886e374f96ec3bb0f9c0 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Fri, 8 Apr 2016 19:30:41 +1000 Subject: cmd: remove bio.BufReader and bio.BufWriter bio.BufReader was never used. bio.BufWriter was used to wrap an existing io.Writer, but the bio.Writer returned would not be seekable, so replace all occurences with bufio.Reader instead. Change-Id: I9c6779e35c63178aa4e104c17bb5bb8b52de0359 Reviewed-on: https://go-review.googlesource.com/21722 Reviewed-by: Brad Fitzpatrick Run-TryBot: Dave Cheney TryBot-Result: Gobot Gobot --- src/cmd/asm/internal/asm/endtoend_test.go | 6 +++--- src/cmd/asm/main.go | 15 ++++++++------- src/cmd/compile/internal/gc/bexport.go | 6 +++--- src/cmd/compile/internal/gc/export.go | 4 ++-- src/cmd/compile/internal/gc/go.go | 3 ++- src/cmd/compile/internal/gc/main.go | 3 +-- src/cmd/internal/bio/buf.go | 12 ------------ src/cmd/internal/obj/link.go | 4 ++-- src/cmd/link/internal/ld/lib.go | 2 +- src/cmd/link/internal/ld/link.go | 9 ++++----- src/cmd/link/internal/ld/pobj.go | 4 ++-- 11 files changed, 28 insertions(+), 40 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/asm/internal/asm/endtoend_test.go b/src/cmd/asm/internal/asm/endtoend_test.go index 8986281f10..bc992a7c99 100644 --- a/src/cmd/asm/internal/asm/endtoend_test.go +++ b/src/cmd/asm/internal/asm/endtoend_test.go @@ -5,6 +5,7 @@ package asm import ( + "bufio" "bytes" "fmt" "io/ioutil" @@ -17,7 +18,6 @@ import ( "testing" "cmd/asm/internal/lex" - "cmd/internal/bio" "cmd/internal/obj" ) @@ -34,7 +34,7 @@ func testEndToEnd(t *testing.T, goarch, file string) { pList := obj.Linknewplist(ctxt) var ok bool testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. - ctxt.Bso = bio.BufWriter(os.Stdout) + ctxt.Bso = bufio.NewWriter(os.Stdout) defer ctxt.Bso.Flush() failed := false ctxt.DiagFunc = func(format string, args ...interface{}) { @@ -272,7 +272,7 @@ func testErrors(t *testing.T, goarch, file string) { pList := obj.Linknewplist(ctxt) var ok bool testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. - ctxt.Bso = bio.BufWriter(os.Stdout) + ctxt.Bso = bufio.NewWriter(os.Stdout) defer ctxt.Bso.Flush() failed := false var errBuf bytes.Buffer diff --git a/src/cmd/asm/main.go b/src/cmd/asm/main.go index 75cb8f75d3..f010ca93f1 100644 --- a/src/cmd/asm/main.go +++ b/src/cmd/asm/main.go @@ -5,6 +5,7 @@ package main import ( + "bufio" "flag" "fmt" "log" @@ -32,11 +33,6 @@ func main() { flags.Parse() - // Create object file, write header. - fd, err := os.Create(*flags.OutputFile) - if err != nil { - log.Fatal(err) - } ctxt := obj.Linknew(architecture.LinkArch) if *flags.PrintOut { ctxt.Debugasm = 1 @@ -46,9 +42,14 @@ func main() { if *flags.Shared || *flags.Dynlink { ctxt.Flag_shared = 1 } - ctxt.Bso = bio.BufWriter(os.Stdout) + ctxt.Bso = bufio.NewWriter(os.Stdout) defer ctxt.Bso.Flush() - output := bio.BufWriter(fd) + + // Create object file, write header. + output, err := bio.Create(*flags.OutputFile) + if err != nil { + log.Fatal(err) + } fmt.Fprintf(output, "go object %s %s %s\n", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion()) fmt.Fprintf(output, "!\n") diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index bb0a34e67b..15e5e3ada6 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -90,9 +90,9 @@ importer. package gc import ( + "bufio" "bytes" "cmd/compile/internal/big" - "cmd/internal/bio" "encoding/binary" "fmt" "sort" @@ -124,7 +124,7 @@ const exportVersion = "v0" const exportInlined = true // default: true type exporter struct { - out *bio.Writer + out *bufio.Writer pkgIndex map[*Pkg]int typIndex map[*Type]int inlined []*Func @@ -136,7 +136,7 @@ type exporter struct { } // export writes the exportlist for localpkg to out and returns the number of bytes written. -func export(out *bio.Writer, trace bool) int { +func export(out *bufio.Writer, trace bool) int { p := exporter{ out: out, pkgIndex: make(map[*Pkg]int), diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index 2f94b9c62f..dc7c0869bf 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -384,7 +384,7 @@ func dumpexport() { if debugFormat { // save a copy of the export data var copy bytes.Buffer - bcopy := bio.BufWriter(©) + bcopy := bufio.NewWriter(©) size = export(bcopy, Debug_export != 0) bcopy.Flush() // flushing to bytes.Buffer cannot fail if n, err := bout.Write(copy.Bytes()); n != size || err != nil { @@ -407,7 +407,7 @@ func dumpexport() { pkgs = savedPkgs pkgMap = savedPkgMap } else { - size = export(bout, Debug_export != 0) + size = export(bout.Writer(), Debug_export != 0) } exportf("\n$$\n") } else { diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index ec7e219d95..d9b28ff8e6 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -5,6 +5,7 @@ package gc import ( + "bufio" "cmd/compile/internal/ssa" "cmd/internal/bio" "cmd/internal/obj" @@ -288,7 +289,7 @@ var Ctxt *obj.Link var writearchive int -var bstdout *bio.Writer +var bstdout *bufio.Writer var Nacl bool diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 03143f5d0a..26acf8861f 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -9,7 +9,6 @@ package gc import ( "bufio" "cmd/compile/internal/ssa" - "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/sys" "flag" @@ -104,7 +103,7 @@ func Main() { Ctxt = obj.Linknew(Thearch.LinkArch) Ctxt.DiagFunc = Yyerror - bstdout = bio.BufWriter(os.Stdout) + bstdout = bufio.NewWriter(os.Stdout) Ctxt.Bso = bstdout localpkg = mkpkg("") diff --git a/src/cmd/internal/bio/buf.go b/src/cmd/internal/bio/buf.go index 0bd4658cdd..983ce46627 100644 --- a/src/cmd/internal/bio/buf.go +++ b/src/cmd/internal/bio/buf.go @@ -51,18 +51,6 @@ func Open(name string) (*Reader, error) { return &Reader{f: f, r: bufio.NewReader(f)}, nil } -// BufWriter returns a Writer on top of w. -// TODO(dfc) remove this method and replace caller with bufio.Writer. -func BufWriter(w io.Writer) *Writer { - return &Writer{w: bufio.NewWriter(w)} -} - -// BufWriter returns a Reader on top of r. -// TODO(dfc) remove this method and replace caller with bufio.Reader. -func BufReader(r io.Reader) *Reader { - return &Reader{r: bufio.NewReader(r)} -} - func (w *Writer) Write(p []byte) (int, error) { return w.w.Write(p) } diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index c48c3d807f..62175f9ed8 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -31,7 +31,7 @@ package obj import ( - "cmd/internal/bio" + "bufio" "cmd/internal/sys" ) @@ -629,7 +629,7 @@ type Link struct { Flag_shared int32 Flag_dynlink bool Flag_optimize bool - Bso *bio.Writer + Bso *bufio.Writer Pathname string Goroot string Goroot_final string diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index e35306dd0e..01dca9fc31 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -241,7 +241,7 @@ const ( var ( headstring string // buffered output - Bso *bio.Writer + Bso *bufio.Writer ) // TODO(dfc) outBuf duplicates bio.Writer diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index cbcc979c85..52b52f1cc0 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -31,7 +31,7 @@ package ld import ( - "cmd/internal/bio" + "bufio" "cmd/internal/sys" "debug/elf" "fmt" @@ -165,10 +165,9 @@ type Link struct { Headtype int Arch *sys.Arch Debugvlog int32 - - Bso *bio.Writer - Windows int32 - Goroot string + Bso *bufio.Writer + Windows int32 + Goroot string // Symbol lookup based on name and indexed by version. Hash []map[string]*LSym diff --git a/src/cmd/link/internal/ld/pobj.go b/src/cmd/link/internal/ld/pobj.go index 50066d32d7..f4fb4d4845 100644 --- a/src/cmd/link/internal/ld/pobj.go +++ b/src/cmd/link/internal/ld/pobj.go @@ -31,7 +31,7 @@ package ld import ( - "cmd/internal/bio" + "bufio" "cmd/internal/obj" "cmd/internal/sys" "flag" @@ -46,7 +46,7 @@ var ( ) func Ldmain() { - Bso = bio.BufWriter(os.Stdout) + Bso = bufio.NewWriter(os.Stdout) Ctxt = linknew(SysArch) Ctxt.Diag = Diag -- cgit v1.3 From 93368be61ebaf8069d0d70034097de580441c412 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Fri, 8 Apr 2016 20:37:54 +1000 Subject: cmd/internal/bio: embed bufio.{Reader,Writer} in bio.{Reader,Writer} Change-Id: Ie95b0b0d4f724f4769cf2d4f8063cb5019fa9bc9 Reviewed-on: https://go-review.googlesource.com/21781 Reviewed-by: Brad Fitzpatrick --- src/cmd/compile/internal/gc/export.go | 2 +- src/cmd/internal/bio/buf.go | 56 ++++++++--------------------------- src/cmd/internal/obj/objfile.go | 2 +- src/cmd/link/internal/ld/objfile.go | 2 +- 4 files changed, 16 insertions(+), 46 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index dc7c0869bf..ae36657a65 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -407,7 +407,7 @@ func dumpexport() { pkgs = savedPkgs pkgMap = savedPkgMap } else { - size = export(bout.Writer(), Debug_export != 0) + size = export(bout.Writer, Debug_export != 0) } exportf("\n$$\n") } else { diff --git a/src/cmd/internal/bio/buf.go b/src/cmd/internal/bio/buf.go index 983ce46627..564ac77cbf 100644 --- a/src/cmd/internal/bio/buf.go +++ b/src/cmd/internal/bio/buf.go @@ -17,21 +17,15 @@ const EOF = -1 // Reader implements a seekable buffered io.Reader. type Reader struct { f *os.File - r *bufio.Reader + *bufio.Reader } // Writer implements a seekable buffered io.Writer. type Writer struct { f *os.File - w *bufio.Writer + *bufio.Writer } -// Reader returns this Reader's underlying bufio.Reader. -func (r *Reader) Reader() *bufio.Reader { return r.r } - -// Writer returns this Writer's underlying bufio.Writer. -func (w *Writer) Writer() *bufio.Writer { return w.w } - // Create creates the file named name and returns a Writer // for that file. func Create(name string) (*Writer, error) { @@ -39,7 +33,7 @@ func Create(name string) (*Writer, error) { if err != nil { return nil, err } - return &Writer{f: f, w: bufio.NewWriter(f)}, nil + return &Writer{f: f, Writer: bufio.NewWriter(f)}, nil } // Open returns a Reader for the file named name. @@ -48,31 +42,23 @@ func Open(name string) (*Reader, error) { if err != nil { return nil, err } - return &Reader{f: f, r: bufio.NewReader(f)}, nil -} - -func (w *Writer) Write(p []byte) (int, error) { - return w.w.Write(p) -} - -func (w *Writer) WriteString(p string) (int, error) { - return w.w.WriteString(p) + return &Reader{f: f, Reader: bufio.NewReader(f)}, nil } func (r *Reader) Seek(offset int64, whence int) int64 { if whence == 1 { - offset -= int64(r.r.Buffered()) + offset -= int64(r.Buffered()) } off, err := r.f.Seek(offset, whence) if err != nil { log.Fatalf("seeking in output: %v", err) } - r.r.Reset(r.f) + r.Reset(r.f) return off } func (w *Writer) Seek(offset int64, whence int) int64 { - if err := w.w.Flush(); err != nil { + if err := w.Flush(); err != nil { log.Fatalf("writing output: %v", err) } off, err := w.f.Seek(offset, whence) @@ -87,12 +73,12 @@ func (r *Reader) Offset() int64 { if err != nil { log.Fatalf("seeking in output [0, 1]: %v", err) } - off -= int64(r.r.Buffered()) + off -= int64(r.Buffered()) return off } func (w *Writer) Offset() int64 { - if err := w.w.Flush(); err != nil { + if err := w.Flush(); err != nil { log.Fatalf("writing output: %v", err) } off, err := w.f.Seek(0, 1) @@ -102,16 +88,8 @@ func (w *Writer) Offset() int64 { return off } -func (w *Writer) Flush() error { - return w.w.Flush() -} - -func (w *Writer) WriteByte(c byte) error { - return w.w.WriteByte(c) -} - func Bread(r *Reader, p []byte) int { - n, err := io.ReadFull(r.r, p) + n, err := io.ReadFull(r, p) if n == 0 { if err != nil && err != io.EOF { n = -1 @@ -121,7 +99,7 @@ func Bread(r *Reader, p []byte) int { } func Bgetc(r *Reader) int { - c, err := r.r.ReadByte() + c, err := r.ReadByte() if err != nil { if err != io.EOF { log.Fatalf("reading input: %v", err) @@ -131,16 +109,8 @@ func Bgetc(r *Reader) int { return int(c) } -func (r *Reader) Read(p []byte) (int, error) { - return r.r.Read(p) -} - -func (r *Reader) Peek(n int) ([]byte, error) { - return r.r.Peek(n) -} - func Brdline(r *Reader, delim int) string { - s, err := r.r.ReadBytes(byte(delim)) + s, err := r.ReadBytes(byte(delim)) if err != nil { log.Fatalf("reading input: %v", err) } @@ -152,7 +122,7 @@ func (r *Reader) Close() error { } func (w *Writer) Close() error { - err := w.w.Flush() + err := w.Flush() err1 := w.f.Close() if err == nil { err = err1 diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index ed6d75eba3..ee21f39d10 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -377,7 +377,7 @@ func (w *objWriter) writeLengths() { func newObjWriter(ctxt *Link, b *bio.Writer) *objWriter { return &objWriter{ ctxt: ctxt, - wr: b.Writer(), + wr: b.Writer, vrefIdx: make(map[string]int), refIdx: make(map[string]int), } diff --git a/src/cmd/link/internal/ld/objfile.go b/src/cmd/link/internal/ld/objfile.go index 61a67cf94c..578afd4c74 100644 --- a/src/cmd/link/internal/ld/objfile.go +++ b/src/cmd/link/internal/ld/objfile.go @@ -150,7 +150,7 @@ type objReader struct { func LoadObjFile(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) { start := f.Offset() r := &objReader{ - rd: f.Reader(), + rd: f.Reader, pkg: pkg, ctxt: ctxt, pn: pn, -- cgit v1.3 From bce9747ed00c53e7ddeea102e87aede1b3ec9bd3 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 9 Apr 2016 15:04:45 +1000 Subject: cmd: remove unused code Generated with honnef.co/go/unused There is a large amount of unused code in cmd/internal/obj/s390x but that can wait til the s390x port is merged. There is some unused code in cmd/internal/unvendor/golang.org/x/arch/arm/armasm but that should be addressed upstream and a new revision imported. Change-Id: I252c0f9ea8c5bb1a0b530a374ef13a0a20ea56aa Reviewed-on: https://go-review.googlesource.com/21782 Reviewed-by: Brad Fitzpatrick Run-TryBot: Dave Cheney --- src/cmd/internal/goobj/read.go | 7 ++---- src/cmd/internal/obj/mips/asm0.go | 4 ---- src/cmd/internal/obj/x86/asm6.go | 5 ----- src/cmd/internal/obj/x86/obj6_test.go | 1 - src/cmd/internal/objfile/pe.go | 2 -- src/cmd/link/internal/amd64/asm.go | 6 ----- src/cmd/link/internal/arm/asm.go | 5 ----- src/cmd/link/internal/arm64/asm.go | 4 ---- src/cmd/link/internal/ld/data.go | 4 ---- src/cmd/link/internal/ld/deadcode.go | 4 +--- src/cmd/link/internal/ld/decodesym.go | 18 --------------- src/cmd/link/internal/ld/go.go | 30 +------------------------ src/cmd/link/internal/ld/macho.go | 7 ++---- src/cmd/link/internal/ld/macho_combine_dwarf.go | 4 +--- src/cmd/link/internal/mips64/asm.go | 4 ---- src/cmd/link/internal/ppc64/asm.go | 4 ---- src/cmd/link/internal/x86/asm.go | 4 ---- 17 files changed, 7 insertions(+), 106 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/internal/goobj/read.go b/src/cmd/internal/goobj/read.go index 5434661384..698d58efe0 100644 --- a/src/cmd/internal/goobj/read.go +++ b/src/cmd/internal/goobj/read.go @@ -229,11 +229,8 @@ var ( errCorruptArchive = errors.New("corrupt archive") errTruncatedArchive = errors.New("truncated archive") - errNotArchive = errors.New("unrecognized archive format") - - errCorruptObject = errors.New("corrupt object file") - errTruncatedObject = errors.New("truncated object file") - errNotObject = errors.New("unrecognized object file format") + errCorruptObject = errors.New("corrupt object file") + errNotObject = errors.New("unrecognized object file format") ) // An objReader is an object file reader. diff --git a/src/cmd/internal/obj/mips/asm0.go b/src/cmd/internal/obj/mips/asm0.go index 521cb66dec..5cb5d1cfd9 100644 --- a/src/cmd/internal/obj/mips/asm0.go +++ b/src/cmd/internal/obj/mips/asm0.go @@ -974,10 +974,6 @@ func OP_JMP(op uint32, i uint32) uint32 { return op | i&0x3FFFFFF } -func oclass(a *obj.Addr) int { - return int(a.Class) - 1 -} - func asmout(ctxt *obj.Link, p *obj.Prog, o *Optab, out []uint32) { o1 := uint32(0) o2 := uint32(0) diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go index b940094b8b..c15b59b5e8 100644 --- a/src/cmd/internal/obj/x86/asm6.go +++ b/src/cmd/internal/obj/x86/asm6.go @@ -884,11 +884,6 @@ var yvex_vpbroadcast = []ytab{ {Yxm, Ynone, Yyr, Zvex_rm_v_r, 2}, } -var yvex_xxmyxm = []ytab{ - {Yxr, Ynone, Yxm, Zvex_r_v_rm, 2}, - {Yyr, Ynone, Yxm, Zvex_r_v_rm, 2}, -} - var ymmxmm0f38 = []ytab{ {Ymm, Ynone, Ymr, Zlitm_r, 3}, {Yxm, Ynone, Yxr, Zlitm_r, 5}, diff --git a/src/cmd/internal/obj/x86/obj6_test.go b/src/cmd/internal/obj/x86/obj6_test.go index a5c80cea3b..fe1f95cc0d 100644 --- a/src/cmd/internal/obj/x86/obj6_test.go +++ b/src/cmd/internal/obj/x86/obj6_test.go @@ -76,7 +76,6 @@ func parseTestData(t *testing.T) *ParsedTestData { } var spaces_re *regexp.Regexp = regexp.MustCompile("\\s+") -var marker_re *regexp.Regexp = regexp.MustCompile("MOVQ \\$([0-9]+), AX") func normalize(s string) string { return spaces_re.ReplaceAllLiteralString(strings.TrimSpace(s), " ") diff --git a/src/cmd/internal/objfile/pe.go b/src/cmd/internal/objfile/pe.go index 1b319941ac..c024762371 100644 --- a/src/cmd/internal/objfile/pe.go +++ b/src/cmd/internal/objfile/pe.go @@ -69,8 +69,6 @@ func (f *peFile) symbols() ([]Sym, error) { text = 0x20 data = 0x40 bss = 0x80 - permX = 0x20000000 - permR = 0x40000000 permW = 0x80000000 ) ch := sect.Characteristics diff --git a/src/cmd/link/internal/amd64/asm.go b/src/cmd/link/internal/amd64/asm.go index 8cecd422e1..a6dce6c2c9 100644 --- a/src/cmd/link/internal/amd64/asm.go +++ b/src/cmd/link/internal/amd64/asm.go @@ -99,12 +99,6 @@ func gentext() { ld.Addaddr(ld.Ctxt, initarray_entry, initfunc) } -func adddynrela(rela *ld.LSym, s *ld.LSym, r *ld.Reloc) { - ld.Addaddrplus(ld.Ctxt, rela, s, int64(r.Off)) - ld.Adduint64(ld.Ctxt, rela, ld.R_X86_64_RELATIVE) - ld.Addaddrplus(ld.Ctxt, rela, r.Sym, r.Add) // Addend -} - func adddynrel(s *ld.LSym, r *ld.Reloc) { targ := r.Sym ld.Ctxt.Cursym = s diff --git a/src/cmd/link/internal/arm/asm.go b/src/cmd/link/internal/arm/asm.go index b89cb20bdf..1188615716 100644 --- a/src/cmd/link/internal/arm/asm.go +++ b/src/cmd/link/internal/arm/asm.go @@ -114,11 +114,6 @@ func braddoff(a int32, b int32) int32 { return int32((uint32(a))&0xff000000 | 0x00ffffff&uint32(a+b)) } -func adddynrela(rel *ld.LSym, s *ld.LSym, r *ld.Reloc) { - ld.Addaddrplus(ld.Ctxt, rel, s, int64(r.Off)) - ld.Adduint32(ld.Ctxt, rel, ld.R_ARM_RELATIVE) -} - func adddynrel(s *ld.LSym, r *ld.Reloc) { targ := r.Sym ld.Ctxt.Cursym = s diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index fd8929dd99..d3ba5ff3f3 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -91,10 +91,6 @@ func gentext() { ld.Addaddr(ld.Ctxt, initarray_entry, initfunc) } -func adddynrela(rel *ld.LSym, s *ld.LSym, r *ld.Reloc) { - log.Fatalf("adddynrela not implemented") -} - func adddynrel(s *ld.LSym, r *ld.Reloc) { log.Fatalf("adddynrel not implemented") } diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index cd910b54c0..2c8cc9ca4f 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -236,10 +236,6 @@ func addaddrplus4(ctxt *Link, s *LSym, t *LSym, add int64) int64 { * Used for the data block. */ -func listnextp(s *LSym) **LSym { - return &s.Next -} - func listsubp(s *LSym) **LSym { return &s.Sub } diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go index b17b96001e..8b2d0d447e 100644 --- a/src/cmd/link/internal/ld/deadcode.go +++ b/src/cmd/link/internal/ld/deadcode.go @@ -170,9 +170,7 @@ type methodref struct { r [3]*Reloc // R_METHOD relocations to fields of runtime.method } -func (m methodref) mtyp() *LSym { return m.r[0].Sym } -func (m methodref) ifn() *LSym { return m.r[1].Sym } -func (m methodref) tfn() *LSym { return m.r[2].Sym } +func (m methodref) ifn() *LSym { return m.r[1].Sym } func (m methodref) isExported() bool { for _, r := range m.m { diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go index bc29938590..1066d220f7 100644 --- a/src/cmd/link/internal/ld/decodesym.go +++ b/src/cmd/link/internal/ld/decodesym.go @@ -56,11 +56,6 @@ func decodetype_kind(s *LSym) uint8 { return uint8(s.P[2*SysArch.PtrSize+7] & obj.KindMask) // 0x13 / 0x1f } -// Type.commonType.kind -func decodetype_noptr(s *LSym) uint8 { - return uint8(s.P[2*SysArch.PtrSize+7] & obj.KindNoPointers) // 0x13 / 0x1f -} - // Type.commonType.kind func decodetype_usegcprog(s *LSym) uint8 { return uint8(s.P[2*SysArch.PtrSize+7] & obj.KindGCProg) // 0x13 / 0x1f @@ -216,19 +211,6 @@ func decodetype_structfieldarrayoff(s *LSym, i int) int { return off } -func decodetype_stringptr(s *LSym, off int) string { - s = decode_reloc_sym(s, int32(off)) - if s == nil { - return "" - } - r := decode_reloc(s, 0) // s has a pointer to the string data at offset 0 - if r == nil { // shouldn't happen. - return "" - } - strlen := int64(decode_inuxi(s.P[SysArch.PtrSize:], SysArch.IntSize)) - return string(r.Sym.P[r.Add : r.Add+strlen]) -} - // decodetype_name decodes the name from a reflect.name. func decodetype_name(s *LSym, off int) string { r := decode_reloc(s, int32(off)) diff --git a/src/cmd/link/internal/ld/go.go b/src/cmd/link/internal/ld/go.go index 5dad90dae6..3af5f7a046 100644 --- a/src/cmd/link/internal/ld/go.go +++ b/src/cmd/link/internal/ld/go.go @@ -419,35 +419,7 @@ type Pkg struct { impby []*Pkg } -var ( - // pkgmap records the imported-by relationship between packages. - // Entries are keyed by package path (e.g., "runtime" or "net/url"). - pkgmap = map[string]*Pkg{} - - pkgall []*Pkg -) - -func lookupPkg(path string) *Pkg { - if p, ok := pkgmap[path]; ok { - return p - } - p := &Pkg{path: path} - pkgmap[path] = p - pkgall = append(pkgall, p) - return p -} - -// imported records that package pkg imports package imp. -func imported(pkg, imp string) { - // everyone imports runtime, even runtime. - if imp == "runtime" { - return - } - - p := lookupPkg(pkg) - i := lookupPkg(imp) - i.impby = append(i.impby, p) -} +var pkgall []*Pkg func (p *Pkg) cycle() *Pkg { if p.checked { diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go index 25d48fbf22..1d9a1a9324 100644 --- a/src/cmd/link/internal/ld/macho.go +++ b/src/cmd/link/internal/ld/macho.go @@ -586,11 +586,8 @@ func Asmbmacho() { // and we can assume OS X. // // See golang.org/issues/12941. - const ( - LC_VERSION_MIN_MACOSX = 0x24 - LC_VERSION_MIN_IPHONEOS = 0x25 - LC_VERSION_MIN_WATCHOS = 0x30 - ) + const LC_VERSION_MIN_MACOSX = 0x24 + ml := newMachoLoad(LC_VERSION_MIN_MACOSX, 2) ml.data[0] = 10<<16 | 7<<8 | 0<<0 // OS X version 10.7.0 ml.data[1] = 10<<16 | 7<<8 | 0<<0 // SDK 10.7.0 diff --git a/src/cmd/link/internal/ld/macho_combine_dwarf.go b/src/cmd/link/internal/ld/macho_combine_dwarf.go index b5a5a8d429..dcc371ec05 100644 --- a/src/cmd/link/internal/ld/macho_combine_dwarf.go +++ b/src/cmd/link/internal/ld/macho_combine_dwarf.go @@ -15,11 +15,9 @@ import ( "unsafe" ) -var fakedwarf, realdwarf, linkseg *macho.Segment +var realdwarf, linkseg *macho.Segment var dwarfstart, linkstart int64 var linkoffset uint32 -var machHeader *macho.FileHeader -var mappedHeader []byte const ( LC_ID_DYLIB = 0xd diff --git a/src/cmd/link/internal/mips64/asm.go b/src/cmd/link/internal/mips64/asm.go index 027736cc11..ad6a1f7524 100644 --- a/src/cmd/link/internal/mips64/asm.go +++ b/src/cmd/link/internal/mips64/asm.go @@ -41,10 +41,6 @@ import ( func gentext() {} -func adddynrela(rel *ld.LSym, s *ld.LSym, r *ld.Reloc) { - log.Fatalf("adddynrela not implemented") -} - func adddynrel(s *ld.LSym, r *ld.Reloc) { log.Fatalf("adddynrel not implemented") } diff --git a/src/cmd/link/internal/ppc64/asm.go b/src/cmd/link/internal/ppc64/asm.go index 13d80545c7..3970f3c5f9 100644 --- a/src/cmd/link/internal/ppc64/asm.go +++ b/src/cmd/link/internal/ppc64/asm.go @@ -265,10 +265,6 @@ func gencallstub(abicase int, stub *ld.LSym, targ *ld.LSym) { ld.Adduint32(ld.Ctxt, stub, 0x4e800420) // bctr } -func adddynrela(rel *ld.LSym, s *ld.LSym, r *ld.Reloc) { - log.Fatalf("adddynrela not implemented") -} - func adddynrel(s *ld.LSym, r *ld.Reloc) { targ := r.Sym ld.Ctxt.Cursym = s diff --git a/src/cmd/link/internal/x86/asm.go b/src/cmd/link/internal/x86/asm.go index a786ba5a48..19a8917ec8 100644 --- a/src/cmd/link/internal/x86/asm.go +++ b/src/cmd/link/internal/x86/asm.go @@ -139,10 +139,6 @@ func gentext() { ld.Addaddr(ld.Ctxt, initarray_entry, initfunc) } -func adddynrela(rela *ld.LSym, s *ld.LSym, r *ld.Reloc) { - log.Fatalf("adddynrela not implemented") -} - func adddynrel(s *ld.LSym, r *ld.Reloc) { targ := r.Sym ld.Ctxt.Cursym = s -- cgit v1.3 From 9d4efdfd12f47f1ed8ce482ebeeb4d4e30a2dbc6 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Fri, 8 Apr 2016 20:44:56 +1000 Subject: cmd/internal/bio: move Bgetc to link/internal/ld Also, remove bio.Brdline. Change-Id: I3e0caed27a373fd71737cf6892de5e8fc208b776 Reviewed-on: https://go-review.googlesource.com/21783 Reviewed-by: Brad Fitzpatrick Run-TryBot: Dave Cheney --- src/cmd/internal/bio/buf.go | 21 --------------------- src/cmd/link/internal/ld/lib.go | 33 ++++++++++++++++++++++----------- 2 files changed, 22 insertions(+), 32 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/internal/bio/buf.go b/src/cmd/internal/bio/buf.go index 564ac77cbf..6a5d821d45 100644 --- a/src/cmd/internal/bio/buf.go +++ b/src/cmd/internal/bio/buf.go @@ -12,8 +12,6 @@ import ( "os" ) -const EOF = -1 - // Reader implements a seekable buffered io.Reader. type Reader struct { f *os.File @@ -98,25 +96,6 @@ func Bread(r *Reader, p []byte) int { return n } -func Bgetc(r *Reader) int { - c, err := r.ReadByte() - if err != nil { - if err != io.EOF { - log.Fatalf("reading input: %v", err) - } - return EOF - } - return int(c) -} - -func Brdline(r *Reader, delim int) string { - s, err := r.ReadBytes(byte(delim)) - if err != nil { - log.Fatalf("reading input: %v", err) - } - return string(s) -} - func (r *Reader) Close() error { return r.f.Close() } diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 01dca9fc31..6e33ec3b05 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1269,10 +1269,10 @@ func ldobj(f *bio.Reader, pkg string, length int64, pn string, file string, when eof := f.Offset() + length start := f.Offset() - c1 := bio.Bgetc(f) - c2 := bio.Bgetc(f) - c3 := bio.Bgetc(f) - c4 := bio.Bgetc(f) + c1 := bgetc(f) + c2 := bgetc(f) + c3 := bgetc(f) + c4 := bgetc(f) f.Seek(start, 0) magic := uint32(c1)<<24 | uint32(c2)<<16 | uint32(c3)<<8 | uint32(c4) @@ -1289,9 +1289,9 @@ func ldobj(f *bio.Reader, pkg string, length int64, pn string, file string, when } /* check the header */ - line := bio.Brdline(f, '\n') - if line == "" { - Diag("truncated object file: %s", pn) + line, err := f.ReadString('\n') + if err != nil { + Diag("truncated object file: %s: %v", pn, err) return nil } @@ -1336,13 +1336,13 @@ func ldobj(f *bio.Reader, pkg string, length int64, pn string, file string, when import0 := f.Offset() c1 = '\n' // the last line ended in \n - c2 = bio.Bgetc(f) - c3 = bio.Bgetc(f) + c2 = bgetc(f) + c3 = bgetc(f) for c1 != '\n' || c2 != '!' || c3 != '\n' { c1 = c2 c2 = c3 - c3 = bio.Bgetc(f) - if c3 == bio.EOF { + c3 = bgetc(f) + if c3 == -1 { Diag("truncated object file: %s", pn) return nil } @@ -2133,3 +2133,14 @@ func Rnd(v int64, r int64) int64 { v -= c return v } + +func bgetc(r *bio.Reader) int { + c, err := r.ReadByte() + if err != nil { + if err != io.EOF { + log.Fatalf("reading input: %v", err) + } + return -1 + } + return int(c) +} -- cgit v1.3 From c31fdd4ee9fccd24a274cebd82dcc7123ad43d0e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 9 Apr 2016 22:54:26 +0000 Subject: cmd/go: fix typo in comment Thanks to deafgoat. Fixes #15215 Change-Id: I9fababc7ecd201ce86020a438e4faee95e7623a8 Reviewed-on: https://go-review.googlesource.com/21792 Reviewed-by: Emmanuel Odeke Reviewed-by: Brad Fitzpatrick --- src/cmd/go/pkg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go index 30ef02beff..9b0c657236 100644 --- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -658,7 +658,7 @@ func disallowVendorVisibility(srcDir string, p *Package, stk *importStack) *Pack // findVendor looks for the last non-terminating "vendor" path element in the given import path. // If there isn't one, findVendor returns ok=false. -// Otherwise, findInternal returns ok=true and the index of the "vendor". +// Otherwise, findVendor returns ok=true and the index of the "vendor". // // Note that terminating "vendor" elements don't count: "x/vendor" is its own package, // not the vendored copy of an import "" (the empty import path). -- cgit v1.3 From 824d8c10fe5e1026c15cbece41ee372b1fd333f3 Mon Sep 17 00:00:00 2001 From: Wisdom Omuya Date: Sat, 9 Apr 2016 19:23:01 -0400 Subject: cmd/go: fix typo in findInternal documentation Fixes #15217 Change-Id: Ib8f7af714197fd209e743f61f28a5b07c04a7f5c Reviewed-on: https://go-review.googlesource.com/21793 Reviewed-by: Brad Fitzpatrick --- src/cmd/go/pkg.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go index 9b0c657236..f330b4db43 100644 --- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -523,7 +523,7 @@ func disallowInternal(srcDir string, p *Package, stk *importStack) *Package { return p } - // Check for "internal" element: four cases depending on begin of string and/or end of string. + // Check for "internal" element: three cases depending on begin of string and/or end of string. i, ok := findInternal(p.ImportPath) if !ok { return p @@ -560,7 +560,7 @@ func disallowInternal(srcDir string, p *Package, stk *importStack) *Package { // If there isn't one, findInternal returns ok=false. // Otherwise, findInternal returns ok=true and the index of the "internal". func findInternal(path string) (index int, ok bool) { - // Four cases, depending on internal at start/end of string or not. + // Three cases, depending on internal at start/end of string or not. // The order matters: we must return the index of the final element, // because the final one produces the most restrictive requirement // on the importer. -- cgit v1.3 From de7ee57c7ead59899d5b412a839c995de0e813b5 Mon Sep 17 00:00:00 2001 From: Marvin Stenger Date: Fri, 8 Apr 2016 18:19:10 +0200 Subject: cmd: remove bio.Bread Replace calls to bio.Bread with calls to io.ReadFull. Change-Id: I2ee8739d01e04a4da9c20b6ce7d1d5b89914b8ad Reviewed-on: https://go-review.googlesource.com/21750 Reviewed-by: Dave Cheney --- src/cmd/internal/bio/buf.go | 11 ----------- src/cmd/link/internal/ld/ar.go | 10 +++++----- src/cmd/link/internal/ld/go.go | 3 ++- src/cmd/link/internal/ld/ldelf.go | 10 ++++++---- src/cmd/link/internal/ld/ldmacho.go | 34 +++++++++++++++++++++++++--------- src/cmd/link/internal/ld/ldpe.go | 14 +++++++++----- src/cmd/link/internal/ld/lib.go | 21 +++++++++++++-------- 7 files changed, 60 insertions(+), 43 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/internal/bio/buf.go b/src/cmd/internal/bio/buf.go index 6a5d821d45..7a077041c2 100644 --- a/src/cmd/internal/bio/buf.go +++ b/src/cmd/internal/bio/buf.go @@ -7,7 +7,6 @@ package bio import ( "bufio" - "io" "log" "os" ) @@ -86,16 +85,6 @@ func (w *Writer) Offset() int64 { return off } -func Bread(r *Reader, p []byte) int { - n, err := io.ReadFull(r, p) - if n == 0 { - if err != nil && err != io.EOF { - n = -1 - } - } - return n -} - func (r *Reader) Close() error { return r.f.Close() } diff --git a/src/cmd/link/internal/ld/ar.go b/src/cmd/link/internal/ld/ar.go index f9357392d7..323dfbefc5 100644 --- a/src/cmd/link/internal/ld/ar.go +++ b/src/cmd/link/internal/ld/ar.go @@ -35,6 +35,7 @@ import ( "cmd/internal/obj" "encoding/binary" "fmt" + "io" "os" ) @@ -76,8 +77,8 @@ func hostArchive(name string) { } defer f.Close() - magbuf := make([]byte, len(ARMAG)) - if bio.Bread(f, magbuf) != len(magbuf) { + var magbuf [len(ARMAG)]byte + if _, err := io.ReadFull(f, magbuf[:]); err != nil { Exitf("file %s too short", name) } @@ -138,9 +139,8 @@ func readArmap(filename string, f *bio.Reader, arhdr ArHdr) archiveMap { wordSize = 8 } - l := atolwhex(arhdr.size) - contents := make([]byte, l) - if bio.Bread(f, contents) != int(l) { + contents := make([]byte, atolwhex(arhdr.size)) + if _, err := io.ReadFull(f, contents); err != nil { Exitf("short read from %s", filename) } diff --git a/src/cmd/link/internal/ld/go.go b/src/cmd/link/internal/ld/go.go index 3af5f7a046..425c75571f 100644 --- a/src/cmd/link/internal/ld/go.go +++ b/src/cmd/link/internal/ld/go.go @@ -11,6 +11,7 @@ import ( "cmd/internal/bio" "cmd/internal/obj" "fmt" + "io" "os" "strings" ) @@ -49,7 +50,7 @@ func ldpkg(f *bio.Reader, pkg string, length int64, filename string, whence int) } bdata := make([]byte, length) - if int64(bio.Bread(f, bdata)) != length { + if _, err := io.ReadFull(f, bdata); err != nil { fmt.Fprintf(os.Stderr, "%s: short pkg read %s\n", os.Args[0], filename) if Debug['u'] != 0 { errorexit() diff --git a/src/cmd/link/internal/ld/ldelf.go b/src/cmd/link/internal/ld/ldelf.go index 1c55daa392..d9581a5189 100644 --- a/src/cmd/link/internal/ld/ldelf.go +++ b/src/cmd/link/internal/ld/ldelf.go @@ -476,7 +476,7 @@ func ldelf(f *bio.Reader, pkg string, length int64, pn string) { var sect *ElfSect var sym ElfSym var symbols []*LSym - if bio.Bread(f, hdrbuf[:]) != len(hdrbuf) { + if _, err := io.ReadFull(f, hdrbuf[:]); err != nil { goto bad } hdr = new(ElfHdrBytes) @@ -986,9 +986,11 @@ func elfmap(elfobj *ElfObj, sect *ElfSect) (err error) { } sect.base = make([]byte, sect.size) - err = fmt.Errorf("short read") - if elfobj.f.Seek(int64(uint64(elfobj.base)+sect.off), 0) < 0 || bio.Bread(elfobj.f, sect.base) != len(sect.base) { - return err + if elfobj.f.Seek(int64(uint64(elfobj.base)+sect.off), 0) < 0 { + return fmt.Errorf("short read: seek not successful") + } + if _, err := io.ReadFull(elfobj.f, sect.base); err != nil { + return fmt.Errorf("short read: %v", err) } return nil diff --git a/src/cmd/link/internal/ld/ldmacho.go b/src/cmd/link/internal/ld/ldmacho.go index dffe6f69ce..8dc4033bbc 100644 --- a/src/cmd/link/internal/ld/ldmacho.go +++ b/src/cmd/link/internal/ld/ldmacho.go @@ -6,6 +6,7 @@ import ( "cmd/internal/sys" "encoding/binary" "fmt" + "io" "log" "sort" ) @@ -299,7 +300,10 @@ func macholoadrel(m *LdMachoObj, sect *LdMachoSect) int { rel := make([]LdMachoRel, sect.nreloc) n := int(sect.nreloc * 8) buf := make([]byte, n) - if m.f.Seek(m.base+int64(sect.reloff), 0) < 0 || bio.Bread(m.f, buf) != n { + if m.f.Seek(m.base+int64(sect.reloff), 0) < 0 { + return -1 + } + if _, err := io.ReadFull(m.f, buf); err != nil { return -1 } var p []byte @@ -345,7 +349,10 @@ func macholoaddsym(m *LdMachoObj, d *LdMachoDysymtab) int { n := int(d.nindirectsyms) p := make([]byte, n*4) - if m.f.Seek(m.base+int64(d.indirectsymoff), 0) < 0 || bio.Bread(m.f, p) != len(p) { + if m.f.Seek(m.base+int64(d.indirectsymoff), 0) < 0 { + return -1 + } + if _, err := io.ReadFull(m.f, p); err != nil { return -1 } @@ -362,7 +369,10 @@ func macholoadsym(m *LdMachoObj, symtab *LdMachoSymtab) int { } strbuf := make([]byte, symtab.strsize) - if m.f.Seek(m.base+int64(symtab.stroff), 0) < 0 || bio.Bread(m.f, strbuf) != len(strbuf) { + if m.f.Seek(m.base+int64(symtab.stroff), 0) < 0 { + return -1 + } + if _, err := io.ReadFull(m.f, strbuf); err != nil { return -1 } @@ -372,7 +382,10 @@ func macholoadsym(m *LdMachoObj, symtab *LdMachoSymtab) int { } n := int(symtab.nsym * uint32(symsize)) symbuf := make([]byte, n) - if m.f.Seek(m.base+int64(symtab.symoff), 0) < 0 || bio.Bread(m.f, symbuf) != len(symbuf) { + if m.f.Seek(m.base+int64(symtab.symoff), 0) < 0 { + return -1 + } + if _, err := io.ReadFull(m.f, symbuf); err != nil { return -1 } sym := make([]LdMachoSym, symtab.nsym) @@ -433,7 +446,7 @@ func ldmacho(f *bio.Reader, pkg string, length int64, pn string) { Ctxt.IncVersion() base := f.Offset() - if bio.Bread(f, hdr[:]) != len(hdr) { + if _, err := io.ReadFull(f, hdr[:]); err != nil { goto bad } @@ -455,8 +468,7 @@ func ldmacho(f *bio.Reader, pkg string, length int64, pn string) { } if is64 { - var tmp [4]uint8 - bio.Bread(f, tmp[:4]) // skip reserved word in header + f.Seek(4, 1) // skip reserved word in header } m = new(LdMachoObj) @@ -494,7 +506,7 @@ func ldmacho(f *bio.Reader, pkg string, length int64, pn string) { m.cmd = make([]LdMachoCmd, ncmd) off = uint32(len(hdr)) cmdp = make([]byte, cmdsz) - if bio.Bread(f, cmdp) != len(cmdp) { + if _, err2 := io.ReadFull(f, cmdp); err2 != nil { err = fmt.Errorf("reading cmds: %v", err) goto bad } @@ -557,7 +569,11 @@ func ldmacho(f *bio.Reader, pkg string, length int64, pn string) { } dat = make([]byte, c.seg.filesz) - if f.Seek(m.base+int64(c.seg.fileoff), 0) < 0 || bio.Bread(f, dat) != len(dat) { + if f.Seek(m.base+int64(c.seg.fileoff), 0) < 0 { + err = fmt.Errorf("cannot load object data: %v", err) + goto bad + } + if _, err2 := io.ReadFull(f, dat); err2 != nil { err = fmt.Errorf("cannot load object data: %v", err) goto bad } diff --git a/src/cmd/link/internal/ld/ldpe.go b/src/cmd/link/internal/ld/ldpe.go index 37a550d5c9..7f7121ff94 100644 --- a/src/cmd/link/internal/ld/ldpe.go +++ b/src/cmd/link/internal/ld/ldpe.go @@ -10,6 +10,7 @@ import ( "cmd/internal/sys" "encoding/binary" "fmt" + "io" "log" "sort" "strconv" @@ -176,13 +177,13 @@ func ldpe(f *bio.Reader, pkg string, length int64, pn string) { // load string table f.Seek(int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) - if bio.Bread(f, symbuf[:4]) != 4 { + if _, err := io.ReadFull(f, symbuf[:4]); err != nil { goto bad } l = Le32(symbuf[:]) peobj.snames = make([]byte, l) f.Seek(int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(peobj.fh.NumberOfSymbols), 0) - if bio.Bread(f, peobj.snames) != len(peobj.snames) { + if _, err := io.ReadFull(f, peobj.snames); err != nil { goto bad } @@ -205,7 +206,7 @@ func ldpe(f *bio.Reader, pkg string, length int64, pn string) { f.Seek(int64(base)+int64(peobj.fh.PointerToSymbolTable), 0) for i := 0; uint32(i) < peobj.fh.NumberOfSymbols; i += numaux + 1 { f.Seek(int64(base)+int64(peobj.fh.PointerToSymbolTable)+int64(len(symbuf))*int64(i), 0) - if bio.Bread(f, symbuf[:]) != len(symbuf) { + if _, err := io.ReadFull(f, symbuf[:]); err != nil { goto bad } @@ -293,7 +294,7 @@ func ldpe(f *bio.Reader, pkg string, length int64, pn string) { f.Seek(int64(peobj.base)+int64(rsect.sh.PointerToRelocations), 0) for j = 0; j < int(rsect.sh.NumberOfRelocations); j++ { rp = &r[j] - if bio.Bread(f, symbuf[:10]) != 10 { + if _, err := io.ReadFull(f, symbuf[:10]); err != nil { goto bad } rva := Le32(symbuf[0:]) @@ -466,7 +467,10 @@ func pemap(peobj *PeObj, sect *PeSect) int { if sect.sh.PointerToRawData == 0 { // .bss doesn't have data in object file return 0 } - if peobj.f.Seek(int64(peobj.base)+int64(sect.sh.PointerToRawData), 0) < 0 || bio.Bread(peobj.f, sect.base) != len(sect.base) { + if peobj.f.Seek(int64(peobj.base)+int64(sect.sh.PointerToRawData), 0) < 0 { + return -1 + } + if _, err := io.ReadFull(peobj.f, sect.base); err != nil { return -1 } diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 6e33ec3b05..1f2df8b9c5 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -745,12 +745,12 @@ func nextar(bp *bio.Reader, off int64, a *ArHdr) int64 { off++ } bp.Seek(off, 0) - buf := make([]byte, SAR_HDR) - if n := bio.Bread(bp, buf); n < len(buf) { - if n >= 0 { - return 0 + var buf [SAR_HDR]byte + if n, err := io.ReadFull(bp, buf[:]); err != nil { + if n == 0 && err != io.EOF { + return -1 } - return -1 + return 0 } a.name = artrim(buf[0:16]) @@ -780,8 +780,11 @@ func objfile(lib *Library) { Exitf("cannot open file %s: %v", lib.File, err) } - magbuf := make([]byte, len(ARMAG)) - if bio.Bread(f, magbuf) != len(magbuf) || !strings.HasPrefix(string(magbuf), ARMAG) { + for i := 0; i < len(ARMAG); i++ { + if c, err := f.ReadByte(); err == nil && c == ARMAG[i] { + continue + } + /* load it as a regular file */ l := f.Seek(0, 2) @@ -811,7 +814,9 @@ func objfile(lib *Library) { if Buildmode == BuildmodeShared { before := f.Offset() pkgdefBytes := make([]byte, atolwhex(arhdr.size)) - bio.Bread(f, pkgdefBytes) + if _, err := io.ReadFull(f, pkgdefBytes); err != nil { + Diag("%s: short read on archive file symbol header: %v", lib.File, err) + } hash := sha1.Sum(pkgdefBytes) lib.hash = hash[:] f.Seek(before, 0) -- cgit v1.3 From 012557b3769f9286b9488fbfd4bddfeee66b6a55 Mon Sep 17 00:00:00 2001 From: Martin Möhrmann Date: Sun, 10 Apr 2016 08:48:55 +0200 Subject: all: replace magic 0x80 with named constant utf8.RuneSelf Change-Id: Id1c2e8e9d60588de866e8b6ca59cc83dd28f848f Reviewed-on: https://go-review.googlesource.com/21756 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/bufio/bufio.go | 2 +- src/cmd/compile/internal/gc/fmt.go | 2 +- src/encoding/asn1/asn1.go | 2 +- src/go/build/build.go | 2 +- src/go/build/read.go | 3 ++- src/go/scanner/scanner.go | 6 +++--- src/html/template/css.go | 2 +- src/net/http/cookiejar/punycode.go | 2 +- 8 files changed, 11 insertions(+), 10 deletions(-) (limited to 'src/cmd') diff --git a/src/bufio/bufio.go b/src/bufio/bufio.go index d2ccc74f52..3b30b8b80c 100644 --- a/src/bufio/bufio.go +++ b/src/bufio/bufio.go @@ -266,7 +266,7 @@ func (b *Reader) ReadRune() (r rune, size int, err error) { return 0, 0, b.readErr() } r, size = rune(b.buf[b.r]), 1 - if r >= 0x80 { + if r >= utf8.RuneSelf { r, size = utf8.DecodeRune(b.buf[b.r:b.w]) } b.r += size diff --git a/src/cmd/compile/internal/gc/fmt.go b/src/cmd/compile/internal/gc/fmt.go index 19f109055d..41d696574c 100644 --- a/src/cmd/compile/internal/gc/fmt.go +++ b/src/cmd/compile/internal/gc/fmt.go @@ -337,7 +337,7 @@ func Vconv(v Val, flag FmtFlag) string { case CTRUNE: x := v.U.(*Mpint).Int64() - if ' ' <= x && x < 0x80 && x != '\\' && x != '\'' { + if ' ' <= x && x < utf8.RuneSelf && x != '\\' && x != '\'' { return fmt.Sprintf("'%c'", int(x)) } if 0 <= x && x < 1<<16 { diff --git a/src/encoding/asn1/asn1.go b/src/encoding/asn1/asn1.go index bd2c96d887..2b5ad08551 100644 --- a/src/encoding/asn1/asn1.go +++ b/src/encoding/asn1/asn1.go @@ -393,7 +393,7 @@ func isPrintable(b byte) bool { // byte slice and returns it. func parseIA5String(bytes []byte) (ret string, err error) { for _, b := range bytes { - if b >= 0x80 { + if b >= utf8.RuneSelf { err = SyntaxError{"IA5String contains invalid character"} return } diff --git a/src/go/build/build.go b/src/go/build/build.go index e61d564fa3..04a41a6c2e 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -1266,7 +1266,7 @@ func safeCgoName(s string, spaces bool) bool { safe = safe[len(safeSpaces):] } for i := 0; i < len(s); i++ { - if c := s[i]; c < 0x80 && bytes.IndexByte(safe, c) < 0 { + if c := s[i]; c < utf8.RuneSelf && bytes.IndexByte(safe, c) < 0 { return false } } diff --git a/src/go/build/read.go b/src/go/build/read.go index d411c1980e..29b8cdc786 100644 --- a/src/go/build/read.go +++ b/src/go/build/read.go @@ -8,6 +8,7 @@ import ( "bufio" "errors" "io" + "unicode/utf8" ) type importReader struct { @@ -20,7 +21,7 @@ type importReader struct { } func isIdent(c byte) bool { - return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= 0x80 + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf } var ( diff --git a/src/go/scanner/scanner.go b/src/go/scanner/scanner.go index 4041d9aa47..ce660c71d5 100644 --- a/src/go/scanner/scanner.go +++ b/src/go/scanner/scanner.go @@ -64,7 +64,7 @@ func (s *Scanner) next() { switch { case r == 0: s.error(s.offset, "illegal character NUL") - case r >= 0x80: + case r >= utf8.RuneSelf: // not ASCII r, w = utf8.DecodeRune(s.src[s.rdOffset:]) if r == utf8.RuneError && w == 1 { @@ -255,11 +255,11 @@ func (s *Scanner) findLineEnd() bool { } func isLetter(ch rune) bool { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) } func isDigit(ch rune) bool { - return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) + return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch) } func (s *Scanner) scanIdentifier() string { diff --git a/src/html/template/css.go b/src/html/template/css.go index 4c27cce85a..9154d8636d 100644 --- a/src/html/template/css.go +++ b/src/html/template/css.go @@ -243,7 +243,7 @@ func cssValueFilter(args ...interface{}) string { return filterFailsafe } default: - if c < 0x80 && isCSSNmchar(rune(c)) { + if c < utf8.RuneSelf && isCSSNmchar(rune(c)) { id = append(id, c) } } diff --git a/src/net/http/cookiejar/punycode.go b/src/net/http/cookiejar/punycode.go index ea7ceb5ef3..a9cc666e8c 100644 --- a/src/net/http/cookiejar/punycode.go +++ b/src/net/http/cookiejar/punycode.go @@ -37,7 +37,7 @@ func encode(prefix, s string) (string, error) { delta, n, bias := int32(0), initialN, initialBias b, remaining := int32(0), int32(0) for _, r := range s { - if r < 0x80 { + if r < utf8.RuneSelf { b++ output = append(output, byte(r)) } else { -- cgit v1.3 From 6b33b0e98e9be77d98b026ae2adf10dd71be5a1b Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Sun, 10 Apr 2016 09:08:00 -0700 Subject: cmd/compile: avoid a spill in append fast path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of spilling newlen, recalculate it. This removes a spill from the fast path, at the cost of a cheap recalculation on the (rare) growth path. This uses 8 bytes less of stack space. It generates two more bytes of code, but that is due to suboptimal register allocation; see far below. Runtime append microbenchmarks are all over the map, presumably due to incidental code movement. Sample code: func s(b []byte) []byte { b = append(b, 1, 2, 3) return b } Before: "".s t=1 size=160 args=0x30 locals=0x48 0x0000 00000 (append.go:8) TEXT "".s(SB), $72-48 0x0000 00000 (append.go:8) MOVQ (TLS), CX 0x0009 00009 (append.go:8) CMPQ SP, 16(CX) 0x000d 00013 (append.go:8) JLS 149 0x0013 00019 (append.go:8) SUBQ $72, SP 0x0017 00023 (append.go:8) FUNCDATA $0, gclocals·6432f8c6a0d23fa7bee6c5d96f21a92a(SB) 0x0017 00023 (append.go:8) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0017 00023 (append.go:9) MOVQ "".b+88(FP), CX 0x001c 00028 (append.go:9) LEAQ 3(CX), DX 0x0020 00032 (append.go:9) MOVQ DX, "".autotmp_0+64(SP) 0x0025 00037 (append.go:9) MOVQ "".b+96(FP), BX 0x002a 00042 (append.go:9) CMPQ DX, BX 0x002d 00045 (append.go:9) JGT $0, 86 0x002f 00047 (append.go:8) MOVQ "".b+80(FP), AX 0x0034 00052 (append.go:9) MOVB $1, (AX)(CX*1) 0x0038 00056 (append.go:9) MOVB $2, 1(AX)(CX*1) 0x003d 00061 (append.go:9) MOVB $3, 2(AX)(CX*1) 0x0042 00066 (append.go:10) MOVQ AX, "".~r1+104(FP) 0x0047 00071 (append.go:10) MOVQ DX, "".~r1+112(FP) 0x004c 00076 (append.go:10) MOVQ BX, "".~r1+120(FP) 0x0051 00081 (append.go:10) ADDQ $72, SP 0x0055 00085 (append.go:10) RET 0x0056 00086 (append.go:9) LEAQ type.[]uint8(SB), AX 0x005d 00093 (append.go:9) MOVQ AX, (SP) 0x0061 00097 (append.go:9) MOVQ "".b+80(FP), BP 0x0066 00102 (append.go:9) MOVQ BP, 8(SP) 0x006b 00107 (append.go:9) MOVQ CX, 16(SP) 0x0070 00112 (append.go:9) MOVQ BX, 24(SP) 0x0075 00117 (append.go:9) MOVQ DX, 32(SP) 0x007a 00122 (append.go:9) PCDATA $0, $0 0x007a 00122 (append.go:9) CALL runtime.growslice(SB) 0x007f 00127 (append.go:9) MOVQ 40(SP), AX 0x0084 00132 (append.go:9) MOVQ 56(SP), BX 0x0089 00137 (append.go:8) MOVQ "".b+88(FP), CX 0x008e 00142 (append.go:9) MOVQ "".autotmp_0+64(SP), DX 0x0093 00147 (append.go:9) JMP 52 0x0095 00149 (append.go:9) NOP 0x0095 00149 (append.go:8) CALL runtime.morestack_noctxt(SB) 0x009a 00154 (append.go:8) JMP 0 After: "".s t=1 size=176 args=0x30 locals=0x40 0x0000 00000 (append.go:8) TEXT "".s(SB), $64-48 0x0000 00000 (append.go:8) MOVQ (TLS), CX 0x0009 00009 (append.go:8) CMPQ SP, 16(CX) 0x000d 00013 (append.go:8) JLS 151 0x0013 00019 (append.go:8) SUBQ $64, SP 0x0017 00023 (append.go:8) FUNCDATA $0, gclocals·6432f8c6a0d23fa7bee6c5d96f21a92a(SB) 0x0017 00023 (append.go:8) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0017 00023 (append.go:9) MOVQ "".b+80(FP), CX 0x001c 00028 (append.go:9) LEAQ 3(CX), DX 0x0020 00032 (append.go:9) MOVQ "".b+88(FP), BX 0x0025 00037 (append.go:9) CMPQ DX, BX 0x0028 00040 (append.go:9) JGT $0, 81 0x002a 00042 (append.go:8) MOVQ "".b+72(FP), AX 0x002f 00047 (append.go:9) MOVB $1, (AX)(CX*1) 0x0033 00051 (append.go:9) MOVB $2, 1(AX)(CX*1) 0x0038 00056 (append.go:9) MOVB $3, 2(AX)(CX*1) 0x003d 00061 (append.go:10) MOVQ AX, "".~r1+96(FP) 0x0042 00066 (append.go:10) MOVQ DX, "".~r1+104(FP) 0x0047 00071 (append.go:10) MOVQ BX, "".~r1+112(FP) 0x004c 00076 (append.go:10) ADDQ $64, SP 0x0050 00080 (append.go:10) RET 0x0051 00081 (append.go:9) LEAQ type.[]uint8(SB), AX 0x0058 00088 (append.go:9) MOVQ AX, (SP) 0x005c 00092 (append.go:9) MOVQ "".b+72(FP), BP 0x0061 00097 (append.go:9) MOVQ BP, 8(SP) 0x0066 00102 (append.go:9) MOVQ CX, 16(SP) 0x006b 00107 (append.go:9) MOVQ BX, 24(SP) 0x0070 00112 (append.go:9) MOVQ DX, 32(SP) 0x0075 00117 (append.go:9) PCDATA $0, $0 0x0075 00117 (append.go:9) CALL runtime.growslice(SB) 0x007a 00122 (append.go:9) MOVQ 40(SP), AX 0x007f 00127 (append.go:9) MOVQ 48(SP), CX 0x0084 00132 (append.go:9) MOVQ 56(SP), BX 0x0089 00137 (append.go:9) ADDQ $3, CX 0x008d 00141 (append.go:9) MOVQ CX, DX 0x0090 00144 (append.go:8) MOVQ "".b+80(FP), CX 0x0095 00149 (append.go:9) JMP 47 0x0097 00151 (append.go:9) NOP 0x0097 00151 (append.go:8) CALL runtime.morestack_noctxt(SB) 0x009c 00156 (append.go:8) JMP 0 Observe that in the following sequence, we should use DX directly instead of using CX as a temporary register, which would make the new code a strict improvement on the old: 0x007f 00127 (append.go:9) MOVQ 48(SP), CX 0x0084 00132 (append.go:9) MOVQ 56(SP), BX 0x0089 00137 (append.go:9) ADDQ $3, CX 0x008d 00141 (append.go:9) MOVQ CX, DX 0x0090 00144 (append.go:8) MOVQ "".b+80(FP), CX Change-Id: I4ee50b18fa53865901d2d7f86c2cbb54c6fa6924 Reviewed-on: https://go-review.googlesource.com/21812 Run-TryBot: Josh Bleecher Snyder TryBot-Result: Gobot Gobot Reviewed-by: Keith Randall --- src/cmd/compile/internal/gc/ssa.go | 30 +++++++++++++++++------------- src/runtime/slice.go | 6 ++++++ 2 files changed, 23 insertions(+), 13 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 7c5f906d76..d69559d945 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -337,12 +337,13 @@ var ( memVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "mem"}} // dummy nodes for temporary variables - ptrVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "ptr"}} - capVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "cap"}} - typVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "typ"}} - idataVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "idata"}} - okVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "ok"}} - deltaVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "delta"}} + ptrVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "ptr"}} + newlenVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "newlen"}} + capVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "cap"}} + typVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "typ"}} + idataVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "idata"}} + okVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "ok"}} + deltaVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "delta"}} ) // startBlock sets the current block we're generating code in to b. @@ -2089,15 +2090,16 @@ func (s *state) expr(n *Node) *ssa.Value { // exprAppend converts an OAPPEND node n to an ssa.Value, adds it to s, and returns the Value. func (s *state) exprAppend(n *Node) *ssa.Value { // append(s, e1, e2, e3). Compile like: - // ptr,len,cap := s + // ptr, len, cap := s // newlen := len + 3 // if newlen > s.cap { - // ptr,_,cap = growslice(s, newlen) + // ptr, len, cap = growslice(s, newlen) + // newlen = len + 3 // recalculate to avoid a spill // } // *(ptr+len) = e1 // *(ptr+len+1) = e2 // *(ptr+len+2) = e3 - // makeslice(ptr,newlen,cap) + // makeslice(ptr, newlen, cap) et := n.Type.Elem() pt := Ptrto(et) @@ -2117,6 +2119,7 @@ func (s *state) exprAppend(n *Node) *ssa.Value { nl := s.newValue2(s.ssaOp(OADD, Types[TINT]), Types[TINT], l, s.constInt(Types[TINT], nargs)) cmp := s.newValue2(s.ssaOp(OGT, Types[TINT]), Types[TBOOL], nl, c) s.vars[&ptrVar] = p + s.vars[&newlenVar] = nl s.vars[&capVar] = c b := s.endBlock() b.Kind = ssa.BlockIf @@ -2132,8 +2135,7 @@ func (s *state) exprAppend(n *Node) *ssa.Value { r := s.rtcall(growslice, true, []*Type{pt, Types[TINT], Types[TINT]}, taddr, p, l, c, nl) s.vars[&ptrVar] = r[0] - // Note: we don't need to read r[1], the result's length. It will be nl. - // (or maybe we should, we just have to spill/restore nl otherwise?) + s.vars[&newlenVar] = s.newValue2(s.ssaOp(OADD, Types[TINT]), Types[TINT], r[1], s.constInt(Types[TINT], nargs)) s.vars[&capVar] = r[2] b = s.endBlock() b.AddEdgeTo(assign) @@ -2154,8 +2156,9 @@ func (s *state) exprAppend(n *Node) *ssa.Value { } } - p = s.variable(&ptrVar, pt) // generates phi for ptr - c = s.variable(&capVar, Types[TINT]) // generates phi for cap + p = s.variable(&ptrVar, pt) // generates phi for ptr + nl = s.variable(&newlenVar, Types[TINT]) // generates phi for nl + c = s.variable(&capVar, Types[TINT]) // generates phi for cap p2 := s.newValue2(ssa.OpPtrIndex, pt, p, l) // TODO: just one write barrier call for all of these writes? // TODO: maybe just one writeBarrier.enabled check? @@ -2178,6 +2181,7 @@ func (s *state) exprAppend(n *Node) *ssa.Value { // make result delete(s.vars, &ptrVar) + delete(s.vars, &newlenVar) delete(s.vars, &capVar) return s.newValue3(ssa.OpSliceMake, n.Type, p, nl, c) } diff --git a/src/runtime/slice.go b/src/runtime/slice.go index 0bc0299f72..4ab221056c 100644 --- a/src/runtime/slice.go +++ b/src/runtime/slice.go @@ -37,6 +37,12 @@ func makeslice(t *slicetype, len64, cap64 int64) slice { // It is passed the slice type, the old slice, and the desired new minimum capacity, // and it returns a new slice with at least that capacity, with the old data // copied into it. +// The new slice's length is set to the old slice's length, +// NOT to the new requested capacity. +// This is for codegen convenience. The old slice's length is used immediately +// to calculate where to write new values during an append. +// TODO: When the old backend is gone, reconsider this decision. +// The SSA backend might prefer the new length or to return only ptr/cap and save stack space. func growslice(t *slicetype, old slice, cap int) slice { if raceenabled { callerpc := getcallerpc(unsafe.Pointer(&t)) -- cgit v1.3 From 0004f34cefcdaad13a5131e3494fb2ff04877cd2 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Sun, 10 Apr 2016 08:26:43 -0700 Subject: cmd/compile: regalloc enforces 2-address instructions Instead of being a hint, resultInArg0 is now enforced by regalloc. This allows us to delete all the code from amd64/ssa.go which deals with converting from a semantically three-address instruction into some copies plus a two-address instruction. Change-Id: Id4f39a80be4b678718bfd42a229f9094ab6ecd7c Reviewed-on: https://go-review.googlesource.com/21816 Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/amd64/ssa.go | 240 ++++---------------- src/cmd/compile/internal/ssa/gen/AMD64.rules | 6 + src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 28 +-- src/cmd/compile/internal/ssa/gen/main.go | 8 +- src/cmd/compile/internal/ssa/op.go | 2 +- src/cmd/compile/internal/ssa/opGen.go | 328 +++++++++++++-------------- src/cmd/compile/internal/ssa/regalloc.go | 47 +++- src/cmd/compile/internal/ssa/rewriteAMD64.go | 47 ++++ 8 files changed, 310 insertions(+), 396 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index 3f8e0ece12..723a2ddec5 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -192,74 +192,23 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = r } - // 2-address opcode arithmetic, symmetric - case ssa.OpAMD64ADDSS, ssa.OpAMD64ADDSD, + // 2-address opcode arithmetic + case ssa.OpAMD64SUBQ, ssa.OpAMD64SUBL, ssa.OpAMD64SUBW, ssa.OpAMD64SUBB, + ssa.OpAMD64MULQ, ssa.OpAMD64MULL, ssa.OpAMD64MULW, ssa.OpAMD64MULB, ssa.OpAMD64ANDQ, ssa.OpAMD64ANDL, ssa.OpAMD64ANDW, ssa.OpAMD64ANDB, ssa.OpAMD64ORQ, ssa.OpAMD64ORL, ssa.OpAMD64ORW, ssa.OpAMD64ORB, ssa.OpAMD64XORQ, ssa.OpAMD64XORL, ssa.OpAMD64XORW, ssa.OpAMD64XORB, - ssa.OpAMD64MULQ, ssa.OpAMD64MULL, ssa.OpAMD64MULW, ssa.OpAMD64MULB, - ssa.OpAMD64MULSS, ssa.OpAMD64MULSD, ssa.OpAMD64PXOR: - r := gc.SSARegNum(v) - x := gc.SSARegNum(v.Args[0]) - y := gc.SSARegNum(v.Args[1]) - if x != r && y != r { - opregreg(moveByType(v.Type), r, x) - x = r - } - p := gc.Prog(v.Op.Asm()) - p.From.Type = obj.TYPE_REG - p.To.Type = obj.TYPE_REG - p.To.Reg = r - if x == r { - p.From.Reg = y - } else { - p.From.Reg = x - } - // 2-address opcode arithmetic, not symmetric - case ssa.OpAMD64SUBQ, ssa.OpAMD64SUBL, ssa.OpAMD64SUBW, ssa.OpAMD64SUBB: - r := gc.SSARegNum(v) - x := gc.SSARegNum(v.Args[0]) - y := gc.SSARegNum(v.Args[1]) - var neg bool - if y == r { - // compute -(y-x) instead - x, y = y, x - neg = true - } - if x != r { - opregreg(moveByType(v.Type), r, x) - } - opregreg(v.Op.Asm(), r, y) - - if neg { - if v.Op == ssa.OpAMD64SUBQ { - p := gc.Prog(x86.ANEGQ) - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } else { // Avoids partial registers write - p := gc.Prog(x86.ANEGL) - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } - } - case ssa.OpAMD64SUBSS, ssa.OpAMD64SUBSD, ssa.OpAMD64DIVSS, ssa.OpAMD64DIVSD: + ssa.OpAMD64SHLQ, ssa.OpAMD64SHLL, ssa.OpAMD64SHLW, ssa.OpAMD64SHLB, + ssa.OpAMD64SHRQ, ssa.OpAMD64SHRL, ssa.OpAMD64SHRW, ssa.OpAMD64SHRB, + ssa.OpAMD64SARQ, ssa.OpAMD64SARL, ssa.OpAMD64SARW, ssa.OpAMD64SARB, + ssa.OpAMD64ADDSS, ssa.OpAMD64ADDSD, ssa.OpAMD64SUBSS, ssa.OpAMD64SUBSD, + ssa.OpAMD64MULSS, ssa.OpAMD64MULSD, ssa.OpAMD64DIVSS, ssa.OpAMD64DIVSD, + ssa.OpAMD64PXOR: r := gc.SSARegNum(v) - x := gc.SSARegNum(v.Args[0]) - y := gc.SSARegNum(v.Args[1]) - if y == r && x != r { - // r/y := x op r/y, need to preserve x and rewrite to - // r/y := r/y op x15 - x15 := int16(x86.REG_X15) - // register move y to x15 - // register move x to y - // rename y with x15 - opregreg(moveByType(v.Type), x15, y) - opregreg(moveByType(v.Type), r, x) - y = x15 - } else if x != r { - opregreg(moveByType(v.Type), r, x) + if r != gc.SSARegNum(v.Args[0]) { + v.Fatalf("input[0] and output not in same register %s", v.LongString()) } - opregreg(v.Op.Asm(), r, y) + opregreg(v.Op.Asm(), r, gc.SSARegNum(v.Args[1])) case ssa.OpAMD64DIVQ, ssa.OpAMD64DIVL, ssa.OpAMD64DIVW, ssa.OpAMD64DIVQU, ssa.OpAMD64DIVLU, ssa.OpAMD64DIVWU, @@ -372,47 +321,20 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { // Do a 64-bit add, the overflow goes into the carry. // Shift right once and pull the carry back into the 63rd bit. r := gc.SSARegNum(v) - x := gc.SSARegNum(v.Args[0]) - y := gc.SSARegNum(v.Args[1]) - if x != r && y != r { - opregreg(moveByType(v.Type), r, x) - x = r + if r != gc.SSARegNum(v.Args[0]) { + v.Fatalf("input[0] and output not in same register %s", v.LongString()) } p := gc.Prog(x86.AADDQ) p.From.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG p.To.Reg = r - if x == r { - p.From.Reg = y - } else { - p.From.Reg = x - } + p.From.Reg = gc.SSARegNum(v.Args[1]) p = gc.Prog(x86.ARCRQ) p.From.Type = obj.TYPE_CONST p.From.Offset = 1 p.To.Type = obj.TYPE_REG p.To.Reg = r - case ssa.OpAMD64SHLQ, ssa.OpAMD64SHLL, ssa.OpAMD64SHLW, ssa.OpAMD64SHLB, - ssa.OpAMD64SHRQ, ssa.OpAMD64SHRL, ssa.OpAMD64SHRW, ssa.OpAMD64SHRB, - ssa.OpAMD64SARQ, ssa.OpAMD64SARL, ssa.OpAMD64SARW, ssa.OpAMD64SARB: - x := gc.SSARegNum(v.Args[0]) - r := gc.SSARegNum(v) - if x != r { - if r == x86.REG_CX { - v.Fatalf("can't implement %s, target and shift both in CX", v.LongString()) - } - p := gc.Prog(moveByType(v.Type)) - p.From.Type = obj.TYPE_REG - p.From.Reg = x - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } - p := gc.Prog(v.Op.Asm()) - p.From.Type = obj.TYPE_REG - p.From.Reg = gc.SSARegNum(v.Args[1]) // should be CX - p.To.Type = obj.TYPE_REG - p.To.Reg = r case ssa.OpAMD64ADDQconst, ssa.OpAMD64ADDLconst, ssa.OpAMD64ADDWconst, ssa.OpAMD64ADDBconst: r := gc.SSARegNum(v) a := gc.SSARegNum(v.Args[0]) @@ -433,7 +355,8 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = r return - } else if v.AuxInt == -1 { + } + if v.AuxInt == -1 { var asm obj.As if v.Op == ssa.OpAMD64ADDQconst { asm = x86.ADECQ @@ -444,14 +367,13 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = r return - } else { - p := gc.Prog(v.Op.Asm()) - p.From.Type = obj.TYPE_CONST - p.From.Offset = v.AuxInt - p.To.Type = obj.TYPE_REG - p.To.Reg = r - return } + p := gc.Prog(v.Op.Asm()) + p.From.Type = obj.TYPE_CONST + p.From.Offset = v.AuxInt + p.To.Type = obj.TYPE_REG + p.To.Reg = r + return } var asm obj.As if v.Op == ssa.OpAMD64ADDQconst { @@ -469,17 +391,11 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpAMD64CMOVQEQconst, ssa.OpAMD64CMOVLEQconst, ssa.OpAMD64CMOVWEQconst, ssa.OpAMD64CMOVQNEconst, ssa.OpAMD64CMOVLNEconst, ssa.OpAMD64CMOVWNEconst: r := gc.SSARegNum(v) - x := gc.SSARegNum(v.Args[0]) - // Arg0 is in/out, move in to out if not already same - if r != x { - p := gc.Prog(moveByType(v.Type)) - p.From.Type = obj.TYPE_REG - p.From.Reg = x - p.To.Type = obj.TYPE_REG - p.To.Reg = r + if r != gc.SSARegNum(v.Args[0]) { + v.Fatalf("input[0] and output not in same register %s", v.LongString()) } - // Constant into AX, after arg0 movement in case arg0 is in AX + // Constant into AX p := gc.Prog(moveByType(v.Type)) p.From.Type = obj.TYPE_CONST p.From.Offset = v.AuxInt @@ -494,13 +410,8 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpAMD64MULQconst, ssa.OpAMD64MULLconst, ssa.OpAMD64MULWconst, ssa.OpAMD64MULBconst: r := gc.SSARegNum(v) - x := gc.SSARegNum(v.Args[0]) - if r != x { - p := gc.Prog(moveByType(v.Type)) - p.From.Type = obj.TYPE_REG - p.From.Reg = x - p.To.Type = obj.TYPE_REG - p.To.Reg = r + if r != gc.SSARegNum(v.Args[0]) { + v.Fatalf("input[0] and output not in same register %s", v.LongString()) } p := gc.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST @@ -508,87 +419,22 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = r // TODO: Teach doasm to compile the three-address multiply imul $c, r1, r2 - // instead of using the MOVQ above. + // then we don't need to use resultInArg0 for these ops. //p.From3 = new(obj.Addr) //p.From3.Type = obj.TYPE_REG //p.From3.Reg = gc.SSARegNum(v.Args[0]) - case ssa.OpAMD64SUBQconst, ssa.OpAMD64SUBLconst, ssa.OpAMD64SUBWconst, ssa.OpAMD64SUBBconst: - x := gc.SSARegNum(v.Args[0]) - r := gc.SSARegNum(v) - // We have 3-op add (lea), so transforming a = b - const into - // a = b + (- const), saves us 1 instruction. We can't fit - // - (-1 << 31) into 4 bytes offset in lea. - // We handle 2-address just fine below. - if v.AuxInt == -1<<31 || x == r { - if x != r { - // This code compensates for the fact that the register allocator - // doesn't understand 2-address instructions yet. TODO: fix that. - p := gc.Prog(moveByType(v.Type)) - p.From.Type = obj.TYPE_REG - p.From.Reg = x - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } - p := gc.Prog(v.Op.Asm()) - p.From.Type = obj.TYPE_CONST - p.From.Offset = v.AuxInt - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } else if x == r && v.AuxInt == -1 { - var asm obj.As - // x = x - (-1) is the same as x++ - // See OpAMD64ADDQconst comments about inc vs add $1,reg - if v.Op == ssa.OpAMD64SUBQconst { - asm = x86.AINCQ - } else { - asm = x86.AINCL - } - p := gc.Prog(asm) - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } else if x == r && v.AuxInt == 1 { - var asm obj.As - if v.Op == ssa.OpAMD64SUBQconst { - asm = x86.ADECQ - } else { - asm = x86.ADECL - } - p := gc.Prog(asm) - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } else { - var asm obj.As - if v.Op == ssa.OpAMD64SUBQconst { - asm = x86.ALEAQ - } else { - asm = x86.ALEAL - } - p := gc.Prog(asm) - p.From.Type = obj.TYPE_MEM - p.From.Reg = x - p.From.Offset = -v.AuxInt - p.To.Type = obj.TYPE_REG - p.To.Reg = r - } - case ssa.OpAMD64ANDQconst, ssa.OpAMD64ANDLconst, ssa.OpAMD64ANDWconst, ssa.OpAMD64ANDBconst, + case ssa.OpAMD64SUBQconst, ssa.OpAMD64SUBLconst, ssa.OpAMD64SUBWconst, ssa.OpAMD64SUBBconst, + ssa.OpAMD64ANDQconst, ssa.OpAMD64ANDLconst, ssa.OpAMD64ANDWconst, ssa.OpAMD64ANDBconst, ssa.OpAMD64ORQconst, ssa.OpAMD64ORLconst, ssa.OpAMD64ORWconst, ssa.OpAMD64ORBconst, ssa.OpAMD64XORQconst, ssa.OpAMD64XORLconst, ssa.OpAMD64XORWconst, ssa.OpAMD64XORBconst, - ssa.OpAMD64SHLQconst, ssa.OpAMD64SHLLconst, ssa.OpAMD64SHLWconst, - ssa.OpAMD64SHLBconst, ssa.OpAMD64SHRQconst, ssa.OpAMD64SHRLconst, ssa.OpAMD64SHRWconst, - ssa.OpAMD64SHRBconst, ssa.OpAMD64SARQconst, ssa.OpAMD64SARLconst, ssa.OpAMD64SARWconst, - ssa.OpAMD64SARBconst, ssa.OpAMD64ROLQconst, ssa.OpAMD64ROLLconst, ssa.OpAMD64ROLWconst, - ssa.OpAMD64ROLBconst: - // This code compensates for the fact that the register allocator - // doesn't understand 2-address instructions yet. TODO: fix that. - x := gc.SSARegNum(v.Args[0]) + ssa.OpAMD64SHLQconst, ssa.OpAMD64SHLLconst, ssa.OpAMD64SHLWconst, ssa.OpAMD64SHLBconst, + ssa.OpAMD64SHRQconst, ssa.OpAMD64SHRLconst, ssa.OpAMD64SHRWconst, ssa.OpAMD64SHRBconst, + ssa.OpAMD64SARQconst, ssa.OpAMD64SARLconst, ssa.OpAMD64SARWconst, ssa.OpAMD64SARBconst, + ssa.OpAMD64ROLQconst, ssa.OpAMD64ROLLconst, ssa.OpAMD64ROLWconst, ssa.OpAMD64ROLBconst: r := gc.SSARegNum(v) - if x != r { - p := gc.Prog(moveByType(v.Type)) - p.From.Type = obj.TYPE_REG - p.From.Reg = x - p.To.Type = obj.TYPE_REG - p.To.Reg = r + if r != gc.SSARegNum(v.Args[0]) { + v.Fatalf("input[0] and output not in same register %s", v.LongString()) } p := gc.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_CONST @@ -821,9 +667,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { p.To.Offset = v.AuxInt case ssa.OpCopy, ssa.OpAMD64MOVQconvert: // TODO: use MOVQreg for reg->reg copies instead of OpCopy? - if v.Type.IsMemory() { - return - } x := gc.SSARegNum(v.Args[0]) y := gc.SSARegNum(v) if x != y { @@ -969,14 +812,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { case ssa.OpAMD64NEGQ, ssa.OpAMD64NEGL, ssa.OpAMD64NEGW, ssa.OpAMD64NEGB, ssa.OpAMD64BSWAPQ, ssa.OpAMD64BSWAPL, ssa.OpAMD64NOTQ, ssa.OpAMD64NOTL, ssa.OpAMD64NOTW, ssa.OpAMD64NOTB: - x := gc.SSARegNum(v.Args[0]) r := gc.SSARegNum(v) - if x != r { - p := gc.Prog(moveByType(v.Type)) - p.From.Type = obj.TYPE_REG - p.From.Reg = x - p.To.Type = obj.TYPE_REG - p.To.Reg = r + if r != gc.SSARegNum(v.Args[0]) { + v.Fatalf("input[0] and output not in same register %s", v.LongString()) } p := gc.Prog(v.Op.Asm()) p.To.Type = obj.TYPE_REG diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index d7f361dc2e..dcd5e6a5e1 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -1273,6 +1273,12 @@ (XORWconst [c] x) && int16(c)==0 -> x (XORBconst [c] x) && int8(c)==0 -> x +// Convert constant subtracts to constant adds +(SUBQconst [c] x) && c != -(1<<31) -> (ADDQconst [-c] x) +(SUBLconst [c] x) -> (ADDLconst [int64(int32(-c))] x) +(SUBWconst [c] x) -> (ADDWconst [int64(int16(-c))] x) +(SUBBconst [c] x) -> (ADDBconst [int64(int8(-c))] x) + // generic constant folding // TODO: more of this (ADDQconst [c] (MOVQconst [d])) -> (MOVQconst [c+d]) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index b1698c0cf1..88bb6bc542 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -111,12 +111,14 @@ func init() { // Common regInfo var ( gp01 = regInfo{inputs: []regMask{}, outputs: gponly} - gp11 = regInfo{inputs: []regMask{gpsp}, outputs: gponly, clobbers: flags} + gp11 = regInfo{inputs: []regMask{gp}, outputs: gponly, clobbers: flags} + gp11sp = regInfo{inputs: []regMask{gpsp}, outputs: gponly, clobbers: flags} gp11nf = regInfo{inputs: []regMask{gpsp}, outputs: gponly} // nf: no flags clobbered gp11sb = regInfo{inputs: []regMask{gpspsb}, outputs: gponly} - gp21 = regInfo{inputs: []regMask{gpsp, gpsp}, outputs: gponly, clobbers: flags} + gp21 = regInfo{inputs: []regMask{gp, gp}, outputs: gponly, clobbers: flags} + gp21sp = regInfo{inputs: []regMask{gpsp, gp}, outputs: gponly, clobbers: flags} gp21sb = regInfo{inputs: []regMask{gpspsb, gpsp}, outputs: gponly} - gp21shift = regInfo{inputs: []regMask{gpsp, cx}, outputs: []regMask{gp &^ cx}, clobbers: flags} + gp21shift = regInfo{inputs: []regMask{gp, cx}, outputs: []regMask{gp}, clobbers: flags} gp11div = regInfo{inputs: []regMask{ax, gpsp &^ dx}, outputs: []regMask{ax}, clobbers: dx | flags} gp11hmul = regInfo{inputs: []regMask{ax, gpsp}, outputs: []regMask{dx}, @@ -128,8 +130,8 @@ func init() { gp1flags = regInfo{inputs: []regMask{gpsp}, outputs: flagsonly} flagsgp = regInfo{inputs: flagsonly, outputs: gponly} - // for CMOVconst -- uses AX to hold constant temporary. AX input is moved before temp. - gp1flagsgp = regInfo{inputs: []regMask{gp, flags}, clobbers: ax | flags, outputs: []regMask{gp &^ ax}} + // for CMOVconst -- uses AX to hold constant temporary. + gp1flagsgp = regInfo{inputs: []regMask{gp &^ ax, flags}, clobbers: ax | flags, outputs: []regMask{gp &^ ax}} readflags = regInfo{inputs: flagsonly, outputs: gponly} flagsgpax = regInfo{inputs: flagsonly, clobbers: ax | flags, outputs: []regMask{gp &^ ax}} @@ -186,14 +188,14 @@ func init() { {name: "MOVSDstoreidx8", argLength: 4, reg: fpstoreidx, asm: "MOVSD", aux: "SymOff"}, // fp64 indexed by 8i store // binary ops - {name: "ADDQ", argLength: 2, reg: gp21, asm: "ADDQ", commutative: true, resultInArg0: true}, // arg0 + arg1 - {name: "ADDL", argLength: 2, reg: gp21, asm: "ADDL", commutative: true, resultInArg0: true}, // arg0 + arg1 - {name: "ADDW", argLength: 2, reg: gp21, asm: "ADDL", commutative: true, resultInArg0: true}, // arg0 + arg1 - {name: "ADDB", argLength: 2, reg: gp21, asm: "ADDL", commutative: true, resultInArg0: true}, // arg0 + arg1 - {name: "ADDQconst", argLength: 1, reg: gp11, asm: "ADDQ", aux: "Int64", resultInArg0: true, typ: "UInt64"}, // arg0 + auxint - {name: "ADDLconst", argLength: 1, reg: gp11, asm: "ADDL", aux: "Int32", resultInArg0: true}, // arg0 + auxint - {name: "ADDWconst", argLength: 1, reg: gp11, asm: "ADDL", aux: "Int16", resultInArg0: true}, // arg0 + auxint - {name: "ADDBconst", argLength: 1, reg: gp11, asm: "ADDL", aux: "Int8", resultInArg0: true}, // arg0 + auxint + {name: "ADDQ", argLength: 2, reg: gp21sp, asm: "ADDQ", commutative: true}, // arg0 + arg1 + {name: "ADDL", argLength: 2, reg: gp21sp, asm: "ADDL", commutative: true}, // arg0 + arg1 + {name: "ADDW", argLength: 2, reg: gp21sp, asm: "ADDL", commutative: true}, // arg0 + arg1 + {name: "ADDB", argLength: 2, reg: gp21sp, asm: "ADDL", commutative: true}, // arg0 + arg1 + {name: "ADDQconst", argLength: 1, reg: gp11sp, asm: "ADDQ", aux: "Int64", typ: "UInt64"}, // arg0 + auxint + {name: "ADDLconst", argLength: 1, reg: gp11sp, asm: "ADDL", aux: "Int32"}, // arg0 + auxint + {name: "ADDWconst", argLength: 1, reg: gp11sp, asm: "ADDL", aux: "Int16"}, // arg0 + auxint + {name: "ADDBconst", argLength: 1, reg: gp11sp, asm: "ADDL", aux: "Int8"}, // arg0 + auxint {name: "SUBQ", argLength: 2, reg: gp21, asm: "SUBQ", resultInArg0: true}, // arg0 - arg1 {name: "SUBL", argLength: 2, reg: gp21, asm: "SUBL", resultInArg0: true}, // arg0 - arg1 diff --git a/src/cmd/compile/internal/ssa/gen/main.go b/src/cmd/compile/internal/ssa/gen/main.go index db3c43d3a3..2aec4a324b 100644 --- a/src/cmd/compile/internal/ssa/gen/main.go +++ b/src/cmd/compile/internal/ssa/gen/main.go @@ -39,7 +39,7 @@ type opData struct { rematerializeable bool argLength int32 // number of arguments, if -1, then this operation has a variable number of arguments commutative bool // this operation is commutative (e.g. addition) - resultInArg0 bool // prefer v and v.Args[0] to be allocated to the same register + resultInArg0 bool // v and v.Args[0] must be allocated to the same register } type blockData struct { @@ -155,6 +155,12 @@ func genOp() { } if v.resultInArg0 { fmt.Fprintln(w, "resultInArg0: true,") + if v.reg.inputs[0] != v.reg.outputs[0] { + log.Fatalf("input[0] and output registers must be equal for %s", v.name) + } + if v.commutative && v.reg.inputs[1] != v.reg.outputs[0] { + log.Fatalf("input[1] and output registers must be equal for %s", v.name) + } } if a.name == "generic" { fmt.Fprintln(w, "generic:true,") diff --git a/src/cmd/compile/internal/ssa/op.go b/src/cmd/compile/internal/ssa/op.go index d10ea230ff..64807ec106 100644 --- a/src/cmd/compile/internal/ssa/op.go +++ b/src/cmd/compile/internal/ssa/op.go @@ -26,7 +26,7 @@ type opInfo struct { generic bool // this is a generic (arch-independent) opcode rematerializeable bool // this op is rematerializeable commutative bool // this operation is commutative (e.g. addition) - resultInArg0 bool // prefer v and v.Args[0] to be allocated to the same register + resultInArg0 bool // v and v.Args[0] must be allocated to the same register } type inputInfo struct { diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 5465d7f5ed..381422adfd 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -971,15 +971,14 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "ADDQ", - argLen: 2, - commutative: true, - resultInArg0: true, - asm: x86.AADDQ, + name: "ADDQ", + argLen: 2, + commutative: true, + asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -988,15 +987,14 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "ADDL", - argLen: 2, - commutative: true, - resultInArg0: true, - asm: x86.AADDL, + name: "ADDL", + argLen: 2, + commutative: true, + asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1005,15 +1003,14 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "ADDW", - argLen: 2, - commutative: true, - resultInArg0: true, - asm: x86.AADDL, + name: "ADDW", + argLen: 2, + commutative: true, + asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1022,15 +1019,14 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "ADDB", - argLen: 2, - commutative: true, - resultInArg0: true, - asm: x86.AADDL, + name: "ADDB", + argLen: 2, + commutative: true, + asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1039,11 +1035,10 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "ADDQconst", - auxType: auxInt64, - argLen: 1, - resultInArg0: true, - asm: x86.AADDQ, + name: "ADDQconst", + auxType: auxInt64, + argLen: 1, + asm: x86.AADDQ, reg: regInfo{ inputs: []inputInfo{ {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 @@ -1055,11 +1050,10 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "ADDLconst", - auxType: auxInt32, - argLen: 1, - resultInArg0: true, - asm: x86.AADDL, + name: "ADDLconst", + auxType: auxInt32, + argLen: 1, + asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 @@ -1071,11 +1065,10 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "ADDWconst", - auxType: auxInt16, - argLen: 1, - resultInArg0: true, - asm: x86.AADDL, + name: "ADDWconst", + auxType: auxInt16, + argLen: 1, + asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 @@ -1087,11 +1080,10 @@ var opcodeTable = [...]opInfo{ }, }, { - name: "ADDBconst", - auxType: auxInt8, - argLen: 1, - resultInArg0: true, - asm: x86.AADDL, + name: "ADDBconst", + auxType: auxInt8, + argLen: 1, + asm: x86.AADDL, reg: regInfo{ inputs: []inputInfo{ {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 @@ -1109,8 +1101,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1125,8 +1117,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1141,8 +1133,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1157,8 +1149,8 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1174,7 +1166,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1190,7 +1182,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1206,7 +1198,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1222,7 +1214,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASUBL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1238,8 +1230,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1255,8 +1247,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1272,8 +1264,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1289,8 +1281,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1306,7 +1298,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1322,7 +1314,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1338,7 +1330,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1354,7 +1346,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AIMULW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1489,8 +1481,8 @@ var opcodeTable = [...]opInfo{ resultInArg0: true, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1686,8 +1678,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1703,8 +1695,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1720,8 +1712,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1737,8 +1729,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1754,7 +1746,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1770,7 +1762,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1786,7 +1778,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1802,7 +1794,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AANDL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1818,8 +1810,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1835,8 +1827,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1852,8 +1844,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1869,8 +1861,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1886,7 +1878,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1902,7 +1894,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1918,7 +1910,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1934,7 +1926,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1950,8 +1942,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1967,8 +1959,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -1984,8 +1976,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2001,8 +1993,8 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 - {1, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {1, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2018,7 +2010,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2034,7 +2026,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2050,7 +2042,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2066,7 +2058,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AXORL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2334,11 +2326,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2350,11 +2342,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2366,11 +2358,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2382,11 +2374,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2398,7 +2390,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHLQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2414,7 +2406,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHLL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2430,7 +2422,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHLL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2446,7 +2438,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHLL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2462,11 +2454,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2478,11 +2470,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2494,11 +2486,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2510,11 +2502,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2526,7 +2518,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHRQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2542,7 +2534,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHRL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2558,7 +2550,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHRW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2574,7 +2566,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASHRB, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2590,11 +2582,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2606,11 +2598,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2622,11 +2614,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2638,11 +2630,11 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 2}, // CX - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ - 65517, // AX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + 65519, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, }, }, @@ -2654,7 +2646,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASARQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2670,7 +2662,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASARL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2686,7 +2678,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASARW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2702,7 +2694,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ASARB, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2718,7 +2710,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AROLQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2734,7 +2726,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AROLL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2750,7 +2742,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AROLW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2766,7 +2758,7 @@ var opcodeTable = [...]opInfo{ asm: x86.AROLB, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2781,7 +2773,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ANEGQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2796,7 +2788,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ANEGL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2811,7 +2803,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ANEGL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2826,7 +2818,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ANEGL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2841,7 +2833,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ANOTQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2856,7 +2848,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ANOTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2871,7 +2863,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ANOTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2886,7 +2878,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ANOTL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2900,7 +2892,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSFQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2914,7 +2906,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSFL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2928,7 +2920,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSFW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2942,7 +2934,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSRQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2956,7 +2948,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSRL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2970,7 +2962,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSRW, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -2987,7 +2979,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 8589934592}, // FLAGS - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934593, // AX FLAGS outputs: []regMask{ @@ -3004,7 +2996,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 8589934592}, // FLAGS - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934593, // AX FLAGS outputs: []regMask{ @@ -3021,7 +3013,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 8589934592}, // FLAGS - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934593, // AX FLAGS outputs: []regMask{ @@ -3038,7 +3030,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 8589934592}, // FLAGS - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934593, // AX FLAGS outputs: []regMask{ @@ -3055,7 +3047,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 8589934592}, // FLAGS - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934593, // AX FLAGS outputs: []regMask{ @@ -3072,7 +3064,7 @@ var opcodeTable = [...]opInfo{ reg: regInfo{ inputs: []inputInfo{ {1, 8589934592}, // FLAGS - {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65518}, // CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934593, // AX FLAGS outputs: []regMask{ @@ -3087,7 +3079,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSWAPQ, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ @@ -3102,7 +3094,7 @@ var opcodeTable = [...]opInfo{ asm: x86.ABSWAPL, reg: regInfo{ inputs: []inputInfo{ - {0, 65535}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 + {0, 65519}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15 }, clobbers: 8589934592, // FLAGS outputs: []regMask{ diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go index aec23a1368..dfae8612d6 100644 --- a/src/cmd/compile/internal/ssa/regalloc.go +++ b/src/cmd/compile/internal/ssa/regalloc.go @@ -527,6 +527,18 @@ func (s *regAllocState) advanceUses(v *Value) { } } +// liveAfterCurrentInstruction reports whether v is live after +// the current instruction is completed. v must be used by the +// current instruction. +func (s *regAllocState) liveAfterCurrentInstruction(v *Value) bool { + u := s.values[v.ID].uses + d := u.dist + for u != nil && u.dist == d { + u = u.next + } + return u != nil && u.dist > d +} + // Sets the state of the registers to that encoded in regs. func (s *regAllocState) setState(regs []endReg) { s.freeRegs(s.used) @@ -891,6 +903,27 @@ func (s *regAllocState) regalloc(f *Func) { args[i.idx] = s.allocValToReg(v.Args[i.idx], i.regs, true, v.Line) } + // If the output clobbers the input register, and the input register is + // live beyond the instruction, make another copy of the input register so + // we don't have to reload the value from the spill location. + if opcodeTable[v.Op].resultInArg0 && + s.liveAfterCurrentInstruction(v.Args[0]) && + countRegs(s.values[v.Args[0].ID].regs) == 1 { + + if opcodeTable[v.Op].commutative && + (!s.liveAfterCurrentInstruction(v.Args[1]) || + countRegs(s.values[v.Args[1].ID].regs) > 1) { + // Input #1 is dead after the instruction, or we have + // more than one copy of it in a register. Either way, + // use that input as the one that is clobbered. + args[0], args[1] = args[1], args[0] + } else { + m := s.compatRegs(v.Args[0].Type) + m &^= s.values[v.Args[0].ID].regs // a register not already holding v.Args[0] + s.allocValToReg(v.Args[0], m, true, v.Line) + } + } + // Now that all args are in regs, we're ready to issue the value itself. // Before we pick a register for the output value, allow input registers // to be deallocated. We do this here so that the output can use the @@ -908,19 +941,9 @@ func (s *regAllocState) regalloc(f *Func) { s.f.Fatalf("bad mask %s\n", v.LongString()) } if opcodeTable[v.Op].resultInArg0 { + // Output must use the same register as input 0. r := register(s.f.getHome(args[0].ID).(*Register).Num) - if (mask&^s.used)>>r&1 != 0 { - mask = regMask(1) << r - } - if opcodeTable[v.Op].commutative { - r := register(s.f.getHome(args[1].ID).(*Register).Num) - if (mask&^s.used)>>r&1 != 0 { - mask = regMask(1) << r - } - } - // TODO: enforce resultInArg0 always, instead of treating it - // as a hint. Then we don't need the special cases adding - // moves all throughout ssa.go:genValue. + mask = regMask(1) << r } r := s.allocReg(v, mask) s.assignReg(r, v, v) diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index 34a393bbc5..a6600513fa 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -16653,6 +16653,17 @@ func rewriteValueAMD64_OpAMD64SUBBconst(v *Value, config *Config) bool { v.AddArg(x) return true } + // match: (SUBBconst [c] x) + // cond: + // result: (ADDBconst [int64(int8(-c))] x) + for { + c := v.AuxInt + x := v.Args[0] + v.reset(OpAMD64ADDBconst) + v.AuxInt = int64(int8(-c)) + v.AddArg(x) + return true + } // match: (SUBBconst (MOVBconst [d]) [c]) // cond: // result: (MOVBconst [int64(int8(d-c))]) @@ -16751,6 +16762,17 @@ func rewriteValueAMD64_OpAMD64SUBLconst(v *Value, config *Config) bool { v.AddArg(x) return true } + // match: (SUBLconst [c] x) + // cond: + // result: (ADDLconst [int64(int32(-c))] x) + for { + c := v.AuxInt + x := v.Args[0] + v.reset(OpAMD64ADDLconst) + v.AuxInt = int64(int32(-c)) + v.AddArg(x) + return true + } // match: (SUBLconst (MOVLconst [d]) [c]) // cond: // result: (MOVLconst [int64(int32(d-c))]) @@ -16854,6 +16876,20 @@ func rewriteValueAMD64_OpAMD64SUBQconst(v *Value, config *Config) bool { v.AddArg(x) return true } + // match: (SUBQconst [c] x) + // cond: c != -(1<<31) + // result: (ADDQconst [-c] x) + for { + c := v.AuxInt + x := v.Args[0] + if !(c != -(1 << 31)) { + break + } + v.reset(OpAMD64ADDQconst) + v.AuxInt = -c + v.AddArg(x) + return true + } // match: (SUBQconst (MOVQconst [d]) [c]) // cond: // result: (MOVQconst [d-c]) @@ -16955,6 +16991,17 @@ func rewriteValueAMD64_OpAMD64SUBWconst(v *Value, config *Config) bool { v.AddArg(x) return true } + // match: (SUBWconst [c] x) + // cond: + // result: (ADDWconst [int64(int16(-c))] x) + for { + c := v.AuxInt + x := v.Args[0] + v.reset(OpAMD64ADDWconst) + v.AuxInt = int64(int16(-c)) + v.AddArg(x) + return true + } // match: (SUBWconst (MOVWconst [d]) [c]) // cond: // result: (MOVWconst [int64(int16(d-c))]) -- cgit v1.3 From 5b3e5766bcc5e1090d0512a5916886ffc24ab246 Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Fri, 8 Apr 2016 14:43:35 +1000 Subject: cmd/go: remove special case that puts godoc in $GOROOT/bin Updates golang/go#15106 Change-Id: I4214b841d63bb7e9c3c5ede2abe21a8a68f06c41 Reviewed-on: https://go-review.googlesource.com/21701 Reviewed-by: Brad Fitzpatrick --- src/cmd/go/go_test.go | 22 ---------------------- src/cmd/go/pkg.go | 9 +-------- 2 files changed, 1 insertion(+), 30 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 8a0416089c..411fd1e322 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -1377,28 +1377,6 @@ func TestInstallToGOBINCommandLinePackage(t *testing.T) { tg.wantExecutable("testdata/bin1/helloworld"+exeSuffix, "go install testdata/src/go-cmd-test/helloworld.go did not write testdata/bin1/helloworld") } -func TestGodocInstalls(t *testing.T) { - testenv.MustHaveExternalNetwork(t) - - // godoc installs into GOBIN - tg := testgo(t) - defer tg.cleanup() - tg.parallel() - tg.tempDir("gobin") - tg.setenv("GOPATH", tg.path(".")) - tg.setenv("GOBIN", tg.path("gobin")) - tg.run("get", "golang.org/x/tools/cmd/godoc") - tg.wantExecutable(tg.path("gobin/godoc"), "did not install godoc to $GOBIN") - tg.unsetenv("GOBIN") - - // godoc installs into GOROOT - goroot := runtime.GOROOT() - tg.setenv("GOROOT", goroot) - tg.check(os.RemoveAll(filepath.Join(goroot, "bin", "godoc"))) - tg.run("install", "golang.org/x/tools/cmd/godoc") - tg.wantExecutable(filepath.Join(goroot, "bin", "godoc"), "did not install godoc to $GOROOT/bin") -} - func TestGoGetNonPkg(t *testing.T) { testenv.MustHaveExternalNetwork(t) diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go index f330b4db43..ef7fd124a9 100644 --- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -682,7 +682,6 @@ type targetDir int const ( toRoot targetDir = iota // to bin dir inside package root (default) toTool // GOROOT/pkg/tool - toBin // GOROOT/bin stalePath // the old import path; fail to build ) @@ -706,7 +705,6 @@ var goTools = map[string]targetDir{ "cmd/trace": toTool, "cmd/vet": toTool, "cmd/yacc": toTool, - "golang.org/x/tools/cmd/godoc": toBin, "code.google.com/p/go.tools/cmd/cover": stalePath, "code.google.com/p/go.tools/cmd/godoc": stalePath, "code.google.com/p/go.tools/cmd/vet": stalePath, @@ -793,12 +791,7 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package // Install cross-compiled binaries to subdirectories of bin. elem = full } - if p.build.BinDir != gobin && goTools[p.ImportPath] == toBin { - // Override BinDir. - // This is from a subrepo but installs to $GOROOT/bin - // by default anyway (like godoc). - p.target = filepath.Join(gorootBin, elem) - } else if p.build.BinDir != "" { + if p.build.BinDir != "" { // Install to GOBIN or bin of GOPATH entry. p.target = filepath.Join(p.build.BinDir, elem) if !p.Goroot && strings.Contains(elem, "/") && gobin != "" { -- cgit v1.3 From e6a8daceb0b0df77f5d2ca34a73561375bb89c63 Mon Sep 17 00:00:00 2001 From: David Symonds Date: Mon, 11 Apr 2016 10:26:38 +1000 Subject: cmd/vet: refresh command for updating whitelist data. This excludes internal and testdata packages, as well as func types. No new whitelist entries were found. Change-Id: Ie7d42ce0a235394e4bcabf09e155726a35cd2d3d Reviewed-on: https://go-review.googlesource.com/21822 Reviewed-by: Rob Pike --- src/cmd/vet/internal/whitelist/whitelist.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/vet/internal/whitelist/whitelist.go b/src/cmd/vet/internal/whitelist/whitelist.go index b6c85850f3..696f7a533d 100644 --- a/src/cmd/vet/internal/whitelist/whitelist.go +++ b/src/cmd/vet/internal/whitelist/whitelist.go @@ -11,7 +11,8 @@ package whitelist // library's exported slice types. var UnkeyedLiteral = map[string]bool{ /* - find $GOROOT/src -type f | grep -v _test.go | xargs grep '^type.*\[\]' | \ + find $GOROOT/src -type f | grep -v _test.go | grep -v /internal/ | grep -v /testdata/ | \ + xargs grep '^type.*\[\]' | grep -v ' func(' | \ grep -v ' map\[' | sed 's,/[^/]*go.type,,' | sed 's,.*src/,,' | \ sed 's, ,.,' | sed 's, .*,,' | grep -v '\.[a-z]' | \ sort | awk '{ print "\"" $0 "\": true," }' -- cgit v1.3 From 2a4158207edb499f8b210aaa7a9af103b93b5ac7 Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Sun, 10 Apr 2016 21:58:37 -0400 Subject: cmd/compile/internal/gc: refactor cgen_div This commit adds two new functions to cgen.go: hasHMUL64 and hasRROTC64. These are used to determine whether or not an architecture supports the instructions needed to perform an optimization in cgen_div. This commit should not affect existing architectures (although it does add s390x to the new functions). However, since most architectures support HMUL the hasHMUL64 function could be modified to enable most of the optimizations in cgen_div on those platforms. Change-Id: I33bf329ddeb6cf2954bd17b7c161012de352fb62 Reviewed-on: https://go-review.googlesource.com/21775 Reviewed-by: Matthew Dempsky Run-TryBot: Matthew Dempsky Reviewed-by: Josh Bleecher Snyder TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/gc/cgen.go | 68 ++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 19 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/cgen.go b/src/cmd/compile/internal/gc/cgen.go index a9cedf7cfc..eacbc30f87 100644 --- a/src/cmd/compile/internal/gc/cgen.go +++ b/src/cmd/compile/internal/gc/cgen.go @@ -2622,24 +2622,48 @@ func cgen_ret(n *Node) { } } +// hasHMUL64 reports whether the architecture supports 64-bit +// signed and unsigned high multiplication (OHMUL). +func hasHMUL64() bool { + switch Ctxt.Arch.Family { + case sys.AMD64, sys.S390X: + return true + case sys.ARM, sys.ARM64, sys.I386, sys.MIPS64, sys.PPC64: + return false + } + Fatalf("unknown architecture") + return false +} + +// hasRROTC64 reports whether the architecture supports 64-bit +// rotate through carry instructions (ORROTC). +func hasRROTC64() bool { + switch Ctxt.Arch.Family { + case sys.AMD64: + return true + case sys.ARM, sys.ARM64, sys.I386, sys.MIPS64, sys.PPC64, sys.S390X: + return false + } + Fatalf("unknown architecture") + return false +} + // generate division according to op, one of: // res = nl / nr // res = nl % nr func cgen_div(op Op, nl *Node, nr *Node, res *Node) { var w int - // TODO(rsc): arm64 needs to support the relevant instructions - // in peep and optoas in order to enable this. - // TODO(rsc): ppc64 needs to support the relevant instructions - // in peep and optoas in order to enable this. - if nr.Op != OLITERAL || Ctxt.Arch.Family == sys.MIPS64 || Ctxt.Arch.Family == sys.ARM64 || Ctxt.Arch.Family == sys.PPC64 { + // Architectures need to support 64-bit high multiplications + // (OHMUL) in order to perform divide by constant optimizations. + if nr.Op != OLITERAL || !hasHMUL64() { goto longdiv } w = int(nl.Type.Width * 8) // Front end handled 32-bit division. We only need to handle 64-bit. - // try to do division by multiply by (2^w)/d - // see hacker's delight chapter 10 + // Try to do division using multiplication: (2^w)/d. + // See Hacker's Delight, chapter 10. switch Simtype[nl.Type.Etype] { default: goto longdiv @@ -2652,6 +2676,17 @@ func cgen_div(op Op, nl *Node, nr *Node, res *Node) { if m.Bad != 0 { break } + + // In order to add the numerator we need to be able to + // avoid overflow. This is done by shifting the result of the + // addition right by 1 and inserting the carry bit into + // the MSB. For now this needs the RROTC instruction. + // TODO(mundaym): Hacker's Delight 2nd ed. chapter 10 proposes + // an alternative sequence of instructions for architectures + // that do not have a shift right with carry instruction. + if m.Ua != 0 && !hasRROTC64() { + goto longdiv + } if op == OMOD { goto longmod } @@ -2665,7 +2700,7 @@ func cgen_div(op Op, nl *Node, nr *Node, res *Node) { Thearch.Cgen_hmul(&n1, &n2, &n3) if m.Ua != 0 { - // need to add numerator accounting for overflow + // Need to add numerator accounting for overflow. Thearch.Gins(Thearch.Optoas(OADD, nl.Type), &n1, &n3) Nodconst(&n2, nl.Type, 1) @@ -2703,7 +2738,7 @@ func cgen_div(op Op, nl *Node, nr *Node, res *Node) { Thearch.Cgen_hmul(&n1, &n2, &n3) if m.Sm < 0 { - // need to add numerator + // Need to add numerator (cannot overflow). Thearch.Gins(Thearch.Optoas(OADD, nl.Type), &n1, &n3) } @@ -2716,8 +2751,8 @@ func cgen_div(op Op, nl *Node, nr *Node, res *Node) { Thearch.Gins(Thearch.Optoas(OSUB, nl.Type), &n1, &n3) // added if m.Sd < 0 { - // this could probably be removed - // by factoring it into the multiplier + // This could probably be removed by factoring it into + // the multiplier. Thearch.Gins(Thearch.Optoas(OMINUS, nl.Type), nil, &n3) } @@ -2729,14 +2764,14 @@ func cgen_div(op Op, nl *Node, nr *Node, res *Node) { goto longdiv - // division and mod using (slow) hardware instruction + // Division and mod using (slow) hardware instruction. longdiv: Thearch.Dodiv(op, nl, nr, res) return - // mod using formula A%B = A-(A/B*B) but - // we know that there is a fast algorithm for A/B + // Mod using formula A%B = A-(A/B*B) but + // we know that there is a fast algorithm for A/B. longmod: var n1 Node Regalloc(&n1, nl.Type, res) @@ -2746,11 +2781,6 @@ longmod: Regalloc(&n2, nl.Type, nil) cgen_div(ODIV, &n1, nr, &n2) a := Thearch.Optoas(OMUL, nl.Type) - if w == 8 { - // use 2-operand 16-bit multiply - // because there is no 2-operand 8-bit multiply - a = Thearch.Optoas(OMUL, Types[TINT16]) // XXX was IMULW - } if !Smallintconst(nr) { var n3 Node -- cgit v1.3 From 20375f64b18e9f904302d8f873e23702117bf4f5 Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Mon, 11 Apr 2016 11:04:15 +1000 Subject: cmd/go: document that -run=^$ skips all tests Change-Id: I7bbdd9600e0d9a647aeea16f1ae9e42a4e0cf44d Reviewed-on: https://go-review.googlesource.com/21823 Reviewed-by: Rob Pike --- src/cmd/go/alldocs.go | 8 +++++--- src/cmd/go/test.go | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 1f5981055c..ac975c0ab7 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -1347,8 +1347,8 @@ control the execution of any test: -bench regexp Run benchmarks matching the regular expression. - By default, no benchmarks run. To run all benchmarks, - use '-bench .' or '-bench=.'. + By default, no benchmarks run. + To run all benchmarks, use '-bench=.'. -benchmem Print memory allocation statistics for benchmarks. @@ -1435,7 +1435,9 @@ control the execution of any test: -run regexp Run only those tests and examples matching the regular - expression. + expression. By default, all tests run. + To skip all tests, use a pattern that matches no test names, + such as '-run=^$'. -short Tell long-running tests to shorten their run time. diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go index a17bc4e982..8dbd9e22bf 100644 --- a/src/cmd/go/test.go +++ b/src/cmd/go/test.go @@ -126,8 +126,8 @@ control the execution of any test: const testFlag2 = ` -bench regexp Run benchmarks matching the regular expression. - By default, no benchmarks run. To run all benchmarks, - use '-bench .' or '-bench=.'. + By default, no benchmarks run. + To run all benchmarks, use '-bench=.'. -benchmem Print memory allocation statistics for benchmarks. @@ -214,7 +214,9 @@ const testFlag2 = ` -run regexp Run only those tests and examples matching the regular - expression. + expression. By default, all tests run. + To skip all tests, use a pattern that matches no test names, + such as '-run=^$'. -short Tell long-running tests to shorten their run time. -- cgit v1.3 From 4f12cc08132f3e5d2ba4b756c91d88c2e58a73b1 Mon Sep 17 00:00:00 2001 From: Shahar Kohanim Date: Thu, 7 Apr 2016 18:00:57 +0300 Subject: cmd/link: symbol generation optimizations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After making dwarf generation backed by LSyms there was a performance regression of about 10%. These changes make on the fly symbol generation faster and are meant to help mitigate that. name old secs new secs delta LinkCmdGo 0.55 ± 9% 0.53 ± 8% -4.42% (p=0.000 n=100+99) name old MaxRSS new MaxRSS delta LinkCmdGo 152k ± 6% 149k ± 3% -1.99% (p=0.000 n=99+97) Change-Id: Iacca3ec924ce401aa83126bc0b10fe89bedf0ba6 Reviewed-on: https://go-review.googlesource.com/21733 Run-TryBot: Shahar Kohanim TryBot-Result: Gobot Gobot Reviewed-by: David Crawshaw --- src/cmd/link/internal/ld/data.go | 32 ++++++++++++++++++-------------- src/cmd/link/internal/ld/dwarf.go | 13 +++++++++---- src/cmd/link/internal/ld/pcln.go | 3 +-- src/cmd/link/internal/ld/symtab.go | 10 ++-------- 4 files changed, 30 insertions(+), 28 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 2c8cc9ca4f..ae7c287f59 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -50,8 +50,9 @@ func Symgrow(ctxt *Link, s *LSym, siz int64) { if int64(len(s.P)) >= siz { return } - for cap(s.P) < int(siz) { - s.P = append(s.P[:len(s.P)], 0) + if cap(s.P) < int(siz) { + p := make([]byte, 2*(siz+1)) + s.P = append(p[:0], s.P...) } s.P = s.P[:siz] } @@ -90,11 +91,8 @@ func Addbytes(ctxt *Link, s *LSym, bytes []byte) int64 { s.Type = obj.SDATA } s.Attr |= AttrReachable - s.Size += int64(len(bytes)) - if int64(int(s.Size)) != s.Size { - log.Fatalf("Addbytes size %d too long", s.Size) - } s.P = append(s.P, bytes...) + s.Size = int64(len(s.P)) return s.Size } @@ -106,7 +104,15 @@ func adduintxx(ctxt *Link, s *LSym, v uint64, wid int) int64 { } func Adduint8(ctxt *Link, s *LSym, v uint8) int64 { - return adduintxx(ctxt, s, uint64(v), 1) + off := s.Size + if s.Type == 0 { + s.Type = obj.SDATA + } + s.Attr |= AttrReachable + s.Size++ + s.P = append(s.P, v) + + return off } func Adduint16(ctxt *Link, s *LSym, v uint16) int64 { @@ -1006,16 +1012,14 @@ func Addstring(s *LSym, str string) int64 { s.Type = obj.SNOPTRDATA } s.Attr |= AttrReachable - r := int32(s.Size) - n := len(str) + 1 + r := s.Size if s.Name == ".shstrtab" { elfsetstring(str, int(r)) } - Symgrow(Ctxt, s, int64(r)+int64(n)) - copy(s.P[r:], str) - s.P[int(r)+len(str)] = 0 - s.Size += int64(n) - return int64(r) + s.P = append(s.P, str...) + s.P = append(s.P, 0) + s.Size = int64(len(s.P)) + return r } // addgostring adds str, as a Go string value, to s. symname is the name of the diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index de2d50a1a9..a3a931f94c 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -88,9 +88,7 @@ func uleb128put(s *LSym, v int64) { func sleb128put(s *LSym, v int64) { b := appendSleb128(encbuf[:0], v) - for _, x := range b { - Adduint8(Ctxt, s, x) - } + Addbytes(Ctxt, s, b) } /* @@ -552,8 +550,15 @@ func findchild(die *DWDie, name string) *DWDie { return nil } +// Used to avoid string allocation when looking up dwarf symbols +var prefixBuf = []byte(infoprefix) + func find(name string) *LSym { - return Linkrlookup(Ctxt, infoprefix+name, 0) + n := append(prefixBuf, name...) + // The string allocation below is optimized away because it is only used in a map lookup. + s := Linkrlookup(Ctxt, string(n), 0) + prefixBuf = n[:len(infoprefix)] + return s } func mustFind(name string) *LSym { diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index a5fea3db76..9a947c7c07 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -127,8 +127,7 @@ func addpctab(ftab *LSym, off int32, d *Pcdata) int32 { var start int32 if len(d.P) > 0 { start = int32(len(ftab.P)) - Symgrow(Ctxt, ftab, int64(start)+int64(len(d.P))) - copy(ftab.P[start:], d.P) + Addbytes(Ctxt, ftab, d.P) } return int32(setuint32(Ctxt, ftab, int64(off), uint32(start))) } diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index ecd5c741bb..c7c2733507 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -54,15 +54,9 @@ func putelfstr(s string) int { s = strings.Replace(s, "·", ".", -1) } - n := len(s) + 1 - for len(Elfstrdat)+n > cap(Elfstrdat) { - Elfstrdat = append(Elfstrdat[:cap(Elfstrdat)], 0)[:len(Elfstrdat)] - } - off := len(Elfstrdat) - Elfstrdat = Elfstrdat[:off+n] - copy(Elfstrdat[off:], s) - + Elfstrdat = append(Elfstrdat, s...) + Elfstrdat = append(Elfstrdat, 0) return off } -- cgit v1.3 From 683917a72154e3409e1ab5ef5b26030388312d0b Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Fri, 1 Apr 2016 07:34:18 +0200 Subject: all: use bytes.Equal, bytes.Contains and strings.Contains, again The previous cleanup was done with a buggy tool, missing some potential rewrites. Change-Id: I333467036e355f999a6a493e8de87e084f374e26 Reviewed-on: https://go-review.googlesource.com/21378 Reviewed-by: Brad Fitzpatrick --- src/cmd/go/go_test.go | 4 ++-- src/html/template/url.go | 2 +- src/net/http/serve_test.go | 2 +- src/path/filepath/path_test.go | 2 +- src/runtime/gcinfo_test.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 411fd1e322..42efa9f312 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -1466,7 +1466,7 @@ func TestGoTestWithPackageListedMultipleTimes(t *testing.T) { defer tg.cleanup() tg.parallel() tg.run("test", "errors", "errors", "errors", "errors", "errors") - if strings.Index(strings.TrimSpace(tg.getStdout()), "\n") != -1 { + if strings.Contains(strings.TrimSpace(tg.getStdout()), "\n") { t.Error("go test errors errors errors errors errors tested the same package multiple times") } } @@ -1495,7 +1495,7 @@ func TestGoListCmdOnlyShowsCommands(t *testing.T) { tg.run("list", "cmd") out := strings.TrimSpace(tg.getStdout()) for _, line := range strings.Split(out, "\n") { - if strings.Index(line, "cmd/") == -1 { + if !strings.Contains(line, "cmd/") { t.Error("go list cmd shows non-commands") break } diff --git a/src/html/template/url.go b/src/html/template/url.go index 2ca76bf389..246bfd32cd 100644 --- a/src/html/template/url.go +++ b/src/html/template/url.go @@ -17,7 +17,7 @@ func urlFilter(args ...interface{}) string { if t == contentTypeURL { return s } - if i := strings.IndexRune(s, ':'); i >= 0 && strings.IndexRune(s[:i], '/') < 0 { + if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') { protocol := strings.ToLower(s[:i]) if protocol != "http" && protocol != "https" && protocol != "mailto" { return "#" + filterFailsafe diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index 4cd6ed077f..e0094234de 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -4267,7 +4267,7 @@ func BenchmarkClient(b *testing.B) { if err != nil { b.Fatalf("ReadAll: %v", err) } - if bytes.Compare(body, data) != 0 { + if !bytes.Equal(body, data) { b.Fatalf("Got body: %q", body) } } diff --git a/src/path/filepath/path_test.go b/src/path/filepath/path_test.go index 3622f9178e..1a4a9d2a1a 100644 --- a/src/path/filepath/path_test.go +++ b/src/path/filepath/path_test.go @@ -1015,7 +1015,7 @@ func TestAbs(t *testing.T) { vol := filepath.VolumeName(root) var extra []string for _, path := range absTests { - if strings.Index(path, "$") != -1 { + if strings.Contains(path, "$") { continue } path = vol + path diff --git a/src/runtime/gcinfo_test.go b/src/runtime/gcinfo_test.go index c1c2354bf9..9a61b4f2b2 100644 --- a/src/runtime/gcinfo_test.go +++ b/src/runtime/gcinfo_test.go @@ -59,7 +59,7 @@ func TestGCInfo(t *testing.T) { func verifyGCInfo(t *testing.T, name string, p interface{}, mask0 []byte) { mask := runtime.GCMask(p) - if bytes.Compare(mask, mask0) != 0 { + if !bytes.Equal(mask, mask0) { t.Errorf("bad GC program for %v:\nwant %+v\ngot %+v", name, mask0, mask) return } -- cgit v1.3 From 6c6089b3fdba9eb0cff863a03074dbac47c92f63 Mon Sep 17 00:00:00 2001 From: Alexandru Moșoi Date: Fri, 1 Apr 2016 15:09:19 +0200 Subject: cmd/compile: bce when max and limit are consts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes 49 more bound checks in make.bash. For example: var a[100]int for i := 0; i < 50; i++ { use a[i+25] } Change-Id: I85e0130ee5d07f0ece9b17044bba1a2047414ce7 Reviewed-on: https://go-review.googlesource.com/21379 Reviewed-by: David Chase Run-TryBot: Alexandru Moșoi TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/ssa/loopbce.go | 41 ++++++++++++++++++ test/checkbce.go | 15 ++++++- test/loopbce.go | 77 +++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/loopbce.go b/src/cmd/compile/internal/ssa/loopbce.go index 17486ac49f..c937ead1b2 100644 --- a/src/cmd/compile/internal/ssa/loopbce.go +++ b/src/cmd/compile/internal/ssa/loopbce.go @@ -240,6 +240,37 @@ func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) { } skip2: + // Simplify + // (IsInBounds (Add64 ind) (Const64 [c])) where 0 <= min <= ind < max <= (Const64 [c]) + // (IsSliceInBounds ind (Const64 [c])) where 0 <= min <= ind < max <= (Const64 [c]) + if v.Op == OpIsInBounds || v.Op == OpIsSliceInBounds { + ind, add := dropAdd64(v.Args[0]) + if ind.Op != OpPhi { + goto skip3 + } + + // ind + add >= 0 <-> min + add >= 0 <-> min >= -add + if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isGreaterOrEqualThan(iv.min, -add) { + if !v.Args[1].isGenericIntConst() || !iv.max.isGenericIntConst() { + goto skip3 + } + + limit := v.Args[1].AuxInt + if v.Op == OpIsSliceInBounds { + // If limit++ overflows signed integer then 0 <= max && max <= limit will be false. + limit++ + } + + if max := iv.max.AuxInt + add; 0 <= max && max <= limit { // handle overflow + if f.pass.debug > 0 { + f.Config.Warnl(b.Line, "Found redundant (%s ind %d), ind < %d", v.Op, v.Args[1].AuxInt, iv.max.AuxInt+add) + } + goto simplify + } + } + } + skip3: + continue simplify: @@ -258,3 +289,13 @@ func dropAdd64(v *Value) (*Value, int64) { } return v, 0 } + +func isGreaterOrEqualThan(v *Value, c int64) bool { + if c == 0 { + return isNonNegative(v) + } + if v.isGenericIntConst() && v.AuxInt >= c { + return true + } + return false +} diff --git a/test/checkbce.go b/test/checkbce.go index 988375fcc7..fa0ea12803 100644 --- a/test/checkbce.go +++ b/test/checkbce.go @@ -57,7 +57,7 @@ func f6(a [32]int, b [64]int, i int) { useInt(b[uint64(i*0x07C4ACDD)>>58]) useInt(a[uint(i*0x07C4ACDD)>>59]) - // The following bounds should removed as they can overflow. + // The following bounds should not be removed because they can overflow. useInt(a[uint32(i*0x106297f105d0cc86)>>26]) // ERROR "Found IsInBounds$" useInt(b[uint64(i*0x106297f105d0cc86)>>57]) // ERROR "Found IsInBounds$" useInt(a[int32(i*0x106297f105d0cc86)>>26]) // ERROR "Found IsInBounds$" @@ -89,6 +89,19 @@ func g3(a []int) { } } +func g4(a [100]int) { + for i := 10; i < 50; i++ { + useInt(a[i-10]) + useInt(a[i]) + useInt(a[i+25]) + useInt(a[i+50]) + + // The following are out of bounds. + useInt(a[i-11]) // ERROR "Found IsInBounds$" + useInt(a[i+51]) // ERROR "Found IsInBounds$" + } +} + //go:noinline func useInt(a int) { } diff --git a/test/loopbce.go b/test/loopbce.go index eb44092705..ea195217e6 100644 --- a/test/loopbce.go +++ b/test/loopbce.go @@ -139,6 +139,70 @@ func h2(a []byte) { } } +func k0(a [100]int) [100]int { + for i := 10; i < 90; i++ { // ERROR "Induction variable with minimum 10 and increment 1$" + a[i-11] = i + a[i-10] = i // ERROR "Found redundant \(IsInBounds ind 100\), ind < 80$" + a[i-5] = i // ERROR "Found redundant \(IsInBounds ind 100\), ind < 85$" + a[i] = i // ERROR "Found redundant \(IsInBounds ind 100\), ind < 90$" + a[i+5] = i // ERROR "Found redundant \(IsInBounds ind 100\), ind < 95$" + a[i+10] = i // ERROR "Found redundant \(IsInBounds ind 100\), ind < 100$" + a[i+11] = i + } + return a +} + +func k1(a [100]int) [100]int { + for i := 10; i < 90; i++ { // ERROR "Induction variable with minimum 10 and increment 1$" + useSlice(a[:i-11]) + useSlice(a[:i-10]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 80$" + useSlice(a[:i-5]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 85$" + useSlice(a[:i]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 90$" + useSlice(a[:i+5]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 95$" + useSlice(a[:i+10]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 100$" + useSlice(a[:i+11]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 101$" + + } + return a +} + +func k2(a [100]int) [100]int { + for i := 10; i < 90; i++ { // ERROR "Induction variable with minimum 10 and increment 1$" + useSlice(a[i-11:]) + useSlice(a[i-10:]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 80$" + useSlice(a[i-5:]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 85$" + useSlice(a[i:]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 90$" + useSlice(a[i+5:]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 95$" + useSlice(a[i+10:]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 100$" + useSlice(a[i+11:]) // ERROR "Found redundant \(IsSliceInBounds ind 100\), ind < 101$" + } + return a +} + +func k3(a [100]int) [100]int { + for i := -10; i < 90; i++ { // ERROR "Induction variable with minimum -10 and increment 1$" + a[i+10] = i // ERROR "Found redundant \(IsInBounds ind 100\), ind < 100$" + } + return a +} + +func k4(a [100]int) [100]int { + min := (-1) << 63 + for i := min; i < min+50; i++ { // ERROR "Induction variable with minimum -9223372036854775808 and increment 1$" + a[i-min] = i // ERROR "Found redundant \(IsInBounds ind 100\), ind < 50$" + } + return a +} + +func k5(a [100]int) [100]int { + max := (1 << 63) - 1 + for i := max - 50; i < max; i++ { // ERROR "Induction variable with minimum 9223372036854775757 and increment 1$" + a[i-max+50] = i + a[i-(max-70)] = i // ERROR "Found redundant \(IsInBounds ind 100\), ind < 70$" + } + return a +} + func nobce1() { // tests overflow of max-min a := int64(9223372036854774057) @@ -168,9 +232,22 @@ func nobce2(a string) { } } +func nobce3(a [100]int64) [100]int64 { + min := int64((-1) << 63) + max := int64((1 << 63) - 1) + for i := min; i < max; i++ { // ERROR "Induction variable with minimum -9223372036854775808 and increment 1$" + a[i] = i + } + return a +} + //go:noinline func useString(a string) { } +//go:noinline +func useSlice(a []int) { +} + func main() { } -- cgit v1.3 From b04e145248d5d3721a41d4bb26704fdb43caaf38 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 31 Mar 2016 21:24:10 -0700 Subject: cmd/compile: fix naming of decomposed structs When a struct is SSAable, we will name its component parts by their field names. For example, type T struct { a, b, c int } If we ever need to spill a variable x of type T, we will spill its individual components to variables named x.a, x.b, and x.c. Change-Id: I857286ff1f2597f2c4bbd7b4c0b936386fb37131 Reviewed-on: https://go-review.googlesource.com/21389 Reviewed-by: David Chase --- src/cmd/compile/internal/gc/ssa.go | 16 ++++++++- src/cmd/compile/internal/gc/type.go | 3 ++ src/cmd/compile/internal/ssa/config.go | 1 + src/cmd/compile/internal/ssa/decompose.go | 27 ++++++++++---- src/cmd/compile/internal/ssa/export_test.go | 3 ++ src/cmd/compile/internal/ssa/type.go | 56 +++++++++++++++-------------- src/cmd/compile/internal/ssa/type_test.go | 49 ++++++++++++------------- 7 files changed, 97 insertions(+), 58 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index d69559d945..5ee370395b 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -4227,7 +4227,7 @@ func (e *ssaExport) SplitInterface(name ssa.LocalSlot) (ssa.LocalSlot, ssa.Local func (e *ssaExport) SplitSlice(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot, ssa.LocalSlot) { n := name.N.(*Node) - ptrType := Ptrto(n.Type.Elem()) + ptrType := Ptrto(name.Type.ElemType().(*Type)) lenType := Types[TINT] if n.Class == PAUTO && !n.Addrtaken { // Split this slice up into three separate variables. @@ -4261,6 +4261,20 @@ func (e *ssaExport) SplitComplex(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSl return ssa.LocalSlot{n, t, name.Off}, ssa.LocalSlot{n, t, name.Off + s} } +func (e *ssaExport) SplitStruct(name ssa.LocalSlot, i int) ssa.LocalSlot { + n := name.N.(*Node) + st := name.Type + ft := st.FieldType(i) + if n.Class == PAUTO && !n.Addrtaken { + // Note: the _ field may appear several times. But + // have no fear, identically-named but distinct Autos are + // ok, albeit maybe confusing for a debugger. + x := e.namedAuto(n.Sym.Name+"."+st.FieldName(i), ft) + return ssa.LocalSlot{x, ft, 0} + } + return ssa.LocalSlot{n, ft, name.Off + st.FieldOff(i)} +} + // namedAuto returns a new AUTO variable with the given name and type. func (e *ssaExport) namedAuto(name string, typ ssa.Type) ssa.GCNode { t := typ.(*Type) diff --git a/src/cmd/compile/internal/gc/type.go b/src/cmd/compile/internal/gc/type.go index eee8e0384a..25c1bcc203 100644 --- a/src/cmd/compile/internal/gc/type.go +++ b/src/cmd/compile/internal/gc/type.go @@ -1193,6 +1193,9 @@ func (t *Type) FieldType(i int) ssa.Type { func (t *Type) FieldOff(i int) int64 { return t.Field(i).Offset } +func (t *Type) FieldName(i int) string { + return t.Field(i).Sym.Name +} func (t *Type) NumElem() int64 { t.wantEtype(TARRAY) diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go index 33357124fc..2a676e39b3 100644 --- a/src/cmd/compile/internal/ssa/config.go +++ b/src/cmd/compile/internal/ssa/config.go @@ -103,6 +103,7 @@ type Frontend interface { SplitInterface(LocalSlot) (LocalSlot, LocalSlot) SplitSlice(LocalSlot) (LocalSlot, LocalSlot, LocalSlot) SplitComplex(LocalSlot) (LocalSlot, LocalSlot) + SplitStruct(LocalSlot, int) LocalSlot // Line returns a string describing the given line number. Line(int32) string diff --git a/src/cmd/compile/internal/ssa/decompose.go b/src/cmd/compile/internal/ssa/decompose.go index eab9974106..de02885d76 100644 --- a/src/cmd/compile/internal/ssa/decompose.go +++ b/src/cmd/compile/internal/ssa/decompose.go @@ -21,6 +21,7 @@ func decomposeBuiltIn(f *Func) { // NOTE: the component values we are making are dead at this point. // We must do the opt pass before any deadcode elimination or we will // lose the name->value correspondence. + var newNames []LocalSlot for _, name := range f.Names { t := name.Type switch { @@ -32,29 +33,31 @@ func decomposeBuiltIn(f *Func) { elemType = f.Config.fe.TypeFloat32() } rName, iName := f.Config.fe.SplitComplex(name) - f.Names = append(f.Names, rName, iName) + newNames = append(newNames, rName, iName) for _, v := range f.NamedValues[name] { r := v.Block.NewValue1(v.Line, OpComplexReal, elemType, v) i := v.Block.NewValue1(v.Line, OpComplexImag, elemType, v) f.NamedValues[rName] = append(f.NamedValues[rName], r) f.NamedValues[iName] = append(f.NamedValues[iName], i) } + delete(f.NamedValues, name) case t.IsString(): ptrType := f.Config.fe.TypeBytePtr() lenType := f.Config.fe.TypeInt() ptrName, lenName := f.Config.fe.SplitString(name) - f.Names = append(f.Names, ptrName, lenName) + newNames = append(newNames, ptrName, lenName) for _, v := range f.NamedValues[name] { ptr := v.Block.NewValue1(v.Line, OpStringPtr, ptrType, v) len := v.Block.NewValue1(v.Line, OpStringLen, lenType, v) f.NamedValues[ptrName] = append(f.NamedValues[ptrName], ptr) f.NamedValues[lenName] = append(f.NamedValues[lenName], len) } + delete(f.NamedValues, name) case t.IsSlice(): ptrType := f.Config.fe.TypeBytePtr() lenType := f.Config.fe.TypeInt() ptrName, lenName, capName := f.Config.fe.SplitSlice(name) - f.Names = append(f.Names, ptrName, lenName, capName) + newNames = append(newNames, ptrName, lenName, capName) for _, v := range f.NamedValues[name] { ptr := v.Block.NewValue1(v.Line, OpSlicePtr, ptrType, v) len := v.Block.NewValue1(v.Line, OpSliceLen, lenType, v) @@ -63,20 +66,25 @@ func decomposeBuiltIn(f *Func) { f.NamedValues[lenName] = append(f.NamedValues[lenName], len) f.NamedValues[capName] = append(f.NamedValues[capName], cap) } + delete(f.NamedValues, name) case t.IsInterface(): ptrType := f.Config.fe.TypeBytePtr() typeName, dataName := f.Config.fe.SplitInterface(name) - f.Names = append(f.Names, typeName, dataName) + newNames = append(newNames, typeName, dataName) for _, v := range f.NamedValues[name] { typ := v.Block.NewValue1(v.Line, OpITab, ptrType, v) data := v.Block.NewValue1(v.Line, OpIData, ptrType, v) f.NamedValues[typeName] = append(f.NamedValues[typeName], typ) f.NamedValues[dataName] = append(f.NamedValues[dataName], data) } + delete(f.NamedValues, name) case t.Size() > f.Config.IntSize: f.Unimplementedf("undecomposed named type %s", t) + default: + newNames = append(newNames, name) } } + f.Names = newNames } func decomposeBuiltInPhi(v *Value) { @@ -181,25 +189,32 @@ func decomposeUser(f *Func) { // We must do the opt pass before any deadcode elimination or we will // lose the name->value correspondence. i := 0 + var fnames []LocalSlot + var newNames []LocalSlot for _, name := range f.Names { t := name.Type switch { case t.IsStruct(): n := t.NumFields() + fnames = fnames[:0] + for i := 0; i < n; i++ { + fnames = append(fnames, f.Config.fe.SplitStruct(name, i)) + } for _, v := range f.NamedValues[name] { for i := 0; i < n; i++ { - fname := LocalSlot{name.N, t.FieldType(i), name.Off + t.FieldOff(i)} // TODO: use actual field name? x := v.Block.NewValue1I(v.Line, OpStructSelect, t.FieldType(i), int64(i), v) - f.NamedValues[fname] = append(f.NamedValues[fname], x) + f.NamedValues[fnames[i]] = append(f.NamedValues[fnames[i]], x) } } delete(f.NamedValues, name) + newNames = append(newNames, fnames...) default: f.Names[i] = name i++ } } f.Names = f.Names[:i] + f.Names = append(f.Names, newNames...) } func decomposeUserPhi(v *Value) { diff --git a/src/cmd/compile/internal/ssa/export_test.go b/src/cmd/compile/internal/ssa/export_test.go index ce577ef055..0a67de9f05 100644 --- a/src/cmd/compile/internal/ssa/export_test.go +++ b/src/cmd/compile/internal/ssa/export_test.go @@ -48,6 +48,9 @@ func (d DummyFrontend) SplitComplex(s LocalSlot) (LocalSlot, LocalSlot) { } return LocalSlot{s.N, d.TypeFloat32(), s.Off}, LocalSlot{s.N, d.TypeFloat32(), s.Off + 4} } +func (d DummyFrontend) SplitStruct(s LocalSlot, i int) LocalSlot { + return LocalSlot{s.N, s.Type.FieldType(i), s.Off + s.Type.FieldOff(i)} +} func (DummyFrontend) Line(line int32) string { return "unknown.go:0" } diff --git a/src/cmd/compile/internal/ssa/type.go b/src/cmd/compile/internal/ssa/type.go index 9643b07556..2a3de282cb 100644 --- a/src/cmd/compile/internal/ssa/type.go +++ b/src/cmd/compile/internal/ssa/type.go @@ -31,9 +31,10 @@ type Type interface { ElemType() Type // given []T or *T or [n]T, return T PtrTo() Type // given T, return *T - NumFields() int // # of fields of a struct - FieldType(i int) Type // type of ith field of the struct - FieldOff(i int) int64 // offset of ith field of the struct + NumFields() int // # of fields of a struct + FieldType(i int) Type // type of ith field of the struct + FieldOff(i int) int64 // offset of ith field of the struct + FieldName(i int) string // name of ith field of the struct NumElem() int64 // # of elements of an array @@ -53,30 +54,31 @@ type CompilerType struct { Int128 bool } -func (t *CompilerType) Size() int64 { return t.size } // Size in bytes -func (t *CompilerType) Alignment() int64 { return 0 } -func (t *CompilerType) IsBoolean() bool { return false } -func (t *CompilerType) IsInteger() bool { return false } -func (t *CompilerType) IsSigned() bool { return false } -func (t *CompilerType) IsFloat() bool { return false } -func (t *CompilerType) IsComplex() bool { return false } -func (t *CompilerType) IsPtrShaped() bool { return false } -func (t *CompilerType) IsString() bool { return false } -func (t *CompilerType) IsSlice() bool { return false } -func (t *CompilerType) IsArray() bool { return false } -func (t *CompilerType) IsStruct() bool { return false } -func (t *CompilerType) IsInterface() bool { return false } -func (t *CompilerType) IsMemory() bool { return t.Memory } -func (t *CompilerType) IsFlags() bool { return t.Flags } -func (t *CompilerType) IsVoid() bool { return t.Void } -func (t *CompilerType) String() string { return t.Name } -func (t *CompilerType) SimpleString() string { return t.Name } -func (t *CompilerType) ElemType() Type { panic("not implemented") } -func (t *CompilerType) PtrTo() Type { panic("not implemented") } -func (t *CompilerType) NumFields() int { panic("not implemented") } -func (t *CompilerType) FieldType(i int) Type { panic("not implemented") } -func (t *CompilerType) FieldOff(i int) int64 { panic("not implemented") } -func (t *CompilerType) NumElem() int64 { panic("not implemented") } +func (t *CompilerType) Size() int64 { return t.size } // Size in bytes +func (t *CompilerType) Alignment() int64 { return 0 } +func (t *CompilerType) IsBoolean() bool { return false } +func (t *CompilerType) IsInteger() bool { return false } +func (t *CompilerType) IsSigned() bool { return false } +func (t *CompilerType) IsFloat() bool { return false } +func (t *CompilerType) IsComplex() bool { return false } +func (t *CompilerType) IsPtrShaped() bool { return false } +func (t *CompilerType) IsString() bool { return false } +func (t *CompilerType) IsSlice() bool { return false } +func (t *CompilerType) IsArray() bool { return false } +func (t *CompilerType) IsStruct() bool { return false } +func (t *CompilerType) IsInterface() bool { return false } +func (t *CompilerType) IsMemory() bool { return t.Memory } +func (t *CompilerType) IsFlags() bool { return t.Flags } +func (t *CompilerType) IsVoid() bool { return t.Void } +func (t *CompilerType) String() string { return t.Name } +func (t *CompilerType) SimpleString() string { return t.Name } +func (t *CompilerType) ElemType() Type { panic("not implemented") } +func (t *CompilerType) PtrTo() Type { panic("not implemented") } +func (t *CompilerType) NumFields() int { panic("not implemented") } +func (t *CompilerType) FieldType(i int) Type { panic("not implemented") } +func (t *CompilerType) FieldOff(i int) int64 { panic("not implemented") } +func (t *CompilerType) FieldName(i int) string { panic("not implemented") } +func (t *CompilerType) NumElem() int64 { panic("not implemented") } // Cmp is a comparison between values a and b. // -1 if a < b diff --git a/src/cmd/compile/internal/ssa/type_test.go b/src/cmd/compile/internal/ssa/type_test.go index cd80abf03f..3b1a892083 100644 --- a/src/cmd/compile/internal/ssa/type_test.go +++ b/src/cmd/compile/internal/ssa/type_test.go @@ -24,30 +24,31 @@ type TypeImpl struct { Name string } -func (t *TypeImpl) Size() int64 { return t.Size_ } -func (t *TypeImpl) Alignment() int64 { return t.Align } -func (t *TypeImpl) IsBoolean() bool { return t.Boolean } -func (t *TypeImpl) IsInteger() bool { return t.Integer } -func (t *TypeImpl) IsSigned() bool { return t.Signed } -func (t *TypeImpl) IsFloat() bool { return t.Float } -func (t *TypeImpl) IsComplex() bool { return t.Complex } -func (t *TypeImpl) IsPtrShaped() bool { return t.Ptr } -func (t *TypeImpl) IsString() bool { return t.string } -func (t *TypeImpl) IsSlice() bool { return t.slice } -func (t *TypeImpl) IsArray() bool { return t.array } -func (t *TypeImpl) IsStruct() bool { return t.struct_ } -func (t *TypeImpl) IsInterface() bool { return t.inter } -func (t *TypeImpl) IsMemory() bool { return false } -func (t *TypeImpl) IsFlags() bool { return false } -func (t *TypeImpl) IsVoid() bool { return false } -func (t *TypeImpl) String() string { return t.Name } -func (t *TypeImpl) SimpleString() string { return t.Name } -func (t *TypeImpl) ElemType() Type { return t.Elem_ } -func (t *TypeImpl) PtrTo() Type { panic("not implemented") } -func (t *TypeImpl) NumFields() int { panic("not implemented") } -func (t *TypeImpl) FieldType(i int) Type { panic("not implemented") } -func (t *TypeImpl) FieldOff(i int) int64 { panic("not implemented") } -func (t *TypeImpl) NumElem() int64 { panic("not implemented") } +func (t *TypeImpl) Size() int64 { return t.Size_ } +func (t *TypeImpl) Alignment() int64 { return t.Align } +func (t *TypeImpl) IsBoolean() bool { return t.Boolean } +func (t *TypeImpl) IsInteger() bool { return t.Integer } +func (t *TypeImpl) IsSigned() bool { return t.Signed } +func (t *TypeImpl) IsFloat() bool { return t.Float } +func (t *TypeImpl) IsComplex() bool { return t.Complex } +func (t *TypeImpl) IsPtrShaped() bool { return t.Ptr } +func (t *TypeImpl) IsString() bool { return t.string } +func (t *TypeImpl) IsSlice() bool { return t.slice } +func (t *TypeImpl) IsArray() bool { return t.array } +func (t *TypeImpl) IsStruct() bool { return t.struct_ } +func (t *TypeImpl) IsInterface() bool { return t.inter } +func (t *TypeImpl) IsMemory() bool { return false } +func (t *TypeImpl) IsFlags() bool { return false } +func (t *TypeImpl) IsVoid() bool { return false } +func (t *TypeImpl) String() string { return t.Name } +func (t *TypeImpl) SimpleString() string { return t.Name } +func (t *TypeImpl) ElemType() Type { return t.Elem_ } +func (t *TypeImpl) PtrTo() Type { panic("not implemented") } +func (t *TypeImpl) NumFields() int { panic("not implemented") } +func (t *TypeImpl) FieldType(i int) Type { panic("not implemented") } +func (t *TypeImpl) FieldOff(i int) int64 { panic("not implemented") } +func (t *TypeImpl) FieldName(i int) string { panic("not implemented") } +func (t *TypeImpl) NumElem() int64 { panic("not implemented") } func (t *TypeImpl) Equal(u Type) bool { x, ok := u.(*TypeImpl) -- cgit v1.3 From 3fafe2e8888dadb6877fa1e7569f5bd1f688dd3a Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 11 Apr 2016 08:57:52 +0200 Subject: internal/trace: support parsing of 1.5 traces 1. Parse out version from trace header. 2. Restore handling of 1.5 traces. 3. Restore optional symbolization of traces. 4. Add some canned 1.5 traces for regression testing (http benchmark trace, runtime/trace stress traces, plus one with broken timestamps). Change-Id: Idb18a001d03ded8e13c2730eeeb37c5836e31256 Reviewed-on: https://go-review.googlesource.com/21803 Run-TryBot: Dmitry Vyukov TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements --- misc/nacl/testzip.proto | 4 + src/cmd/trace/main.go | 2 +- src/internal/trace/parser.go | 314 +++++++++++++++------ src/internal/trace/parser_test.go | 61 +++- src/internal/trace/testdata/http_1_5_good | Bin 0 -> 42218 bytes src/internal/trace/testdata/stress_1_5_good | Bin 0 -> 7446 bytes src/internal/trace/testdata/stress_1_5_unordered | Bin 0 -> 8194 bytes .../trace/testdata/stress_start_stop_1_5_good | Bin 0 -> 6997 bytes src/runtime/trace.go | 2 +- src/runtime/trace/trace_test.go | 4 +- 10 files changed, 295 insertions(+), 92 deletions(-) create mode 100644 src/internal/trace/testdata/http_1_5_good create mode 100644 src/internal/trace/testdata/stress_1_5_good create mode 100644 src/internal/trace/testdata/stress_1_5_unordered create mode 100644 src/internal/trace/testdata/stress_start_stop_1_5_good (limited to 'src/cmd') diff --git a/misc/nacl/testzip.proto b/misc/nacl/testzip.proto index 42db92f327..8c14b87f0a 100644 --- a/misc/nacl/testzip.proto +++ b/misc/nacl/testzip.proto @@ -109,6 +109,10 @@ go src=.. png testdata + + internal + trace + testdata + + io + mime diff --git a/src/cmd/trace/main.go b/src/cmd/trace/main.go index 12bf8c3c16..cfd222e132 100644 --- a/src/cmd/trace/main.go +++ b/src/cmd/trace/main.go @@ -99,7 +99,7 @@ func parseEvents() ([]*trace.Event, error) { defer tracef.Close() // Parse and symbolize. - events, err := trace.Parse(bufio.NewReader(tracef)) + events, err := trace.Parse(bufio.NewReader(tracef), programBinary) if err != nil { loader.err = fmt.Errorf("failed to parse trace: %v", err) return diff --git a/src/internal/trace/parser.go b/src/internal/trace/parser.go index 65530b15c3..82ddb8b6c8 100644 --- a/src/internal/trace/parser.go +++ b/src/internal/trace/parser.go @@ -5,17 +5,22 @@ package trace import ( + "bufio" "bytes" "fmt" "io" + "os" + "os/exec" "sort" + "strconv" + "strings" ) // Event describes one event in the trace. type Event struct { Off int // offset in input file (for debugging and error reporting) Type byte // one of Ev* - Seq int64 // sequence number + seq int64 // sequence number Ts int64 // timestamp in nanoseconds P int // P on which the event happened (can be one of TimerP, NetpollP, SyscallP) G uint64 // G on which the event happened @@ -53,12 +58,12 @@ const ( ) // Parse parses, post-processes and verifies the trace. -func Parse(r io.Reader) ([]*Event, error) { - rawEvents, strings, err := readTrace(r) +func Parse(r io.Reader, bin string) ([]*Event, error) { + ver, rawEvents, strings, err := readTrace(r) if err != nil { return nil, err } - events, stacks, err := parseEvents(rawEvents, strings) + events, stacks, err := parseEvents(ver, rawEvents, strings) if err != nil { return nil, err } @@ -66,7 +71,7 @@ func Parse(r io.Reader) ([]*Event, error) { if err != nil { return nil, err } - err = postProcessTrace(events) + err = postProcessTrace(ver, events) if err != nil { return nil, err } @@ -76,6 +81,11 @@ func Parse(r io.Reader) ([]*Event, error) { ev.Stk = stacks[ev.StkID] } } + if ver < 1007 && bin != "" { + if err := symbolize(events, bin); err != nil { + return nil, err + } + } return events, nil } @@ -88,61 +98,82 @@ type rawEvent struct { // readTrace does wire-format parsing and verification. // It does not care about specific event types and argument meaning. -func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) { +func readTrace(r io.Reader) (ver int, events []rawEvent, strings map[uint64]string, err error) { // Read and validate trace header. var buf [16]byte - off, err := r.Read(buf[:]) - if off != 16 || err != nil { - return nil, nil, fmt.Errorf("failed to read header: read %v, err %v", off, err) + off, err := io.ReadFull(r, buf[:]) + if err != nil { + err = fmt.Errorf("failed to read header: read %v, err %v", off, err) + return } - if !bytes.Equal(buf[:], []byte("go 1.5 trace\x00\x00\x00\x00")) { - return nil, nil, fmt.Errorf("not a trace file") + ver, err = parseHeader(buf[:]) + if err != nil { + return + } + switch ver { + case 1005, 1007: + break + default: + err = fmt.Errorf("unsupported trace file version %v.%v (update Go toolchain) %v", ver/1000, ver%1000, ver) + return } // Read events. - var events []rawEvent - strings := make(map[uint64]string) + strings = make(map[uint64]string) for { // Read event type and number of arguments (1 byte). off0 := off - n, err := r.Read(buf[:1]) + var n int + n, err = r.Read(buf[:1]) if err == io.EOF { + err = nil break } if err != nil || n != 1 { - return nil, nil, fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err) + err = fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err) + return } off += n typ := buf[0] << 2 >> 2 narg := buf[0] >> 6 + if typ == EvNone || typ >= EvCount || EventDescriptions[typ].minVersion > ver { + err = fmt.Errorf("unknown event type %v at offset 0x%x", typ, off0) + return + } if typ == EvString { // String dictionary entry [ID, length, string]. var id uint64 id, off, err = readVal(r, off) if err != nil { - return nil, nil, err + return } if id == 0 { - return nil, nil, fmt.Errorf("string at offset %d has invalid id 0", off) + err = fmt.Errorf("string at offset %d has invalid id 0", off) + return } if strings[id] != "" { - return nil, nil, fmt.Errorf("string at offset %d has duplicate id %v", off, id) + err = fmt.Errorf("string at offset %d has duplicate id %v", off, id) + return } var ln uint64 ln, off, err = readVal(r, off) if err != nil { - return nil, nil, err + return } if ln == 0 { - return nil, nil, fmt.Errorf("string at offset %d has invalid length 0", off) + err = fmt.Errorf("string at offset %d has invalid length 0", off) + return } if ln > 1e6 { - return nil, nil, fmt.Errorf("string at offset %d has too large length %v", off, ln) + err = fmt.Errorf("string at offset %d has too large length %v", off, ln) + return } buf := make([]byte, ln) - n, err := io.ReadFull(r, buf) + var n int + n, err = io.ReadFull(r, buf) if err != nil { - return nil, nil, fmt.Errorf("failed to read trace at offset %d: read %v, want %v, error %v", off, n, ln, err) + err = fmt.Errorf("failed to read trace at offset %d: read %v, want %v, error %v", off, n, ln, err) + return } off += n strings[id] = string(buf) @@ -154,7 +185,8 @@ func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) { var v uint64 v, off, err = readVal(r, off) if err != nil { - return nil, nil, err + err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err) + return } ev.args = append(ev.args, v) } @@ -163,39 +195,62 @@ func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) { var v uint64 v, off, err = readVal(r, off) if err != nil { - return nil, nil, err + err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err) + return } evLen := v off1 := off for evLen > uint64(off-off1) { v, off, err = readVal(r, off) if err != nil { - return nil, nil, err + err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err) + return } ev.args = append(ev.args, v) } if evLen != uint64(off-off1) { - return nil, nil, fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1) + err = fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1) + return } } events = append(events, ev) } - return events, strings, nil + return +} + +// parseHeader parses trace header of the form "go 1.7 trace\x00\x00\x00\x00" +// and returns parsed version as 1007. +func parseHeader(buf []byte) (int, error) { + if len(buf) != 16 { + return 0, fmt.Errorf("bad header length") + } + if buf[0] != 'g' || buf[1] != 'o' || buf[2] != ' ' || + buf[3] < '1' || buf[3] > '9' || + buf[4] != '.' || + buf[5] < '1' || buf[5] > '9' { + return 0, fmt.Errorf("not a trace file") + } + ver := int(buf[5] - '0') + i := 0 + for ; buf[6+i] >= '0' && buf[6+i] <= '9' && i < 2; i++ { + ver = ver*10 + int(buf[6+i]-'0') + } + ver += int(buf[3]-'0') * 1000 + if !bytes.Equal(buf[6+i:], []byte(" trace\x00\x00\x00\x00")[:10-i]) { + return 0, fmt.Errorf("not a trace file") + } + return ver, nil } // Parse events transforms raw events into events. // It does analyze and verify per-event-type arguments. -func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Event, stacks map[uint64][]*Frame, err error) { +func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (events []*Event, stacks map[uint64][]*Frame, err error) { var ticksPerSec, lastSeq, lastTs int64 var lastG, timerGoid uint64 var lastP int lastGs := make(map[int]uint64) // last goroutine running on P stacks = make(map[uint64][]*Frame) for _, raw := range rawEvents { - if raw.typ == EvNone || raw.typ >= EvCount { - err = fmt.Errorf("unknown event type %v at offset 0x%x", raw.typ, raw.off) - return - } desc := EventDescriptions[raw.typ] if desc.Name == "" { err = fmt.Errorf("missing description for event type %v", raw.typ) @@ -246,7 +301,11 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve raw.off, size) return } - if want := 2 + 4*size; uint64(len(raw.args)) != want { + want := 2 + 4*size + if ver < 1007 { + want = 2 + size + } + if uint64(len(raw.args)) != want { err = fmt.Errorf("EvStack has wrong number of arguments at offset 0x%x: want %v, got %v", raw.off, want, len(raw.args)) return @@ -255,19 +314,23 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve if id != 0 && size > 0 { stk := make([]*Frame, size) for i := 0; i < int(size); i++ { - pc := raw.args[2+i*4+0] - fn := raw.args[2+i*4+1] - file := raw.args[2+i*4+2] - line := raw.args[2+i*4+3] - stk[i] = &Frame{PC: pc, Fn: strings[fn], File: strings[file], Line: int(line)} + if ver < 1007 { + stk[i] = &Frame{PC: raw.args[2+i]} + } else { + pc := raw.args[2+i*4+0] + fn := raw.args[2+i*4+1] + file := raw.args[2+i*4+2] + line := raw.args[2+i*4+3] + stk[i] = &Frame{PC: pc, Fn: strings[fn], File: strings[file], Line: int(line)} + } } stacks[id] = stk } default: e := &Event{Off: raw.off, Type: raw.typ, P: lastP, G: lastG} - e.Seq = lastSeq + int64(raw.args[0]) + e.seq = lastSeq + int64(raw.args[0]) e.Ts = lastTs + int64(raw.args[1]) - lastSeq = e.Seq + lastSeq = e.seq lastTs = e.Ts for i := range desc.Args { e.Args[i] = raw.args[i+2] @@ -289,7 +352,7 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve case EvGoSysExit: // EvGoSysExit emission is delayed until the thread has a P. // Give it the real sequence number and time stamp. - e.Seq = int64(e.Args[1]) + e.seq = int64(e.Args[1]) if e.Args[2] != 0 { e.Ts = int64(e.Args[2]) } @@ -387,7 +450,7 @@ var ErrTimeOrder = fmt.Errorf("time stamps out of order") // The resulting trace is guaranteed to be consistent // (for example, a P does not run two Gs at the same time, or a G is indeed // blocked before an unblock event). -func postProcessTrace(events []*Event) error { +func postProcessTrace(ver int, events []*Event) error { const ( gDead = iota gRunnable @@ -510,7 +573,12 @@ func postProcessTrace(events []*Event) error { g.evStart = ev p.g = ev.G if g.evCreate != nil { - ev.StkID = g.evCreate.Args[1] + if ver < 1007 { + // +1 because symbolizer expects return pc. + ev.Stk = []*Frame{{PC: g.evCreate.Args[1] + 1}} + } else { + ev.StkID = g.evCreate.Args[1] + } g.evCreate = nil } @@ -611,6 +679,79 @@ func postProcessTrace(events []*Event) error { return nil } +// symbolize attaches func/file/line info to stack traces. +func symbolize(events []*Event, bin string) error { + // First, collect and dedup all pcs. + pcs := make(map[uint64]*Frame) + for _, ev := range events { + for _, f := range ev.Stk { + pcs[f.PC] = nil + } + } + + // Start addr2line. + cmd := exec.Command("go", "tool", "addr2line", bin) + in, err := cmd.StdinPipe() + if err != nil { + return fmt.Errorf("failed to pipe addr2line stdin: %v", err) + } + cmd.Stderr = os.Stderr + out, err := cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("failed to pipe addr2line stdout: %v", err) + } + err = cmd.Start() + if err != nil { + return fmt.Errorf("failed to start addr2line: %v", err) + } + outb := bufio.NewReader(out) + + // Write all pcs to addr2line. + // Need to copy pcs to an array, because map iteration order is non-deterministic. + var pcArray []uint64 + for pc := range pcs { + pcArray = append(pcArray, pc) + _, err := fmt.Fprintf(in, "0x%x\n", pc-1) + if err != nil { + return fmt.Errorf("failed to write to addr2line: %v", err) + } + } + in.Close() + + // Read in answers. + for _, pc := range pcArray { + fn, err := outb.ReadString('\n') + if err != nil { + return fmt.Errorf("failed to read from addr2line: %v", err) + } + file, err := outb.ReadString('\n') + if err != nil { + return fmt.Errorf("failed to read from addr2line: %v", err) + } + f := &Frame{PC: pc} + f.Fn = fn[:len(fn)-1] + f.File = file[:len(file)-1] + if colon := strings.LastIndex(f.File, ":"); colon != -1 { + ln, err := strconv.Atoi(f.File[colon+1:]) + if err == nil { + f.File = f.File[:colon] + f.Line = ln + } + } + pcs[pc] = f + } + cmd.Wait() + + // Replace frames in events array. + for _, ev := range events { + for i, f := range ev.Stk { + ev.Stk[i] = pcs[f.PC] + } + } + + return nil +} + // readVal reads unsigned base-128 value from r. func readVal(r io.Reader, off0 int) (v uint64, off int, err error) { off = off0 @@ -637,7 +778,7 @@ func (l eventList) Len() int { } func (l eventList) Less(i, j int) bool { - return l[i].Seq < l[j].Seq + return l[i].seq < l[j].seq } func (l eventList) Swap(i, j int) { @@ -701,46 +842,47 @@ const ( ) var EventDescriptions = [EvCount]struct { - Name string - Stack bool - Args []string + Name string + minVersion int + Stack bool + Args []string }{ - EvNone: {"None", false, []string{}}, - EvBatch: {"Batch", false, []string{"p", "seq", "ticks"}}, - EvFrequency: {"Frequency", false, []string{"freq", "unused"}}, - EvStack: {"Stack", false, []string{"id", "siz"}}, - EvGomaxprocs: {"Gomaxprocs", true, []string{"procs"}}, - EvProcStart: {"ProcStart", false, []string{"thread"}}, - EvProcStop: {"ProcStop", false, []string{}}, - EvGCStart: {"GCStart", true, []string{}}, - EvGCDone: {"GCDone", false, []string{}}, - EvGCScanStart: {"GCScanStart", false, []string{}}, - EvGCScanDone: {"GCScanDone", false, []string{}}, - EvGCSweepStart: {"GCSweepStart", true, []string{}}, - EvGCSweepDone: {"GCSweepDone", false, []string{}}, - EvGoCreate: {"GoCreate", true, []string{"g", "stack"}}, - EvGoStart: {"GoStart", false, []string{"g"}}, - EvGoEnd: {"GoEnd", false, []string{}}, - EvGoStop: {"GoStop", true, []string{}}, - EvGoSched: {"GoSched", true, []string{}}, - EvGoPreempt: {"GoPreempt", true, []string{}}, - EvGoSleep: {"GoSleep", true, []string{}}, - EvGoBlock: {"GoBlock", true, []string{}}, - EvGoUnblock: {"GoUnblock", true, []string{"g"}}, - EvGoBlockSend: {"GoBlockSend", true, []string{}}, - EvGoBlockRecv: {"GoBlockRecv", true, []string{}}, - EvGoBlockSelect: {"GoBlockSelect", true, []string{}}, - EvGoBlockSync: {"GoBlockSync", true, []string{}}, - EvGoBlockCond: {"GoBlockCond", true, []string{}}, - EvGoBlockNet: {"GoBlockNet", true, []string{}}, - EvGoSysCall: {"GoSysCall", true, []string{}}, - EvGoSysExit: {"GoSysExit", false, []string{"g", "seq", "ts"}}, - EvGoSysBlock: {"GoSysBlock", false, []string{}}, - EvGoWaiting: {"GoWaiting", false, []string{"g"}}, - EvGoInSyscall: {"GoInSyscall", false, []string{"g"}}, - EvHeapAlloc: {"HeapAlloc", false, []string{"mem"}}, - EvNextGC: {"NextGC", false, []string{"mem"}}, - EvTimerGoroutine: {"TimerGoroutine", false, []string{"g", "unused"}}, - EvFutileWakeup: {"FutileWakeup", false, []string{}}, - EvString: {"String", false, []string{}}, + EvNone: {"None", 1005, false, []string{}}, + EvBatch: {"Batch", 1005, false, []string{"p", "seq", "ticks"}}, + EvFrequency: {"Frequency", 1005, false, []string{"freq", "unused"}}, + EvStack: {"Stack", 1005, false, []string{"id", "siz"}}, + EvGomaxprocs: {"Gomaxprocs", 1005, true, []string{"procs"}}, + EvProcStart: {"ProcStart", 1005, false, []string{"thread"}}, + EvProcStop: {"ProcStop", 1005, false, []string{}}, + EvGCStart: {"GCStart", 1005, true, []string{}}, + EvGCDone: {"GCDone", 1005, false, []string{}}, + EvGCScanStart: {"GCScanStart", 1005, false, []string{}}, + EvGCScanDone: {"GCScanDone", 1005, false, []string{}}, + EvGCSweepStart: {"GCSweepStart", 1005, true, []string{}}, + EvGCSweepDone: {"GCSweepDone", 1005, false, []string{}}, + EvGoCreate: {"GoCreate", 1005, true, []string{"g", "stack"}}, + EvGoStart: {"GoStart", 1005, false, []string{"g"}}, + EvGoEnd: {"GoEnd", 1005, false, []string{}}, + EvGoStop: {"GoStop", 1005, true, []string{}}, + EvGoSched: {"GoSched", 1005, true, []string{}}, + EvGoPreempt: {"GoPreempt", 1005, true, []string{}}, + EvGoSleep: {"GoSleep", 1005, true, []string{}}, + EvGoBlock: {"GoBlock", 1005, true, []string{}}, + EvGoUnblock: {"GoUnblock", 1005, true, []string{"g"}}, + EvGoBlockSend: {"GoBlockSend", 1005, true, []string{}}, + EvGoBlockRecv: {"GoBlockRecv", 1005, true, []string{}}, + EvGoBlockSelect: {"GoBlockSelect", 1005, true, []string{}}, + EvGoBlockSync: {"GoBlockSync", 1005, true, []string{}}, + EvGoBlockCond: {"GoBlockCond", 1005, true, []string{}}, + EvGoBlockNet: {"GoBlockNet", 1005, true, []string{}}, + EvGoSysCall: {"GoSysCall", 1005, true, []string{}}, + EvGoSysExit: {"GoSysExit", 1005, false, []string{"g", "seq", "ts"}}, + EvGoSysBlock: {"GoSysBlock", 1005, false, []string{}}, + EvGoWaiting: {"GoWaiting", 1005, false, []string{"g"}}, + EvGoInSyscall: {"GoInSyscall", 1005, false, []string{"g"}}, + EvHeapAlloc: {"HeapAlloc", 1005, false, []string{"mem"}}, + EvNextGC: {"NextGC", 1005, false, []string{"mem"}}, + EvTimerGoroutine: {"TimerGoroutine", 1005, false, []string{"g", "unused"}}, + EvFutileWakeup: {"FutileWakeup", 1005, false, []string{}}, + EvString: {"String", 1007, false, []string{}}, } diff --git a/src/internal/trace/parser_test.go b/src/internal/trace/parser_test.go index fecefc4053..db8d2a30ce 100644 --- a/src/internal/trace/parser_test.go +++ b/src/internal/trace/parser_test.go @@ -5,6 +5,9 @@ package trace import ( + "bytes" + "io/ioutil" + "path/filepath" "strings" "testing" ) @@ -22,9 +25,63 @@ func TestCorruptedInputs(t *testing.T) { "go 1.5 trace\x00\x00\x00\x00\xc3\x0200", } for _, data := range tests { - events, err := Parse(strings.NewReader(data)) + events, err := Parse(strings.NewReader(data), "") if err == nil || events != nil { - t.Fatalf("no error on input: %q\n", data) + t.Fatalf("no error on input: %q", data) + } + } +} + +func TestParseCanned(t *testing.T) { + files, err := ioutil.ReadDir("./testdata") + if err != nil { + t.Fatalf("failed to read ./testdata: %v", err) + } + for _, f := range files { + data, err := ioutil.ReadFile(filepath.Join("./testdata", f.Name())) + if err != nil { + t.Fatalf("failed to read input file: %v", err) + } + _, err = Parse(bytes.NewReader(data), "") + switch { + case strings.HasSuffix(f.Name(), "_good"): + if err != nil { + t.Errorf("failed to parse good trace %v: %v", f.Name(), err) + } + case strings.HasSuffix(f.Name(), "_unordered"): + if err != ErrTimeOrder { + t.Errorf("unordered trace is not detected %v: %v", f.Name(), err) + } + default: + t.Errorf("unknown input file suffix: %v", f.Name()) + } + } +} + +func TestParseVersion(t *testing.T) { + tests := map[string]int{ + "go 1.5 trace\x00\x00\x00\x00": 1005, + "go 1.7 trace\x00\x00\x00\x00": 1007, + "go 1.10 trace\x00\x00\x00": 1010, + "go 1.25 trace\x00\x00\x00": 1025, + "go 1.234 trace\x00\x00": 1234, + "go 1.2345 trace\x00": -1, + "go 0.0 trace\x00\x00\x00\x00": -1, + "go a.b trace\x00\x00\x00\x00": -1, + } + for header, ver := range tests { + ver1, err := parseHeader([]byte(header)) + if ver == -1 { + if err == nil { + t.Fatalf("no error on input: %q, version %v", header, ver1) + } + } else { + if err != nil { + t.Fatalf("failed to parse: %q (%v)", header, err) + } + if ver != ver1 { + t.Fatalf("wrong version: %v, want %v, input: %q", ver1, ver, header) + } } } } diff --git a/src/internal/trace/testdata/http_1_5_good b/src/internal/trace/testdata/http_1_5_good new file mode 100644 index 0000000000..0736cae674 Binary files /dev/null and b/src/internal/trace/testdata/http_1_5_good differ diff --git a/src/internal/trace/testdata/stress_1_5_good b/src/internal/trace/testdata/stress_1_5_good new file mode 100644 index 0000000000..c5055ebd19 Binary files /dev/null and b/src/internal/trace/testdata/stress_1_5_good differ diff --git a/src/internal/trace/testdata/stress_1_5_unordered b/src/internal/trace/testdata/stress_1_5_unordered new file mode 100644 index 0000000000..11f7d745ca Binary files /dev/null and b/src/internal/trace/testdata/stress_1_5_unordered differ diff --git a/src/internal/trace/testdata/stress_start_stop_1_5_good b/src/internal/trace/testdata/stress_start_stop_1_5_good new file mode 100644 index 0000000000..72a887b844 Binary files /dev/null and b/src/internal/trace/testdata/stress_start_stop_1_5_good differ diff --git a/src/runtime/trace.go b/src/runtime/trace.go index f54e5e0a7e..dcf534549a 100644 --- a/src/runtime/trace.go +++ b/src/runtime/trace.go @@ -356,7 +356,7 @@ func ReadTrace() []byte { trace.headerWritten = true trace.lockOwner = nil unlock(&trace.lock) - return []byte("go 1.5 trace\x00\x00\x00\x00") + return []byte("go 1.7 trace\x00\x00\x00\x00") } // Wait for new data. if trace.fullHead == 0 && !trace.shutdown { diff --git a/src/runtime/trace/trace_test.go b/src/runtime/trace/trace_test.go index b787a2fc27..d10e928a66 100644 --- a/src/runtime/trace/trace_test.go +++ b/src/runtime/trace/trace_test.go @@ -52,7 +52,7 @@ func TestTrace(t *testing.T) { t.Fatalf("failed to start tracing: %v", err) } Stop() - _, err := trace.Parse(buf) + _, err := trace.Parse(buf, "") if err == trace.ErrTimeOrder { t.Skipf("skipping trace: %v", err) } @@ -62,7 +62,7 @@ func TestTrace(t *testing.T) { } func parseTrace(t *testing.T, r io.Reader) ([]*trace.Event, map[uint64]*trace.GDesc, error) { - events, err := trace.Parse(r) + events, err := trace.Parse(r, "") if err == trace.ErrTimeOrder { t.Skipf("skipping trace: %v", err) } -- cgit v1.3 From e79fef8e55f8a893c65f41566bbec10339d45dec Mon Sep 17 00:00:00 2001 From: Shawn Walker-Salas Date: Thu, 7 Apr 2016 15:26:57 -0700 Subject: cmd/link: external linking can fail on Solaris 11.2+ Workaround external linking issues encountered on Solaris 11.2+ due to the go.o object file being created with a NULL STT_FILE symtab entry by using a placeholder name. Fixes #14957 Change-Id: I89c501b4c548469f3c878151947d35588057982b Reviewed-on: https://go-review.googlesource.com/21636 Reviewed-by: David Crawshaw --- src/cmd/link/internal/ld/symtab.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index c7c2733507..ae0b17c259 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -198,7 +198,9 @@ func Asmelfsym() { // Some linkers will add a FILE sym if one is not present. // Avoid having the working directory inserted into the symbol table. - putelfsyment(0, 0, 0, STB_LOCAL<<4|STT_FILE, SHN_ABS, 0) + // It is added with a name to avoid problems with external linking + // encountered on some versions of Solaris. See issue #14957. + putelfsyment(putelfstr("go.go"), 0, 0, STB_LOCAL<<4|STT_FILE, SHN_ABS, 0) numelfsym++ elfbind = STB_LOCAL -- cgit v1.3 From cd6b2b7451c6feb277d38820f41f81ce4a036af2 Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Sun, 10 Apr 2016 20:01:49 -0400 Subject: cmd/internal/obj/s390x: add MULHD instruction Emulate 64-bit signed high multiplication ((a*b)>>64). To do this we use the 64-bit unsigned high multiplication method and then fix the result as shown in Hacker's Delight 2nd ed., chapter 8-3. Required to enable some division optimizations. Change-Id: I9194f428e09d3d029cb1afb4715cd5424b5d922e Reviewed-on: https://go-review.googlesource.com/21774 Reviewed-by: Bill O'Farrell Run-TryBot: Brad Fitzpatrick Reviewed-by: Brad Fitzpatrick --- src/cmd/asm/internal/asm/testdata/s390x.s | 4 ++++ src/cmd/internal/obj/s390x/a.out.go | 1 + src/cmd/internal/obj/s390x/anames.go | 1 + src/cmd/internal/obj/s390x/asmz.go | 34 +++++++++++++++++++++++-------- 4 files changed, 32 insertions(+), 8 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/asm/internal/asm/testdata/s390x.s b/src/cmd/asm/internal/asm/testdata/s390x.s index 148cd2eaae..f1dc9aff2d 100644 --- a/src/cmd/asm/internal/asm/testdata/s390x.s +++ b/src/cmd/asm/internal/asm/testdata/s390x.s @@ -61,6 +61,10 @@ TEXT main·foo(SB),7,$16-0 // TEXT main.foo(SB), 7, $16-0 MULLW R6, R7, R8 // b9040087b91c0086 MULLW $8192, R6 // c26000002000 MULLW $8192, R6, R7 // b9040076c27000002000 + MULHD R9, R8 // b90400b8b98600a9ebb9003f000ab98000b8b90900abebb8003f000ab98000b9b9e9b08a + MULHD R7, R2, R1 // b90400b2b98600a7ebb7003f000ab98000b2b90900abebb2003f000ab98000b7b9e9b01a + MULHDU R3, R4 // b90400b4b98600a3b904004a + MULHDU R5, R6, R7 // b90400b6b98600a5b904007a DIVD R1, R2 // b90400b2b90d00a1b904002b DIVD R1, R2, R3 // b90400b2b90d00a1b904003b DIVW R4, R5 // b90400b5b91d00a4b904005b diff --git a/src/cmd/internal/obj/s390x/a.out.go b/src/cmd/internal/obj/s390x/a.out.go index 2cb03ae603..e7256d1d41 100644 --- a/src/cmd/internal/obj/s390x/a.out.go +++ b/src/cmd/internal/obj/s390x/a.out.go @@ -218,6 +218,7 @@ const ( ADIVDU AMULLW AMULLD + AMULHD AMULHDU ASUB ASUBC diff --git a/src/cmd/internal/obj/s390x/anames.go b/src/cmd/internal/obj/s390x/anames.go index e79a147a90..62dd181eda 100644 --- a/src/cmd/internal/obj/s390x/anames.go +++ b/src/cmd/internal/obj/s390x/anames.go @@ -17,6 +17,7 @@ var Anames = []string{ "DIVDU", "MULLW", "MULLD", + "MULHD", "MULHDU", "SUB", "SUBC", diff --git a/src/cmd/internal/obj/s390x/asmz.go b/src/cmd/internal/obj/s390x/asmz.go index bccd7c3bd8..cf3b11424b 100644 --- a/src/cmd/internal/obj/s390x/asmz.go +++ b/src/cmd/internal/obj/s390x/asmz.go @@ -150,6 +150,8 @@ var optab = []Optab{ Optab{AMULLW, C_REG, C_NONE, C_NONE, C_REG, 2, 0}, Optab{AMULLW, C_LCON, C_REG, C_NONE, C_REG, 22, 0}, Optab{AMULLW, C_LCON, C_NONE, C_NONE, C_REG, 22, 0}, + Optab{AMULHD, C_REG, C_NONE, C_NONE, C_REG, 4, 0}, + Optab{AMULHD, C_REG, C_REG, C_NONE, C_REG, 4, 0}, Optab{ASUBC, C_REG, C_REG, C_NONE, C_REG, 10, 0}, Optab{ASUBC, C_REG, C_NONE, C_NONE, C_REG, 10, 0}, Optab{ADIVW, C_REG, C_REG, C_NONE, C_REG, 2, 0}, @@ -793,10 +795,11 @@ func buildop(ctxt *obj.Link) { case ADIVW: opset(AADDE, r) opset(AMULLD, r) - opset(AMULHDU, r) opset(ADIVD, r) opset(ADIVDU, r) opset(ADIVWU, r) + case AMULHD: + opset(AMULHDU, r) case AMOVBZ: opset(AMOVH, r) opset(AMOVHZ, r) @@ -2580,8 +2583,6 @@ func asmout(ctxt *obj.Link, asm *[]byte) { opcode = op_MSGFR case AMULLD: opcode = op_MSGR - case AMULHDU: - opcode = op_MLGR case ADIVW: opcode = op_DSGFR case ADIVWU: @@ -2628,11 +2629,6 @@ func asmout(ctxt *obj.Link, asm *[]byte) { zRRE(opcode, REGTMP, uint32(p.From.Reg), asm) zRRE(op_LGR, uint32(p.To.Reg), REGTMP2, asm) - case AMULHDU: - zRRE(op_LGR, REGTMP2, uint32(r), asm) - zRRE(opcode, REGTMP, uint32(p.From.Reg), asm) - zRRE(op_LGR, uint32(p.To.Reg), REGTMP, asm) - case AFADD, AFADDS: if r == int(p.To.Reg) { zRRE(opcode, uint32(p.To.Reg), uint32(p.From.Reg), asm) @@ -2695,6 +2691,28 @@ func asmout(ctxt *obj.Link, asm *[]byte) { zRIL(_a, op_IIHF, uint32(p.To.Reg), uint32(v>>32), asm) } + case 4: // multiply high (a*b)>>64 + r := p.Reg + if r == 0 { + r = p.To.Reg + } + zRRE(op_LGR, REGTMP2, uint32(r), asm) + zRRE(op_MLGR, REGTMP, uint32(p.From.Reg), asm) + switch p.As { + case AMULHDU: + // Unsigned: move result into correct register. + zRRE(op_LGR, uint32(p.To.Reg), REGTMP, asm) + case AMULHD: + // Signed: need to convert result. + // See Hacker's Delight 8-3. + zRSY(op_SRAG, REGTMP2, uint32(p.From.Reg), 0, 63, asm) + zRRE(op_NGR, REGTMP2, uint32(r), asm) + zRRE(op_SGR, REGTMP, REGTMP2, asm) + zRSY(op_SRAG, REGTMP2, uint32(r), 0, 63, asm) + zRRE(op_NGR, REGTMP2, uint32(p.From.Reg), asm) + zRRF(op_SGRK, REGTMP2, 0, uint32(p.To.Reg), REGTMP, asm) + } + case 5: // syscall zI(op_SVC, 0, asm) -- cgit v1.3 From 7f53391f6b7f2387a5ed00398d34b046c321966f Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 11 Apr 2016 12:22:26 -0700 Subject: cmd/compile: fix -N build The decomposer of builtin types is confused by having structs still around from the user-type decomposer. They're all dead though, so just enabling a deadcode pass fixes things. Change-Id: I2df6bc7e829be03eabfd24c8dda1bff96f3d7091 Reviewed-on: https://go-review.googlesource.com/21839 Run-TryBot: Keith Randall TryBot-Result: Gobot Gobot Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/compile.go | 6 +++--- src/cmd/compile/internal/ssa/decompose.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/compile.go b/src/cmd/compile/internal/ssa/compile.go index d52ae9c6da..b4215f119e 100644 --- a/src/cmd/compile/internal/ssa/compile.go +++ b/src/cmd/compile/internal/ssa/compile.go @@ -230,9 +230,9 @@ var passes = [...]pass{ {name: "early deadcode", fn: deadcode}, // remove generated dead code to avoid doing pointless work during opt {name: "short circuit", fn: shortcircuit}, {name: "decompose user", fn: decomposeUser, required: true}, - {name: "opt", fn: opt, required: true}, // TODO: split required rules and optimizing rules - {name: "zero arg cse", fn: zcse, required: true}, // required to merge OpSB values - {name: "opt deadcode", fn: deadcode}, // remove any blocks orphaned during opt + {name: "opt", fn: opt, required: true}, // TODO: split required rules and optimizing rules + {name: "zero arg cse", fn: zcse, required: true}, // required to merge OpSB values + {name: "opt deadcode", fn: deadcode, required: true}, // remove any blocks orphaned during opt {name: "generic cse", fn: cse}, {name: "phiopt", fn: phiopt}, {name: "nilcheckelim", fn: nilcheckelim}, diff --git a/src/cmd/compile/internal/ssa/decompose.go b/src/cmd/compile/internal/ssa/decompose.go index de02885d76..53116ba593 100644 --- a/src/cmd/compile/internal/ssa/decompose.go +++ b/src/cmd/compile/internal/ssa/decompose.go @@ -79,7 +79,7 @@ func decomposeBuiltIn(f *Func) { } delete(f.NamedValues, name) case t.Size() > f.Config.IntSize: - f.Unimplementedf("undecomposed named type %s", t) + f.Unimplementedf("undecomposed named type %s %s", name, t) default: newNames = append(newNames, name) } -- cgit v1.3 From 7e40627a0e595aa321efaf44f8507b678ee5eb1e Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 11 Apr 2016 13:17:52 -0700 Subject: cmd/compile: zero all three argstorage slots These changes were missed when going from 2 to 3 argstorage slots. https://go-review.googlesource.com/20296/ Change-Id: I930a307bb0b695bf1ae088030c9bbb6d14ca31d2 Reviewed-on: https://go-review.googlesource.com/21841 Reviewed-by: Brad Fitzpatrick Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/ssa/func.go | 10 ++++++++-- src/cmd/compile/internal/ssa/value.go | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 6e47b7f19c..8dd75f6093 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -284,7 +284,10 @@ func (b *Block) NewValue2I(line int32, op Op, t Type, auxint int64, arg0, arg1 * func (b *Block) NewValue3(line int32, op Op, t Type, arg0, arg1, arg2 *Value) *Value { v := b.Func.newValue(op, t, b, line) v.AuxInt = 0 - v.Args = []*Value{arg0, arg1, arg2} + v.Args = v.argstorage[:3] + v.argstorage[0] = arg0 + v.argstorage[1] = arg1 + v.argstorage[2] = arg2 arg0.Uses++ arg1.Uses++ arg2.Uses++ @@ -295,7 +298,10 @@ func (b *Block) NewValue3(line int32, op Op, t Type, arg0, arg1, arg2 *Value) *V func (b *Block) NewValue3I(line int32, op Op, t Type, auxint int64, arg0, arg1, arg2 *Value) *Value { v := b.Func.newValue(op, t, b, line) v.AuxInt = auxint - v.Args = []*Value{arg0, arg1, arg2} + v.Args = v.argstorage[:3] + v.argstorage[0] = arg0 + v.argstorage[1] = arg1 + v.argstorage[2] = arg2 arg0.Uses++ arg1.Uses++ arg2.Uses++ diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go index fd4eb64db1..6c364ad932 100644 --- a/src/cmd/compile/internal/ssa/value.go +++ b/src/cmd/compile/internal/ssa/value.go @@ -185,6 +185,7 @@ func (v *Value) resetArgs() { } v.argstorage[0] = nil v.argstorage[1] = nil + v.argstorage[2] = nil v.Args = v.argstorage[:0] } -- cgit v1.3 From 32efa16c3d63dd630e2190a8c0f30c0a941f6fd7 Mon Sep 17 00:00:00 2001 From: David Chase Date: Fri, 1 Apr 2016 14:51:29 -0400 Subject: cmd/compile: added stats printing to stackalloc This is controlled by the "regalloc" stats flag, since regalloc calls stackalloc. The plan is for this to allow comparison of cheaper stack allocation algorithms with what we have now. Change-Id: Ibf64a780344c69babfcbb328fd6d053ea2e02cfc Reviewed-on: https://go-review.googlesource.com/21393 Run-TryBot: David Chase Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/stackalloc.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/stackalloc.go b/src/cmd/compile/internal/ssa/stackalloc.go index 1de22dc96e..e3ef66ab1b 100644 --- a/src/cmd/compile/internal/ssa/stackalloc.go +++ b/src/cmd/compile/internal/ssa/stackalloc.go @@ -22,6 +22,13 @@ type stackAllocState struct { names []LocalSlot slots []int used []bool + + nArgSlot, // Number of Values sourced to arg slot + nNotNeed, // Number of Values not needing a stack slot + nNamedSlot, // Number of Values using a named stack slot + nReuse, // Number of values reusing a stack slot + nAuto, // Number of autos allocated for stack slots. + nSelfInterfere int32 // Number of self-interferences } func newStackAllocState(f *Func) *stackAllocState { @@ -54,6 +61,7 @@ func putStackAllocState(s *stackAllocState) { s.f.Config.stackAllocState = s s.f = nil s.live = nil + s.nArgSlot, s.nNotNeed, s.nNamedSlot, s.nReuse, s.nAuto, s.nSelfInterfere = 0, 0, 0, 0, 0, 0 } type stackValState struct { @@ -75,6 +83,13 @@ func stackalloc(f *Func, spillLive [][]ID) [][]ID { defer putStackAllocState(s) s.stackalloc() + if f.pass.stats > 0 { + f.logStat("stack_alloc_stats", + s.nArgSlot, "arg_slots", s.nNotNeed, "slot_not_needed", + s.nNamedSlot, "named_slots", s.nAuto, "auto_slots", + s.nReuse, "reused_slots", s.nSelfInterfere, "self_interfering") + } + return s.live } @@ -170,9 +185,11 @@ func (s *stackAllocState) stackalloc() { for _, b := range f.Blocks { for _, v := range b.Values { if !s.values[v.ID].needSlot { + s.nNotNeed++ continue } if v.Op == OpArg { + s.nArgSlot++ continue // already picked } @@ -190,12 +207,14 @@ func (s *stackAllocState) stackalloc() { if h != nil && h.(LocalSlot).N == name.N && h.(LocalSlot).Off == name.Off { // A variable can interfere with itself. // It is rare, but but it can happen. + s.nSelfInterfere++ goto noname } } if f.pass.debug > stackDebug { fmt.Printf("stackalloc %s to %s\n", v, name.Name()) } + s.nNamedSlot++ f.setHome(v, name) continue } @@ -217,11 +236,13 @@ func (s *stackAllocState) stackalloc() { var i int for i = 0; i < len(locs); i++ { if !used[i] { + s.nReuse++ break } } // If there is no unused stack slot, allocate a new one. if i == len(locs) { + s.nAuto++ locs = append(locs, LocalSlot{N: f.Config.fe.Auto(v.Type), Type: v.Type, Off: 0}) locations[v.Type] = locs } -- cgit v1.3 From a4650a2111b2bb826ca64a13bdad9c96e3095e47 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Sun, 10 Apr 2016 09:44:17 -0700 Subject: cmd/compile: avoid write barrier in append fast path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we are writing the result of an append back to the same slice, we don’t need a write barrier on the fast path. This re-implements an optimization that was present in the old backend. Updates #14921 Fixes #14969 Sample code: var x []byte func p() { x = append(x, 1, 2, 3) } Before: "".p t=1 size=224 args=0x0 locals=0x48 0x0000 00000 (append.go:21) TEXT "".p(SB), $72-0 0x0000 00000 (append.go:21) MOVQ (TLS), CX 0x0009 00009 (append.go:21) CMPQ SP, 16(CX) 0x000d 00013 (append.go:21) JLS 199 0x0013 00019 (append.go:21) SUBQ $72, SP 0x0017 00023 (append.go:21) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0017 00023 (append.go:21) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0017 00023 (append.go:19) MOVQ "".x+16(SB), CX 0x001e 00030 (append.go:19) MOVQ "".x(SB), DX 0x0025 00037 (append.go:19) MOVQ "".x+8(SB), BX 0x002c 00044 (append.go:19) MOVQ BX, "".autotmp_0+64(SP) 0x0031 00049 (append.go:22) LEAQ 3(BX), BP 0x0035 00053 (append.go:22) CMPQ BP, CX 0x0038 00056 (append.go:22) JGT $0, 131 0x003a 00058 (append.go:22) MOVB $1, (DX)(BX*1) 0x003e 00062 (append.go:22) MOVB $2, 1(DX)(BX*1) 0x0043 00067 (append.go:22) MOVB $3, 2(DX)(BX*1) 0x0048 00072 (append.go:22) MOVQ BP, "".x+8(SB) 0x004f 00079 (append.go:22) MOVQ CX, "".x+16(SB) 0x0056 00086 (append.go:22) MOVL runtime.writeBarrier(SB), AX 0x005c 00092 (append.go:22) TESTB AL, AL 0x005e 00094 (append.go:22) JNE $0, 108 0x0060 00096 (append.go:22) MOVQ DX, "".x(SB) 0x0067 00103 (append.go:23) ADDQ $72, SP 0x006b 00107 (append.go:23) RET 0x006c 00108 (append.go:22) LEAQ "".x(SB), CX 0x0073 00115 (append.go:22) MOVQ CX, (SP) 0x0077 00119 (append.go:22) MOVQ DX, 8(SP) 0x007c 00124 (append.go:22) PCDATA $0, $0 0x007c 00124 (append.go:22) CALL runtime.writebarrierptr(SB) 0x0081 00129 (append.go:23) JMP 103 0x0083 00131 (append.go:22) LEAQ type.[]uint8(SB), AX 0x008a 00138 (append.go:22) MOVQ AX, (SP) 0x008e 00142 (append.go:22) MOVQ DX, 8(SP) 0x0093 00147 (append.go:22) MOVQ BX, 16(SP) 0x0098 00152 (append.go:22) MOVQ CX, 24(SP) 0x009d 00157 (append.go:22) MOVQ BP, 32(SP) 0x00a2 00162 (append.go:22) PCDATA $0, $0 0x00a2 00162 (append.go:22) CALL runtime.growslice(SB) 0x00a7 00167 (append.go:22) MOVQ 40(SP), DX 0x00ac 00172 (append.go:22) MOVQ 48(SP), AX 0x00b1 00177 (append.go:22) MOVQ 56(SP), CX 0x00b6 00182 (append.go:22) ADDQ $3, AX 0x00ba 00186 (append.go:19) MOVQ "".autotmp_0+64(SP), BX 0x00bf 00191 (append.go:22) MOVQ AX, BP 0x00c2 00194 (append.go:22) JMP 58 0x00c7 00199 (append.go:22) NOP 0x00c7 00199 (append.go:21) CALL runtime.morestack_noctxt(SB) 0x00cc 00204 (append.go:21) JMP 0 After: "".p t=1 size=208 args=0x0 locals=0x48 0x0000 00000 (append.go:21) TEXT "".p(SB), $72-0 0x0000 00000 (append.go:21) MOVQ (TLS), CX 0x0009 00009 (append.go:21) CMPQ SP, 16(CX) 0x000d 00013 (append.go:21) JLS 191 0x0013 00019 (append.go:21) SUBQ $72, SP 0x0017 00023 (append.go:21) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0017 00023 (append.go:21) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0017 00023 (append.go:19) MOVQ "".x+16(SB), CX 0x001e 00030 (append.go:19) MOVQ "".x+8(SB), DX 0x0025 00037 (append.go:19) MOVQ DX, "".autotmp_0+64(SP) 0x002a 00042 (append.go:19) MOVQ "".x(SB), BX 0x0031 00049 (append.go:22) LEAQ 3(DX), BP 0x0035 00053 (append.go:22) MOVQ BP, "".x+8(SB) 0x003c 00060 (append.go:22) CMPQ BP, CX 0x003f 00063 (append.go:22) JGT $0, 84 0x0041 00065 (append.go:22) MOVB $1, (BX)(DX*1) 0x0045 00069 (append.go:22) MOVB $2, 1(BX)(DX*1) 0x004a 00074 (append.go:22) MOVB $3, 2(BX)(DX*1) 0x004f 00079 (append.go:23) ADDQ $72, SP 0x0053 00083 (append.go:23) RET 0x0054 00084 (append.go:22) LEAQ type.[]uint8(SB), AX 0x005b 00091 (append.go:22) MOVQ AX, (SP) 0x005f 00095 (append.go:22) MOVQ BX, 8(SP) 0x0064 00100 (append.go:22) MOVQ DX, 16(SP) 0x0069 00105 (append.go:22) MOVQ CX, 24(SP) 0x006e 00110 (append.go:22) MOVQ BP, 32(SP) 0x0073 00115 (append.go:22) PCDATA $0, $0 0x0073 00115 (append.go:22) CALL runtime.growslice(SB) 0x0078 00120 (append.go:22) MOVQ 40(SP), CX 0x007d 00125 (append.go:22) MOVQ 56(SP), AX 0x0082 00130 (append.go:22) MOVQ AX, "".x+16(SB) 0x0089 00137 (append.go:22) MOVL runtime.writeBarrier(SB), AX 0x008f 00143 (append.go:22) TESTB AL, AL 0x0091 00145 (append.go:22) JNE $0, 168 0x0093 00147 (append.go:22) MOVQ CX, "".x(SB) 0x009a 00154 (append.go:22) MOVQ "".x(SB), BX 0x00a1 00161 (append.go:19) MOVQ "".autotmp_0+64(SP), DX 0x00a6 00166 (append.go:22) JMP 65 0x00a8 00168 (append.go:22) LEAQ "".x(SB), DX 0x00af 00175 (append.go:22) MOVQ DX, (SP) 0x00b3 00179 (append.go:22) MOVQ CX, 8(SP) 0x00b8 00184 (append.go:22) PCDATA $0, $0 0x00b8 00184 (append.go:22) CALL runtime.writebarrierptr(SB) 0x00bd 00189 (append.go:22) JMP 154 0x00bf 00191 (append.go:22) NOP 0x00bf 00191 (append.go:21) CALL runtime.morestack_noctxt(SB) 0x00c4 00196 (append.go:21) JMP 0 Change-Id: I77a41ad3a22557a4bb4654de7d6d24a029efe34a Reviewed-on: https://go-review.googlesource.com/21813 Run-TryBot: Josh Bleecher Snyder TryBot-Result: Gobot Gobot Reviewed-by: Keith Randall --- src/cmd/compile/internal/gc/ssa.go | 123 +++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 27 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 5ee370395b..beb68b0385 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -683,14 +683,27 @@ func (s *state) stmt(n *Node) { // Evaluate RHS. rhs := n.Right - if rhs != nil && (rhs.Op == OSTRUCTLIT || rhs.Op == OARRAYLIT) { - // All literals with nonzero fields have already been - // rewritten during walk. Any that remain are just T{} - // or equivalents. Use the zero value. - if !iszero(rhs) { - Fatalf("literal with nonzero value in SSA: %v", rhs) + if rhs != nil { + switch rhs.Op { + case OSTRUCTLIT, OARRAYLIT: + // All literals with nonzero fields have already been + // rewritten during walk. Any that remain are just T{} + // or equivalents. Use the zero value. + if !iszero(rhs) { + Fatalf("literal with nonzero value in SSA: %v", rhs) + } + rhs = nil + case OAPPEND: + // If we're writing the result of an append back to the same slice, + // handle it specially to avoid write barriers on the fast (non-growth) path. + // If the slice can be SSA'd, it'll be on the stack, + // so there will be no write barriers, + // so there's no need to attempt to prevent them. + if samesafeexpr(n.Left, rhs.List.First()) && !s.canSSA(n.Left) { + s.append(rhs, true) + return + } } - rhs = nil } var r *ssa.Value needwb := n.Op == OASWB && rhs != nil @@ -709,11 +722,11 @@ func (s *state) stmt(n *Node) { } } if rhs != nil && rhs.Op == OAPPEND { - // Yuck! The frontend gets rid of the write barrier, but we need it! - // At least, we need it in the case where growslice is called. - // TODO: Do the write barrier on just the growslice branch. + // The frontend gets rid of the write barrier to enable the special OAPPEND + // handling above, but since this is not a special case, we need it. // TODO: just add a ptr graying to the end of growslice? - // TODO: check whether we need to do this for ODOTTYPE and ORECV also. + // TODO: check whether we need to provide special handling and a write barrier + // for ODOTTYPE and ORECV also. // They get similar wb-removal treatment in walk.go:OAS. needwb = true } @@ -2079,7 +2092,7 @@ func (s *state) expr(n *Node) *ssa.Value { return s.newValue1(ssa.OpGetG, n.Type, s.mem()) case OAPPEND: - return s.exprAppend(n) + return s.append(n, false) default: s.Unimplementedf("unhandled expr %s", opnames[n.Op]) @@ -2087,25 +2100,57 @@ func (s *state) expr(n *Node) *ssa.Value { } } -// exprAppend converts an OAPPEND node n to an ssa.Value, adds it to s, and returns the Value. -func (s *state) exprAppend(n *Node) *ssa.Value { - // append(s, e1, e2, e3). Compile like: +// append converts an OAPPEND node to SSA. +// If inplace is false, it converts the OAPPEND expression n to an ssa.Value, +// adds it to s, and returns the Value. +// If inplace is true, it writes the result of the OAPPEND expression n +// back to the slice being appended to, and returns nil. +// inplace MUST be set to false if the slice can be SSA'd. +func (s *state) append(n *Node, inplace bool) *ssa.Value { + // If inplace is false, process as expression "append(s, e1, e2, e3)": + // // ptr, len, cap := s // newlen := len + 3 - // if newlen > s.cap { + // if newlen > cap { // ptr, len, cap = growslice(s, newlen) // newlen = len + 3 // recalculate to avoid a spill // } + // // with write barriers, if needed: + // *(ptr+len) = e1 + // *(ptr+len+1) = e2 + // *(ptr+len+2) = e3 + // return makeslice(ptr, newlen, cap) + // + // + // If inplace is true, process as statement "s = append(s, e1, e2, e3)": + // + // a := &s + // ptr, len, cap := s + // newlen := len + 3 + // *a.len = newlen // store newlen immediately to avoid a spill + // if newlen > cap { + // newptr, _, newcap = growslice(ptr, len, cap, newlen) + // *a.cap = newcap // write before ptr to avoid a spill + // *a.ptr = newptr // with write barrier + // } + // // with write barriers, if needed: // *(ptr+len) = e1 // *(ptr+len+1) = e2 // *(ptr+len+2) = e3 - // makeslice(ptr, newlen, cap) et := n.Type.Elem() pt := Ptrto(et) // Evaluate slice - slice := s.expr(n.List.First()) + sn := n.List.First() // the slice node is the first in the list + + var slice, addr *ssa.Value + if inplace { + addr = s.addr(sn, false) + slice = s.newValue2(ssa.OpLoad, n.Type, addr, s.mem()) + } else { + slice = s.expr(sn) + } // Allocate new blocks grow := s.f.NewBlock(ssa.BlockPlain) @@ -2117,10 +2162,20 @@ func (s *state) exprAppend(n *Node) *ssa.Value { l := s.newValue1(ssa.OpSliceLen, Types[TINT], slice) c := s.newValue1(ssa.OpSliceCap, Types[TINT], slice) nl := s.newValue2(s.ssaOp(OADD, Types[TINT]), Types[TINT], l, s.constInt(Types[TINT], nargs)) + + if inplace { + lenaddr := s.newValue1I(ssa.OpOffPtr, pt, int64(Array_nel), addr) + s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, s.config.IntSize, lenaddr, nl, s.mem()) + } + cmp := s.newValue2(s.ssaOp(OGT, Types[TINT]), Types[TBOOL], nl, c) s.vars[&ptrVar] = p - s.vars[&newlenVar] = nl - s.vars[&capVar] = c + + if !inplace { + s.vars[&newlenVar] = nl + s.vars[&capVar] = c + } + b := s.endBlock() b.Kind = ssa.BlockIf b.Likely = ssa.BranchUnlikely @@ -2134,9 +2189,18 @@ func (s *state) exprAppend(n *Node) *ssa.Value { r := s.rtcall(growslice, true, []*Type{pt, Types[TINT], Types[TINT]}, taddr, p, l, c, nl) - s.vars[&ptrVar] = r[0] - s.vars[&newlenVar] = s.newValue2(s.ssaOp(OADD, Types[TINT]), Types[TINT], r[1], s.constInt(Types[TINT], nargs)) - s.vars[&capVar] = r[2] + if inplace { + capaddr := s.newValue1I(ssa.OpOffPtr, pt, int64(Array_cap), addr) + s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, s.config.IntSize, capaddr, r[2], s.mem()) + s.insertWBstore(pt, addr, r[0], n.Lineno, 0) + // load the value we just stored to avoid having to spill it + s.vars[&ptrVar] = s.newValue2(ssa.OpLoad, pt, addr, s.mem()) + } else { + s.vars[&ptrVar] = r[0] + s.vars[&newlenVar] = s.newValue2(s.ssaOp(OADD, Types[TINT]), Types[TINT], r[1], s.constInt(Types[TINT], nargs)) + s.vars[&capVar] = r[2] + } + b = s.endBlock() b.AddEdgeTo(assign) @@ -2156,9 +2220,11 @@ func (s *state) exprAppend(n *Node) *ssa.Value { } } - p = s.variable(&ptrVar, pt) // generates phi for ptr - nl = s.variable(&newlenVar, Types[TINT]) // generates phi for nl - c = s.variable(&capVar, Types[TINT]) // generates phi for cap + p = s.variable(&ptrVar, pt) // generates phi for ptr + if !inplace { + nl = s.variable(&newlenVar, Types[TINT]) // generates phi for nl + c = s.variable(&capVar, Types[TINT]) // generates phi for cap + } p2 := s.newValue2(ssa.OpPtrIndex, pt, p, l) // TODO: just one write barrier call for all of these writes? // TODO: maybe just one writeBarrier.enabled check? @@ -2179,10 +2245,13 @@ func (s *state) exprAppend(n *Node) *ssa.Value { } } - // make result delete(s.vars, &ptrVar) + if inplace { + return nil + } delete(s.vars, &newlenVar) delete(s.vars, &capVar) + // make result return s.newValue3(ssa.OpSliceMake, n.Type, p, nl, c) } -- cgit v1.3 From d1feddb7ae6389ee4175ee85b7168cf58a04b952 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Mon, 4 Apr 2016 13:22:34 -0700 Subject: cmd/vet: improve documentation for flags, slightly The way that -all works was unclear from the documentation and made worse by recent changes to the flag package. Improve matters by making the help message say "default true" for the tests that do default to true, and tweak some of the wording. Before: Usage of vet: vet [flags] directory... vet [flags] files... # Must be a single package For more information run go doc cmd/vet Flags: -all enable all non-experimental checks (default unset) -asmdecl check assembly against Go declarations (default unset) ... After: Usage of vet: vet [flags] directory... vet [flags] files... # Must be a single package By default, -all is set and all non-experimental checks are run. For more information run go doc cmd/vet Flags: -all enable all non-experimental checks (default true) -asmdecl check assembly against Go declarations (default true) ... Change-Id: Ie94b27381a9ad2382a10a7542a93bce1d59fa8f5 Reviewed-on: https://go-review.googlesource.com/21495 Reviewed-by: Andrew Gerrand --- src/cmd/vet/doc.go | 11 +++++------ src/cmd/vet/main.go | 3 ++- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/vet/doc.go b/src/cmd/vet/doc.go index 2b5e8fcb59..c697f3bc36 100644 --- a/src/cmd/vet/doc.go +++ b/src/cmd/vet/doc.go @@ -29,11 +29,10 @@ check every possible problem and depends on unreliable heuristics so it should be used as guidance only, not as a firm indicator of program correctness. -By default all checks are performed. If any flags are explicitly set -to true, only those tests are run. Conversely, if any flag is -explicitly set to false, only those tests are disabled. -Thus -printf=true runs the printf check, -printf=false runs all checks -except the printf check. +By default the -all flag is set so all checks are performed. +If any flags are explicitly set to true, only those tests are run. Conversely, if +any flag is explicitly set to false, only those tests are disabled. Thus -printf=true +runs the printf check, -printf=false runs all checks except the printf check. Available checks: @@ -194,4 +193,4 @@ These flags configure the behavior of vet: -shadowstrict Whether to be strict about shadowing; can be noisy. */ -package main // import "golang.org/x/tools/cmd/vet" +package main diff --git a/src/cmd/vet/main.go b/src/cmd/vet/main.go index a2142dcabb..8212a14f03 100644 --- a/src/cmd/vet/main.go +++ b/src/cmd/vet/main.go @@ -100,7 +100,7 @@ func (ts *triState) Set(value string) error { func (ts *triState) String() string { switch *ts { case unset: - return "unset" + return "true" // An unset flag will be set by -all, so defaults to true. case setTrue: return "true" case setFalse: @@ -164,6 +164,7 @@ func Usage() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n") fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n") + fmt.Fprintf(os.Stderr, "By default, -all is set and all non-experimental checks are run.\n") fmt.Fprintf(os.Stderr, "For more information run\n") fmt.Fprintf(os.Stderr, "\tgo doc cmd/vet\n\n") fmt.Fprintf(os.Stderr, "Flags:\n") -- cgit v1.3 From 07669d2737aa51107a4e54b61d6704f6ad8035b5 Mon Sep 17 00:00:00 2001 From: Martin Möhrmann Date: Thu, 7 Apr 2016 08:01:47 +0200 Subject: cmd/compile: cleanup pragcgo Removes dynimport, dynexport, dynlinker cases since they can not be reached due to prefix check for "go:cgo_" in getlinepragma. Replaces the if chains for verb distinction by a switch statement. Replaces fmt.Sprintf by fmt.Sprintln for string concatenation. Removes the more, getimpsym and getquoted functions by introducing a pragmaFields function that partitions a pragma into its components. Adds tests for cgo pragmas. Change-Id: I43c7b9550feb3ddccaff7fb02198a3f994444123 Reviewed-on: https://go-review.googlesource.com/21607 Reviewed-by: Matthew Dempsky Run-TryBot: Matthew Dempsky TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/gc/lex.go | 218 ++++++++++++++------------------ src/cmd/compile/internal/gc/lex_test.go | 79 ++++++++++++ 2 files changed, 173 insertions(+), 124 deletions(-) create mode 100644 src/cmd/compile/internal/gc/lex_test.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/lex.go b/src/cmd/compile/internal/gc/lex.go index 2dbbd9276b..4b95bb7124 100644 --- a/src/cmd/compile/internal/gc/lex.go +++ b/src/cmd/compile/internal/gc/lex.go @@ -44,6 +44,10 @@ func isDigit(c rune) bool { return '0' <= c && c <= '9' } +func isQuoted(s string) bool { + return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' +} + func plan9quote(s string) string { if s == "" { return "''" @@ -853,15 +857,6 @@ func internString(b []byte) string { return s } -func more(pp *string) bool { - p := *pp - for p != "" && isSpace(rune(p[0])) { - p = p[1:] - } - *pp = p - return p != "" -} - // read and interpret syntax that looks like // //line parse.y:15 // as a discontinuity in sequential line numbers. @@ -887,7 +882,7 @@ func (l *lexer) getlinepragma() rune { text := strings.TrimSuffix(lexbuf.String(), "\r") if strings.HasPrefix(text, "go:cgo_") { - pragcgo(text) + pragcgobuf += pragcgo(text) } verb := text @@ -991,139 +986,114 @@ func (l *lexer) getlinepragma() rune { return c } -func getimpsym(pp *string) string { - more(pp) // skip spaces - p := *pp - if p == "" || p[0] == '"' { - return "" - } - i := 0 - for i < len(p) && !isSpace(rune(p[i])) && p[i] != '"' { - i++ - } - sym := p[:i] - *pp = p[i:] - return sym -} - -func getquoted(pp *string) (string, bool) { - more(pp) // skip spaces - p := *pp - if p == "" || p[0] != '"' { - return "", false - } - p = p[1:] - i := strings.Index(p, `"`) - if i < 0 { - return "", false - } - *pp = p[i+1:] - return p[:i], true -} - -// Copied nearly verbatim from the C compiler's #pragma parser. -// TODO: Rewrite more cleanly once the compiler is written in Go. -func pragcgo(text string) { - var q string +func pragcgo(text string) string { + f := pragmaFields(text) - if i := strings.Index(text, " "); i >= 0 { - text, q = text[:i], text[i:] - } + verb := f[0][3:] // skip "go:" + switch verb { + case "cgo_export_static", "cgo_export_dynamic": + switch { + case len(f) == 2 && !isQuoted(f[1]): + local := plan9quote(f[1]) + return fmt.Sprintln(verb, local) - verb := text[3:] // skip "go:" + case len(f) == 3 && !isQuoted(f[1]) && !isQuoted(f[2]): + local := plan9quote(f[1]) + remote := plan9quote(f[2]) + return fmt.Sprintln(verb, local, remote) - if verb == "cgo_dynamic_linker" || verb == "dynlinker" { - p, ok := getquoted(&q) - if !ok { - Yyerror("usage: //go:cgo_dynamic_linker \"path\"") - return + default: + Yyerror(`usage: //go:%s local [remote]`, verb) } - pragcgobuf += fmt.Sprintf("cgo_dynamic_linker %v\n", plan9quote(p)) - return + case "cgo_import_dynamic": + switch { + case len(f) == 2 && !isQuoted(f[1]): + local := plan9quote(f[1]) + return fmt.Sprintln(verb, local) - } + case len(f) == 3 && !isQuoted(f[1]) && !isQuoted(f[2]): + local := plan9quote(f[1]) + remote := plan9quote(f[2]) + return fmt.Sprintln(verb, local, remote) - if verb == "dynexport" { - verb = "cgo_export_dynamic" - } - if verb == "cgo_export_static" || verb == "cgo_export_dynamic" { - local := getimpsym(&q) - var remote string - if local == "" { - goto err2 - } - if !more(&q) { - pragcgobuf += fmt.Sprintf("%s %v\n", verb, plan9quote(local)) - return - } + case len(f) == 4 && !isQuoted(f[1]) && !isQuoted(f[2]) && isQuoted(f[3]): + local := plan9quote(f[1]) + remote := plan9quote(f[2]) + library := plan9quote(strings.Trim(f[3], `"`)) + return fmt.Sprintln(verb, local, remote, library) - remote = getimpsym(&q) - if remote == "" { - goto err2 + default: + Yyerror(`usage: //go:cgo_import_dynamic local [remote ["library"]]`) } - pragcgobuf += fmt.Sprintf("%s %v %v\n", verb, plan9quote(local), plan9quote(remote)) - return - - err2: - Yyerror("usage: //go:%s local [remote]", verb) - return - } + case "cgo_import_static": + switch { + case len(f) == 2 && !isQuoted(f[1]): + local := plan9quote(f[1]) + return fmt.Sprintln(verb, local) - if verb == "cgo_import_dynamic" || verb == "dynimport" { - var ok bool - local := getimpsym(&q) - var p string - var remote string - if local == "" { - goto err3 - } - if !more(&q) { - pragcgobuf += fmt.Sprintf("cgo_import_dynamic %v\n", plan9quote(local)) - return + default: + Yyerror(`usage: //go:cgo_import_static local`) } + case "cgo_dynamic_linker": + switch { + case len(f) == 2 && isQuoted(f[1]): + path := plan9quote(strings.Trim(f[1], `"`)) + return fmt.Sprintln(verb, path) - remote = getimpsym(&q) - if remote == "" { - goto err3 - } - if !more(&q) { - pragcgobuf += fmt.Sprintf("cgo_import_dynamic %v %v\n", plan9quote(local), plan9quote(remote)) - return + default: + Yyerror(`usage: //go:cgo_dynamic_linker "path"`) } + case "cgo_ldflag": + switch { + case len(f) == 2 && isQuoted(f[1]): + arg := plan9quote(strings.Trim(f[1], `"`)) + return fmt.Sprintln(verb, arg) - p, ok = getquoted(&q) - if !ok { - goto err3 + default: + Yyerror(`usage: //go:cgo_ldflag "arg"`) } - pragcgobuf += fmt.Sprintf("cgo_import_dynamic %v %v %v\n", plan9quote(local), plan9quote(remote), plan9quote(p)) - return - - err3: - Yyerror("usage: //go:cgo_import_dynamic local [remote [\"library\"]]") - return } + return "" +} - if verb == "cgo_import_static" { - local := getimpsym(&q) - if local == "" || more(&q) { - Yyerror("usage: //go:cgo_import_static local") - return +// pragmaFields is similar to strings.FieldsFunc(s, isSpace) +// but does not split when inside double quoted regions and always +// splits before the start and after the end of a double quoted region. +// pragmaFields does not recognize escaped quotes. If a quote in s is not +// closed the part after the opening quote will not be returned as a field. +func pragmaFields(s string) []string { + var a []string + inQuote := false + fieldStart := -1 // Set to -1 when looking for start of field. + for i, c := range s { + switch { + case c == '"': + if inQuote { + inQuote = false + a = append(a, s[fieldStart:i+1]) + fieldStart = -1 + } else { + inQuote = true + if fieldStart >= 0 { + a = append(a, s[fieldStart:i]) + } + fieldStart = i + } + case !inQuote && isSpace(c): + if fieldStart >= 0 { + a = append(a, s[fieldStart:i]) + fieldStart = -1 + } + default: + if fieldStart == -1 { + fieldStart = i + } } - pragcgobuf += fmt.Sprintf("cgo_import_static %v\n", plan9quote(local)) - return - } - - if verb == "cgo_ldflag" { - p, ok := getquoted(&q) - if !ok { - Yyerror("usage: //go:cgo_ldflag \"arg\"") - return - } - pragcgobuf += fmt.Sprintf("cgo_ldflag %v\n", plan9quote(p)) - return - + if !inQuote && fieldStart >= 0 { // Last field might end at the end of the string. + a = append(a, s[fieldStart:]) } + return a } func (l *lexer) getr() rune { diff --git a/src/cmd/compile/internal/gc/lex_test.go b/src/cmd/compile/internal/gc/lex_test.go new file mode 100644 index 0000000000..9230b30dad --- /dev/null +++ b/src/cmd/compile/internal/gc/lex_test.go @@ -0,0 +1,79 @@ +// 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 "testing" + +func eq(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestPragmaFields(t *testing.T) { + + var tests = []struct { + in string + want []string + }{ + {"", []string{}}, + {" \t ", []string{}}, + {`""""`, []string{`""`, `""`}}, + {" a'b'c ", []string{"a'b'c"}}, + {"1 2 3 4", []string{"1", "2", "3", "4"}}, + {"\n☺\t☹\n", []string{"☺", "☹"}}, + {`"1 2 " 3 " 4 5"`, []string{`"1 2 "`, `3`, `" 4 5"`}}, + {`"1""2 3""4"`, []string{`"1"`, `"2 3"`, `"4"`}}, + {`12"34"`, []string{`12`, `"34"`}}, + {`12"34 `, []string{`12`}}, + } + + for _, tt := range tests { + got := pragmaFields(tt.in) + if !eq(got, tt.want) { + t.Errorf("pragmaFields(%q) = %v; want %v", tt.in, got, tt.want) + continue + } + } +} + +func TestPragcgo(t *testing.T) { + + var tests = []struct { + in string + want string + }{ + {`go:cgo_export_dynamic local`, "cgo_export_dynamic local\n"}, + {`go:cgo_export_dynamic local remote`, "cgo_export_dynamic local remote\n"}, + {`go:cgo_export_dynamic local' remote'`, "cgo_export_dynamic 'local''' 'remote'''\n"}, + {`go:cgo_export_static local`, "cgo_export_static local\n"}, + {`go:cgo_export_static local remote`, "cgo_export_static local remote\n"}, + {`go:cgo_export_static local' remote'`, "cgo_export_static 'local''' 'remote'''\n"}, + {`go:cgo_import_dynamic local`, "cgo_import_dynamic local\n"}, + {`go:cgo_import_dynamic local remote`, "cgo_import_dynamic local remote\n"}, + {`go:cgo_import_dynamic local remote "library"`, "cgo_import_dynamic local remote library\n"}, + {`go:cgo_import_dynamic local' remote' "lib rary"`, "cgo_import_dynamic 'local''' 'remote''' 'lib rary'\n"}, + {`go:cgo_import_static local`, "cgo_import_static local\n"}, + {`go:cgo_import_static local'`, "cgo_import_static 'local'''\n"}, + {`go:cgo_dynamic_linker "/path/"`, "cgo_dynamic_linker /path/\n"}, + {`go:cgo_dynamic_linker "/p ath/"`, "cgo_dynamic_linker '/p ath/'\n"}, + {`go:cgo_ldflag "arg"`, "cgo_ldflag arg\n"}, + {`go:cgo_ldflag "a rg"`, "cgo_ldflag 'a rg'\n"}, + } + + for _, tt := range tests { + got := pragcgo(tt.in) + if got != tt.want { + t.Errorf("pragcgo(%q) = %q; want %q", tt.in, got, tt.want) + continue + } + } +} -- cgit v1.3 From b6cd6d7d3211bd9030dec4115b6202d93fe570a3 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Thu, 31 Mar 2016 02:01:48 -0700 Subject: cmd/go: fix vcsFromDir returning bad root on Windows Apply golang/tools@5804fef4c0556d3e5e7d2440740a3d7aced7d58b. In the context of cmd/go build tool, import path is a '/'-separated path. This can be inferred from `go help importpath` and `go help packages`. vcsFromDir documentation says on return, root is the import path corresponding to the root of the repository. On Windows and other OSes where os.PathSeparator is not '/', that wasn't true since root would contain characters other than '/', and therefore it wasn't a valid import path corresponding to the root of the repository. Fix that by using filepath.ToSlash. Add test coverage for vcsFromDir, it was previously not tested. It's taken from golang.org/x/tools/go/vcs tests, and modified to improve style. Additionally, remove an unneccessary statement from the documentation "(thus root is a prefix of importPath)". There is no variable importPath that is being referred to (it's possible p.ImportPath was being referred to). Without it, the description of root value matches the documentation of repoRoot.root struct field: // root is the import path corresponding to the root of the // repository root string Rename and change signature of vcsForDir(p *Package) to vcsFromDir(dir, srcRoot string). This is more in sync with the x/tools version. It's also simpler, since vcsFromDir only needs those two values from Package, and nothing more. Change "for" to "from" in name because it's more consistent and clear. Update usage of vcsFromDir to match the new signature, and respect that returned root is a '/'-separated path rather than a os.PathSeparator separated path. Fixes #15040. Updates #7723. Helps #11490. Change-Id: Idf51b9239f57248739daaa200aa1c6e633cb5f7f Reviewed-on: https://go-review.googlesource.com/21345 Reviewed-by: Alex Brainman Run-TryBot: Alex Brainman TryBot-Result: Gobot Gobot --- src/cmd/go/get.go | 8 ++++---- src/cmd/go/vcs.go | 13 ++++++------- src/cmd/go/vcs_test.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 11 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/go/get.go b/src/cmd/go/get.go index 7e0045fb1d..b52991a5fc 100644 --- a/src/cmd/go/get.go +++ b/src/cmd/go/get.go @@ -45,7 +45,7 @@ missing packages but does not use it to look for updates to existing packages. Get also accepts build flags to control the installation. See 'go help build'. -When checking out a new package, get creates the target directory +When checking out a new package, get creates the target directory GOPATH/src/. If the GOPATH contains multiple entries, get uses the first one. See 'go help gopath'. @@ -346,7 +346,7 @@ func downloadPackage(p *Package) error { if p.build.SrcRoot != "" { // Directory exists. Look for checkout along path to src. - vcs, rootPath, err = vcsForDir(p) + vcs, rootPath, err = vcsFromDir(p.Dir, p.build.SrcRoot) if err != nil { return err } @@ -354,7 +354,7 @@ func downloadPackage(p *Package) error { // Double-check where it came from. if *getU && vcs.remoteRepo != nil { - dir := filepath.Join(p.build.SrcRoot, rootPath) + dir := filepath.Join(p.build.SrcRoot, filepath.FromSlash(rootPath)) remote, err := vcs.remoteRepo(vcs, dir) if err != nil { return err @@ -401,7 +401,7 @@ func downloadPackage(p *Package) error { p.build.SrcRoot = filepath.Join(list[0], "src") p.build.PkgRoot = filepath.Join(list[0], "pkg") } - root := filepath.Join(p.build.SrcRoot, rootPath) + root := filepath.Join(p.build.SrcRoot, filepath.FromSlash(rootPath)) // If we've considered this repository already, don't do it again. if downloadRootCache[root] { return nil diff --git a/src/cmd/go/vcs.go b/src/cmd/go/vcs.go index a9663b2185..e3342999fa 100644 --- a/src/cmd/go/vcs.go +++ b/src/cmd/go/vcs.go @@ -479,15 +479,14 @@ type vcsPath struct { regexp *regexp.Regexp // cached compiled form of re } -// vcsForDir inspects dir and its parents to determine the +// vcsFromDir inspects dir and its parents to determine the // version control system and code repository to use. // On return, root is the import path -// corresponding to the root of the repository -// (thus root is a prefix of importPath). -func vcsForDir(p *Package) (vcs *vcsCmd, root string, err error) { +// corresponding to the root of the repository. +func vcsFromDir(dir, srcRoot string) (vcs *vcsCmd, root string, err error) { // Clean and double-check that dir is in (a subdirectory of) srcRoot. - dir := filepath.Clean(p.Dir) - srcRoot := filepath.Clean(p.build.SrcRoot) + dir = filepath.Clean(dir) + srcRoot = filepath.Clean(srcRoot) if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator { return nil, "", fmt.Errorf("directory %q is outside source root %q", dir, srcRoot) } @@ -496,7 +495,7 @@ func vcsForDir(p *Package) (vcs *vcsCmd, root string, err error) { for len(dir) > len(srcRoot) { for _, vcs := range vcsList { if fi, err := os.Stat(filepath.Join(dir, "."+vcs.cmd)); err == nil && fi.IsDir() { - return vcs, dir[len(srcRoot)+1:], nil + return vcs, filepath.ToSlash(dir[len(srcRoot)+1:]), nil } } diff --git a/src/cmd/go/vcs_test.go b/src/cmd/go/vcs_test.go index 52a534a3a3..d951189459 100644 --- a/src/cmd/go/vcs_test.go +++ b/src/cmd/go/vcs_test.go @@ -6,6 +6,10 @@ package main import ( "internal/testenv" + "io/ioutil" + "os" + "path" + "path/filepath" "testing" ) @@ -128,6 +132,37 @@ func TestRepoRootForImportPath(t *testing.T) { } } +// Test that vcsFromDir correctly inspects a given directory and returns the right VCS and root. +func TestFromDir(t *testing.T) { + tempDir, err := ioutil.TempDir("", "vcstest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + + for _, vcs := range vcsList { + dir := filepath.Join(tempDir, "example.com", vcs.name, "."+vcs.cmd) + err := os.MkdirAll(dir, 0755) + if err != nil { + t.Fatal(err) + } + + want := repoRoot{ + vcs: vcs, + root: path.Join("example.com", vcs.name), + } + var got repoRoot + got.vcs, got.root, err = vcsFromDir(dir, tempDir) + if err != nil { + t.Errorf("FromDir(%q, %q): %v", dir, tempDir, err) + continue + } + if got.vcs.name != want.vcs.name || got.root != want.root { + t.Errorf("FromDir(%q, %q) = VCS(%s) Root(%s), want VCS(%s) Root(%s)", dir, tempDir, got.vcs, got.root, want.vcs, want.root) + } + } +} + func TestIsSecure(t *testing.T) { tests := []struct { vcs *vcsCmd -- cgit v1.3 From a223ccae386449169774597b15a00f2d70addce7 Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Mon, 11 Apr 2016 20:23:19 -0400 Subject: cmd/compile/internal/s390x: add s390x support s390x does not require duffzero/duffcopy since it has storage-to-storage instructions that can copy/clear up to 256 bytes at a time. peep contains several new passes to optimize instruction sequences that match s390x instructions such as the compare-and-branch and load/store multiple instructions. copyprop and subprop have been extended to work with moves that require sign/zero extension. This work could be ported to other architectures that do not used sized math however it does add complexity and will probably be rendered unnecessary by ssa in the near future. Change-Id: I1b64b281b452ed82a85655a0df69cb224d2a6941 Reviewed-on: https://go-review.googlesource.com/20873 Run-TryBot: Michael Munday TryBot-Result: Gobot Gobot Reviewed-by: Bill O'Farrell Reviewed-by: Brad Fitzpatrick --- src/cmd/compile/internal/s390x/cgen.go | 178 ++++ src/cmd/compile/internal/s390x/galign.go | 66 ++ src/cmd/compile/internal/s390x/ggen.go | 577 +++++++++++ src/cmd/compile/internal/s390x/gsubr.go | 1115 ++++++++++++++++++++ src/cmd/compile/internal/s390x/peep.go | 1664 ++++++++++++++++++++++++++++++ src/cmd/compile/internal/s390x/prog.go | 179 ++++ src/cmd/compile/internal/s390x/reg.go | 130 +++ src/cmd/compile/main.go | 3 + src/cmd/dist/buildtool.go | 1 + 9 files changed, 3913 insertions(+) create mode 100644 src/cmd/compile/internal/s390x/cgen.go create mode 100644 src/cmd/compile/internal/s390x/galign.go create mode 100644 src/cmd/compile/internal/s390x/ggen.go create mode 100644 src/cmd/compile/internal/s390x/gsubr.go create mode 100644 src/cmd/compile/internal/s390x/peep.go create mode 100644 src/cmd/compile/internal/s390x/prog.go create mode 100644 src/cmd/compile/internal/s390x/reg.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/s390x/cgen.go b/src/cmd/compile/internal/s390x/cgen.go new file mode 100644 index 0000000000..28bb34e0ef --- /dev/null +++ b/src/cmd/compile/internal/s390x/cgen.go @@ -0,0 +1,178 @@ +// 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 s390x + +import ( + "cmd/compile/internal/gc" + "cmd/internal/obj" + "cmd/internal/obj/s390x" +) + +type direction int + +const ( + _FORWARDS direction = iota + _BACKWARDS +) + +// blockcopy copies w bytes from &n to &res +func blockcopy(n, res *gc.Node, osrc, odst, w int64) { + var dst gc.Node + var src gc.Node + if n.Ullman >= res.Ullman { + gc.Agenr(n, &dst, res) // temporarily use dst + gc.Regalloc(&src, gc.Types[gc.Tptr], nil) + gins(s390x.AMOVD, &dst, &src) + if res.Op == gc.ONAME { + gc.Gvardef(res) + } + gc.Agen(res, &dst) + } else { + if res.Op == gc.ONAME { + gc.Gvardef(res) + } + gc.Agenr(res, &dst, res) + gc.Agenr(n, &src, nil) + } + defer gc.Regfree(&src) + defer gc.Regfree(&dst) + + var tmp gc.Node + gc.Regalloc(&tmp, gc.Types[gc.Tptr], nil) + defer gc.Regfree(&tmp) + + offset := int64(0) + dir := _FORWARDS + if osrc < odst && odst < osrc+w { + // Reverse. Can't use MVC, fall back onto basic moves. + dir = _BACKWARDS + const copiesPerIter = 2 + if w >= 8*copiesPerIter { + cnt := w - (w % (8 * copiesPerIter)) + ginscon(s390x.AADD, w, &src) + ginscon(s390x.AADD, w, &dst) + + var end gc.Node + gc.Regalloc(&end, gc.Types[gc.Tptr], nil) + p := gins(s390x.ASUB, nil, &end) + p.From.Type = obj.TYPE_CONST + p.From.Offset = cnt + p.Reg = src.Reg + + var label *obj.Prog + for i := 0; i < copiesPerIter; i++ { + offset := int64(-8 * (i + 1)) + p := gins(s390x.AMOVD, &src, &tmp) + p.From.Type = obj.TYPE_MEM + p.From.Offset = offset + if i == 0 { + label = p + } + p = gins(s390x.AMOVD, &tmp, &dst) + p.To.Type = obj.TYPE_MEM + p.To.Offset = offset + } + + ginscon(s390x.ASUB, 8*copiesPerIter, &src) + ginscon(s390x.ASUB, 8*copiesPerIter, &dst) + gins(s390x.ACMP, &src, &end) + gc.Patch(gc.Gbranch(s390x.ABNE, nil, 0), label) + gc.Regfree(&end) + + w -= cnt + } else { + offset = w + } + } + + if dir == _FORWARDS && w > 1024 { + // Loop over MVCs + cnt := w - (w % 256) + + var end gc.Node + gc.Regalloc(&end, gc.Types[gc.Tptr], nil) + add := gins(s390x.AADD, nil, &end) + add.From.Type = obj.TYPE_CONST + add.From.Offset = cnt + add.Reg = src.Reg + + mvc := gins(s390x.AMVC, &src, &dst) + mvc.From.Type = obj.TYPE_MEM + mvc.From.Offset = 0 + mvc.To.Type = obj.TYPE_MEM + mvc.To.Offset = 0 + mvc.From3 = new(obj.Addr) + mvc.From3.Type = obj.TYPE_CONST + mvc.From3.Offset = 256 + + ginscon(s390x.AADD, 256, &src) + ginscon(s390x.AADD, 256, &dst) + gins(s390x.ACMP, &src, &end) + gc.Patch(gc.Gbranch(s390x.ABNE, nil, 0), mvc) + gc.Regfree(&end) + + w -= cnt + } + + for w > 0 { + cnt := w + // If in reverse we can only do 8, 4, 2 or 1 bytes at a time. + if dir == _BACKWARDS { + switch { + case cnt >= 8: + cnt = 8 + case cnt >= 4: + cnt = 4 + case cnt >= 2: + cnt = 2 + } + } else if cnt > 256 { + cnt = 256 + } + + switch cnt { + case 8, 4, 2, 1: + op := s390x.AMOVB + switch cnt { + case 8: + op = s390x.AMOVD + case 4: + op = s390x.AMOVW + case 2: + op = s390x.AMOVH + } + load := gins(op, &src, &tmp) + load.From.Type = obj.TYPE_MEM + load.From.Offset = offset + + store := gins(op, &tmp, &dst) + store.To.Type = obj.TYPE_MEM + store.To.Offset = offset + + if dir == _BACKWARDS { + load.From.Offset -= cnt + store.To.Offset -= cnt + } + + default: + p := gins(s390x.AMVC, &src, &dst) + p.From.Type = obj.TYPE_MEM + p.From.Offset = offset + p.To.Type = obj.TYPE_MEM + p.To.Offset = offset + p.From3 = new(obj.Addr) + p.From3.Type = obj.TYPE_CONST + p.From3.Offset = cnt + } + + switch dir { + case _FORWARDS: + offset += cnt + case _BACKWARDS: + offset -= cnt + } + w -= cnt + } +} diff --git a/src/cmd/compile/internal/s390x/galign.go b/src/cmd/compile/internal/s390x/galign.go new file mode 100644 index 0000000000..d0d621e557 --- /dev/null +++ b/src/cmd/compile/internal/s390x/galign.go @@ -0,0 +1,66 @@ +// 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 s390x + +import ( + "cmd/compile/internal/gc" + "cmd/internal/obj/s390x" +) + +func betypeinit() { + gc.Widthptr = 8 + gc.Widthint = 8 + gc.Widthreg = 8 +} + +func Main() { + gc.Thearch.LinkArch = &s390x.Links390x + gc.Thearch.REGSP = s390x.REGSP + gc.Thearch.REGCTXT = s390x.REGCTXT + gc.Thearch.REGCALLX = s390x.REG_R3 + gc.Thearch.REGCALLX2 = s390x.REG_R4 + gc.Thearch.REGRETURN = s390x.REG_R3 + gc.Thearch.REGMIN = s390x.REG_R0 + gc.Thearch.REGMAX = s390x.REG_R15 + gc.Thearch.FREGMIN = s390x.REG_F0 + gc.Thearch.FREGMAX = s390x.REG_F15 + gc.Thearch.MAXWIDTH = 1 << 50 + gc.Thearch.ReservedRegs = resvd + + gc.Thearch.Betypeinit = betypeinit + gc.Thearch.Cgen_hmul = cgen_hmul + gc.Thearch.Cgen_shift = cgen_shift + gc.Thearch.Clearfat = clearfat + gc.Thearch.Defframe = defframe + gc.Thearch.Dodiv = dodiv + gc.Thearch.Excise = excise + gc.Thearch.Expandchecks = expandchecks + gc.Thearch.Getg = getg + gc.Thearch.Gins = gins + gc.Thearch.Ginscmp = ginscmp + gc.Thearch.Ginscon = ginscon + gc.Thearch.Ginsnop = ginsnop + gc.Thearch.Gmove = gmove + gc.Thearch.Peep = peep + gc.Thearch.Proginfo = proginfo + gc.Thearch.Regtyp = isReg + gc.Thearch.Sameaddr = sameaddr + gc.Thearch.Smallindir = smallindir + gc.Thearch.Stackaddr = stackaddr + gc.Thearch.Blockcopy = blockcopy + gc.Thearch.Sudoaddable = sudoaddable + gc.Thearch.Sudoclean = sudoclean + gc.Thearch.Excludedregs = excludedregs + gc.Thearch.RtoB = RtoB + gc.Thearch.FtoB = RtoB + gc.Thearch.BtoR = BtoR + gc.Thearch.BtoF = BtoF + gc.Thearch.Optoas = optoas + gc.Thearch.Doregbits = doregbits + gc.Thearch.Regnames = regnames + + gc.Main() + gc.Exit(0) +} diff --git a/src/cmd/compile/internal/s390x/ggen.go b/src/cmd/compile/internal/s390x/ggen.go new file mode 100644 index 0000000000..39885baace --- /dev/null +++ b/src/cmd/compile/internal/s390x/ggen.go @@ -0,0 +1,577 @@ +// 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 s390x + +import ( + "cmd/compile/internal/gc" + "cmd/internal/obj" + "cmd/internal/obj/s390x" + "fmt" +) + +// clearLoopCutOff is the (somewhat arbitrary) value above which it is better +// to have a loop of clear instructions (e.g. XCs) rather than just generating +// multiple instructions (i.e. loop unrolling). +// Must be between 256 and 4096. +const clearLoopCutoff = 1024 + +func defframe(ptxt *obj.Prog) { + // fill in argument size, stack size + ptxt.To.Type = obj.TYPE_TEXTSIZE + + ptxt.To.Val = int32(gc.Rnd(gc.Curfn.Type.ArgWidth(), int64(gc.Widthptr))) + frame := uint32(gc.Rnd(gc.Stksize+gc.Maxarg, int64(gc.Widthreg))) + ptxt.To.Offset = int64(frame) + + // insert code to zero ambiguously live variables + // so that the garbage collector only sees initialized values + // when it looks for pointers. + p := ptxt + + hi := int64(0) + lo := hi + + // iterate through declarations - they are sorted in decreasing xoffset order. + for _, n := range gc.Curfn.Func.Dcl { + if !n.Name.Needzero { + continue + } + if n.Class != gc.PAUTO { + gc.Fatalf("needzero class %d", n.Class) + } + if n.Type.Width%int64(gc.Widthptr) != 0 || n.Xoffset%int64(gc.Widthptr) != 0 || n.Type.Width == 0 { + gc.Fatalf("var %v has size %d offset %d", gc.Nconv(n, gc.FmtLong), int(n.Type.Width), int(n.Xoffset)) + } + + if lo != hi && n.Xoffset+n.Type.Width >= lo-int64(2*gc.Widthreg) { + // merge with range we already have + lo = n.Xoffset + + continue + } + + // zero old range + p = zerorange(p, int64(frame), lo, hi) + + // set new range + hi = n.Xoffset + n.Type.Width + + lo = n.Xoffset + } + + // zero final range + zerorange(p, int64(frame), lo, hi) +} + +// zerorange clears the stack in the given range. +func zerorange(p *obj.Prog, frame int64, lo int64, hi int64) *obj.Prog { + cnt := hi - lo + if cnt == 0 { + return p + } + + // Adjust the frame to account for LR. + frame += gc.Ctxt.FixedFrameSize() + offset := frame + lo + reg := int16(s390x.REGSP) + + // If the offset cannot fit in a 12-bit unsigned displacement then we + // need to create a copy of the stack pointer that we can adjust. + // We also need to do this if we are going to loop. + if offset < 0 || offset > 4096-clearLoopCutoff || cnt > clearLoopCutoff { + p = appendpp(p, s390x.AADD, obj.TYPE_CONST, 0, offset, obj.TYPE_REG, s390x.REGRT1, 0) + p.Reg = int16(s390x.REGSP) + reg = s390x.REGRT1 + offset = 0 + } + + // Generate a loop of large clears. + if cnt > clearLoopCutoff { + n := cnt - (cnt % 256) + end := int16(s390x.REGRT2) + p = appendpp(p, s390x.AADD, obj.TYPE_CONST, 0, offset+n, obj.TYPE_REG, end, 0) + p.Reg = reg + p = appendpp(p, s390x.AXC, obj.TYPE_MEM, reg, offset, obj.TYPE_MEM, reg, offset) + p.From3 = new(obj.Addr) + p.From3.Type = obj.TYPE_CONST + p.From3.Offset = 256 + pl := p + p = appendpp(p, s390x.AADD, obj.TYPE_CONST, 0, 256, obj.TYPE_REG, reg, 0) + p = appendpp(p, s390x.ACMP, obj.TYPE_REG, reg, 0, obj.TYPE_REG, end, 0) + p = appendpp(p, s390x.ABNE, obj.TYPE_NONE, 0, 0, obj.TYPE_BRANCH, 0, 0) + gc.Patch(p, pl) + + cnt -= n + } + + // Generate remaining clear instructions without a loop. + for cnt > 0 { + n := cnt + + // Can clear at most 256 bytes per instruction. + if n > 256 { + n = 256 + } + + switch n { + // Handle very small clears with move instructions. + case 8, 4, 2, 1: + ins := s390x.AMOVB + switch n { + case 8: + ins = s390x.AMOVD + case 4: + ins = s390x.AMOVW + case 2: + ins = s390x.AMOVH + } + p = appendpp(p, ins, obj.TYPE_CONST, 0, 0, obj.TYPE_MEM, reg, offset) + + // Handle clears that would require multiple move instructions with XC. + default: + p = appendpp(p, s390x.AXC, obj.TYPE_MEM, reg, offset, obj.TYPE_MEM, reg, offset) + p.From3 = new(obj.Addr) + p.From3.Type = obj.TYPE_CONST + p.From3.Offset = n + } + + cnt -= n + offset += n + } + + return p +} + +func appendpp(p *obj.Prog, as obj.As, ftype obj.AddrType, freg int16, foffset int64, ttype obj.AddrType, treg int16, toffset int64) *obj.Prog { + q := gc.Ctxt.NewProg() + gc.Clearp(q) + q.As = as + q.Lineno = p.Lineno + q.From.Type = ftype + q.From.Reg = freg + q.From.Offset = foffset + q.To.Type = ttype + q.To.Reg = treg + q.To.Offset = toffset + q.Link = p.Link + p.Link = q + return q +} + +func ginsnop() { + var reg gc.Node + gc.Nodreg(®, gc.Types[gc.TINT], s390x.REG_R0) + gins(s390x.AOR, ®, ®) +} + +var panicdiv *gc.Node + +/* + * generate division. + * generates one of: + * res = nl / nr + * res = nl % nr + * according to op. + */ +func dodiv(op gc.Op, nl *gc.Node, nr *gc.Node, res *gc.Node) { + // Have to be careful about handling + // most negative int divided by -1 correctly. + // The hardware will generate undefined result. + // Also need to explicitly trap on division on zero, + // the hardware will silently generate undefined result. + // DIVW will leave unpredicable result in higher 32-bit, + // so always use DIVD/DIVDU. + t := nl.Type + + t0 := t + check := 0 + if t.IsSigned() { + check = 1 + if gc.Isconst(nl, gc.CTINT) && nl.Int64() != -(1<= nr.Ullman { + gc.Cgen(nl, &tl) + gc.Cgen(nr, &tr) + } else { + gc.Cgen(nr, &tr) + gc.Cgen(nl, &tl) + } + + if t != t0 { + // Convert + tl2 := tl + + tr2 := tr + tl.Type = t + tr.Type = t + gmove(&tl2, &tl) + gmove(&tr2, &tr) + } + + // Handle divide-by-zero panic. + p1 := gins(optoas(gc.OCMP, t), &tr, nil) + + p1.To.Type = obj.TYPE_REG + p1.To.Reg = s390x.REGZERO + p1 = gc.Gbranch(optoas(gc.ONE, t), nil, +1) + if panicdiv == nil { + panicdiv = gc.Sysfunc("panicdivide") + } + gc.Ginscall(panicdiv, -1) + gc.Patch(p1, gc.Pc) + + var p2 *obj.Prog + if check != 0 { + var nm1 gc.Node + gc.Nodconst(&nm1, t, -1) + gins(optoas(gc.OCMP, t), &tr, &nm1) + p1 := gc.Gbranch(optoas(gc.ONE, t), nil, +1) + if op == gc.ODIV { + // a / (-1) is -a. + gins(optoas(gc.OMINUS, t), nil, &tl) + + gmove(&tl, res) + } else { + // a % (-1) is 0. + var nz gc.Node + gc.Nodconst(&nz, t, 0) + + gmove(&nz, res) + } + + p2 = gc.Gbranch(obj.AJMP, nil, 0) + gc.Patch(p1, gc.Pc) + } + + p1 = gins(a, &tr, &tl) + if op == gc.ODIV { + gc.Regfree(&tr) + gmove(&tl, res) + } else { + // A%B = A-(A/B*B) + var tm gc.Node + gc.Regalloc(&tm, t, nil) + + // patch div to use the 3 register form + // TODO(minux): add gins3? + p1.Reg = p1.To.Reg + + p1.To.Reg = tm.Reg + gins(optoas(gc.OMUL, t), &tr, &tm) + gc.Regfree(&tr) + gins(optoas(gc.OSUB, t), &tm, &tl) + gc.Regfree(&tm) + gmove(&tl, res) + } + + gc.Regfree(&tl) + if check != 0 { + gc.Patch(p2, gc.Pc) + } +} + +/* + * generate high multiply: + * res = (nl*nr) >> width + */ +func cgen_hmul(nl *gc.Node, nr *gc.Node, res *gc.Node) { + // largest ullman on left. + if nl.Ullman < nr.Ullman { + nl, nr = nr, nl + } + + t := nl.Type + w := int(t.Width) * 8 + var n1 gc.Node + gc.Cgenr(nl, &n1, res) + var n2 gc.Node + gc.Cgenr(nr, &n2, nil) + switch gc.Simtype[t.Etype] { + case gc.TINT8, + gc.TINT16, + gc.TINT32: + gins(optoas(gc.OMUL, t), &n2, &n1) + p := gins(s390x.ASRAD, nil, &n1) + p.From.Type = obj.TYPE_CONST + p.From.Offset = int64(w) + + case gc.TUINT8, + gc.TUINT16, + gc.TUINT32: + gins(optoas(gc.OMUL, t), &n2, &n1) + p := gins(s390x.ASRD, nil, &n1) + p.From.Type = obj.TYPE_CONST + p.From.Offset = int64(w) + + case gc.TINT64: + gins(s390x.AMULHD, &n2, &n1) + + case gc.TUINT64: + gins(s390x.AMULHDU, &n2, &n1) + + default: + gc.Fatalf("cgen_hmul %v", t) + } + + gc.Cgen(&n1, res) + gc.Regfree(&n1) + gc.Regfree(&n2) +} + +/* + * generate shift according to op, one of: + * res = nl << nr + * res = nl >> nr + */ +func cgen_shift(op gc.Op, bounded bool, nl *gc.Node, nr *gc.Node, res *gc.Node) { + a := optoas(op, nl.Type) + + if nr.Op == gc.OLITERAL { + var n1 gc.Node + gc.Regalloc(&n1, nl.Type, res) + gc.Cgen(nl, &n1) + sc := uint64(nr.Int64()) + if sc >= uint64(nl.Type.Width*8) { + // large shift gets 2 shifts by width-1 + var n3 gc.Node + gc.Nodconst(&n3, gc.Types[gc.TUINT32], nl.Type.Width*8-1) + + gins(a, &n3, &n1) + gins(a, &n3, &n1) + } else { + gins(a, nr, &n1) + } + gmove(&n1, res) + gc.Regfree(&n1) + return + } + + if nl.Ullman >= gc.UINF { + var n4 gc.Node + gc.Tempname(&n4, nl.Type) + gc.Cgen(nl, &n4) + nl = &n4 + } + + if nr.Ullman >= gc.UINF { + var n5 gc.Node + gc.Tempname(&n5, nr.Type) + gc.Cgen(nr, &n5) + nr = &n5 + } + + // Allow either uint32 or uint64 as shift type, + // to avoid unnecessary conversion from uint32 to uint64 + // just to do the comparison. + tcount := gc.Types[gc.Simtype[nr.Type.Etype]] + + if tcount.Etype < gc.TUINT32 { + tcount = gc.Types[gc.TUINT32] + } + + var n1 gc.Node + gc.Regalloc(&n1, nr.Type, nil) // to hold the shift type in CX + var n3 gc.Node + gc.Regalloc(&n3, tcount, &n1) // to clear high bits of CX + + var n2 gc.Node + gc.Regalloc(&n2, nl.Type, res) + + if nl.Ullman >= nr.Ullman { + gc.Cgen(nl, &n2) + gc.Cgen(nr, &n1) + gmove(&n1, &n3) + } else { + gc.Cgen(nr, &n1) + gmove(&n1, &n3) + gc.Cgen(nl, &n2) + } + + gc.Regfree(&n3) + + // test and fix up large shifts + if !bounded { + gc.Nodconst(&n3, tcount, nl.Type.Width*8) + gins(optoas(gc.OCMP, tcount), &n1, &n3) + p1 := gc.Gbranch(optoas(gc.OLT, tcount), nil, 1) + if op == gc.ORSH && nl.Type.IsSigned() { + gc.Nodconst(&n3, gc.Types[gc.TUINT32], nl.Type.Width*8-1) + gins(a, &n3, &n2) + } else { + gc.Nodconst(&n3, nl.Type, 0) + gmove(&n3, &n2) + } + + gc.Patch(p1, gc.Pc) + } + + gins(a, &n1, &n2) + + gmove(&n2, res) + + gc.Regfree(&n1) + gc.Regfree(&n2) +} + +// clearfat clears (i.e. replaces with zeros) the value pointed to by nl. +func clearfat(nl *gc.Node) { + if gc.Debug['g'] != 0 { + fmt.Printf("clearfat %v (%v, size: %d)\n", nl, nl.Type, nl.Type.Width) + } + + // Avoid taking the address for simple enough types. + if gc.Componentgen(nil, nl) { + return + } + + var dst gc.Node + gc.Regalloc(&dst, gc.Types[gc.Tptr], nil) + gc.Agen(nl, &dst) + + var boff int64 + w := nl.Type.Width + if w > clearLoopCutoff { + // Generate a loop clearing 256 bytes per iteration using XCs. + var end gc.Node + gc.Regalloc(&end, gc.Types[gc.Tptr], nil) + p := gins(s390x.AMOVD, &dst, &end) + p.From.Type = obj.TYPE_ADDR + p.From.Offset = w - (w % 256) + + p = gins(s390x.AXC, &dst, &dst) + p.From.Type = obj.TYPE_MEM + p.From.Offset = 0 + p.To.Type = obj.TYPE_MEM + p.To.Offset = 0 + p.From3 = new(obj.Addr) + p.From3.Offset = 256 + p.From3.Type = obj.TYPE_CONST + pl := p + + ginscon(s390x.AADD, 256, &dst) + gins(s390x.ACMP, &dst, &end) + gc.Patch(gc.Gbranch(s390x.ABNE, nil, 0), pl) + gc.Regfree(&end) + w = w % 256 + } + + // Generate instructions to clear the remaining memory. + for w > 0 { + n := w + + // Can clear at most 256 bytes per instruction. + if n > 256 { + n = 256 + } + + switch n { + // Handle very small clears using moves. + case 8, 4, 2, 1: + ins := s390x.AMOVB + switch n { + case 8: + ins = s390x.AMOVD + case 4: + ins = s390x.AMOVW + case 2: + ins = s390x.AMOVH + } + p := gins(ins, nil, &dst) + p.From.Type = obj.TYPE_CONST + p.From.Offset = 0 + p.To.Type = obj.TYPE_MEM + p.To.Offset = boff + + // Handle clears that would require multiple moves with a XC. + default: + p := gins(s390x.AXC, &dst, &dst) + p.From.Type = obj.TYPE_MEM + p.From.Offset = boff + p.To.Type = obj.TYPE_MEM + p.To.Offset = boff + p.From3 = new(obj.Addr) + p.From3.Offset = n + p.From3.Type = obj.TYPE_CONST + } + + boff += n + w -= n + } + + gc.Regfree(&dst) +} + +// Called after regopt and peep have run. +// Expand CHECKNIL pseudo-op into actual nil pointer check. +func expandchecks(firstp *obj.Prog) { + for p := firstp; p != nil; p = p.Link { + if gc.Debug_checknil != 0 && gc.Ctxt.Debugvlog != 0 { + fmt.Printf("expandchecks: %v\n", p) + } + if p.As != obj.ACHECKNIL { + continue + } + if gc.Debug_checknil != 0 && p.Lineno > 1 { // p->lineno==1 in generated wrappers + gc.Warnl(p.Lineno, "generated nil check") + } + if p.From.Type != obj.TYPE_REG { + gc.Fatalf("invalid nil check %v\n", p) + } + + // check is + // CMPBNE arg, $0, 2(PC) [likely] + // MOVD R0, 0(R0) + p1 := gc.Ctxt.NewProg() + + gc.Clearp(p1) + p1.Link = p.Link + p.Link = p1 + p1.Lineno = p.Lineno + p1.Pc = 9999 + p.As = s390x.ACMPBNE + p.From3 = new(obj.Addr) + p.From3.Type = obj.TYPE_CONST + p.From3.Offset = 0 + + p.To.Type = obj.TYPE_BRANCH + p.To.Val = p1.Link + + // crash by write to memory address 0. + p1.As = s390x.AMOVD + + p1.From.Type = obj.TYPE_REG + p1.From.Reg = s390x.REGZERO + p1.To.Type = obj.TYPE_MEM + p1.To.Reg = s390x.REGZERO + p1.To.Offset = 0 + } +} + +// res = runtime.getg() +func getg(res *gc.Node) { + var n1 gc.Node + gc.Nodreg(&n1, res.Type, s390x.REGG) + gmove(&n1, res) +} diff --git a/src/cmd/compile/internal/s390x/gsubr.go b/src/cmd/compile/internal/s390x/gsubr.go new file mode 100644 index 0000000000..e9cfd23e42 --- /dev/null +++ b/src/cmd/compile/internal/s390x/gsubr.go @@ -0,0 +1,1115 @@ +// Derived from Inferno utils/6c/txt.c +// http://code.google.com/p/inferno-os/source/browse/utils/6c/txt.c +// +// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. +// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net) +// Portions Copyright © 1997-1999 Vita Nuova Limited +// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com) +// Portions Copyright © 2004,2006 Bruce Ellis +// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net) +// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others +// Portions Copyright © 2009 The Go Authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package s390x + +import ( + "cmd/compile/internal/gc" + "cmd/internal/obj" + "cmd/internal/obj/s390x" + "fmt" +) + +var resvd = []int{ + s390x.REGZERO, // R0 + s390x.REGTMP, // R10 + s390x.REGTMP2, // R11 + s390x.REGCTXT, // R12 + s390x.REGG, // R13 + s390x.REG_LR, // R14 + s390x.REGSP, // R15 +} + +// generate +// as $c, n +func ginscon(as obj.As, c int64, n2 *gc.Node) { + var n1 gc.Node + + gc.Nodconst(&n1, gc.Types[gc.TINT64], c) + + if as != s390x.AMOVD && (c < -s390x.BIG || c > s390x.BIG) || n2.Op != gc.OREGISTER || as == s390x.AMULLD { + // cannot have more than 16-bit of immediate in ADD, etc. + // instead, MOV into register first. + var ntmp gc.Node + gc.Regalloc(&ntmp, gc.Types[gc.TINT64], nil) + + rawgins(s390x.AMOVD, &n1, &ntmp) + rawgins(as, &ntmp, n2) + gc.Regfree(&ntmp) + return + } + + rawgins(as, &n1, n2) +} + +// generate +// as n, $c (CMP/CMPU) +func ginscon2(as obj.As, n2 *gc.Node, c int64) { + var n1 gc.Node + + gc.Nodconst(&n1, gc.Types[gc.TINT64], c) + + switch as { + default: + gc.Fatalf("ginscon2") + + case s390x.ACMP: + if -s390x.BIG <= c && c <= s390x.BIG { + rawgins(as, n2, &n1) + return + } + + case s390x.ACMPU: + if 0 <= c && c <= 2*s390x.BIG { + rawgins(as, n2, &n1) + return + } + } + + // MOV n1 into register first + var ntmp gc.Node + gc.Regalloc(&ntmp, gc.Types[gc.TINT64], nil) + + rawgins(s390x.AMOVD, &n1, &ntmp) + rawgins(as, n2, &ntmp) + gc.Regfree(&ntmp) +} + +func ginscmp(op gc.Op, t *gc.Type, n1, n2 *gc.Node, likely int) *obj.Prog { + if t.IsInteger() && n1.Op == gc.OLITERAL && n2.Op != gc.OLITERAL { + // Reverse comparison to place constant last. + op = gc.Brrev(op) + n1, n2 = n2, n1 + } + + var r1, r2, g1, g2 gc.Node + gc.Regalloc(&r1, t, n1) + gc.Regalloc(&g1, n1.Type, &r1) + gc.Cgen(n1, &g1) + gmove(&g1, &r1) + if t.IsInteger() && gc.Isconst(n2, gc.CTINT) { + ginscon2(optoas(gc.OCMP, t), &r1, n2.Int64()) + } else { + gc.Regalloc(&r2, t, n2) + gc.Regalloc(&g2, n1.Type, &r2) + gc.Cgen(n2, &g2) + gmove(&g2, &r2) + rawgins(optoas(gc.OCMP, t), &r1, &r2) + gc.Regfree(&g2) + gc.Regfree(&r2) + } + gc.Regfree(&g1) + gc.Regfree(&r1) + return gc.Gbranch(optoas(op, t), nil, likely) +} + +// gmvc tries to move f to t using a mvc instruction. +// If successful it returns true, otherwise it returns false. +func gmvc(f, t *gc.Node) bool { + ft := int(gc.Simsimtype(f.Type)) + tt := int(gc.Simsimtype(t.Type)) + + if ft != tt { + return false + } + + if f.Op != gc.OINDREG || t.Op != gc.OINDREG { + return false + } + + if f.Xoffset < 0 || f.Xoffset >= 4096-8 { + return false + } + + if t.Xoffset < 0 || t.Xoffset >= 4096-8 { + return false + } + + var len int64 + switch ft { + case gc.TUINT8, gc.TINT8, gc.TBOOL: + len = 1 + case gc.TUINT16, gc.TINT16: + len = 2 + case gc.TUINT32, gc.TINT32, gc.TFLOAT32: + len = 4 + case gc.TUINT64, gc.TINT64, gc.TFLOAT64, gc.TPTR64: + len = 8 + case gc.TUNSAFEPTR: + len = int64(gc.Widthptr) + default: + return false + } + + p := gc.Prog(s390x.AMVC) + gc.Naddr(&p.From, f) + gc.Naddr(&p.To, t) + p.From3 = new(obj.Addr) + p.From3.Offset = len + p.From3.Type = obj.TYPE_CONST + return true +} + +// generate move: +// t = f +// hard part is conversions. +func gmove(f *gc.Node, t *gc.Node) { + if gc.Debug['M'] != 0 { + fmt.Printf("gmove %v -> %v\n", gc.Nconv(f, gc.FmtLong), gc.Nconv(t, gc.FmtLong)) + } + + ft := int(gc.Simsimtype(f.Type)) + tt := int(gc.Simsimtype(t.Type)) + cvt := t.Type + + if gc.Iscomplex[ft] || gc.Iscomplex[tt] { + gc.Complexmove(f, t) + return + } + + var a obj.As + + // cannot have two memory operands + if gc.Ismem(f) && gc.Ismem(t) { + if gmvc(f, t) { + return + } + goto hard + } + + // convert constant to desired type + if f.Op == gc.OLITERAL { + var con gc.Node + f.Convconst(&con, t.Type) + f = &con + ft = tt // so big switch will choose a simple mov + + // some constants can't move directly to memory. + if gc.Ismem(t) { + // float constants come from memory. + if t.Type.IsFloat() { + goto hard + } + + // all immediates are 16-bit sign-extended + // unless moving into a register. + if t.Type.IsInteger() { + if i := con.Int64(); int64(int16(i)) != i { + goto hard + } + } + + // immediate moves to memory have a 12-bit unsigned displacement + if t.Xoffset < 0 || t.Xoffset >= 4096-8 { + goto hard + } + } + } + + // a float-to-int or int-to-float conversion requires the source operand in a register + if gc.Ismem(f) && ((f.Type.IsFloat() && t.Type.IsInteger()) || (f.Type.IsInteger() && t.Type.IsFloat())) { + cvt = f.Type + goto hard + } + + // a float32-to-float64 or float64-to-float32 conversion requires the source operand in a register + if gc.Ismem(f) && f.Type.IsFloat() && t.Type.IsFloat() && (ft != tt) { + cvt = f.Type + goto hard + } + + // value -> value copy, only one memory operand. + // figure out the instruction to use. + // break out of switch for one-instruction gins. + // goto rdst for "destination must be register". + // goto hard for "convert to cvt type first". + // otherwise handle and return. + switch uint32(ft)<<16 | uint32(tt) { + default: + gc.Fatalf("gmove %v -> %v", gc.Tconv(f.Type, gc.FmtLong), gc.Tconv(t.Type, gc.FmtLong)) + + // integer copy and truncate + case gc.TINT8<<16 | gc.TINT8, + gc.TUINT8<<16 | gc.TINT8, + gc.TINT16<<16 | gc.TINT8, + gc.TUINT16<<16 | gc.TINT8, + gc.TINT32<<16 | gc.TINT8, + gc.TUINT32<<16 | gc.TINT8, + gc.TINT64<<16 | gc.TINT8, + gc.TUINT64<<16 | gc.TINT8: + a = s390x.AMOVB + + case gc.TINT8<<16 | gc.TUINT8, + gc.TUINT8<<16 | gc.TUINT8, + gc.TINT16<<16 | gc.TUINT8, + gc.TUINT16<<16 | gc.TUINT8, + gc.TINT32<<16 | gc.TUINT8, + gc.TUINT32<<16 | gc.TUINT8, + gc.TINT64<<16 | gc.TUINT8, + gc.TUINT64<<16 | gc.TUINT8: + a = s390x.AMOVBZ + + case gc.TINT16<<16 | gc.TINT16, + gc.TUINT16<<16 | gc.TINT16, + gc.TINT32<<16 | gc.TINT16, + gc.TUINT32<<16 | gc.TINT16, + gc.TINT64<<16 | gc.TINT16, + gc.TUINT64<<16 | gc.TINT16: + a = s390x.AMOVH + + case gc.TINT16<<16 | gc.TUINT16, + gc.TUINT16<<16 | gc.TUINT16, + gc.TINT32<<16 | gc.TUINT16, + gc.TUINT32<<16 | gc.TUINT16, + gc.TINT64<<16 | gc.TUINT16, + gc.TUINT64<<16 | gc.TUINT16: + a = s390x.AMOVHZ + + case gc.TINT32<<16 | gc.TINT32, + gc.TUINT32<<16 | gc.TINT32, + gc.TINT64<<16 | gc.TINT32, + gc.TUINT64<<16 | gc.TINT32: + a = s390x.AMOVW + + case gc.TINT32<<16 | gc.TUINT32, + gc.TUINT32<<16 | gc.TUINT32, + gc.TINT64<<16 | gc.TUINT32, + gc.TUINT64<<16 | gc.TUINT32: + a = s390x.AMOVWZ + + case gc.TINT64<<16 | gc.TINT64, + gc.TINT64<<16 | gc.TUINT64, + gc.TUINT64<<16 | gc.TINT64, + gc.TUINT64<<16 | gc.TUINT64: + a = s390x.AMOVD + + // sign extend int8 + case gc.TINT8<<16 | gc.TINT16, + gc.TINT8<<16 | gc.TUINT16, + gc.TINT8<<16 | gc.TINT32, + gc.TINT8<<16 | gc.TUINT32, + gc.TINT8<<16 | gc.TINT64, + gc.TINT8<<16 | gc.TUINT64: + a = s390x.AMOVB + goto rdst + + // sign extend uint8 + case gc.TUINT8<<16 | gc.TINT16, + gc.TUINT8<<16 | gc.TUINT16, + gc.TUINT8<<16 | gc.TINT32, + gc.TUINT8<<16 | gc.TUINT32, + gc.TUINT8<<16 | gc.TINT64, + gc.TUINT8<<16 | gc.TUINT64: + a = s390x.AMOVBZ + goto rdst + + // sign extend int16 + case gc.TINT16<<16 | gc.TINT32, + gc.TINT16<<16 | gc.TUINT32, + gc.TINT16<<16 | gc.TINT64, + gc.TINT16<<16 | gc.TUINT64: + a = s390x.AMOVH + goto rdst + + // zero extend uint16 + case gc.TUINT16<<16 | gc.TINT32, + gc.TUINT16<<16 | gc.TUINT32, + gc.TUINT16<<16 | gc.TINT64, + gc.TUINT16<<16 | gc.TUINT64: + a = s390x.AMOVHZ + goto rdst + + // sign extend int32 + case gc.TINT32<<16 | gc.TINT64, + gc.TINT32<<16 | gc.TUINT64: + a = s390x.AMOVW + goto rdst + + // zero extend uint32 + case gc.TUINT32<<16 | gc.TINT64, + gc.TUINT32<<16 | gc.TUINT64: + a = s390x.AMOVWZ + goto rdst + + // float to integer + case gc.TFLOAT32<<16 | gc.TUINT8, + gc.TFLOAT32<<16 | gc.TUINT16: + cvt = gc.Types[gc.TUINT32] + goto hard + + case gc.TFLOAT32<<16 | gc.TUINT32: + a = s390x.ACLFEBR + goto rdst + + case gc.TFLOAT32<<16 | gc.TUINT64: + a = s390x.ACLGEBR + goto rdst + + case gc.TFLOAT64<<16 | gc.TUINT8, + gc.TFLOAT64<<16 | gc.TUINT16: + cvt = gc.Types[gc.TUINT32] + goto hard + + case gc.TFLOAT64<<16 | gc.TUINT32: + a = s390x.ACLFDBR + goto rdst + + case gc.TFLOAT64<<16 | gc.TUINT64: + a = s390x.ACLGDBR + goto rdst + + case gc.TFLOAT32<<16 | gc.TINT8, + gc.TFLOAT32<<16 | gc.TINT16: + cvt = gc.Types[gc.TINT32] + goto hard + + case gc.TFLOAT32<<16 | gc.TINT32: + a = s390x.ACFEBRA + goto rdst + + case gc.TFLOAT32<<16 | gc.TINT64: + a = s390x.ACGEBRA + goto rdst + + case gc.TFLOAT64<<16 | gc.TINT8, + gc.TFLOAT64<<16 | gc.TINT16: + cvt = gc.Types[gc.TINT32] + goto hard + + case gc.TFLOAT64<<16 | gc.TINT32: + a = s390x.ACFDBRA + goto rdst + + case gc.TFLOAT64<<16 | gc.TINT64: + a = s390x.ACGDBRA + goto rdst + + // integer to float + case gc.TUINT8<<16 | gc.TFLOAT32, + gc.TUINT16<<16 | gc.TFLOAT32: + cvt = gc.Types[gc.TUINT32] + goto hard + + case gc.TUINT32<<16 | gc.TFLOAT32: + a = s390x.ACELFBR + goto rdst + + case gc.TUINT64<<16 | gc.TFLOAT32: + a = s390x.ACELGBR + goto rdst + + case gc.TUINT8<<16 | gc.TFLOAT64, + gc.TUINT16<<16 | gc.TFLOAT64: + cvt = gc.Types[gc.TUINT32] + goto hard + + case gc.TUINT32<<16 | gc.TFLOAT64: + a = s390x.ACDLFBR + goto rdst + + case gc.TUINT64<<16 | gc.TFLOAT64: + a = s390x.ACDLGBR + goto rdst + + case gc.TINT8<<16 | gc.TFLOAT32, + gc.TINT16<<16 | gc.TFLOAT32: + cvt = gc.Types[gc.TINT32] + goto hard + + case gc.TINT32<<16 | gc.TFLOAT32: + a = s390x.ACEFBRA + goto rdst + + case gc.TINT64<<16 | gc.TFLOAT32: + a = s390x.ACEGBRA + goto rdst + + case gc.TINT8<<16 | gc.TFLOAT64, + gc.TINT16<<16 | gc.TFLOAT64: + cvt = gc.Types[gc.TINT32] + goto hard + + case gc.TINT32<<16 | gc.TFLOAT64: + a = s390x.ACDFBRA + goto rdst + + case gc.TINT64<<16 | gc.TFLOAT64: + a = s390x.ACDGBRA + goto rdst + + // float to float + case gc.TFLOAT32<<16 | gc.TFLOAT32: + a = s390x.AFMOVS + + case gc.TFLOAT64<<16 | gc.TFLOAT64: + a = s390x.AFMOVD + + case gc.TFLOAT32<<16 | gc.TFLOAT64: + a = s390x.ALDEBR + goto rdst + + case gc.TFLOAT64<<16 | gc.TFLOAT32: + a = s390x.ALEDBR + goto rdst + } + + gins(a, f, t) + return + + // requires register destination +rdst: + if t != nil && t.Op == gc.OREGISTER { + gins(a, f, t) + return + } else { + var r1 gc.Node + gc.Regalloc(&r1, t.Type, t) + + gins(a, f, &r1) + gmove(&r1, t) + gc.Regfree(&r1) + return + } + + // requires register intermediate +hard: + var r1 gc.Node + gc.Regalloc(&r1, cvt, t) + + gmove(f, &r1) + gmove(&r1, t) + gc.Regfree(&r1) + return +} + +func intLiteral(n *gc.Node) (x int64, ok bool) { + switch { + case n == nil: + return + case gc.Isconst(n, gc.CTINT): + return n.Int64(), true + case gc.Isconst(n, gc.CTBOOL): + return int64(obj.Bool2int(n.Bool())), true + } + return +} + +// gins is called by the front end. +// It synthesizes some multiple-instruction sequences +// so the front end can stay simpler. +func gins(as obj.As, f, t *gc.Node) *obj.Prog { + if t != nil { + if as >= obj.A_ARCHSPECIFIC { + if x, ok := intLiteral(f); ok { + ginscon(as, x, t) + return nil // caller must not use + } + } + if as == s390x.ACMP || as == s390x.ACMPU { + if x, ok := intLiteral(t); ok { + ginscon2(as, f, x) + return nil // caller must not use + } + } + } + return rawgins(as, f, t) +} + +// generate one instruction: +// as f, t +func rawgins(as obj.As, f *gc.Node, t *gc.Node) *obj.Prog { + // self move check + // TODO(mundaym): use sized math and extend to MOVB, MOVWZ etc. + switch as { + case s390x.AMOVD, s390x.AFMOVS, s390x.AFMOVD: + if f != nil && t != nil && + f.Op == gc.OREGISTER && t.Op == gc.OREGISTER && + f.Reg == t.Reg { + return nil + } + } + + p := gc.Prog(as) + gc.Naddr(&p.From, f) + gc.Naddr(&p.To, t) + + switch as { + // Bad things the front end has done to us. Crash to find call stack. + case s390x.AMULLD: + if p.From.Type == obj.TYPE_CONST { + gc.Debug['h'] = 1 + gc.Fatalf("bad inst: %v", p) + } + case s390x.ACMP, s390x.ACMPU: + if p.From.Type == obj.TYPE_MEM || p.To.Type == obj.TYPE_MEM { + gc.Debug['h'] = 1 + gc.Fatalf("bad inst: %v", p) + } + } + + if gc.Debug['g'] != 0 { + fmt.Printf("%v\n", p) + } + + w := int32(0) + switch as { + case s390x.AMOVB, s390x.AMOVBZ: + w = 1 + + case s390x.AMOVH, s390x.AMOVHZ: + w = 2 + + case s390x.AMOVW, s390x.AMOVWZ: + w = 4 + + case s390x.AMOVD: + if p.From.Type == obj.TYPE_CONST || p.From.Type == obj.TYPE_ADDR { + break + } + w = 8 + } + + if w != 0 && ((f != nil && p.From.Width < int64(w)) || (t != nil && p.To.Type != obj.TYPE_REG && p.To.Width > int64(w))) { + gc.Dump("f", f) + gc.Dump("t", t) + gc.Fatalf("bad width: %v (%d, %d)\n", p, p.From.Width, p.To.Width) + } + + return p +} + +// optoas returns the Axxx equivalent of Oxxx for type t +func optoas(op gc.Op, t *gc.Type) obj.As { + if t == nil { + gc.Fatalf("optoas: t is nil") + } + + // avoid constant conversions in switches below + const ( + OMINUS_ = uint32(gc.OMINUS) << 16 + OLSH_ = uint32(gc.OLSH) << 16 + ORSH_ = uint32(gc.ORSH) << 16 + OADD_ = uint32(gc.OADD) << 16 + OSUB_ = uint32(gc.OSUB) << 16 + OMUL_ = uint32(gc.OMUL) << 16 + ODIV_ = uint32(gc.ODIV) << 16 + OOR_ = uint32(gc.OOR) << 16 + OAND_ = uint32(gc.OAND) << 16 + OXOR_ = uint32(gc.OXOR) << 16 + OEQ_ = uint32(gc.OEQ) << 16 + ONE_ = uint32(gc.ONE) << 16 + OLT_ = uint32(gc.OLT) << 16 + OLE_ = uint32(gc.OLE) << 16 + OGE_ = uint32(gc.OGE) << 16 + OGT_ = uint32(gc.OGT) << 16 + OCMP_ = uint32(gc.OCMP) << 16 + OAS_ = uint32(gc.OAS) << 16 + OHMUL_ = uint32(gc.OHMUL) << 16 + OSQRT_ = uint32(gc.OSQRT) << 16 + OLROT_ = uint32(gc.OLROT) << 16 + ) + + a := obj.AXXX + switch uint32(op)<<16 | uint32(gc.Simtype[t.Etype]) { + default: + gc.Fatalf("optoas: no entry for op=%v type=%v", gc.Oconv(op, 0), t) + + case OEQ_ | gc.TBOOL, + OEQ_ | gc.TINT8, + OEQ_ | gc.TUINT8, + OEQ_ | gc.TINT16, + OEQ_ | gc.TUINT16, + OEQ_ | gc.TINT32, + OEQ_ | gc.TUINT32, + OEQ_ | gc.TINT64, + OEQ_ | gc.TUINT64, + OEQ_ | gc.TPTR32, + OEQ_ | gc.TPTR64, + OEQ_ | gc.TFLOAT32, + OEQ_ | gc.TFLOAT64: + a = s390x.ABEQ + + case ONE_ | gc.TBOOL, + ONE_ | gc.TINT8, + ONE_ | gc.TUINT8, + ONE_ | gc.TINT16, + ONE_ | gc.TUINT16, + ONE_ | gc.TINT32, + ONE_ | gc.TUINT32, + ONE_ | gc.TINT64, + ONE_ | gc.TUINT64, + ONE_ | gc.TPTR32, + ONE_ | gc.TPTR64, + ONE_ | gc.TFLOAT32, + ONE_ | gc.TFLOAT64: + a = s390x.ABNE + + case OLT_ | gc.TINT8, // ACMP + OLT_ | gc.TINT16, + OLT_ | gc.TINT32, + OLT_ | gc.TINT64, + OLT_ | gc.TUINT8, + // ACMPU + OLT_ | gc.TUINT16, + OLT_ | gc.TUINT32, + OLT_ | gc.TUINT64, + OLT_ | gc.TFLOAT32, + // AFCMPU + OLT_ | gc.TFLOAT64: + a = s390x.ABLT + + case OLE_ | gc.TINT8, // ACMP + OLE_ | gc.TINT16, + OLE_ | gc.TINT32, + OLE_ | gc.TINT64, + OLE_ | gc.TUINT8, + // ACMPU + OLE_ | gc.TUINT16, + OLE_ | gc.TUINT32, + OLE_ | gc.TUINT64, + OLE_ | gc.TFLOAT32, + OLE_ | gc.TFLOAT64: + a = s390x.ABLE + + case OGT_ | gc.TINT8, + OGT_ | gc.TINT16, + OGT_ | gc.TINT32, + OGT_ | gc.TINT64, + OGT_ | gc.TUINT8, + OGT_ | gc.TUINT16, + OGT_ | gc.TUINT32, + OGT_ | gc.TUINT64, + OGT_ | gc.TFLOAT32, + OGT_ | gc.TFLOAT64: + a = s390x.ABGT + + case OGE_ | gc.TINT8, + OGE_ | gc.TINT16, + OGE_ | gc.TINT32, + OGE_ | gc.TINT64, + OGE_ | gc.TUINT8, + OGE_ | gc.TUINT16, + OGE_ | gc.TUINT32, + OGE_ | gc.TUINT64, + OGE_ | gc.TFLOAT32, + OGE_ | gc.TFLOAT64: + a = s390x.ABGE + + case OCMP_ | gc.TBOOL, + OCMP_ | gc.TINT8, + OCMP_ | gc.TINT16, + OCMP_ | gc.TINT32, + OCMP_ | gc.TPTR32, + OCMP_ | gc.TINT64: + a = s390x.ACMP + + case OCMP_ | gc.TUINT8, + OCMP_ | gc.TUINT16, + OCMP_ | gc.TUINT32, + OCMP_ | gc.TUINT64, + OCMP_ | gc.TPTR64: + a = s390x.ACMPU + + case OCMP_ | gc.TFLOAT32: + a = s390x.ACEBR + + case OCMP_ | gc.TFLOAT64: + a = s390x.AFCMPU + + case OAS_ | gc.TBOOL, + OAS_ | gc.TINT8: + a = s390x.AMOVB + + case OAS_ | gc.TUINT8: + a = s390x.AMOVBZ + + case OAS_ | gc.TINT16: + a = s390x.AMOVH + + case OAS_ | gc.TUINT16: + a = s390x.AMOVHZ + + case OAS_ | gc.TINT32: + a = s390x.AMOVW + + case OAS_ | gc.TUINT32, + OAS_ | gc.TPTR32: + a = s390x.AMOVWZ + + case OAS_ | gc.TINT64, + OAS_ | gc.TUINT64, + OAS_ | gc.TPTR64: + a = s390x.AMOVD + + case OAS_ | gc.TFLOAT32: + a = s390x.AFMOVS + + case OAS_ | gc.TFLOAT64: + a = s390x.AFMOVD + + case OADD_ | gc.TINT8, + OADD_ | gc.TUINT8, + OADD_ | gc.TINT16, + OADD_ | gc.TUINT16, + OADD_ | gc.TINT32, + OADD_ | gc.TUINT32, + OADD_ | gc.TPTR32, + OADD_ | gc.TINT64, + OADD_ | gc.TUINT64, + OADD_ | gc.TPTR64: + a = s390x.AADD + + case OADD_ | gc.TFLOAT32: + a = s390x.AFADDS + + case OADD_ | gc.TFLOAT64: + a = s390x.AFADD + + case OSUB_ | gc.TINT8, + OSUB_ | gc.TUINT8, + OSUB_ | gc.TINT16, + OSUB_ | gc.TUINT16, + OSUB_ | gc.TINT32, + OSUB_ | gc.TUINT32, + OSUB_ | gc.TPTR32, + OSUB_ | gc.TINT64, + OSUB_ | gc.TUINT64, + OSUB_ | gc.TPTR64: + a = s390x.ASUB + + case OSUB_ | gc.TFLOAT32: + a = s390x.AFSUBS + + case OSUB_ | gc.TFLOAT64: + a = s390x.AFSUB + + case OMINUS_ | gc.TINT8, + OMINUS_ | gc.TUINT8, + OMINUS_ | gc.TINT16, + OMINUS_ | gc.TUINT16, + OMINUS_ | gc.TINT32, + OMINUS_ | gc.TUINT32, + OMINUS_ | gc.TPTR32, + OMINUS_ | gc.TINT64, + OMINUS_ | gc.TUINT64, + OMINUS_ | gc.TPTR64: + a = s390x.ANEG + + case OAND_ | gc.TINT8, + OAND_ | gc.TUINT8, + OAND_ | gc.TINT16, + OAND_ | gc.TUINT16, + OAND_ | gc.TINT32, + OAND_ | gc.TUINT32, + OAND_ | gc.TPTR32, + OAND_ | gc.TINT64, + OAND_ | gc.TUINT64, + OAND_ | gc.TPTR64: + a = s390x.AAND + + case OOR_ | gc.TINT8, + OOR_ | gc.TUINT8, + OOR_ | gc.TINT16, + OOR_ | gc.TUINT16, + OOR_ | gc.TINT32, + OOR_ | gc.TUINT32, + OOR_ | gc.TPTR32, + OOR_ | gc.TINT64, + OOR_ | gc.TUINT64, + OOR_ | gc.TPTR64: + a = s390x.AOR + + case OXOR_ | gc.TINT8, + OXOR_ | gc.TUINT8, + OXOR_ | gc.TINT16, + OXOR_ | gc.TUINT16, + OXOR_ | gc.TINT32, + OXOR_ | gc.TUINT32, + OXOR_ | gc.TPTR32, + OXOR_ | gc.TINT64, + OXOR_ | gc.TUINT64, + OXOR_ | gc.TPTR64: + a = s390x.AXOR + + case OLSH_ | gc.TINT8, + OLSH_ | gc.TUINT8, + OLSH_ | gc.TINT16, + OLSH_ | gc.TUINT16, + OLSH_ | gc.TINT32, + OLSH_ | gc.TUINT32, + OLSH_ | gc.TPTR32, + OLSH_ | gc.TINT64, + OLSH_ | gc.TUINT64, + OLSH_ | gc.TPTR64: + a = s390x.ASLD + + case ORSH_ | gc.TUINT8, + ORSH_ | gc.TUINT16, + ORSH_ | gc.TUINT32, + ORSH_ | gc.TPTR32, + ORSH_ | gc.TUINT64, + ORSH_ | gc.TPTR64: + a = s390x.ASRD + + case ORSH_ | gc.TINT8, + ORSH_ | gc.TINT16, + ORSH_ | gc.TINT32, + ORSH_ | gc.TINT64: + a = s390x.ASRAD + + case OHMUL_ | gc.TINT64: + a = s390x.AMULHD + + case OHMUL_ | gc.TUINT64, + OHMUL_ | gc.TPTR64: + a = s390x.AMULHDU + + case OMUL_ | gc.TINT8, + OMUL_ | gc.TINT16, + OMUL_ | gc.TINT32, + OMUL_ | gc.TINT64: + a = s390x.AMULLD + + case OMUL_ | gc.TUINT8, + OMUL_ | gc.TUINT16, + OMUL_ | gc.TUINT32, + OMUL_ | gc.TPTR32, + // don't use word multiply, the high 32-bit are undefined. + OMUL_ | gc.TUINT64, + OMUL_ | gc.TPTR64: + // for 64-bit multiplies, signedness doesn't matter. + a = s390x.AMULLD + + case OMUL_ | gc.TFLOAT32: + a = s390x.AFMULS + + case OMUL_ | gc.TFLOAT64: + a = s390x.AFMUL + + case ODIV_ | gc.TINT8, + ODIV_ | gc.TINT16, + ODIV_ | gc.TINT32, + ODIV_ | gc.TINT64: + a = s390x.ADIVD + + case ODIV_ | gc.TUINT8, + ODIV_ | gc.TUINT16, + ODIV_ | gc.TUINT32, + ODIV_ | gc.TPTR32, + ODIV_ | gc.TUINT64, + ODIV_ | gc.TPTR64: + a = s390x.ADIVDU + + case ODIV_ | gc.TFLOAT32: + a = s390x.AFDIVS + + case ODIV_ | gc.TFLOAT64: + a = s390x.AFDIV + + case OSQRT_ | gc.TFLOAT64: + a = s390x.AFSQRT + + case OLROT_ | gc.TUINT32, + OLROT_ | gc.TPTR32, + OLROT_ | gc.TINT32: + a = s390x.ARLL + + case OLROT_ | gc.TUINT64, + OLROT_ | gc.TPTR64, + OLROT_ | gc.TINT64: + a = s390x.ARLLG + } + + return a +} + +const ( + ODynam = 1 << 0 + OAddable = 1 << 1 +) + +var clean [20]gc.Node + +var cleani int = 0 + +func sudoclean() { + if clean[cleani-1].Op != gc.OEMPTY { + gc.Regfree(&clean[cleani-1]) + } + if clean[cleani-2].Op != gc.OEMPTY { + gc.Regfree(&clean[cleani-2]) + } + cleani -= 2 +} + +/* + * generate code to compute address of n, + * a reference to a (perhaps nested) field inside + * an array or struct. + * return 0 on failure, 1 on success. + * on success, leaves usable address in a. + * + * caller is responsible for calling sudoclean + * after successful sudoaddable, + * to release the register used for a. + */ +func sudoaddable(as obj.As, n *gc.Node, a *obj.Addr) bool { + if n.Type == nil { + return false + } + + *a = obj.Addr{} + + switch n.Op { + case gc.OLITERAL: + if !gc.Isconst(n, gc.CTINT) { + return false + } + v := n.Int64() + switch as { + default: + return false + + // operations that can cope with a 32-bit immediate + // TODO(mundaym): logical operations can work on high bits + case s390x.AADD, + s390x.AADDC, + s390x.ASUB, + s390x.AMULLW, + s390x.AAND, + s390x.AOR, + s390x.AXOR, + s390x.ASLD, + s390x.ASLW, + s390x.ASRAW, + s390x.ASRAD, + s390x.ASRW, + s390x.ASRD, + s390x.AMOVB, + s390x.AMOVBZ, + s390x.AMOVH, + s390x.AMOVHZ, + s390x.AMOVW, + s390x.AMOVWZ, + s390x.AMOVD: + if int64(int32(v)) != v { + return false + } + + // for comparisons avoid immediates unless they can + // fit into a int8/uint8 + // this favours combined compare and branch instructions + case s390x.ACMP: + if int64(int8(v)) != v { + return false + } + case s390x.ACMPU: + if int64(uint8(v)) != v { + return false + } + } + + cleani += 2 + reg := &clean[cleani-1] + reg1 := &clean[cleani-2] + reg.Op = gc.OEMPTY + reg1.Op = gc.OEMPTY + gc.Naddr(a, n) + return true + + case gc.ODOT, + gc.ODOTPTR: + cleani += 2 + reg := &clean[cleani-1] + reg1 := &clean[cleani-2] + reg.Op = gc.OEMPTY + reg1.Op = gc.OEMPTY + var nn *gc.Node + var oary [10]int64 + o := gc.Dotoffset(n, oary[:], &nn) + if nn == nil { + sudoclean() + return false + } + + if nn.Addable && o == 1 && oary[0] >= 0 { + // directly addressable set of DOTs + n1 := *nn + + n1.Type = n.Type + n1.Xoffset += oary[0] + // check that the offset fits into a 12-bit displacement + if n1.Xoffset < 0 || n1.Xoffset >= (1<<12)-8 { + sudoclean() + return false + } + gc.Naddr(a, &n1) + return true + } + + gc.Regalloc(reg, gc.Types[gc.Tptr], nil) + n1 := *reg + n1.Op = gc.OINDREG + if oary[0] >= 0 { + gc.Agen(nn, reg) + n1.Xoffset = oary[0] + } else { + gc.Cgen(nn, reg) + gc.Cgen_checknil(reg) + n1.Xoffset = -(oary[0] + 1) + } + + for i := 1; i < o; i++ { + if oary[i] >= 0 { + gc.Fatalf("can't happen") + } + gins(s390x.AMOVD, &n1, reg) + gc.Cgen_checknil(reg) + n1.Xoffset = -(oary[i] + 1) + } + + a.Type = obj.TYPE_NONE + a.Index = 0 + // check that the offset fits into a 12-bit displacement + if n1.Xoffset < 0 || n1.Xoffset >= (1<<12)-8 { + tmp := n1 + tmp.Op = gc.OREGISTER + tmp.Type = gc.Types[gc.Tptr] + tmp.Xoffset = 0 + gc.Cgen_checknil(&tmp) + ginscon(s390x.AADD, n1.Xoffset, &tmp) + n1.Xoffset = 0 + } + gc.Naddr(a, &n1) + return true + } + + return false +} diff --git a/src/cmd/compile/internal/s390x/peep.go b/src/cmd/compile/internal/s390x/peep.go new file mode 100644 index 0000000000..86258d67da --- /dev/null +++ b/src/cmd/compile/internal/s390x/peep.go @@ -0,0 +1,1664 @@ +// Derived from Inferno utils/6c/peep.c +// http://code.google.com/p/inferno-os/source/browse/utils/6c/peep.c +// +// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. +// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net) +// Portions Copyright © 1997-1999 Vita Nuova Limited +// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com) +// Portions Copyright © 2004,2006 Bruce Ellis +// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net) +// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others +// Portions Copyright © 2009 The Go Authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package s390x + +import ( + "cmd/compile/internal/gc" + "cmd/internal/obj" + "cmd/internal/obj/s390x" + "fmt" +) + +type usage int + +const ( + _None usage = iota // no usage found + _Read // only read from + _ReadWriteSame // both read from and written to in a single operand + _Write // only written to + _ReadWriteDiff // both read from and written to in different operands +) + +var gactive uint32 + +func peep(firstp *obj.Prog) { + g := gc.Flowstart(firstp, nil) + if g == nil { + return + } + gactive = 0 + + run := func(name string, pass func(r *gc.Flow) int) int { + n := pass(g.Start) + if gc.Debug['P'] != 0 { + fmt.Println(name, ":", n) + } + if gc.Debug['P'] != 0 && gc.Debug['v'] != 0 { + gc.Dumpit(name, g.Start, 0) + } + return n + } + + for { + n := 0 + n += run("constant propagation", constantPropagation) + n += run("copy propagation", copyPropagation) + n += run("cast propagation", castPropagation) + n += run("remove load-hit-stores", removeLoadHitStores) + n += run("dead code elimination", deadCodeElimination) + if n == 0 { + break + } + } + run("fuse op moves", fuseOpMoves) + run("fuse clears", fuseClear) + run("load pipelining", loadPipelining) + run("fuse compare branch", fuseCompareBranch) + run("simplify ops", simplifyOps) + run("dead code elimination", deadCodeElimination) + + // TODO(mundaym): load/store multiple aren't currently handled by copyu + // so this pass must be last. + run("fuse multiple", fuseMultiple) + + gc.Flowend(g) +} + +func pushback(r0 *gc.Flow) { + var r *gc.Flow + + var b *gc.Flow + p0 := r0.Prog + for r = gc.Uniqp(r0); r != nil && gc.Uniqs(r) != nil; r = gc.Uniqp(r) { + p := r.Prog + if p.As != obj.ANOP { + if !(isReg(&p.From) || isConst(&p.From)) || !isReg(&p.To) { + break + } + if copyu(p, &p0.To, nil) != _None || copyu(p0, &p.To, nil) != _None { + break + } + } + + if p.As == obj.ACALL { + break + } + b = r + } + + if b == nil { + if gc.Debug['P'] != 0 && gc.Debug['v'] != 0 { + fmt.Printf("no pushback: %v\n", r0.Prog) + if r != nil { + fmt.Printf("\t%v [%v]\n", r.Prog, gc.Uniqs(r) != nil) + } + } + + return + } + + if gc.Debug['P'] != 0 && gc.Debug['v'] != 0 { + fmt.Printf("pushback\n") + for r := b; ; r = r.Link { + fmt.Printf("\t%v\n", r.Prog) + if r == r0 { + break + } + } + } + + t := obj.Prog(*r0.Prog) + for r = gc.Uniqp(r0); ; r = gc.Uniqp(r) { + p0 = r.Link.Prog + p := r.Prog + p0.As = p.As + p0.Lineno = p.Lineno + p0.From = p.From + p0.To = p.To + p0.From3 = p.From3 + p0.Reg = p.Reg + p0.RegTo2 = p.RegTo2 + if r == b { + break + } + } + + p0 = r.Prog + p0.As = t.As + p0.Lineno = t.Lineno + p0.From = t.From + p0.To = t.To + p0.From3 = t.From3 + p0.Reg = t.Reg + p0.RegTo2 = t.RegTo2 + + if gc.Debug['P'] != 0 && gc.Debug['v'] != 0 { + fmt.Printf("\tafter\n") + for r := (*gc.Flow)(b); ; r = r.Link { + fmt.Printf("\t%v\n", r.Prog) + if r == r0 { + break + } + } + } +} + +// excise replaces the given instruction with a NOP and clears +// its operands. +func excise(r *gc.Flow) { + p := r.Prog + if gc.Debug['P'] != 0 && gc.Debug['v'] != 0 { + fmt.Printf("%v ===delete===\n", p) + } + obj.Nopout(p) + gc.Ostats.Ndelmov++ +} + +// isZero returns true if a is either the constant 0 or the register +// REGZERO. +func isZero(a *obj.Addr) bool { + if a.Type == obj.TYPE_CONST && a.Offset == 0 { + return true + } + if a.Type == obj.TYPE_REG && a.Reg == s390x.REGZERO { + return true + } + return false +} + +// isReg returns true if a is a general purpose or floating point +// register (GPR or FPR). +// +// TODO(mundaym): currently this excludes REGZER0, but not other +// special registers. +func isReg(a *obj.Addr) bool { + return a.Type == obj.TYPE_REG && + s390x.REG_R0 <= a.Reg && + a.Reg <= s390x.REG_F15 && + a.Reg != s390x.REGZERO +} + +// isGPR returns true if a is a general purpose register (GPR). +// REGZERO is treated as a GPR. +func isGPR(a *obj.Addr) bool { + return a.Type == obj.TYPE_REG && + s390x.REG_R0 <= a.Reg && + a.Reg <= s390x.REG_R15 +} + +// isFPR returns true if a is a floating point register (FPR). +func isFPR(a *obj.Addr) bool { + return a.Type == obj.TYPE_REG && + s390x.REG_F0 <= a.Reg && + a.Reg <= s390x.REG_F15 +} + +// isConst returns true if a refers to a constant (integer or +// floating point, not string currently). +func isConst(a *obj.Addr) bool { + return a.Type == obj.TYPE_CONST || a.Type == obj.TYPE_FCONST +} + +// isBDMem returns true if a refers to a memory location addressable by a +// base register (B) and a displacement (D), such as: +// x+8(R1) +// and +// 0(R10) +// It returns false if the address contains an index register (X) such as: +// 16(R1)(R2*1) +// or if a relocation is required. +func isBDMem(a *obj.Addr) bool { + return a.Type == obj.TYPE_MEM && + a.Index == 0 && + (a.Name == obj.NAME_NONE || a.Name == obj.NAME_AUTO || a.Name == obj.NAME_PARAM) +} + +// the idea is to substitute +// one register for another +// from one MOV to another +// MOV a, R1 +// ADD b, R1 / no use of R2 +// MOV R1, R2 +// would be converted to +// MOV a, R2 +// ADD b, R2 +// MOV R2, R1 +// hopefully, then the former or latter MOV +// will be eliminated by copy propagation. +// +// r0 (the argument, not the register) is the MOV at the end of the +// above sequences. subprop returns true if it modified any instructions. +func subprop(r0 *gc.Flow) bool { + p := r0.Prog + v1 := &p.From + if !isReg(v1) { + return false + } + v2 := &p.To + if !isReg(v2) { + return false + } + cast := false + switch p.As { + case s390x.AMOVW, s390x.AMOVWZ, + s390x.AMOVH, s390x.AMOVHZ, + s390x.AMOVB, s390x.AMOVBZ: + cast = true + } + for r := gc.Uniqp(r0); r != nil; r = gc.Uniqp(r) { + if gc.Uniqs(r) == nil { + break + } + p = r.Prog + switch copyu(p, v1, nil) { + case _Write, _ReadWriteDiff: + if p.As == obj.ACALL { + return false + } + if (!cast || p.As == r0.Prog.As) && p.To.Type == v1.Type && p.To.Reg == v1.Reg { + copysub(&p.To, v1, v2) + for r = gc.Uniqs(r); r != r0; r = gc.Uniqs(r) { + p = r.Prog + copysub(&p.From, v1, v2) + copysub1(p, v1, v2) + copysub(&p.To, v1, v2) + } + v1.Reg, v2.Reg = v2.Reg, v1.Reg + return true + } + if cast { + return false + } + case _ReadWriteSame: + if cast { + return false + } + } + if copyu(p, v2, nil) != _None { + return false + } + } + return false +} + +// The idea is to remove redundant copies. +// v1->v2 F=0 +// (use v2 s/v2/v1/)* +// set v1 F=1 +// use v2 return fail (v1->v2 move must remain) +// ----------------- +// v1->v2 F=0 +// (use v2 s/v2/v1/)* +// set v1 F=1 +// set v2 return success (caller can remove v1->v2 move) +func copyprop(r *gc.Flow) bool { + p := r.Prog + + canSub := false + switch p.As { + case s390x.AFMOVS, s390x.AFMOVD, s390x.AMOVD: + canSub = true + default: + for rr := gc.Uniqp(r); rr != nil; rr = gc.Uniqp(rr) { + if gc.Uniqs(rr) == nil { + break + } + switch copyu(rr.Prog, &p.From, nil) { + case _Read, _None: + continue + } + // write + if rr.Prog.As == p.As { + canSub = true + } + break + } + } + if !canSub { + return false + } + if copyas(&p.From, &p.To) { + return true + } + + gactive++ + return copy1(&p.From, &p.To, r.S1, 0) +} + +// copy1 replaces uses of v2 with v1 starting at r and returns true if +// all uses were rewritten. +func copy1(v1 *obj.Addr, v2 *obj.Addr, r *gc.Flow, f int) bool { + if uint32(r.Active) == gactive { + return true + } + r.Active = int32(gactive) + for ; r != nil; r = r.S1 { + p := r.Prog + if f == 0 && gc.Uniqp(r) == nil { + // Multiple predecessors; conservatively + // assume v1 was set on other path + f = 1 + } + t := copyu(p, v2, nil) + switch t { + case _ReadWriteSame: + return false + case _Write: + return true + case _Read, _ReadWriteDiff: + if f != 0 { + return false + } + if copyu(p, v2, v1) != 0 { + return false + } + if t == _ReadWriteDiff { + return true + } + } + if f == 0 { + switch copyu(p, v1, nil) { + case _ReadWriteSame, _ReadWriteDiff, _Write: + f = 1 + } + } + if r.S2 != nil { + if !copy1(v1, v2, r.S2, f) { + return false + } + } + } + return true +} + +// If s==nil, copyu returns the set/use of v in p; otherwise, it +// modifies p to replace reads of v with reads of s and returns 0 for +// success or non-zero for failure. +// +// If s==nil, copy returns one of the following values: +// _Read if v only used +// _ReadWriteSame if v is set and used in one address (read-alter-rewrite; +// can't substitute) +// _Write if v is only set +// _ReadWriteDiff if v is set in one address and used in another (so addresses +// can be rewritten independently) +// _None otherwise (not touched) +func copyu(p *obj.Prog, v *obj.Addr, s *obj.Addr) usage { + if p.From3Type() != obj.TYPE_NONE && p.From3Type() != obj.TYPE_CONST { + // Currently we never generate a From3 with anything other than a constant in it. + fmt.Printf("copyu: From3 (%v) not implemented\n", gc.Ctxt.Dconv(p.From3)) + } + + switch p.As { + default: + fmt.Printf("copyu: can't find %v\n", obj.Aconv(p.As)) + return _ReadWriteSame + + case // read p.From, write p.To + s390x.AMOVH, + s390x.AMOVHZ, + s390x.AMOVB, + s390x.AMOVBZ, + s390x.AMOVW, + s390x.AMOVWZ, + s390x.AMOVD, + s390x.ANEG, + s390x.AADDME, + s390x.AADDZE, + s390x.ASUBME, + s390x.ASUBZE, + s390x.AFMOVS, + s390x.AFMOVD, + s390x.ALEDBR, + s390x.AFNEG, + s390x.ALDEBR, + s390x.ACLFEBR, + s390x.ACLGEBR, + s390x.ACLFDBR, + s390x.ACLGDBR, + s390x.ACFEBRA, + s390x.ACGEBRA, + s390x.ACFDBRA, + s390x.ACGDBRA, + s390x.ACELFBR, + s390x.ACELGBR, + s390x.ACDLFBR, + s390x.ACDLGBR, + s390x.ACEFBRA, + s390x.ACEGBRA, + s390x.ACDFBRA, + s390x.ACDGBRA, + s390x.AFSQRT: + + if s != nil { + copysub(&p.From, v, s) + + // Update only indirect uses of v in p.To + if !copyas(&p.To, v) { + copysub(&p.To, v, s) + } + return _None + } + + if copyas(&p.To, v) { + // Fix up implicit from + if p.From.Type == obj.TYPE_NONE { + p.From = p.To + } + if copyau(&p.From, v) { + return _ReadWriteDiff + } + return _Write + } + + if copyau(&p.From, v) { + return _Read + } + if copyau(&p.To, v) { + // p.To only indirectly uses v + return _Read + } + + return _None + + // read p.From, read p.Reg, write p.To + case s390x.AADD, + s390x.AADDC, + s390x.AADDE, + s390x.ASUB, + s390x.ASLW, + s390x.ASRW, + s390x.ASRAW, + s390x.ASLD, + s390x.ASRD, + s390x.ASRAD, + s390x.ARLL, + s390x.ARLLG, + s390x.AOR, + s390x.AORN, + s390x.AAND, + s390x.AANDN, + s390x.ANAND, + s390x.ANOR, + s390x.AXOR, + s390x.AMULLW, + s390x.AMULLD, + s390x.AMULHD, + s390x.AMULHDU, + s390x.ADIVW, + s390x.ADIVD, + s390x.ADIVWU, + s390x.ADIVDU, + s390x.AFADDS, + s390x.AFADD, + s390x.AFSUBS, + s390x.AFSUB, + s390x.AFMULS, + s390x.AFMUL, + s390x.AFDIVS, + s390x.AFDIV: + if s != nil { + copysub(&p.From, v, s) + copysub1(p, v, s) + + // Update only indirect uses of v in p.To + if !copyas(&p.To, v) { + copysub(&p.To, v, s) + } + } + + if copyas(&p.To, v) { + if p.Reg == 0 { + p.Reg = p.To.Reg + } + if copyau(&p.From, v) || copyau1(p, v) { + return _ReadWriteDiff + } + return _Write + } + + if copyau(&p.From, v) { + return _Read + } + if copyau1(p, v) { + return _Read + } + if copyau(&p.To, v) { + return _Read + } + return _None + + case s390x.ABEQ, + s390x.ABGT, + s390x.ABGE, + s390x.ABLT, + s390x.ABLE, + s390x.ABNE, + s390x.ABVC, + s390x.ABVS: + return _None + + case obj.ACHECKNIL, // read p.From + s390x.ACMP, // read p.From, read p.To + s390x.ACMPU, + s390x.ACMPW, + s390x.ACMPWU, + s390x.AFCMPO, + s390x.AFCMPU, + s390x.ACEBR, + s390x.AMVC, + s390x.ACLC, + s390x.AXC, + s390x.AOC, + s390x.ANC: + if s != nil { + copysub(&p.From, v, s) + copysub(&p.To, v, s) + return _None + } + + if copyau(&p.From, v) { + return _Read + } + if copyau(&p.To, v) { + return _Read + } + return _None + + case s390x.ACMPBNE, s390x.ACMPBEQ, + s390x.ACMPBLT, s390x.ACMPBLE, + s390x.ACMPBGT, s390x.ACMPBGE, + s390x.ACMPUBNE, s390x.ACMPUBEQ, + s390x.ACMPUBLT, s390x.ACMPUBLE, + s390x.ACMPUBGT, s390x.ACMPUBGE: + if s != nil { + copysub(&p.From, v, s) + copysub1(p, v, s) + return _None + } + if copyau(&p.From, v) { + return _Read + } + if copyau1(p, v) { + return _Read + } + return _None + + case s390x.ACLEAR: + if s != nil { + copysub(&p.To, v, s) + return _None + } + if copyau(&p.To, v) { + return _Read + } + return _None + + // go never generates a branch to a GPR + // read p.To + case s390x.ABR: + if s != nil { + copysub(&p.To, v, s) + return _None + } + + if copyau(&p.To, v) { + return _Read + } + return _None + + case obj.ARET, obj.AUNDEF: + if s != nil { + return _None + } + + // All registers die at this point, so claim + // everything is set (and not used). + return _Write + + case s390x.ABL: + if v.Type == obj.TYPE_REG { + if s390x.REGARG != -1 && v.Reg == s390x.REGARG { + return _ReadWriteSame + } + if p.From.Type == obj.TYPE_REG && p.From.Reg == v.Reg { + return _ReadWriteSame + } + if v.Reg == s390x.REGZERO { + // Deliberately inserted nops set R0. + return _ReadWriteSame + } + if v.Reg == s390x.REGCTXT { + // Context register for closures. + // TODO(mundaym): not sure if we need to exclude this. + return _ReadWriteSame + } + } + if s != nil { + copysub(&p.To, v, s) + return _None + } + if copyau(&p.To, v) { + return _ReadWriteDiff + } + return _Write + + case obj.ATEXT: + if v.Type == obj.TYPE_REG { + if v.Reg == s390x.REGARG { + return _Write + } + } + return _None + + case obj.APCDATA, + obj.AFUNCDATA, + obj.AVARDEF, + obj.AVARKILL, + obj.AVARLIVE, + obj.AUSEFIELD, + obj.ANOP: + return _None + } +} + +// copyas returns 1 if a and v address the same register. +// +// If a is the from operand, this means this operation reads the +// register in v. If a is the to operand, this means this operation +// writes the register in v. +func copyas(a *obj.Addr, v *obj.Addr) bool { + if isReg(v) { + if a.Type == v.Type { + if a.Reg == v.Reg { + return true + } + } + } + return false +} + +// copyau returns 1 if a either directly or indirectly addresses the +// same register as v. +// +// If a is the from operand, this means this operation reads the +// register in v. If a is the to operand, this means the operation +// either reads or writes the register in v (if !copyas(a, v), then +// the operation reads the register in v). +func copyau(a *obj.Addr, v *obj.Addr) bool { + if copyas(a, v) { + return true + } + if v.Type == obj.TYPE_REG { + if a.Type == obj.TYPE_MEM || (a.Type == obj.TYPE_ADDR && a.Reg != 0) { + if v.Reg == a.Reg { + return true + } + } + } + return false +} + +// copyau1 returns 1 if p.Reg references the same register as v and v +// is a direct reference. +func copyau1(p *obj.Prog, v *obj.Addr) bool { + if isReg(v) && v.Reg != 0 { + if p.Reg == v.Reg { + return true + } + } + return false +} + +// copysub replaces v.Reg with s.Reg if a.Reg and v.Reg are direct +// references to the same register. +func copysub(a, v, s *obj.Addr) { + if copyau(a, v) { + a.Reg = s.Reg + } +} + +// copysub1 replaces p.Reg with s.Reg if p.Reg and v.Reg are direct +// references to the same register. +func copysub1(p *obj.Prog, v, s *obj.Addr) { + if copyau1(p, v) { + p.Reg = s.Reg + } +} + +func sameaddr(a *obj.Addr, v *obj.Addr) bool { + if a.Type != v.Type { + return false + } + if isReg(v) && a.Reg == v.Reg { + return true + } + if v.Type == obj.NAME_AUTO || v.Type == obj.NAME_PARAM { + // TODO(mundaym): is the offset enough here? Node? + if v.Offset == a.Offset { + return true + } + } + return false +} + +func smallindir(a *obj.Addr, reg *obj.Addr) bool { + return reg.Type == obj.TYPE_REG && + a.Type == obj.TYPE_MEM && + a.Reg == reg.Reg && + 0 <= a.Offset && a.Offset < 4096 +} + +func stackaddr(a *obj.Addr) bool { + // TODO(mundaym): the name implies this should check + // for TYPE_ADDR with a base register REGSP. + return a.Type == obj.TYPE_REG && a.Reg == s390x.REGSP +} + +// isMove returns true if p is a move. Moves may imply +// sign/zero extension. +func isMove(p *obj.Prog) bool { + switch p.As { + case s390x.AMOVD, + s390x.AMOVW, s390x.AMOVWZ, + s390x.AMOVH, s390x.AMOVHZ, + s390x.AMOVB, s390x.AMOVBZ, + s390x.AFMOVD, s390x.AFMOVS: + return true + } + return false +} + +// isLoad returns true if p is a move from memory to a register. +func isLoad(p *obj.Prog) bool { + if !isMove(p) { + return false + } + if !(isGPR(&p.To) || isFPR(&p.To)) { + return false + } + if p.From.Type != obj.TYPE_MEM { + return false + } + return true +} + +// isStore returns true if p is a move from a register to memory. +func isStore(p *obj.Prog) bool { + if !isMove(p) { + return false + } + if !(isGPR(&p.From) || isFPR(&p.From) || isConst(&p.From)) { + return false + } + if p.To.Type != obj.TYPE_MEM { + return false + } + return true +} + +// sameStackMem returns true if a and b are both memory operands +// and address the same location which must reside on the stack. +func sameStackMem(a, b *obj.Addr) bool { + if a.Type != obj.TYPE_MEM || + b.Type != obj.TYPE_MEM || + a.Name != b.Name || + a.Sym != b.Sym || + a.Node != b.Node || + a.Reg != b.Reg || + a.Index != b.Index || + a.Offset != b.Offset { + return false + } + switch a.Name { + case obj.NAME_NONE: + return a.Reg == s390x.REGSP + case obj.NAME_PARAM, obj.NAME_AUTO: + // params and autos are always on the stack + return true + } + return false +} + +// removeLoadHitStores trys to remove loads that take place +// immediately after a store to the same location. Returns +// true if load-hit-stores were removed. +// +// For example: +// MOVD R1, 0(R15) +// MOVD 0(R15), R2 +// Would become: +// MOVD R1, 0(R15) +// MOVD R1, R2 +func removeLoadHitStores(r *gc.Flow) int { + n := 0 + for ; r != nil; r = r.Link { + p := r.Prog + if !isStore(p) { + continue + } + for rr := gc.Uniqs(r); rr != nil; rr = gc.Uniqs(rr) { + pp := rr.Prog + if gc.Uniqp(rr) == nil { + break + } + if pp.As == obj.ANOP { + continue + } + if isLoad(pp) && sameStackMem(&p.To, &pp.From) { + if size(p.As) >= size(pp.As) && isGPR(&p.From) == isGPR(&pp.To) { + pp.From = p.From + } + } + if !isMove(pp) || isStore(pp) { + break + } + if copyau(&p.From, &pp.To) { + break + } + } + } + return n +} + +// size returns the width of the given move. +func size(as obj.As) int { + switch as { + case s390x.AMOVD, s390x.AFMOVD: + return 8 + case s390x.AMOVW, s390x.AMOVWZ, s390x.AFMOVS: + return 4 + case s390x.AMOVH, s390x.AMOVHZ: + return 2 + case s390x.AMOVB, s390x.AMOVBZ: + return 1 + } + return -1 +} + +// castPropagation tries to eliminate unecessary casts. +// +// For example: +// MOVHZ R1, R2 // uint16 +// MOVB R2, 0(R15) // int8 +// Can be simplified to: +// MOVB R1, 0(R15) +func castPropagation(r *gc.Flow) int { + n := 0 + for ; r != nil; r = r.Link { + p := r.Prog + if !isMove(p) || !isGPR(&p.To) { + continue + } + + // r is a move with a destination register + var move *gc.Flow + for rr := gc.Uniqs(r); rr != nil; rr = gc.Uniqs(rr) { + if gc.Uniqp(rr) == nil { + // branch target: leave alone + break + } + pp := rr.Prog + if isMove(pp) && copyas(&pp.From, &p.To) { + if pp.To.Type == obj.TYPE_MEM { + if p.From.Type == obj.TYPE_MEM || + p.From.Type == obj.TYPE_ADDR { + break + } + if p.From.Type == obj.TYPE_CONST && + int64(int16(p.From.Offset)) != p.From.Offset { + break + } + } + move = rr + break + } + if pp.As == obj.ANOP { + continue + } + break + } + if move == nil { + continue + } + + // we have a move that reads from our destination reg, check if any future + // instructions also read from the reg + mp := move.Prog + if !copyas(&mp.From, &mp.To) { + safe := false + for rr := gc.Uniqs(move); rr != nil; rr = gc.Uniqs(rr) { + if gc.Uniqp(rr) == nil { + break + } + switch copyu(rr.Prog, &p.To, nil) { + case _None: + continue + case _Write: + safe = true + } + break + } + if !safe { + continue + } + } + + // at this point we have something like: + // MOV* const/mem/reg, reg + // MOV* reg, reg/mem + // now check if this is a cast that cannot be forward propagated + execute := false + if p.As == mp.As || isZero(&p.From) || size(p.As) == size(mp.As) { + execute = true + } else if isGPR(&p.From) && size(p.As) >= size(mp.As) { + execute = true + } + + if execute { + mp.From = p.From + excise(r) + n++ + } + } + return n +} + +// fuseClear merges memory clear operations. +// +// Looks for this pattern (sequence of clears): +// MOVD R0, n(R15) +// MOVD R0, n+8(R15) +// MOVD R0, n+16(R15) +// Replaces with: +// CLEAR $24, n(R15) +func fuseClear(r *gc.Flow) int { + n := 0 + var align int64 + var clear *obj.Prog + for ; r != nil; r = r.Link { + // If there is a branch into the instruction stream then + // we can't fuse into previous instructions. + if gc.Uniqp(r) == nil { + clear = nil + } + + p := r.Prog + if p.As == obj.ANOP { + continue + } + if p.As == s390x.AXC { + if p.From.Reg == p.To.Reg && p.From.Offset == p.To.Offset { + // TODO(mundaym): merge clears? + p.As = s390x.ACLEAR + p.From.Offset = p.From3.Offset + p.From3 = nil + p.From.Type = obj.TYPE_CONST + p.From.Reg = 0 + clear = p + } else { + clear = nil + } + continue + } + + // Is our source a constant zero? + if !isZero(&p.From) { + clear = nil + continue + } + + // Are we moving to memory? + if p.To.Type != obj.TYPE_MEM || + p.To.Index != 0 || + p.To.Offset >= 4096 || + !(p.To.Name == obj.NAME_NONE || p.To.Name == obj.NAME_AUTO || p.To.Name == obj.NAME_PARAM) { + clear = nil + continue + } + + size := int64(0) + switch p.As { + default: + clear = nil + continue + case s390x.AMOVB, s390x.AMOVBZ: + size = 1 + case s390x.AMOVH, s390x.AMOVHZ: + size = 2 + case s390x.AMOVW, s390x.AMOVWZ: + size = 4 + case s390x.AMOVD: + size = 8 + } + + // doubleword aligned clears should be kept doubleword + // aligned + if (size == 8 && align != 8) || (size != 8 && align == 8) { + clear = nil + } + + if clear != nil && + clear.To.Reg == p.To.Reg && + clear.To.Name == p.To.Name && + clear.To.Node == p.To.Node && + clear.To.Sym == p.To.Sym { + + min := clear.To.Offset + max := clear.To.Offset + clear.From.Offset + + // previous clear is already clearing this region + if min <= p.To.Offset && max >= p.To.Offset+size { + excise(r) + n++ + continue + } + + // merge forwards + if max == p.To.Offset { + clear.From.Offset += size + excise(r) + n++ + continue + } + + // merge backwards + if min-size == p.To.Offset { + clear.From.Offset += size + clear.To.Offset -= size + excise(r) + n++ + continue + } + } + + // transform into clear + p.From.Type = obj.TYPE_CONST + p.From.Offset = size + p.From.Reg = 0 + p.As = s390x.ACLEAR + clear = p + align = size + } + return n +} + +// fuseMultiple merges memory loads and stores into load multiple and +// store multiple operations. +// +// Looks for this pattern (sequence of loads or stores): +// MOVD R1, 0(R15) +// MOVD R2, 8(R15) +// MOVD R3, 16(R15) +// Replaces with: +// STMG R1, R3, 0(R15) +func fuseMultiple(r *gc.Flow) int { + n := 0 + var fused *obj.Prog + for ; r != nil; r = r.Link { + // If there is a branch into the instruction stream then + // we can't fuse into previous instructions. + if gc.Uniqp(r) == nil { + fused = nil + } + + p := r.Prog + + isStore := isGPR(&p.From) && isBDMem(&p.To) + isLoad := isGPR(&p.To) && isBDMem(&p.From) + + // are we a candidate? + size := int64(0) + switch p.As { + default: + fused = nil + continue + case obj.ANOP: + // skip over nops + continue + case s390x.AMOVW, s390x.AMOVWZ: + size = 4 + // TODO(mundaym): 32-bit load multiple is currently not supported + // as it requires sign/zero extension. + if !isStore { + fused = nil + continue + } + case s390x.AMOVD: + size = 8 + if !isLoad && !isStore { + fused = nil + continue + } + } + + // If we merge two loads/stores with different source/destination Nodes + // then we will lose a reference the second Node which means that the + // compiler might mark the Node as unused and free its slot on the stack. + // TODO(mundaym): allow this by adding a dummy reference to the Node. + if fused == nil || + fused.From.Node != p.From.Node || + fused.From.Type != p.From.Type || + fused.To.Node != p.To.Node || + fused.To.Type != p.To.Type { + fused = p + continue + } + + // check two addresses + ca := func(a, b *obj.Addr, offset int64) bool { + return a.Reg == b.Reg && a.Offset+offset == b.Offset && + a.Sym == b.Sym && a.Name == b.Name + } + + switch fused.As { + default: + fused = p + case s390x.AMOVW, s390x.AMOVWZ: + if size == 4 && fused.From.Reg+1 == p.From.Reg && ca(&fused.To, &p.To, 4) { + fused.As = s390x.ASTMY + fused.Reg = p.From.Reg + excise(r) + n++ + } else { + fused = p + } + case s390x.AMOVD: + if size == 8 && fused.From.Reg+1 == p.From.Reg && ca(&fused.To, &p.To, 8) { + fused.As = s390x.ASTMG + fused.Reg = p.From.Reg + excise(r) + n++ + } else if size == 8 && fused.To.Reg+1 == p.To.Reg && ca(&fused.From, &p.From, 8) { + fused.As = s390x.ALMG + fused.Reg = fused.To.Reg + fused.To.Reg = p.To.Reg + excise(r) + n++ + } else { + fused = p + } + case s390x.ASTMG, s390x.ASTMY: + if (fused.As == s390x.ASTMY && size != 4) || + (fused.As == s390x.ASTMG && size != 8) { + fused = p + continue + } + offset := size * int64(fused.Reg-fused.From.Reg+1) + if fused.Reg+1 == p.From.Reg && ca(&fused.To, &p.To, offset) { + fused.Reg = p.From.Reg + excise(r) + n++ + } else { + fused = p + } + case s390x.ALMG: + offset := 8 * int64(fused.To.Reg-fused.Reg+1) + if size == 8 && fused.To.Reg+1 == p.To.Reg && ca(&fused.From, &p.From, offset) { + fused.To.Reg = p.To.Reg + excise(r) + n++ + } else { + fused = p + } + } + } + return n +} + +// simplifyOps looks for side-effect free ops that can be removed or +// replaced with moves. +// +// For example: +// XOR $0, R1 => NOP +// ADD $0, R1, R2 => MOVD R1, R2 +func simplifyOps(r *gc.Flow) int { + n := 0 + for ; r != nil; r = r.Link { + p := r.Prog + + // if the target is R0 then this is a required NOP + if isGPR(&p.To) && p.To.Reg == s390x.REGZERO { + continue + } + + switch p.As { + case s390x.AADD, s390x.ASUB, + s390x.AOR, s390x.AXOR, + s390x.ASLW, s390x.ASRW, s390x.ASRAW, + s390x.ASLD, s390x.ASRD, s390x.ASRAD, + s390x.ARLL, s390x.ARLLG: + if isZero(&p.From) && isGPR(&p.To) { + if p.Reg == 0 || p.Reg == p.To.Reg { + excise(r) + n++ + } else { + p.As = s390x.AMOVD + p.From.Type = obj.TYPE_REG + p.From.Reg = p.Reg + p.Reg = 0 + } + } + case s390x.AMULLW, s390x.AAND: + if isZero(&p.From) && isGPR(&p.To) { + p.As = s390x.AMOVD + p.From.Type = obj.TYPE_REG + p.From.Reg = s390x.REGZERO + p.Reg = 0 + } + } + } + return n +} + +// fuseOpMoves looks for moves following 2-operand operations and trys to merge them into +// a 3-operand operation. +// +// For example: +// ADD R1, R2 +// MOVD R2, R3 +// might become +// ADD R1, R2, R3 +func fuseOpMoves(r *gc.Flow) int { + n := 0 + for ; r != nil; r = r.Link { + p := r.Prog + switch p.As { + case s390x.AADD: + case s390x.ASUB: + if isConst(&p.From) && int64(int16(p.From.Offset)) != p.From.Offset { + continue + } + case s390x.ASLW, + s390x.ASRW, + s390x.ASRAW, + s390x.ASLD, + s390x.ASRD, + s390x.ASRAD, + s390x.ARLL, + s390x.ARLLG: + // ok - p.From will be a reg or a constant + case s390x.AOR, + s390x.AORN, + s390x.AAND, + s390x.AANDN, + s390x.ANAND, + s390x.ANOR, + s390x.AXOR, + s390x.AMULLW, + s390x.AMULLD: + if isConst(&p.From) { + // these instructions can either use 3 register form + // or have an immediate but not both + continue + } + default: + continue + } + + if p.Reg != 0 && p.Reg != p.To.Reg { + continue + } + + var move *gc.Flow + rr := gc.Uniqs(r) + for { + if rr == nil || gc.Uniqp(rr) == nil || rr == r { + break + } + pp := rr.Prog + switch copyu(pp, &p.To, nil) { + case _None: + rr = gc.Uniqs(rr) + continue + case _Read: + if move == nil && pp.As == s390x.AMOVD && isGPR(&pp.From) && isGPR(&pp.To) { + move = rr + rr = gc.Uniqs(rr) + continue + } + case _Write: + if move == nil { + // dead code + excise(r) + n++ + } else { + for prev := gc.Uniqp(move); prev != r; prev = gc.Uniqp(prev) { + if copyu(prev.Prog, &move.Prog.To, nil) != 0 { + move = nil + break + } + } + if move == nil { + break + } + p.Reg, p.To.Reg = p.To.Reg, move.Prog.To.Reg + excise(move) + n++ + + // clean up + if p.From.Reg == p.To.Reg && isCommutative(p.As) { + p.From.Reg, p.Reg = p.Reg, 0 + } + if p.To.Reg == p.Reg { + p.Reg = 0 + } + // we could try again if p has become a 2-operand op + // but in testing nothing extra was extracted + } + } + break + } + } + return n +} + +// isCommutative returns true if the order of input operands +// does not affect the result. For example: +// x + y == y + x so ADD is commutative +// x ^ y == y ^ x so XOR is commutative +func isCommutative(as obj.As) bool { + switch as { + case s390x.AADD, + s390x.AOR, + s390x.AAND, + s390x.AXOR, + s390x.AMULLW, + s390x.AMULLD: + return true + } + return false +} + +// applyCast applies the cast implied by the given move +// instruction to v and returns the result. +func applyCast(cast obj.As, v int64) int64 { + switch cast { + case s390x.AMOVWZ: + return int64(uint32(v)) + case s390x.AMOVHZ: + return int64(uint16(v)) + case s390x.AMOVBZ: + return int64(uint8(v)) + case s390x.AMOVW: + return int64(int32(v)) + case s390x.AMOVH: + return int64(int16(v)) + case s390x.AMOVB: + return int64(int8(v)) + } + return v +} + +// constantPropagation removes redundant constant copies. +func constantPropagation(r *gc.Flow) int { + n := 0 + // find MOV $con,R followed by + // another MOV $con,R without + // setting R in the interim + for ; r != nil; r = r.Link { + p := r.Prog + if isMove(p) { + if !isReg(&p.To) { + continue + } + if !isConst(&p.From) { + continue + } + } else { + continue + } + + rr := r + for { + rr = gc.Uniqs(rr) + if rr == nil || rr == r { + break + } + if gc.Uniqp(rr) == nil { + break + } + + pp := rr.Prog + t := copyu(pp, &p.To, nil) + switch t { + case _None: + continue + case _Read: + if !isGPR(&pp.From) || !isMove(pp) { + continue + } + if p.From.Type == obj.TYPE_CONST { + v := applyCast(p.As, p.From.Offset) + if isGPR(&pp.To) { + if int64(int32(v)) == v || ((v>>32)<<32) == v { + pp.From.Reg = 0 + pp.From.Offset = v + pp.From.Type = obj.TYPE_CONST + n++ + } + } else if int64(int16(v)) == v { + pp.From.Reg = 0 + pp.From.Offset = v + pp.From.Type = obj.TYPE_CONST + n++ + } + } + continue + case _Write: + if p.As != pp.As || p.From.Type != pp.From.Type { + break + } + if p.From.Type == obj.TYPE_CONST && p.From.Offset == pp.From.Offset { + excise(rr) + n++ + continue + } else if p.From.Type == obj.TYPE_FCONST { + if p.From.Val.(float64) == pp.From.Val.(float64) { + excise(rr) + n++ + continue + } + } + } + break + } + } + return n +} + +// copyPropagation tries to eliminate register-to-register moves. +func copyPropagation(r *gc.Flow) int { + n := 0 + for ; r != nil; r = r.Link { + p := r.Prog + if isMove(p) && isReg(&p.To) { + // Convert uses to $0 to uses of R0 and + // propagate R0 + if isGPR(&p.To) && isZero(&p.From) { + p.From.Type = obj.TYPE_REG + p.From.Reg = s390x.REGZERO + } + + // Try to eliminate reg->reg moves + if isGPR(&p.From) || isFPR(&p.From) { + if copyprop(r) || (subprop(r) && copyprop(r)) { + excise(r) + n++ + } + } + } + } + return n +} + +// loadPipelining pushes any load from memory as early as possible. +func loadPipelining(r *gc.Flow) int { + for ; r != nil; r = r.Link { + p := r.Prog + if isLoad(p) { + pushback(r) + } + } + return 0 +} + +// fuseCompareBranch finds comparisons followed by a branch and converts +// them into a compare-and-branch instruction (which avoid setting the +// condition code). +func fuseCompareBranch(r *gc.Flow) int { + n := 0 + for ; r != nil; r = r.Link { + p := r.Prog + r1 := gc.Uniqs(r) + if r1 == nil { + continue + } + p1 := r1.Prog + + var ins obj.As + switch p.As { + case s390x.ACMP: + switch p1.As { + case s390x.ABCL, s390x.ABC: + continue + case s390x.ABEQ: + ins = s390x.ACMPBEQ + case s390x.ABGE: + ins = s390x.ACMPBGE + case s390x.ABGT: + ins = s390x.ACMPBGT + case s390x.ABLE: + ins = s390x.ACMPBLE + case s390x.ABLT: + ins = s390x.ACMPBLT + case s390x.ABNE: + ins = s390x.ACMPBNE + default: + continue + } + + case s390x.ACMPU: + switch p1.As { + case s390x.ABCL, s390x.ABC: + continue + case s390x.ABEQ: + ins = s390x.ACMPUBEQ + case s390x.ABGE: + ins = s390x.ACMPUBGE + case s390x.ABGT: + ins = s390x.ACMPUBGT + case s390x.ABLE: + ins = s390x.ACMPUBLE + case s390x.ABLT: + ins = s390x.ACMPUBLT + case s390x.ABNE: + ins = s390x.ACMPUBNE + default: + continue + } + + case s390x.ACMPW, s390x.ACMPWU: + continue + + default: + continue + } + + if gc.Debug['P'] != 0 && gc.Debug['v'] != 0 { + fmt.Printf("cnb %v; %v ", p, p1) + } + + if p1.To.Sym != nil { + continue + } + + if p.To.Type == obj.TYPE_REG { + p1.As = ins + p1.From = p.From + p1.Reg = p.To.Reg + p1.From3 = nil + } else if p.To.Type == obj.TYPE_CONST { + switch p.As { + case s390x.ACMP, s390x.ACMPW: + if (p.To.Offset < -(1 << 7)) || (p.To.Offset >= ((1 << 7) - 1)) { + continue + } + case s390x.ACMPU, s390x.ACMPWU: + if p.To.Offset >= (1 << 8) { + continue + } + default: + } + p1.As = ins + p1.From = p.From + p1.Reg = 0 + p1.From3 = new(obj.Addr) + *(p1.From3) = p.To + } else { + continue + } + + if gc.Debug['P'] != 0 && gc.Debug['v'] != 0 { + fmt.Printf("%v\n", p1) + } + excise(r) + n++ + } + return n +} + +// deadCodeElimination removes writes to registers which are written +// to again before they are next read. +func deadCodeElimination(r *gc.Flow) int { + n := 0 + for ; r != nil; r = r.Link { + p := r.Prog + // Currently there are no instructions which write to multiple + // registers in copyu. This check will need to change if there + // ever are. + if !(isGPR(&p.To) || isFPR(&p.To)) || copyu(p, &p.To, nil) != _Write { + continue + } + for rr := gc.Uniqs(r); rr != nil; rr = gc.Uniqs(rr) { + t := copyu(rr.Prog, &p.To, nil) + if t == _None { + continue + } + if t == _Write { + excise(r) + n++ + } + break + } + } + return n +} diff --git a/src/cmd/compile/internal/s390x/prog.go b/src/cmd/compile/internal/s390x/prog.go new file mode 100644 index 0000000000..306adf85c3 --- /dev/null +++ b/src/cmd/compile/internal/s390x/prog.go @@ -0,0 +1,179 @@ +// 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 s390x + +import ( + "cmd/compile/internal/gc" + "cmd/internal/obj" + "cmd/internal/obj/s390x" +) + +// This table gives the basic information about instruction +// generated by the compiler and processed in the optimizer. +// See opt.h for bit definitions. +// +// Instructions not generated need not be listed. +// As an exception to that rule, we typically write down all the +// size variants of an operation even if we just use a subset. +var progtable = [s390x.ALAST & obj.AMask]obj.ProgInfo{ + obj.ATYPE & obj.AMask: {Flags: gc.Pseudo | gc.Skip}, + obj.ATEXT & obj.AMask: {Flags: gc.Pseudo}, + obj.AFUNCDATA & obj.AMask: {Flags: gc.Pseudo}, + obj.APCDATA & obj.AMask: {Flags: gc.Pseudo}, + obj.AUNDEF & obj.AMask: {Flags: gc.Break}, + obj.AUSEFIELD & obj.AMask: {Flags: gc.OK}, + obj.ACHECKNIL & obj.AMask: {Flags: gc.LeftRead}, + obj.AVARDEF & obj.AMask: {Flags: gc.Pseudo | gc.RightWrite}, + obj.AVARKILL & obj.AMask: {Flags: gc.Pseudo | gc.RightWrite}, + obj.AVARLIVE & obj.AMask: {Flags: gc.Pseudo | gc.LeftRead}, + + // NOP is an internal no-op that also stands + // for USED and SET annotations. + obj.ANOP & obj.AMask: {Flags: gc.LeftRead | gc.RightWrite}, + + // Integer + s390x.AADD & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ASUB & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ANEG & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AAND & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AOR & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AXOR & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AMULLD & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AMULLW & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AMULHD & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AMULHDU & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ADIVD & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ADIVDU & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ASLD & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ASRD & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ASRAD & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ARLL & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ARLLG & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.ACMP & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightRead}, + s390x.ACMPU & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightRead}, + + // Floating point. + s390x.AFADD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AFADDS & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AFSUB & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AFSUBS & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AFMUL & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AFMULS & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AFDIV & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AFDIVS & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead | gc.RightWrite}, + s390x.AFCMPU & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightRead}, + s390x.ACEBR & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightRead}, + s390x.ALEDBR & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ALDEBR & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.AFSQRT & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite}, + + // Conversions + s390x.ACEFBRA & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACDFBRA & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACEGBRA & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACDGBRA & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACFEBRA & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACFDBRA & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACGEBRA & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACGDBRA & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACELFBR & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACDLFBR & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACELGBR & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACDLGBR & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACLFEBR & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACLFDBR & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACLGEBR & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightWrite | gc.Conv}, + s390x.ACLGDBR & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightWrite | gc.Conv}, + + // Moves + s390x.AMOVB & obj.AMask: {Flags: gc.SizeB | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv}, + s390x.AMOVBZ & obj.AMask: {Flags: gc.SizeB | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv}, + s390x.AMOVH & obj.AMask: {Flags: gc.SizeW | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv}, + s390x.AMOVHZ & obj.AMask: {Flags: gc.SizeW | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv}, + s390x.AMOVW & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv}, + s390x.AMOVWZ & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv}, + s390x.AMOVD & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightWrite | gc.Move}, + s390x.AFMOVS & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv}, + s390x.AFMOVD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Move}, + + // Storage operations + s390x.AMVC & obj.AMask: {Flags: gc.LeftRead | gc.LeftAddr | gc.RightWrite | gc.RightAddr}, + s390x.ACLC & obj.AMask: {Flags: gc.LeftRead | gc.LeftAddr | gc.RightRead | gc.RightAddr}, + s390x.AXC & obj.AMask: {Flags: gc.LeftRead | gc.LeftAddr | gc.RightWrite | gc.RightAddr}, + s390x.AOC & obj.AMask: {Flags: gc.LeftRead | gc.LeftAddr | gc.RightWrite | gc.RightAddr}, + s390x.ANC & obj.AMask: {Flags: gc.LeftRead | gc.LeftAddr | gc.RightWrite | gc.RightAddr}, + + // Jumps + s390x.ABR & obj.AMask: {Flags: gc.Jump | gc.Break}, + s390x.ABL & obj.AMask: {Flags: gc.Call}, + s390x.ABEQ & obj.AMask: {Flags: gc.Cjmp}, + s390x.ABNE & obj.AMask: {Flags: gc.Cjmp}, + s390x.ABGE & obj.AMask: {Flags: gc.Cjmp}, + s390x.ABLT & obj.AMask: {Flags: gc.Cjmp}, + s390x.ABGT & obj.AMask: {Flags: gc.Cjmp}, + s390x.ABLE & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPBEQ & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPBNE & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPBGE & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPBLT & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPBGT & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPBLE & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPUBEQ & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPUBNE & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPUBGE & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPUBLT & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPUBGT & obj.AMask: {Flags: gc.Cjmp}, + s390x.ACMPUBLE & obj.AMask: {Flags: gc.Cjmp}, + + // Macros + s390x.ACLEAR & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightAddr | gc.RightWrite}, + + // Load/store multiple + s390x.ASTMG & obj.AMask: {Flags: gc.SizeQ | gc.LeftRead | gc.RightAddr | gc.RightWrite}, + s390x.ASTMY & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightAddr | gc.RightWrite}, + s390x.ALMG & obj.AMask: {Flags: gc.SizeQ | gc.LeftAddr | gc.LeftRead | gc.RightWrite}, + s390x.ALMY & obj.AMask: {Flags: gc.SizeL | gc.LeftAddr | gc.LeftRead | gc.RightWrite}, + + obj.ARET & obj.AMask: {Flags: gc.Break}, +} + +func proginfo(p *obj.Prog) { + info := &p.Info + *info = progtable[p.As&obj.AMask] + if info.Flags == 0 { + gc.Fatalf("proginfo: unknown instruction %v", p) + } + + if (info.Flags&gc.RegRead != 0) && p.Reg == 0 { + info.Flags &^= gc.RegRead + info.Flags |= gc.RightRead /*CanRegRead |*/ + } + + if (p.From.Type == obj.TYPE_MEM || p.From.Type == obj.TYPE_ADDR) && p.From.Reg != 0 { + info.Regindex |= RtoB(int(p.From.Reg)) + } + + if (p.To.Type == obj.TYPE_MEM || p.To.Type == obj.TYPE_ADDR) && p.To.Reg != 0 { + info.Regindex |= RtoB(int(p.To.Reg)) + } + + if p.From.Type == obj.TYPE_ADDR && p.From.Sym != nil && (info.Flags&gc.LeftRead != 0) { + info.Flags &^= gc.LeftRead + info.Flags |= gc.LeftAddr + } + + switch p.As { + // load multiple sets a range of registers + case s390x.ALMG, s390x.ALMY: + for r := p.Reg; r <= p.To.Reg; r++ { + info.Regset |= RtoB(int(r)) + } + // store multiple reads a range of registers + case s390x.ASTMG, s390x.ASTMY: + for r := p.From.Reg; r <= p.Reg; r++ { + info.Reguse |= RtoB(int(r)) + } + } +} diff --git a/src/cmd/compile/internal/s390x/reg.go b/src/cmd/compile/internal/s390x/reg.go new file mode 100644 index 0000000000..4cb8a9da05 --- /dev/null +++ b/src/cmd/compile/internal/s390x/reg.go @@ -0,0 +1,130 @@ +// Derived from Inferno utils/6c/reg.c +// http://code.google.com/p/inferno-os/source/browse/utils/6c/reg.c +// +// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. +// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net) +// Portions Copyright © 1997-1999 Vita Nuova Limited +// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com) +// Portions Copyright © 2004,2006 Bruce Ellis +// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net) +// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others +// Portions Copyright © 2009 The Go Authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package s390x + +import "cmd/internal/obj/s390x" +import "cmd/compile/internal/gc" + +const ( + NREGVAR = 32 /* 16 general + 16 floating */ +) + +var regname = []string{ + ".R0", + ".R1", + ".R2", + ".R3", + ".R4", + ".R5", + ".R6", + ".R7", + ".R8", + ".R9", + ".R10", + ".R11", + ".R12", + ".R13", + ".R14", + ".R15", + ".F0", + ".F1", + ".F2", + ".F3", + ".F4", + ".F5", + ".F6", + ".F7", + ".F8", + ".F9", + ".F10", + ".F11", + ".F12", + ".F13", + ".F14", + ".F15", +} + +func regnames(n *int) []string { + *n = NREGVAR + return regname +} + +func excludedregs() uint64 { + // Exclude registers with fixed functions + return RtoB(s390x.REG_R0) | + RtoB(s390x.REGSP) | + RtoB(s390x.REGG) | + RtoB(s390x.REGTMP) | + RtoB(s390x.REGTMP2) | + RtoB(s390x.REG_LR) +} + +func doregbits(r int) uint64 { + return 0 +} + +/* + * track register variables including external registers: + * bit reg + * 0 R0 + * ... ... + * 15 R15 + * 16+0 F0 + * 16+1 F1 + * ... ... + * 16+15 F15 + */ +func RtoB(r int) uint64 { + if r >= s390x.REG_R0 && r <= s390x.REG_R15 { + return 1 << uint(r-s390x.REG_R0) + } + if r >= s390x.REG_F0 && r <= s390x.REG_F15 { + return 1 << uint(16+r-s390x.REG_F0) + } + return 0 +} + +func BtoR(b uint64) int { + b &= 0xffff + if b == 0 { + return 0 + } + return gc.Bitno(b) + s390x.REG_R0 +} + +func BtoF(b uint64) int { + b >>= 16 + b &= 0xffff + if b == 0 { + return 0 + } + return gc.Bitno(b) + s390x.REG_F0 +} diff --git a/src/cmd/compile/main.go b/src/cmd/compile/main.go index 49c2fb6263..8b8161efd1 100644 --- a/src/cmd/compile/main.go +++ b/src/cmd/compile/main.go @@ -10,6 +10,7 @@ import ( "cmd/compile/internal/arm64" "cmd/compile/internal/mips64" "cmd/compile/internal/ppc64" + "cmd/compile/internal/s390x" "cmd/compile/internal/x86" "cmd/internal/obj" "fmt" @@ -38,5 +39,7 @@ func main() { mips64.Main() case "ppc64", "ppc64le": ppc64.Main() + case "s390x": + s390x.Main() } } diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 777c92c726..a535316ca0 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -38,6 +38,7 @@ var bootstrapDirs = []string{ "compile/internal/ppc64", "compile/internal/ssa", "compile/internal/x86", + "compile/internal/s390x", "internal/bio", "internal/gcprog", "internal/obj", -- cgit v1.3 From 9743e4b0311c37ebacc2c9063a1cd778510eae09 Mon Sep 17 00:00:00 2001 From: Alexandru Moșoi Date: Mon, 11 Apr 2016 21:51:29 +0200 Subject: cmd/compile: share dominator tree among many passes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These passes do not modify the dominator tree too much. % benchstat old.txt new.txt name old time/op new time/op delta Template 335ms ± 3% 325ms ± 8% ~ (p=0.074 n=8+9) GoTypes 1.05s ± 1% 1.05s ± 3% ~ (p=0.095 n=9+10) Compiler 5.37s ± 4% 5.29s ± 1% -1.42% (p=0.022 n=9+10) MakeBash 34.9s ± 3% 34.4s ± 2% ~ (p=0.095 n=9+10) name old alloc/op new alloc/op delta Template 55.4MB ± 0% 54.9MB ± 0% -0.81% (p=0.000 n=10+10) GoTypes 179MB ± 0% 178MB ± 0% -0.89% (p=0.000 n=10+10) Compiler 807MB ± 0% 798MB ± 0% -1.10% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Template 498k ± 0% 496k ± 0% -0.29% (p=0.000 n=9+9) GoTypes 1.42M ± 0% 1.41M ± 0% -0.24% (p=0.000 n=10+10) Compiler 5.61M ± 0% 5.60M ± 0% -0.12% (p=0.000 n=10+10) Change-Id: I4cd20cfba3f132ebf371e16046ab14d7e42799ec Reviewed-on: https://go-review.googlesource.com/21806 Run-TryBot: Alexandru Moșoi TryBot-Result: Gobot Gobot Reviewed-by: David Chase --- src/cmd/compile/internal/ssa/compile.go | 5 +++++ src/cmd/compile/internal/ssa/dom.go | 6 ++++++ src/cmd/compile/internal/ssa/func.go | 3 +++ src/cmd/compile/internal/ssa/loopbce.go | 18 ++++++++---------- src/cmd/compile/internal/ssa/nilcheck.go | 2 +- src/cmd/compile/internal/ssa/nilcheck_test.go | 10 ++++++++++ src/cmd/compile/internal/ssa/prove.go | 9 +++------ 7 files changed, 36 insertions(+), 17 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/ssa/compile.go b/src/cmd/compile/internal/ssa/compile.go index b4215f119e..f4f0d8cab2 100644 --- a/src/cmd/compile/internal/ssa/compile.go +++ b/src/cmd/compile/internal/ssa/compile.go @@ -234,6 +234,7 @@ var passes = [...]pass{ {name: "zero arg cse", fn: zcse, required: true}, // required to merge OpSB values {name: "opt deadcode", fn: deadcode, required: true}, // remove any blocks orphaned during opt {name: "generic cse", fn: cse}, + {name: "generic domtree", fn: domTree}, {name: "phiopt", fn: phiopt}, {name: "nilcheckelim", fn: nilcheckelim}, {name: "prove", fn: prove}, @@ -288,6 +289,10 @@ var passOrder = [...]constraint{ {"opt", "nilcheckelim"}, // tighten should happen before lowering to avoid splitting naturally paired instructions such as CMP/SET {"tighten", "lower"}, + // nilcheckelim, prove and loopbce share idom. + {"generic domtree", "nilcheckelim"}, + {"generic domtree", "prove"}, + {"generic domtree", "loopbce"}, // tighten will be most effective when as many values have been removed as possible {"generic deadcode", "tighten"}, {"generic cse", "tighten"}, diff --git a/src/cmd/compile/internal/ssa/dom.go b/src/cmd/compile/internal/ssa/dom.go index 0fffcdc2af..fedaf602e4 100644 --- a/src/cmd/compile/internal/ssa/dom.go +++ b/src/cmd/compile/internal/ssa/dom.go @@ -364,3 +364,9 @@ func intersect(b, c *Block, postnum []int, idom []*Block) *Block { } return b } + +// build immediate dominators. +func domTree(f *Func) { + f.idom = dominators(f) + f.sdom = newSparseTree(f, f.idom) +} diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 8dd75f6093..da44f26106 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -36,6 +36,9 @@ type Func struct { freeValues *Value // free Values linked by argstorage[0]. All other fields except ID are 0/nil. freeBlocks *Block // free Blocks linked by succstorage[0]. All other fields except ID are 0/nil. + idom []*Block // precomputed immediate dominators + sdom sparseTree // precomputed dominator tree + constants map[int64][]*Value // constants cache, keyed by constant value; users must check value's Op and Type } diff --git a/src/cmd/compile/internal/ssa/loopbce.go b/src/cmd/compile/internal/ssa/loopbce.go index c937ead1b2..9bd2d3f0de 100644 --- a/src/cmd/compile/internal/ssa/loopbce.go +++ b/src/cmd/compile/internal/ssa/loopbce.go @@ -31,7 +31,7 @@ type indVar struct { // // // TODO: handle 32 bit operations -func findIndVar(f *Func, sdom sparseTree) []indVar { +func findIndVar(f *Func) []indVar { var iv []indVar nextb: @@ -110,7 +110,7 @@ nextb: // Second condition: b.Succs[entry] dominates nxt so that // nxt is computed when inc < max, meaning nxt <= max. - if !sdom.isAncestorEq(b.Succs[entry], nxt.Block) { + if !f.sdom.isAncestorEq(b.Succs[entry], nxt.Block) { // inc+ind can only be reached through the branch that enters the loop. continue } @@ -160,20 +160,18 @@ nextb: // loopbce performs loop based bounds check elimination. func loopbce(f *Func) { - idom := dominators(f) - sdom := newSparseTree(f, idom) - ivList := findIndVar(f, sdom) + ivList := findIndVar(f) m := make(map[*Value]indVar) for _, iv := range ivList { m[iv.ind] = iv } - removeBoundsChecks(f, sdom, m) + removeBoundsChecks(f, m) } // removesBoundsChecks remove IsInBounds and IsSliceInBounds based on the induction variables. -func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) { +func removeBoundsChecks(f *Func, m map[*Value]indVar) { for _, b := range f.Blocks { if b.Kind != BlockIf { continue @@ -202,7 +200,7 @@ func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) { goto skip1 } - if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) { + if iv, has := m[ind]; has && f.sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) { if v.Args[1] == iv.max { if f.pass.debug > 0 { f.Config.Warnl(b.Line, "Found redundant %s", v.Op) @@ -229,7 +227,7 @@ func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) { goto skip2 } - if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) { + if iv, has := m[ind]; has && f.sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) { if v.Args[1].Op == OpSliceCap && iv.max.Op == OpSliceLen && v.Args[1].Args[0] == iv.max.Args[0] { if f.pass.debug > 0 { f.Config.Warnl(b.Line, "Found redundant %s (len promoted to cap)", v.Op) @@ -250,7 +248,7 @@ func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) { } // ind + add >= 0 <-> min + add >= 0 <-> min >= -add - if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isGreaterOrEqualThan(iv.min, -add) { + if iv, has := m[ind]; has && f.sdom.isAncestorEq(iv.entry, b) && isGreaterOrEqualThan(iv.min, -add) { if !v.Args[1].isGenericIntConst() || !iv.max.isGenericIntConst() { goto skip3 } diff --git a/src/cmd/compile/internal/ssa/nilcheck.go b/src/cmd/compile/internal/ssa/nilcheck.go index 881e3b2eff..753e48aad5 100644 --- a/src/cmd/compile/internal/ssa/nilcheck.go +++ b/src/cmd/compile/internal/ssa/nilcheck.go @@ -11,7 +11,7 @@ func nilcheckelim(f *Func) { // A nil check is redundant if the same nil check was successful in a // dominating block. The efficacy of this pass depends heavily on the // efficacy of the cse pass. - idom := dominators(f) + idom := f.idom domTree := make([][]*Block, f.NumBlocks()) // Create a block ID -> [dominees] mapping diff --git a/src/cmd/compile/internal/ssa/nilcheck_test.go b/src/cmd/compile/internal/ssa/nilcheck_test.go index d1f38b6951..c1c8f94767 100644 --- a/src/cmd/compile/internal/ssa/nilcheck_test.go +++ b/src/cmd/compile/internal/ssa/nilcheck_test.go @@ -49,6 +49,7 @@ func benchmarkNilCheckDeep(b *testing.B, depth int) { b.ReportAllocs() for i := 0; i < b.N; i++ { + domTree(fun.f) nilcheckelim(fun.f) } } @@ -83,6 +84,7 @@ func TestNilcheckSimple(t *testing.T) { Exit("mem"))) CheckFunc(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check @@ -120,6 +122,7 @@ func TestNilcheckDomOrder(t *testing.T) { Goto("exit"))) CheckFunc(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check @@ -153,6 +156,7 @@ func TestNilcheckAddr(t *testing.T) { Exit("mem"))) CheckFunc(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check @@ -187,6 +191,7 @@ func TestNilcheckAddPtr(t *testing.T) { Exit("mem"))) CheckFunc(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check @@ -231,6 +236,7 @@ func TestNilcheckPhi(t *testing.T) { Exit("mem"))) CheckFunc(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check @@ -272,6 +278,7 @@ func TestNilcheckKeepRemove(t *testing.T) { Exit("mem"))) CheckFunc(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check @@ -319,6 +326,7 @@ func TestNilcheckInFalseBranch(t *testing.T) { Exit("mem"))) CheckFunc(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check @@ -370,6 +378,7 @@ func TestNilcheckUser(t *testing.T) { CheckFunc(fun.f) // we need the opt here to rewrite the user nilcheck opt(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check @@ -414,6 +423,7 @@ func TestNilcheckBug(t *testing.T) { CheckFunc(fun.f) // we need the opt here to rewrite the user nilcheck opt(fun.f) + domTree(fun.f) nilcheckelim(fun.f) // clean up the removed nil check diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index a12a996263..f4a10b508a 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -445,9 +445,6 @@ var ( // else branch of the first comparison is executed, we already know that i < len(a). // The code for the second panic can be removed. func prove(f *Func) { - idom := dominators(f) - sdom := newSparseTree(f, idom) - // current node state type walkState int const ( @@ -471,8 +468,8 @@ func prove(f *Func) { for len(work) > 0 { node := work[len(work)-1] work = work[:len(work)-1] - parent := idom[node.block.ID] - branch := getBranch(sdom, parent, node.block) + parent := f.idom[node.block.ID] + branch := getBranch(f.sdom, parent, node.block) switch node.state { case descend: @@ -491,7 +488,7 @@ func prove(f *Func) { block: node.block, state: simplify, }) - for s := sdom.Child(node.block); s != nil; s = sdom.Sibling(s) { + for s := f.sdom.Child(node.block); s != nil; s = f.sdom.Sibling(s) { work = append(work, bp{ block: s, state: descend, -- cgit v1.3 From 204b6f48c5107d3132033324fd492ca0253568dc Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Tue, 12 Apr 2016 09:41:11 +0200 Subject: cmd/pprof/internal: move to cmd/internal/pprof Make internal pprof packages available to cmd/trace. cmd/trace needs access to them to generate symbolized svg profiles (create and serialize Profile struct). And potentially generate svg programmatically instead of invoking go tool pprof. Change-Id: Iafd0c87ffdd4ddc081093be0b39761f19507907a Reviewed-on: https://go-review.googlesource.com/21870 Run-TryBot: Dmitry Vyukov TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/cmd/internal/pprof/commands/commands.go | 243 ++++ src/cmd/internal/pprof/driver/driver.go | 1041 +++++++++++++ src/cmd/internal/pprof/driver/interactive.go | 492 +++++++ src/cmd/internal/pprof/fetch/fetch.go | 82 ++ src/cmd/internal/pprof/plugin/plugin.go | 213 +++ src/cmd/internal/pprof/profile/encode.go | 470 ++++++ src/cmd/internal/pprof/profile/filter.go | 158 ++ src/cmd/internal/pprof/profile/legacy_profile.go | 1251 ++++++++++++++++ src/cmd/internal/pprof/profile/profile.go | 572 ++++++++ src/cmd/internal/pprof/profile/profile_test.go | 24 + src/cmd/internal/pprof/profile/proto.go | 360 +++++ src/cmd/internal/pprof/profile/proto_test.go | 67 + src/cmd/internal/pprof/profile/prune.go | 97 ++ src/cmd/internal/pprof/report/report.go | 1682 ++++++++++++++++++++++ src/cmd/internal/pprof/report/source.go | 454 ++++++ src/cmd/internal/pprof/report/source_html.go | 77 + src/cmd/internal/pprof/svg/svg.go | 71 + src/cmd/internal/pprof/svg/svgpan.go | 291 ++++ src/cmd/internal/pprof/symbolizer/symbolizer.go | 195 +++ src/cmd/internal/pprof/symbolz/symbolz.go | 111 ++ src/cmd/internal/pprof/tempfile/tempfile.go | 45 + src/cmd/pprof/internal/commands/commands.go | 243 ---- src/cmd/pprof/internal/driver/driver.go | 1041 ------------- src/cmd/pprof/internal/driver/interactive.go | 492 ------- src/cmd/pprof/internal/fetch/fetch.go | 82 -- src/cmd/pprof/internal/plugin/plugin.go | 213 --- src/cmd/pprof/internal/profile/encode.go | 470 ------ src/cmd/pprof/internal/profile/filter.go | 158 -- src/cmd/pprof/internal/profile/legacy_profile.go | 1251 ---------------- src/cmd/pprof/internal/profile/profile.go | 572 -------- src/cmd/pprof/internal/profile/profile_test.go | 24 - src/cmd/pprof/internal/profile/proto.go | 360 ----- src/cmd/pprof/internal/profile/proto_test.go | 67 - src/cmd/pprof/internal/profile/prune.go | 97 -- src/cmd/pprof/internal/report/report.go | 1682 ---------------------- src/cmd/pprof/internal/report/source.go | 454 ------ src/cmd/pprof/internal/report/source_html.go | 77 - src/cmd/pprof/internal/svg/svg.go | 71 - src/cmd/pprof/internal/svg/svgpan.go | 291 ---- src/cmd/pprof/internal/symbolizer/symbolizer.go | 195 --- src/cmd/pprof/internal/symbolz/symbolz.go | 111 -- src/cmd/pprof/internal/tempfile/tempfile.go | 45 - src/cmd/pprof/pprof.go | 14 +- 43 files changed, 8003 insertions(+), 8003 deletions(-) create mode 100644 src/cmd/internal/pprof/commands/commands.go create mode 100644 src/cmd/internal/pprof/driver/driver.go create mode 100644 src/cmd/internal/pprof/driver/interactive.go create mode 100644 src/cmd/internal/pprof/fetch/fetch.go create mode 100644 src/cmd/internal/pprof/plugin/plugin.go create mode 100644 src/cmd/internal/pprof/profile/encode.go create mode 100644 src/cmd/internal/pprof/profile/filter.go create mode 100644 src/cmd/internal/pprof/profile/legacy_profile.go create mode 100644 src/cmd/internal/pprof/profile/profile.go create mode 100644 src/cmd/internal/pprof/profile/profile_test.go create mode 100644 src/cmd/internal/pprof/profile/proto.go create mode 100644 src/cmd/internal/pprof/profile/proto_test.go create mode 100644 src/cmd/internal/pprof/profile/prune.go create mode 100644 src/cmd/internal/pprof/report/report.go create mode 100644 src/cmd/internal/pprof/report/source.go create mode 100644 src/cmd/internal/pprof/report/source_html.go create mode 100644 src/cmd/internal/pprof/svg/svg.go create mode 100644 src/cmd/internal/pprof/svg/svgpan.go create mode 100644 src/cmd/internal/pprof/symbolizer/symbolizer.go create mode 100644 src/cmd/internal/pprof/symbolz/symbolz.go create mode 100644 src/cmd/internal/pprof/tempfile/tempfile.go delete mode 100644 src/cmd/pprof/internal/commands/commands.go delete mode 100644 src/cmd/pprof/internal/driver/driver.go delete mode 100644 src/cmd/pprof/internal/driver/interactive.go delete mode 100644 src/cmd/pprof/internal/fetch/fetch.go delete mode 100644 src/cmd/pprof/internal/plugin/plugin.go delete mode 100644 src/cmd/pprof/internal/profile/encode.go delete mode 100644 src/cmd/pprof/internal/profile/filter.go delete mode 100644 src/cmd/pprof/internal/profile/legacy_profile.go delete mode 100644 src/cmd/pprof/internal/profile/profile.go delete mode 100644 src/cmd/pprof/internal/profile/profile_test.go delete mode 100644 src/cmd/pprof/internal/profile/proto.go delete mode 100644 src/cmd/pprof/internal/profile/proto_test.go delete mode 100644 src/cmd/pprof/internal/profile/prune.go delete mode 100644 src/cmd/pprof/internal/report/report.go delete mode 100644 src/cmd/pprof/internal/report/source.go delete mode 100644 src/cmd/pprof/internal/report/source_html.go delete mode 100644 src/cmd/pprof/internal/svg/svg.go delete mode 100644 src/cmd/pprof/internal/svg/svgpan.go delete mode 100644 src/cmd/pprof/internal/symbolizer/symbolizer.go delete mode 100644 src/cmd/pprof/internal/symbolz/symbolz.go delete mode 100644 src/cmd/pprof/internal/tempfile/tempfile.go (limited to 'src/cmd') diff --git a/src/cmd/internal/pprof/commands/commands.go b/src/cmd/internal/pprof/commands/commands.go new file mode 100644 index 0000000000..5018c02af1 --- /dev/null +++ b/src/cmd/internal/pprof/commands/commands.go @@ -0,0 +1,243 @@ +// Copyright 2014 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 commands defines and manages the basic pprof commands +package commands + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "runtime" + "strings" + "time" + + "cmd/internal/pprof/plugin" + "cmd/internal/pprof/report" + "cmd/internal/pprof/svg" + "cmd/internal/pprof/tempfile" +) + +// Commands describes the commands accepted by pprof. +type Commands map[string]*Command + +// Command describes the actions for a pprof command. Includes a +// function for command-line completion, the report format to use +// during report generation, any postprocessing functions, and whether +// the command expects a regexp parameter (typically a function name). +type Command struct { + Complete Completer // autocomplete for interactive mode + Format int // report format to generate + PostProcess PostProcessor // postprocessing to run on report + HasParam bool // Collect a parameter from the CLI + Usage string // Help text +} + +// Completer is a function for command-line autocompletion +type Completer func(prefix string) string + +// PostProcessor is a function that applies post-processing to the report output +type PostProcessor func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error + +// PProf returns the basic pprof report-generation commands +func PProf(c Completer, interactive **bool) Commands { + return Commands{ + // Commands that require no post-processing. + "tags": {nil, report.Tags, nil, false, "Outputs all tags in the profile"}, + "raw": {c, report.Raw, nil, false, "Outputs a text representation of the raw profile"}, + "dot": {c, report.Dot, nil, false, "Outputs a graph in DOT format"}, + "top": {c, report.Text, nil, false, "Outputs top entries in text form"}, + "tree": {c, report.Tree, nil, false, "Outputs a text rendering of call graph"}, + "text": {c, report.Text, nil, false, "Outputs top entries in text form"}, + "disasm": {c, report.Dis, nil, true, "Output annotated assembly for functions matching regexp or address"}, + "list": {c, report.List, nil, true, "Output annotated source for functions matching regexp"}, + "peek": {c, report.Tree, nil, true, "Output callers/callees of functions matching regexp"}, + + // Save binary formats to a file + "callgrind": {c, report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format"}, + "proto": {c, report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format"}, + + // Generate report in DOT format and postprocess with dot + "gif": {c, report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format"}, + "pdf": {c, report.Dot, invokeDot("pdf"), false, "Outputs a graph in PDF format"}, + "png": {c, report.Dot, invokeDot("png"), false, "Outputs a graph image in PNG format"}, + "ps": {c, report.Dot, invokeDot("ps"), false, "Outputs a graph in PS format"}, + + // Save SVG output into a file after including svgpan library + "svg": {c, report.Dot, saveSVGToFile(), false, "Outputs a graph in SVG format"}, + + // Visualize postprocessed dot output + "eog": {c, report.Dot, invokeVisualizer(interactive, invokeDot("svg"), "svg", []string{"eog"}), false, "Visualize graph through eog"}, + "evince": {c, report.Dot, invokeVisualizer(interactive, invokeDot("pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince"}, + "gv": {c, report.Dot, invokeVisualizer(interactive, invokeDot("ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv"}, + "web": {c, report.Dot, invokeVisualizer(interactive, saveSVGToFile(), "svg", browsers()), false, "Visualize graph through web browser"}, + + // Visualize HTML directly generated by report. + "weblist": {c, report.WebList, invokeVisualizer(interactive, awayFromTTY("html"), "html", browsers()), true, "Output annotated source in HTML for functions matching regexp or address"}, + } +} + +// browsers returns a list of commands to attempt for web visualization +// on the current platform +func browsers() []string { + var cmds []string + if exe := os.Getenv("BROWSER"); exe != "" { + cmds = append(cmds, exe) + } + switch runtime.GOOS { + case "darwin": + cmds = append(cmds, "/usr/bin/open") + case "windows": + cmds = append(cmds, "cmd /c start") + default: + cmds = append(cmds, "xdg-open") + } + cmds = append(cmds, "chrome", "google-chrome", "firefox") + return cmds +} + +// NewCompleter creates an autocompletion function for a set of commands. +func NewCompleter(cs Commands) Completer { + return func(line string) string { + switch tokens := strings.Fields(line); len(tokens) { + case 0: + // Nothing to complete + case 1: + // Single token -- complete command name + found := "" + for c := range cs { + if strings.HasPrefix(c, tokens[0]) { + if found != "" { + return line + } + found = c + } + } + if found != "" { + return found + } + default: + // Multiple tokens -- complete using command completer + if c, ok := cs[tokens[0]]; ok { + if c.Complete != nil { + lastTokenIdx := len(tokens) - 1 + lastToken := tokens[lastTokenIdx] + if strings.HasPrefix(lastToken, "-") { + lastToken = "-" + c.Complete(lastToken[1:]) + } else { + lastToken = c.Complete(lastToken) + } + return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ") + } + } + } + return line + } +} + +// awayFromTTY saves the output in a file if it would otherwise go to +// the terminal screen. This is used to avoid dumping binary data on +// the screen. +func awayFromTTY(format string) PostProcessor { + return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { + if output == os.Stdout && ui.IsTerminal() { + tempFile, err := tempfile.New("", "profile", "."+format) + if err != nil { + return err + } + ui.PrintErr("Generating report in ", tempFile.Name()) + _, err = fmt.Fprint(tempFile, input) + return err + } + _, err := fmt.Fprint(output, input) + return err + } +} + +func invokeDot(format string) PostProcessor { + divert := awayFromTTY(format) + return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { + if _, err := exec.LookPath("dot"); err != nil { + ui.PrintErr("Cannot find dot, have you installed Graphviz?") + return err + } + cmd := exec.Command("dot", "-T"+format) + var buf bytes.Buffer + cmd.Stdin, cmd.Stdout, cmd.Stderr = input, &buf, os.Stderr + if err := cmd.Run(); err != nil { + return err + } + return divert(&buf, output, ui) + } +} + +func saveSVGToFile() PostProcessor { + generateSVG := invokeDot("svg") + divert := awayFromTTY("svg") + return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { + baseSVG := &bytes.Buffer{} + generateSVG(input, baseSVG, ui) + massaged := &bytes.Buffer{} + fmt.Fprint(massaged, svg.Massage(*baseSVG)) + return divert(massaged, output, ui) + } +} + +var vizTmpDir string + +func makeVizTmpDir() error { + if vizTmpDir != "" { + return nil + } + name, err := ioutil.TempDir("", "pprof-") + if err != nil { + return err + } + vizTmpDir = name + return nil +} + +func invokeVisualizer(interactive **bool, format PostProcessor, suffix string, visualizers []string) PostProcessor { + return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { + if err := makeVizTmpDir(); err != nil { + return err + } + tempFile, err := tempfile.New(vizTmpDir, "pprof", "."+suffix) + if err != nil { + return err + } + tempfile.DeferDelete(tempFile.Name()) + if err = format(input, tempFile, ui); err != nil { + return err + } + tempFile.Close() // on windows, if the file is Open, start cannot access it. + // Try visualizers until one is successful + for _, v := range visualizers { + // Separate command and arguments for exec.Command. + args := strings.Split(v, " ") + if len(args) == 0 { + continue + } + viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...) + viewer.Stderr = os.Stderr + if err = viewer.Start(); err == nil { + // The viewer might just send a message to another program + // to open the file. Give that program a little time to open the + // file before we remove it. + time.Sleep(1 * time.Second) + + if !**interactive { + // In command-line mode, wait for the viewer to be closed + // before proceeding + return viewer.Wait() + } + return nil + } + } + return err + } +} diff --git a/src/cmd/internal/pprof/driver/driver.go b/src/cmd/internal/pprof/driver/driver.go new file mode 100644 index 0000000000..782acfdf32 --- /dev/null +++ b/src/cmd/internal/pprof/driver/driver.go @@ -0,0 +1,1041 @@ +// Copyright 2014 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 driver implements the core pprof functionality. It can be +// parameterized with a flag implementation, fetch and symbolize +// mechanisms. +package driver + +import ( + "bytes" + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" + + "cmd/internal/pprof/commands" + "cmd/internal/pprof/plugin" + "cmd/internal/pprof/profile" + "cmd/internal/pprof/report" + "cmd/internal/pprof/tempfile" +) + +// PProf acquires a profile, and symbolizes it using a profile +// manager. Then it generates a report formatted according to the +// options selected through the flags package. +func PProf(flagset plugin.FlagSet, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, overrides commands.Commands) error { + // Remove any temporary files created during pprof processing. + defer tempfile.Cleanup() + + f, err := getFlags(flagset, overrides, ui) + if err != nil { + return err + } + + obj.SetConfig(*f.flagTools) + + sources := f.profileSource + if len(sources) > 1 { + source := sources[0] + // If the first argument is a supported object file, treat as executable. + if file, err := obj.Open(source, 0); err == nil { + file.Close() + f.profileExecName = source + sources = sources[1:] + } else if *f.flagBuildID == "" && isBuildID(source) { + f.flagBuildID = &source + sources = sources[1:] + } + } + + // errMu protects concurrent accesses to errset and err. errset is set if an + // error is encountered by one of the goroutines grabbing a profile. + errMu, errset := sync.Mutex{}, false + + // Fetch profiles. + wg := sync.WaitGroup{} + profs := make([]*profile.Profile, len(sources)) + for i, source := range sources { + wg.Add(1) + go func(i int, src string) { + defer wg.Done() + p, grabErr := grabProfile(src, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f) + if grabErr != nil { + errMu.Lock() + defer errMu.Unlock() + errset, err = true, grabErr + return + } + profs[i] = p + }(i, source) + } + wg.Wait() + if errset { + return err + } + + // Merge profiles. + prof := profs[0] + for _, p := range profs[1:] { + if err = prof.Merge(p, 1); err != nil { + return err + } + } + + if *f.flagBase != "" { + // Fetch base profile and subtract from current profile. + base, err := grabProfile(*f.flagBase, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f) + if err != nil { + return err + } + + if err = prof.Merge(base, -1); err != nil { + return err + } + } + + if err := processFlags(prof, ui, f); err != nil { + return err + } + + if !*f.flagRuntime { + prof.RemoveUninteresting() + } + + if *f.flagInteractive { + return interactive(prof, obj, ui, f) + } + + return generate(false, prof, obj, ui, f) +} + +// isBuildID determines if the profile may contain a build ID, by +// checking that it is a string of hex digits. +func isBuildID(id string) bool { + return strings.Trim(id, "0123456789abcdefABCDEF") == "" +} + +// adjustURL updates the profile source URL based on heuristics. It +// will append ?seconds=sec for CPU profiles if not already +// specified. Returns the hostname if the profile is remote. +func adjustURL(source string, sec int, ui plugin.UI) (adjusted, host string, duration time.Duration) { + // If there is a local file with this name, just use it. + if _, err := os.Stat(source); err == nil { + return source, "", 0 + } + + url, err := url.Parse(source) + + // Automatically add http:// to URLs of the form hostname:port/path. + // url.Parse treats "hostname" as the Scheme. + if err != nil || (url.Host == "" && url.Scheme != "" && url.Scheme != "file") { + url, err = url.Parse("http://" + source) + if err != nil { + return source, "", 0 + } + } + if scheme := strings.ToLower(url.Scheme); scheme == "" || scheme == "file" { + url.Scheme = "" + return url.String(), "", 0 + } + + values := url.Query() + if urlSeconds := values.Get("seconds"); urlSeconds != "" { + if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil { + if sec >= 0 { + ui.PrintErr("Overriding -seconds for URL ", source) + } + sec = int(us) + } + } + + switch strings.ToLower(url.Path) { + case "", "/": + // Apply default /profilez. + url.Path = "/profilez" + case "/protoz": + // Rewrite to /profilez?type=proto + url.Path = "/profilez" + values.Set("type", "proto") + } + + if hasDuration(url.Path) { + if sec > 0 { + duration = time.Duration(sec) * time.Second + values.Set("seconds", fmt.Sprintf("%d", sec)) + } else { + // Assume default duration: 30 seconds + duration = 30 * time.Second + } + } + url.RawQuery = values.Encode() + return url.String(), url.Host, duration +} + +func hasDuration(path string) bool { + for _, trigger := range []string{"profilez", "wallz", "/profile"} { + if strings.Contains(path, trigger) { + return true + } + } + return false +} + +// preprocess does filtering and aggregation of a profile based on the +// requested options. +func preprocess(prof *profile.Profile, ui plugin.UI, f *flags) error { + if *f.flagFocus != "" || *f.flagIgnore != "" || *f.flagHide != "" { + focus, ignore, hide, err := compileFocusIgnore(*f.flagFocus, *f.flagIgnore, *f.flagHide) + if err != nil { + return err + } + fm, im, hm := prof.FilterSamplesByName(focus, ignore, hide) + + warnNoMatches(fm, *f.flagFocus, "Focus", ui) + warnNoMatches(im, *f.flagIgnore, "Ignore", ui) + warnNoMatches(hm, *f.flagHide, "Hide", ui) + } + + if *f.flagTagFocus != "" || *f.flagTagIgnore != "" { + focus, err := compileTagFilter(*f.flagTagFocus, ui) + if err != nil { + return err + } + ignore, err := compileTagFilter(*f.flagTagIgnore, ui) + if err != nil { + return err + } + fm, im := prof.FilterSamplesByTag(focus, ignore) + + warnNoMatches(fm, *f.flagTagFocus, "TagFocus", ui) + warnNoMatches(im, *f.flagTagIgnore, "TagIgnore", ui) + } + + return aggregate(prof, f) +} + +func compileFocusIgnore(focus, ignore, hide string) (f, i, h *regexp.Regexp, err error) { + if focus != "" { + if f, err = regexp.Compile(focus); err != nil { + return nil, nil, nil, fmt.Errorf("parsing focus regexp: %v", err) + } + } + + if ignore != "" { + if i, err = regexp.Compile(ignore); err != nil { + return nil, nil, nil, fmt.Errorf("parsing ignore regexp: %v", err) + } + } + + if hide != "" { + if h, err = regexp.Compile(hide); err != nil { + return nil, nil, nil, fmt.Errorf("parsing hide regexp: %v", err) + } + } + return +} + +func compileTagFilter(filter string, ui plugin.UI) (f func(string, string, int64) bool, err error) { + if filter == "" { + return nil, nil + } + if numFilter := parseTagFilterRange(filter); numFilter != nil { + ui.PrintErr("Interpreted '", filter, "' as range, not regexp") + return func(key, val string, num int64) bool { + if val != "" { + return false + } + return numFilter(num, key) + }, nil + } + fx, err := regexp.Compile(filter) + if err != nil { + return nil, err + } + + return func(key, val string, num int64) bool { + if val == "" { + return false + } + return fx.MatchString(key + ":" + val) + }, nil +} + +var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)") + +// parseTagFilterRange returns a function to checks if a value is +// contained on the range described by a string. It can recognize +// strings of the form: +// "32kb" -- matches values == 32kb +// ":64kb" -- matches values <= 64kb +// "4mb:" -- matches values >= 4mb +// "12kb:64mb" -- matches values between 12kb and 64mb (both included). +func parseTagFilterRange(filter string) func(int64, string) bool { + ranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2) + if len(ranges) == 0 { + return nil // No ranges were identified + } + v, err := strconv.ParseInt(ranges[0][1], 10, 64) + if err != nil { + panic(fmt.Errorf("Failed to parse int %s: %v", ranges[0][1], err)) + } + value, unit := report.ScaleValue(v, ranges[0][2], ranges[0][2]) + if len(ranges) == 1 { + switch match := ranges[0][0]; filter { + case match: + return func(v int64, u string) bool { + sv, su := report.ScaleValue(v, u, unit) + return su == unit && sv == value + } + case match + ":": + return func(v int64, u string) bool { + sv, su := report.ScaleValue(v, u, unit) + return su == unit && sv >= value + } + case ":" + match: + return func(v int64, u string) bool { + sv, su := report.ScaleValue(v, u, unit) + return su == unit && sv <= value + } + } + return nil + } + if filter != ranges[0][0]+":"+ranges[1][0] { + return nil + } + if v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil { + panic(fmt.Errorf("Failed to parse int %s: %v", ranges[1][1], err)) + } + value2, unit2 := report.ScaleValue(v, ranges[1][2], unit) + if unit != unit2 { + return nil + } + return func(v int64, u string) bool { + sv, su := report.ScaleValue(v, u, unit) + return su == unit && sv >= value && sv <= value2 + } +} + +func warnNoMatches(match bool, rx, option string, ui plugin.UI) { + if !match && rx != "" && rx != "." { + ui.PrintErr(option + " expression matched no samples: " + rx) + } +} + +// grabProfile fetches and symbolizes a profile. +func grabProfile(source, exec, buildid string, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, f *flags) (*profile.Profile, error) { + source, host, duration := adjustURL(source, *f.flagSeconds, ui) + remote := host != "" + + if remote { + ui.Print("Fetching profile from ", source) + if duration != 0 { + ui.Print("Please wait... (" + duration.String() + ")") + } + } + + now := time.Now() + // Fetch profile from source. + // Give 50% slack on the timeout. + p, err := fetch(source, duration+duration/2, ui) + if err != nil { + return nil, err + } + + // Update the time/duration if the profile source doesn't include it. + // TODO(rsilvera): Remove this when we remove support for legacy profiles. + if remote { + if p.TimeNanos == 0 { + p.TimeNanos = now.UnixNano() + } + if duration != 0 && p.DurationNanos == 0 { + p.DurationNanos = int64(duration) + } + } + + // Replace executable/buildID with the options provided in the + // command line. Assume the executable is the first Mapping entry. + if exec != "" || buildid != "" { + if len(p.Mapping) == 0 { + // Create a fake mapping to hold the user option, and associate + // all samples to it. + m := &profile.Mapping{ + ID: 1, + } + for _, l := range p.Location { + l.Mapping = m + } + p.Mapping = []*profile.Mapping{m} + } + if exec != "" { + p.Mapping[0].File = exec + } + if buildid != "" { + p.Mapping[0].BuildID = buildid + } + } + + if err := sym(*f.flagSymbolize, source, p, obj, ui); err != nil { + return nil, err + } + + // Save a copy of any remote profiles, unless the user is explicitly + // saving it. + if remote && !f.isFormat("proto") { + prefix := "pprof." + if len(p.Mapping) > 0 && p.Mapping[0].File != "" { + prefix = prefix + filepath.Base(p.Mapping[0].File) + "." + } + if !strings.ContainsRune(host, os.PathSeparator) { + prefix = prefix + host + "." + } + for _, s := range p.SampleType { + prefix = prefix + s.Type + "." + } + + dir := os.Getenv("PPROF_TMPDIR") + tempFile, err := tempfile.New(dir, prefix, ".pb.gz") + if err == nil { + if err = p.Write(tempFile); err == nil { + ui.PrintErr("Saved profile in ", tempFile.Name()) + } + } + if err != nil { + ui.PrintErr("Could not save profile: ", err) + } + } + + if err := p.Demangle(obj.Demangle); err != nil { + ui.PrintErr("Failed to demangle profile: ", err) + } + + if err := p.CheckValid(); err != nil { + return nil, fmt.Errorf("Grab %s: %v", source, err) + } + + return p, nil +} + +type flags struct { + flagInteractive *bool // Accept commands interactively + flagCommands map[string]*bool // pprof commands without parameters + flagParamCommands map[string]*string // pprof commands with parameters + + flagOutput *string // Output file name + + flagCum *bool // Sort by cumulative data + flagCallTree *bool // generate a context-sensitive call tree + + flagAddresses *bool // Report at address level + flagLines *bool // Report at source line level + flagFiles *bool // Report at file level + flagFunctions *bool // Report at function level [default] + + flagSymbolize *string // Symbolization options (=none to disable) + flagBuildID *string // Override build if for first mapping + + flagNodeCount *int // Max number of nodes to show + flagNodeFraction *float64 // Hide nodes below *total + flagEdgeFraction *float64 // Hide edges below *total + flagTrim *bool // Set to false to ignore NodeCount/*Fraction + flagRuntime *bool // Show runtime call frames in memory profiles + flagFocus *string // Restricts to paths going through a node matching regexp + flagIgnore *string // Skips paths going through any nodes matching regexp + flagHide *string // Skips sample locations matching regexp + flagTagFocus *string // Restrict to samples tagged with key:value matching regexp + flagTagIgnore *string // Discard samples tagged with key:value matching regexp + flagDropNegative *bool // Skip negative values + + flagBase *string // Source for base profile to user for comparison + + flagSeconds *int // Length of time for dynamic profiles + + flagTotalDelay *bool // Display total delay at each region + flagContentions *bool // Display number of delays at each region + flagMeanDelay *bool // Display mean delay at each region + + flagInUseSpace *bool // Display in-use memory size + flagInUseObjects *bool // Display in-use object counts + flagAllocSpace *bool // Display allocated memory size + flagAllocObjects *bool // Display allocated object counts + flagDisplayUnit *string // Measurement unit to use on reports + flagDivideBy *float64 // Ratio to divide sample values + + flagSampleIndex *int // Sample value to use in reports. + flagMean *bool // Use mean of sample_index over count + + flagTools *string + profileSource []string + profileExecName string + + extraUsage string + commands commands.Commands +} + +func (f *flags) isFormat(format string) bool { + if fl := f.flagCommands[format]; fl != nil { + return *fl + } + if fl := f.flagParamCommands[format]; fl != nil { + return *fl != "" + } + return false +} + +// String provides a printable representation for the current set of flags. +func (f *flags) String(p *profile.Profile) string { + var ret string + + if ix := *f.flagSampleIndex; ix != -1 { + ret += fmt.Sprintf(" %-25s : %d (%s)\n", "sample_index", ix, p.SampleType[ix].Type) + } + if ix := *f.flagMean; ix { + ret += boolFlagString("mean") + } + if *f.flagDisplayUnit != "minimum" { + ret += stringFlagString("unit", *f.flagDisplayUnit) + } + + switch { + case *f.flagInteractive: + ret += boolFlagString("interactive") + } + for name, fl := range f.flagCommands { + if *fl { + ret += boolFlagString(name) + } + } + + if *f.flagCum { + ret += boolFlagString("cum") + } + if *f.flagCallTree { + ret += boolFlagString("call_tree") + } + + switch { + case *f.flagAddresses: + ret += boolFlagString("addresses") + case *f.flagLines: + ret += boolFlagString("lines") + case *f.flagFiles: + ret += boolFlagString("files") + case *f.flagFunctions: + ret += boolFlagString("functions") + } + + if *f.flagNodeCount != -1 { + ret += intFlagString("nodecount", *f.flagNodeCount) + } + + ret += floatFlagString("nodefraction", *f.flagNodeFraction) + ret += floatFlagString("edgefraction", *f.flagEdgeFraction) + + if *f.flagFocus != "" { + ret += stringFlagString("focus", *f.flagFocus) + } + if *f.flagIgnore != "" { + ret += stringFlagString("ignore", *f.flagIgnore) + } + if *f.flagHide != "" { + ret += stringFlagString("hide", *f.flagHide) + } + + if *f.flagTagFocus != "" { + ret += stringFlagString("tagfocus", *f.flagTagFocus) + } + if *f.flagTagIgnore != "" { + ret += stringFlagString("tagignore", *f.flagTagIgnore) + } + + return ret +} + +func boolFlagString(label string) string { + return fmt.Sprintf(" %-25s : true\n", label) +} + +func stringFlagString(label, value string) string { + return fmt.Sprintf(" %-25s : %s\n", label, value) +} + +func intFlagString(label string, value int) string { + return fmt.Sprintf(" %-25s : %d\n", label, value) +} + +func floatFlagString(label string, value float64) string { + return fmt.Sprintf(" %-25s : %f\n", label, value) +} + +// Utility routines to set flag values. +func newBool(b bool) *bool { + return &b +} + +func newString(s string) *string { + return &s +} + +func newFloat64(fl float64) *float64 { + return &fl +} + +func newInt(i int) *int { + return &i +} + +func (f *flags) usage(ui plugin.UI) { + var commandMsg []string + for name, cmd := range f.commands { + if cmd.HasParam { + name = name + "=p" + } + commandMsg = append(commandMsg, + fmt.Sprintf(" -%-16s %s", name, cmd.Usage)) + } + + sort.Strings(commandMsg) + + text := usageMsgHdr + strings.Join(commandMsg, "\n") + "\n" + usageMsg + "\n" + if f.extraUsage != "" { + text += f.extraUsage + "\n" + } + text += usageMsgVars + ui.Print(text) +} + +func getFlags(flag plugin.FlagSet, overrides commands.Commands, ui plugin.UI) (*flags, error) { + f := &flags{ + flagInteractive: flag.Bool("interactive", false, "Accepts commands interactively"), + flagCommands: make(map[string]*bool), + flagParamCommands: make(map[string]*string), + + // Filename for file-based output formats, stdout by default. + flagOutput: flag.String("output", "", "Output filename for file-based outputs "), + // Comparisons. + flagBase: flag.String("base", "", "Source for base profile for comparison"), + flagDropNegative: flag.Bool("drop_negative", false, "Ignore negative differences"), + + // Data sorting criteria. + flagCum: flag.Bool("cum", false, "Sort by cumulative data"), + // Graph handling options. + flagCallTree: flag.Bool("call_tree", false, "Create a context-sensitive call tree"), + // Granularity of output resolution. + flagAddresses: flag.Bool("addresses", false, "Report at address level"), + flagLines: flag.Bool("lines", false, "Report at source line level"), + flagFiles: flag.Bool("files", false, "Report at source file level"), + flagFunctions: flag.Bool("functions", false, "Report at function level [default]"), + // Internal options. + flagSymbolize: flag.String("symbolize", "", "Options for profile symbolization"), + flagBuildID: flag.String("buildid", "", "Override build id for first mapping"), + // Filtering options + flagNodeCount: flag.Int("nodecount", -1, "Max number of nodes to show"), + flagNodeFraction: flag.Float64("nodefraction", 0.005, "Hide nodes below *total"), + flagEdgeFraction: flag.Float64("edgefraction", 0.001, "Hide edges below *total"), + flagTrim: flag.Bool("trim", true, "Honor nodefraction/edgefraction/nodecount defaults"), + flagRuntime: flag.Bool("runtime", false, "Show runtime call frames in memory profiles"), + flagFocus: flag.String("focus", "", "Restricts to paths going through a node matching regexp"), + flagIgnore: flag.String("ignore", "", "Skips paths going through any nodes matching regexp"), + flagHide: flag.String("hide", "", "Skips nodes matching regexp"), + flagTagFocus: flag.String("tagfocus", "", "Restrict to samples with tags in range or matched by regexp"), + flagTagIgnore: flag.String("tagignore", "", "Discard samples with tags in range or matched by regexp"), + // CPU profile options + flagSeconds: flag.Int("seconds", -1, "Length of time for dynamic profiles"), + // Heap profile options + flagInUseSpace: flag.Bool("inuse_space", false, "Display in-use memory size"), + flagInUseObjects: flag.Bool("inuse_objects", false, "Display in-use object counts"), + flagAllocSpace: flag.Bool("alloc_space", false, "Display allocated memory size"), + flagAllocObjects: flag.Bool("alloc_objects", false, "Display allocated object counts"), + flagDisplayUnit: flag.String("unit", "minimum", "Measurement units to display"), + flagDivideBy: flag.Float64("divide_by", 1.0, "Ratio to divide all samples before visualization"), + flagSampleIndex: flag.Int("sample_index", -1, "Index of sample value to report"), + flagMean: flag.Bool("mean", false, "Average sample value over first value (count)"), + // Contention profile options + flagTotalDelay: flag.Bool("total_delay", false, "Display total delay at each region"), + flagContentions: flag.Bool("contentions", false, "Display number of delays at each region"), + flagMeanDelay: flag.Bool("mean_delay", false, "Display mean delay at each region"), + flagTools: flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames"), + extraUsage: flag.ExtraUsage(), + } + + // Flags used during command processing + interactive := &f.flagInteractive + f.commands = commands.PProf(functionCompleter, interactive) + + // Override commands + for name, cmd := range overrides { + f.commands[name] = cmd + } + + for name, cmd := range f.commands { + if cmd.HasParam { + f.flagParamCommands[name] = flag.String(name, "", "Generate a report in "+name+" format, matching regexp") + } else { + f.flagCommands[name] = flag.Bool(name, false, "Generate a report in "+name+" format") + } + } + + args := flag.Parse(func() { f.usage(ui) }) + if len(args) == 0 { + return nil, fmt.Errorf("no profile source specified") + } + + f.profileSource = args + + // Instruct legacy heapz parsers to grab historical allocation data, + // instead of the default in-use data. Not available with tcmalloc. + if *f.flagAllocSpace || *f.flagAllocObjects { + profile.LegacyHeapAllocated = true + } + + if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir == "" { + profileDir = os.Getenv("HOME") + "/pprof" + os.Setenv("PPROF_TMPDIR", profileDir) + if err := os.MkdirAll(profileDir, 0755); err != nil { + return nil, fmt.Errorf("failed to access temp dir %s: %v", profileDir, err) + } + } + + return f, nil +} + +func processFlags(p *profile.Profile, ui plugin.UI, f *flags) error { + flagDis := f.isFormat("disasm") + flagPeek := f.isFormat("peek") + flagWebList := f.isFormat("weblist") + flagList := f.isFormat("list") + + if flagDis || flagWebList { + // Collect all samples at address granularity for assembly + // listing. + f.flagNodeCount = newInt(0) + f.flagAddresses = newBool(true) + f.flagLines = newBool(false) + f.flagFiles = newBool(false) + f.flagFunctions = newBool(false) + } + + if flagPeek { + // Collect all samples at function granularity for peek command + f.flagNodeCount = newInt(0) + f.flagAddresses = newBool(false) + f.flagLines = newBool(false) + f.flagFiles = newBool(false) + f.flagFunctions = newBool(true) + } + + if flagList { + // Collect all samples at fileline granularity for source + // listing. + f.flagNodeCount = newInt(0) + f.flagAddresses = newBool(false) + f.flagLines = newBool(true) + f.flagFiles = newBool(false) + f.flagFunctions = newBool(false) + } + + if !*f.flagTrim { + f.flagNodeCount = newInt(0) + f.flagNodeFraction = newFloat64(0) + f.flagEdgeFraction = newFloat64(0) + } + + if oc := countFlagMap(f.flagCommands, f.flagParamCommands); oc == 0 { + f.flagInteractive = newBool(true) + } else if oc > 1 { + f.usage(ui) + return fmt.Errorf("must set at most one output format") + } + + // Apply nodecount defaults for non-interactive mode. The + // interactive shell will apply defaults for the interactive mode. + if *f.flagNodeCount < 0 && !*f.flagInteractive { + switch { + default: + f.flagNodeCount = newInt(80) + case f.isFormat("text"): + f.flagNodeCount = newInt(0) + } + } + + // Apply legacy options and diagnose conflicts. + if rc := countFlags([]*bool{f.flagAddresses, f.flagLines, f.flagFiles, f.flagFunctions}); rc == 0 { + f.flagFunctions = newBool(true) + } else if rc > 1 { + f.usage(ui) + return fmt.Errorf("must set at most one granularity option") + } + + var err error + si, sm := *f.flagSampleIndex, *f.flagMean || *f.flagMeanDelay + si, err = sampleIndex(p, &f.flagTotalDelay, si, 1, "delay", "-total_delay", err) + si, err = sampleIndex(p, &f.flagMeanDelay, si, 1, "delay", "-mean_delay", err) + si, err = sampleIndex(p, &f.flagContentions, si, 0, "contentions", "-contentions", err) + + si, err = sampleIndex(p, &f.flagInUseSpace, si, 1, "inuse_space", "-inuse_space", err) + si, err = sampleIndex(p, &f.flagInUseObjects, si, 0, "inuse_objects", "-inuse_objects", err) + si, err = sampleIndex(p, &f.flagAllocSpace, si, 1, "alloc_space", "-alloc_space", err) + si, err = sampleIndex(p, &f.flagAllocObjects, si, 0, "alloc_objects", "-alloc_objects", err) + + if si == -1 { + // Use last value if none is requested. + si = len(p.SampleType) - 1 + } else if si < 0 || si >= len(p.SampleType) { + err = fmt.Errorf("sample_index value %d out of range [0..%d]", si, len(p.SampleType)-1) + } + + if err != nil { + f.usage(ui) + return err + } + f.flagSampleIndex, f.flagMean = newInt(si), newBool(sm) + return nil +} + +func sampleIndex(p *profile.Profile, flag **bool, + sampleIndex int, + newSampleIndex int, + sampleType, option string, + err error) (int, error) { + if err != nil || !**flag { + return sampleIndex, err + } + *flag = newBool(false) + if sampleIndex != -1 { + return 0, fmt.Errorf("set at most one sample value selection option") + } + if newSampleIndex >= len(p.SampleType) || + p.SampleType[newSampleIndex].Type != sampleType { + return 0, fmt.Errorf("option %s not valid for this profile", option) + } + return newSampleIndex, nil +} + +func countFlags(bs []*bool) int { + var c int + for _, b := range bs { + if *b { + c++ + } + } + return c +} + +func countFlagMap(bms map[string]*bool, bmrxs map[string]*string) int { + var c int + for _, b := range bms { + if *b { + c++ + } + } + for _, s := range bmrxs { + if *s != "" { + c++ + } + } + return c +} + +var usageMsgHdr = "usage: pprof [options] [binary] ...\n" + + "Output format (only set one):\n" + +var usageMsg = "Output file parameters (for file-based output formats):\n" + + " -output=f Generate output on file f (stdout by default)\n" + + "Output granularity (only set one):\n" + + " -functions Report at function level [default]\n" + + " -files Report at source file level\n" + + " -lines Report at source line level\n" + + " -addresses Report at address level\n" + + "Comparison options:\n" + + " -base Show delta from this profile\n" + + " -drop_negative Ignore negative differences\n" + + "Sorting options:\n" + + " -cum Sort by cumulative data\n\n" + + "Dynamic profile options:\n" + + " -seconds=N Length of time for dynamic profiles\n" + + "Profile trimming options:\n" + + " -nodecount=N Max number of nodes to show\n" + + " -nodefraction=f Hide nodes below *total\n" + + " -edgefraction=f Hide edges below *total\n" + + "Sample value selection option (by index):\n" + + " -sample_index Index of sample value to display\n" + + " -mean Average sample value over first value\n" + + "Sample value selection option (for heap profiles):\n" + + " -inuse_space Display in-use memory size\n" + + " -inuse_objects Display in-use object counts\n" + + " -alloc_space Display allocated memory size\n" + + " -alloc_objects Display allocated object counts\n" + + "Sample value selection option (for contention profiles):\n" + + " -total_delay Display total delay at each region\n" + + " -contentions Display number of delays at each region\n" + + " -mean_delay Display mean delay at each region\n" + + "Filtering options:\n" + + " -runtime Show runtime call frames in memory profiles\n" + + " -focus=r Restricts to paths going through a node matching regexp\n" + + " -ignore=r Skips paths going through any nodes matching regexp\n" + + " -tagfocus=r Restrict to samples tagged with key:value matching regexp\n" + + " Restrict to samples with numeric tags in range (eg \"32kb:1mb\")\n" + + " -tagignore=r Discard samples tagged with key:value matching regexp\n" + + " Avoid samples with numeric tags in range (eg \"1mb:\")\n" + + "Miscellaneous:\n" + + " -call_tree Generate a context-sensitive call tree\n" + + " -unit=u Convert all samples to unit u for display\n" + + " -divide_by=f Scale all samples by dividing them by f\n" + + " -buildid=id Override build id for main binary in profile\n" + + " -tools=path Search path for object-level tools\n" + + " -help This message" + +var usageMsgVars = "Environment Variables:\n" + + " PPROF_TMPDIR Location for saved profiles (default $HOME/pprof)\n" + + " PPROF_TOOLS Search path for object-level tools\n" + + " PPROF_BINARY_PATH Search path for local binary files\n" + + " default: $HOME/pprof/binaries\n" + + " finds binaries by $name and $buildid/$name" + +func aggregate(prof *profile.Profile, f *flags) error { + switch { + case f.isFormat("proto"), f.isFormat("raw"): + // No aggregation for raw profiles. + case f.isFormat("callgrind"): + // Aggregate to file/line for callgrind. + fallthrough + case *f.flagLines: + return prof.Aggregate(true, true, true, true, false) + case *f.flagFiles: + return prof.Aggregate(true, false, true, false, false) + case *f.flagFunctions: + return prof.Aggregate(true, true, false, false, false) + case f.isFormat("weblist"), f.isFormat("disasm"): + return prof.Aggregate(false, true, true, true, true) + } + return nil +} + +// parseOptions parses the options into report.Options +// Returns a function to postprocess the report after generation. +func parseOptions(f *flags) (o *report.Options, p commands.PostProcessor, err error) { + + if *f.flagDivideBy == 0 { + return nil, nil, fmt.Errorf("zero divisor specified") + } + + o = &report.Options{ + CumSort: *f.flagCum, + CallTree: *f.flagCallTree, + PrintAddresses: *f.flagAddresses, + DropNegative: *f.flagDropNegative, + Ratio: 1 / *f.flagDivideBy, + + NodeCount: *f.flagNodeCount, + NodeFraction: *f.flagNodeFraction, + EdgeFraction: *f.flagEdgeFraction, + OutputUnit: *f.flagDisplayUnit, + } + + for cmd, b := range f.flagCommands { + if *b { + pcmd := f.commands[cmd] + o.OutputFormat = pcmd.Format + return o, pcmd.PostProcess, nil + } + } + + for cmd, rx := range f.flagParamCommands { + if *rx != "" { + pcmd := f.commands[cmd] + if o.Symbol, err = regexp.Compile(*rx); err != nil { + return nil, nil, fmt.Errorf("parsing -%s regexp: %v", cmd, err) + } + o.OutputFormat = pcmd.Format + return o, pcmd.PostProcess, nil + } + } + + return nil, nil, fmt.Errorf("no output format selected") +} + +type sampleValueFunc func(*profile.Sample) int64 + +// sampleFormat returns a function to extract values out of a profile.Sample, +// and the type/units of those values. +func sampleFormat(p *profile.Profile, f *flags) (sampleValueFunc, string, string) { + valueIndex := *f.flagSampleIndex + + if *f.flagMean { + return meanExtractor(valueIndex), "mean_" + p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit + } + + return valueExtractor(valueIndex), p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit +} + +func valueExtractor(ix int) sampleValueFunc { + return func(s *profile.Sample) int64 { + return s.Value[ix] + } +} + +func meanExtractor(ix int) sampleValueFunc { + return func(s *profile.Sample) int64 { + if s.Value[0] == 0 { + return 0 + } + return s.Value[ix] / s.Value[0] + } +} + +func generate(interactive bool, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error { + o, postProcess, err := parseOptions(f) + if err != nil { + return err + } + + var w io.Writer + if *f.flagOutput == "" { + w = os.Stdout + } else { + ui.PrintErr("Generating report in ", *f.flagOutput) + outputFile, err := os.Create(*f.flagOutput) + if err != nil { + return err + } + defer outputFile.Close() + w = outputFile + } + + if prof.Empty() { + return fmt.Errorf("profile is empty") + } + + value, stype, unit := sampleFormat(prof, f) + o.SampleType = stype + rpt := report.New(prof, *o, value, unit) + + // Do not apply filters if we're just generating a proto, so we + // still have all the data. + if o.OutputFormat != report.Proto { + // Delay applying focus/ignore until after creating the report so + // the report reflects the total number of samples. + if err := preprocess(prof, ui, f); err != nil { + return err + } + } + + if postProcess == nil { + return report.Generate(w, rpt, obj) + } + + var dot bytes.Buffer + if err = report.Generate(&dot, rpt, obj); err != nil { + return err + } + + return postProcess(&dot, w, ui) +} diff --git a/src/cmd/internal/pprof/driver/interactive.go b/src/cmd/internal/pprof/driver/interactive.go new file mode 100644 index 0000000000..1b08226527 --- /dev/null +++ b/src/cmd/internal/pprof/driver/interactive.go @@ -0,0 +1,492 @@ +// Copyright 2014 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 driver + +import ( + "fmt" + "io" + "regexp" + "sort" + "strconv" + "strings" + + "cmd/internal/pprof/commands" + "cmd/internal/pprof/plugin" + "cmd/internal/pprof/profile" +) + +var profileFunctionNames = []string{} + +// functionCompleter replaces provided substring with a function +// name retrieved from a profile if a single match exists. Otherwise, +// it returns unchanged substring. It defaults to no-op if the profile +// is not specified. +func functionCompleter(substring string) string { + found := "" + for _, fName := range profileFunctionNames { + if strings.Contains(fName, substring) { + if found != "" { + return substring + } + found = fName + } + } + if found != "" { + return found + } + return substring +} + +// updateAutoComplete enhances autocompletion with information that can be +// retrieved from the profile +func updateAutoComplete(p *profile.Profile) { + profileFunctionNames = nil // remove function names retrieved previously + for _, fn := range p.Function { + profileFunctionNames = append(profileFunctionNames, fn.Name) + } +} + +// splitCommand splits the command line input into tokens separated by +// spaces. Takes care to separate commands of the form 'top10' into +// two tokens: 'top' and '10' +func splitCommand(input string) []string { + fields := strings.Fields(input) + if num := strings.IndexAny(fields[0], "0123456789"); num != -1 { + inputNumber := fields[0][num:] + fields[0] = fields[0][:num] + fields = append([]string{fields[0], inputNumber}, fields[1:]...) + } + return fields +} + +// interactive displays a prompt and reads commands for profile +// manipulation/visualization. +func interactive(p *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error { + updateAutoComplete(p) + + // Enter command processing loop. + ui.Print("Entering interactive mode (type \"help\" for commands)") + ui.SetAutoComplete(commands.NewCompleter(f.commands)) + + for { + input, err := readCommand(p, ui, f) + if err != nil { + if err != io.EOF { + return err + } + if input == "" { + return nil + } + } + // Process simple commands. + switch input { + case "": + continue + case ":": + f.flagFocus = newString("") + f.flagIgnore = newString("") + f.flagTagFocus = newString("") + f.flagTagIgnore = newString("") + f.flagHide = newString("") + continue + } + + fields := splitCommand(input) + // Process report generation commands. + if _, ok := f.commands[fields[0]]; ok { + if err := generateReport(p, fields, obj, ui, f); err != nil { + if err == io.EOF { + return nil + } + ui.PrintErr(err) + } + continue + } + + switch cmd := fields[0]; cmd { + case "help": + commandHelp(fields, ui, f) + continue + case "exit", "quit": + return nil + } + + // Process option settings. + if of, err := optFlags(p, input, f); err == nil { + f = of + } else { + ui.PrintErr("Error: ", err.Error()) + } + } +} + +func generateReport(p *profile.Profile, cmd []string, obj plugin.ObjTool, ui plugin.UI, f *flags) error { + prof := p.Copy() + + cf, err := cmdFlags(prof, cmd, ui, f) + if err != nil { + return err + } + + return generate(true, prof, obj, ui, cf) +} + +// validateRegex checks if a string is a valid regular expression. +func validateRegex(v string) error { + _, err := regexp.Compile(v) + return err +} + +// readCommand prompts for and reads the next command. +func readCommand(p *profile.Profile, ui plugin.UI, f *flags) (string, error) { + //ui.Print("Options:\n", f.String(p)) + s, err := ui.ReadLine() + return strings.TrimSpace(s), err +} + +func commandHelp(_ []string, ui plugin.UI, f *flags) error { + help := ` + Commands: + cmd [n] [--cum] [focus_regex]* [-ignore_regex]* + Produce a text report with the top n entries. + Include samples matching focus_regex, and exclude ignore_regex. + Add --cum to sort using cumulative data. + Available commands: +` + var commands []string + for name, cmd := range f.commands { + commands = append(commands, fmt.Sprintf(" %-12s %s", name, cmd.Usage)) + } + sort.Strings(commands) + + help = help + strings.Join(commands, "\n") + ` + peek func_regex + Display callers and callees of functions matching func_regex. + + dot [n] [focus_regex]* [-ignore_regex]* [>file] + Produce an annotated callgraph with the top n entries. + Include samples matching focus_regex, and exclude ignore_regex. + For other outputs, replace dot with: + - Graphic formats: dot, svg, pdf, ps, gif, png (use > to name output file) + - Graph viewer: gv, web, evince, eog + + callgrind [n] [focus_regex]* [-ignore_regex]* [>file] + Produce a file in callgrind-compatible format. + Include samples matching focus_regex, and exclude ignore_regex. + + weblist func_regex [-ignore_regex]* + Show annotated source with interspersed assembly in a web browser. + + list func_regex [-ignore_regex]* + Print source for routines matching func_regex, and exclude ignore_regex. + + disasm func_regex [-ignore_regex]* + Disassemble routines matching func_regex, and exclude ignore_regex. + + tags tag_regex [-ignore_regex]* + List tags with key:value matching tag_regex and exclude ignore_regex. + + quit/exit/^D + Exit pprof. + + option=value + The following options can be set individually: + cum/flat: Sort entries based on cumulative or flat data + call_tree: Build context-sensitive call trees + nodecount: Max number of entries to display + nodefraction: Min frequency ratio of nodes to display + edgefraction: Min frequency ratio of edges to display + focus/ignore: Regexp to include/exclude samples by name/file + tagfocus/tagignore: Regexp or value range to filter samples by tag + eg "1mb", "1mb:2mb", ":64kb" + + functions: Level of aggregation for sample data + files: + lines: + addresses: + + unit: Measurement unit to use on reports + + Sample value selection by index: + sample_index: Index of sample value to display + mean: Average sample value over first value + + Sample value selection by name: + alloc_space for heap profiles + alloc_objects + inuse_space + inuse_objects + + total_delay for contention profiles + mean_delay + contentions + + : Clear focus/ignore/hide/tagfocus/tagignore` + + ui.Print(help) + return nil +} + +// cmdFlags parses the options of an interactive command and returns +// an updated flags object. +func cmdFlags(prof *profile.Profile, input []string, ui plugin.UI, f *flags) (*flags, error) { + cf := *f + + var focus, ignore string + output := *cf.flagOutput + nodeCount := *cf.flagNodeCount + cmd := input[0] + + // Update output flags based on parameters. + tokens := input[1:] + for p := 0; p < len(tokens); p++ { + t := tokens[p] + if t == "" { + continue + } + if c, err := strconv.ParseInt(t, 10, 32); err == nil { + nodeCount = int(c) + continue + } + switch t[0] { + case '>': + if len(t) > 1 { + output = t[1:] + continue + } + // find next token + for p++; p < len(tokens); p++ { + if tokens[p] != "" { + output = tokens[p] + break + } + } + case '-': + if t == "--cum" || t == "-cum" { + cf.flagCum = newBool(true) + continue + } + ignore = catRegex(ignore, t[1:]) + default: + focus = catRegex(focus, t) + } + } + + pcmd, ok := f.commands[cmd] + if !ok { + return nil, fmt.Errorf("Unexpected parse failure: %v", input) + } + // Reset flags + cf.flagCommands = make(map[string]*bool) + cf.flagParamCommands = make(map[string]*string) + + if !pcmd.HasParam { + cf.flagCommands[cmd] = newBool(true) + + switch cmd { + case "tags": + cf.flagTagFocus = newString(focus) + cf.flagTagIgnore = newString(ignore) + default: + cf.flagFocus = newString(catRegex(*cf.flagFocus, focus)) + cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore)) + } + } else { + if focus == "" { + focus = "." + } + cf.flagParamCommands[cmd] = newString(focus) + cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore)) + } + + if nodeCount < 0 { + switch cmd { + case "text", "top": + // Default text/top to 10 nodes on interactive mode + nodeCount = 10 + default: + nodeCount = 80 + } + } + + cf.flagNodeCount = newInt(nodeCount) + cf.flagOutput = newString(output) + + // Do regular flags processing + if err := processFlags(prof, ui, &cf); err != nil { + cf.usage(ui) + return nil, err + } + + return &cf, nil +} + +func catRegex(a, b string) string { + if a == "" { + return b + } + if b == "" { + return a + } + return a + "|" + b +} + +// optFlags parses an interactive option setting and returns +// an updated flags object. +func optFlags(p *profile.Profile, input string, f *flags) (*flags, error) { + inputs := strings.SplitN(input, "=", 2) + option := strings.ToLower(strings.TrimSpace(inputs[0])) + var value string + if len(inputs) == 2 { + value = strings.TrimSpace(inputs[1]) + } + + of := *f + + var err error + var bv bool + var uv uint64 + var fv float64 + + switch option { + case "cum": + if bv, err = parseBool(value); err != nil { + return nil, err + } + of.flagCum = newBool(bv) + case "flat": + if bv, err = parseBool(value); err != nil { + return nil, err + } + of.flagCum = newBool(!bv) + case "call_tree": + if bv, err = parseBool(value); err != nil { + return nil, err + } + of.flagCallTree = newBool(bv) + case "unit": + of.flagDisplayUnit = newString(value) + case "sample_index": + if uv, err = strconv.ParseUint(value, 10, 32); err != nil { + return nil, err + } + if ix := int(uv); ix < 0 || ix >= len(p.SampleType) { + return nil, fmt.Errorf("sample_index out of range [0..%d]", len(p.SampleType)-1) + } + of.flagSampleIndex = newInt(int(uv)) + case "mean": + if bv, err = parseBool(value); err != nil { + return nil, err + } + of.flagMean = newBool(bv) + case "nodecount": + if uv, err = strconv.ParseUint(value, 10, 32); err != nil { + return nil, err + } + of.flagNodeCount = newInt(int(uv)) + case "nodefraction": + if fv, err = strconv.ParseFloat(value, 64); err != nil { + return nil, err + } + of.flagNodeFraction = newFloat64(fv) + case "edgefraction": + if fv, err = strconv.ParseFloat(value, 64); err != nil { + return nil, err + } + of.flagEdgeFraction = newFloat64(fv) + case "focus": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagFocus = newString(value) + case "ignore": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagIgnore = newString(value) + case "tagfocus": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagTagFocus = newString(value) + case "tagignore": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagTagIgnore = newString(value) + case "hide": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagHide = newString(value) + case "addresses", "files", "lines", "functions": + if bv, err = parseBool(value); err != nil { + return nil, err + } + if !bv { + return nil, fmt.Errorf("select one of addresses/files/lines/functions") + } + setGranularityToggle(option, &of) + default: + if ix := findSampleIndex(p, "", option); ix >= 0 { + of.flagSampleIndex = newInt(ix) + } else if ix := findSampleIndex(p, "total_", option); ix >= 0 { + of.flagSampleIndex = newInt(ix) + of.flagMean = newBool(false) + } else if ix := findSampleIndex(p, "mean_", option); ix >= 1 { + of.flagSampleIndex = newInt(ix) + of.flagMean = newBool(true) + } else { + return nil, fmt.Errorf("unrecognized command: %s", input) + } + } + return &of, nil +} + +// parseBool parses a string as a boolean value. +func parseBool(v string) (bool, error) { + switch strings.ToLower(v) { + case "true", "t", "yes", "y", "1", "": + return true, nil + case "false", "f", "no", "n", "0": + return false, nil + } + return false, fmt.Errorf(`illegal input "%s" for bool value`, v) +} + +func findSampleIndex(p *profile.Profile, prefix, sampleType string) int { + if !strings.HasPrefix(sampleType, prefix) { + return -1 + } + sampleType = strings.TrimPrefix(sampleType, prefix) + for i, r := range p.SampleType { + if r.Type == sampleType { + return i + } + } + return -1 +} + +// setGranularityToggle manages the set of granularity options. These +// operate as a toggle; turning one on turns the others off. +func setGranularityToggle(o string, fl *flags) { + t, f := newBool(true), newBool(false) + fl.flagFunctions = f + fl.flagFiles = f + fl.flagLines = f + fl.flagAddresses = f + switch o { + case "functions": + fl.flagFunctions = t + case "files": + fl.flagFiles = t + case "lines": + fl.flagLines = t + case "addresses": + fl.flagAddresses = t + default: + panic(fmt.Errorf("unexpected option %s", o)) + } +} diff --git a/src/cmd/internal/pprof/fetch/fetch.go b/src/cmd/internal/pprof/fetch/fetch.go new file mode 100644 index 0000000000..ffd282e74d --- /dev/null +++ b/src/cmd/internal/pprof/fetch/fetch.go @@ -0,0 +1,82 @@ +// Copyright 2014 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 fetch provides an extensible mechanism to fetch a profile +// from a data source. +package fetch + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" + "time" + + "cmd/internal/pprof/plugin" + "cmd/internal/pprof/profile" +) + +// FetchProfile reads from a data source (network, file) and generates a +// profile. +func FetchProfile(source string, timeout time.Duration) (*profile.Profile, error) { + return Fetcher(source, timeout, plugin.StandardUI()) +} + +// Fetcher is the plugin.Fetcher version of FetchProfile. +func Fetcher(source string, timeout time.Duration, ui plugin.UI) (*profile.Profile, error) { + var f io.ReadCloser + var err error + + url, err := url.Parse(source) + if err == nil && url.Host != "" { + f, err = FetchURL(source, timeout) + } else { + f, err = os.Open(source) + } + if err != nil { + return nil, err + } + defer f.Close() + return profile.Parse(f) +} + +// FetchURL fetches a profile from a URL using HTTP. +func FetchURL(source string, timeout time.Duration) (io.ReadCloser, error) { + resp, err := httpGet(source, timeout) + if err != nil { + return nil, fmt.Errorf("http fetch %s: %v", source, err) + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("server response: %s", resp.Status) + } + + return resp.Body, nil +} + +// PostURL issues a POST to a URL over HTTP. +func PostURL(source, post string) ([]byte, error) { + resp, err := http.Post(source, "application/octet-stream", strings.NewReader(post)) + if err != nil { + return nil, fmt.Errorf("http post %s: %v", source, err) + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("server response: %s", resp.Status) + } + defer resp.Body.Close() + return ioutil.ReadAll(resp.Body) +} + +// httpGet is a wrapper around http.Get; it is defined as a variable +// so it can be redefined during for testing. +var httpGet = func(url string, timeout time.Duration) (*http.Response, error) { + client := &http.Client{ + Transport: &http.Transport{ + ResponseHeaderTimeout: timeout + 5*time.Second, + }, + } + return client.Get(url) +} diff --git a/src/cmd/internal/pprof/plugin/plugin.go b/src/cmd/internal/pprof/plugin/plugin.go new file mode 100644 index 0000000000..d5025d5517 --- /dev/null +++ b/src/cmd/internal/pprof/plugin/plugin.go @@ -0,0 +1,213 @@ +// Copyright 2014 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 plugin defines the plugin implementations that the main pprof driver requires. +package plugin + +import ( + "bufio" + "fmt" + "os" + "regexp" + "strings" + "time" + + "cmd/internal/pprof/profile" +) + +// A FlagSet creates and parses command-line flags. +// It is similar to the standard flag.FlagSet. +type FlagSet interface { + // Bool, Int, Float64, and String define new flags, + // like the functions of the same name in package flag. + Bool(name string, def bool, usage string) *bool + Int(name string, def int, usage string) *int + Float64(name string, def float64, usage string) *float64 + String(name string, def string, usage string) *string + + // ExtraUsage returns any additional text that should be + // printed after the standard usage message. + // The typical use of ExtraUsage is to show any custom flags + // defined by the specific pprof plugins being used. + ExtraUsage() string + + // Parse initializes the flags with their values for this run + // and returns the non-flag command line arguments. + // If an unknown flag is encountered or there are no arguments, + // Parse should call usage and return nil. + Parse(usage func()) []string +} + +// An ObjTool inspects shared libraries and executable files. +type ObjTool interface { + // Open opens the named object file. + // If the object is a shared library, start is the address where + // it is mapped into memory in the address space being inspected. + Open(file string, start uint64) (ObjFile, error) + + // Demangle translates a batch of symbol names from mangled + // form to human-readable form. + Demangle(names []string) (map[string]string, error) + + // Disasm disassembles the named object file, starting at + // the start address and stopping at (before) the end address. + Disasm(file string, start, end uint64) ([]Inst, error) + + // SetConfig configures the tool. + // The implementation defines the meaning of the string + // and can ignore it entirely. + SetConfig(config string) +} + +// NoObjTool returns a trivial implementation of the ObjTool interface. +// Open returns an error indicating that the requested file does not exist. +// Demangle returns an empty map and a nil error. +// Disasm returns an error. +// SetConfig is a no-op. +func NoObjTool() ObjTool { + return noObjTool{} +} + +type noObjTool struct{} + +func (noObjTool) Open(file string, start uint64) (ObjFile, error) { + return nil, &os.PathError{Op: "open", Path: file, Err: os.ErrNotExist} +} + +func (noObjTool) Demangle(name []string) (map[string]string, error) { + return make(map[string]string), nil +} + +func (noObjTool) Disasm(file string, start, end uint64) ([]Inst, error) { + return nil, fmt.Errorf("disassembly not supported") +} + +func (noObjTool) SetConfig(config string) { +} + +// An ObjFile is a single object file: a shared library or executable. +type ObjFile interface { + // Name returns the underlyinf file name, if available + Name() string + + // Base returns the base address to use when looking up symbols in the file. + Base() uint64 + + // BuildID returns the GNU build ID of the file, or an empty string. + BuildID() string + + // SourceLine reports the source line information for a given + // address in the file. Due to inlining, the source line information + // is in general a list of positions representing a call stack, + // with the leaf function first. + SourceLine(addr uint64) ([]Frame, error) + + // Symbols returns a list of symbols in the object file. + // If r is not nil, Symbols restricts the list to symbols + // with names matching the regular expression. + // If addr is not zero, Symbols restricts the list to symbols + // containing that address. + Symbols(r *regexp.Regexp, addr uint64) ([]*Sym, error) + + // Close closes the file, releasing associated resources. + Close() error +} + +// A Frame describes a single line in a source file. +type Frame struct { + Func string // name of function + File string // source file name + Line int // line in file +} + +// A Sym describes a single symbol in an object file. +type Sym struct { + Name []string // names of symbol (many if symbol was dedup'ed) + File string // object file containing symbol + Start uint64 // start virtual address + End uint64 // virtual address of last byte in sym (Start+size-1) +} + +// An Inst is a single instruction in an assembly listing. +type Inst struct { + Addr uint64 // virtual address of instruction + Text string // instruction text + File string // source file + Line int // source line +} + +// A UI manages user interactions. +type UI interface { + // Read returns a line of text (a command) read from the user. + ReadLine() (string, error) + + // Print shows a message to the user. + // It formats the text as fmt.Print would and adds a final \n if not already present. + // For line-based UI, Print writes to standard error. + // (Standard output is reserved for report data.) + Print(...interface{}) + + // PrintErr shows an error message to the user. + // It formats the text as fmt.Print would and adds a final \n if not already present. + // For line-based UI, PrintErr writes to standard error. + PrintErr(...interface{}) + + // IsTerminal returns whether the UI is known to be tied to an + // interactive terminal (as opposed to being redirected to a file). + IsTerminal() bool + + // SetAutoComplete instructs the UI to call complete(cmd) to obtain + // the auto-completion of cmd, if the UI supports auto-completion at all. + SetAutoComplete(complete func(string) string) +} + +// StandardUI returns a UI that reads from standard input, +// prints messages to standard output, +// prints errors to standard error, and doesn't use auto-completion. +func StandardUI() UI { + return &stdUI{r: bufio.NewReader(os.Stdin)} +} + +type stdUI struct { + r *bufio.Reader +} + +func (ui *stdUI) ReadLine() (string, error) { + os.Stdout.WriteString("(pprof) ") + return ui.r.ReadString('\n') +} + +func (ui *stdUI) Print(args ...interface{}) { + ui.fprint(os.Stderr, args) +} + +func (ui *stdUI) PrintErr(args ...interface{}) { + ui.fprint(os.Stderr, args) +} + +func (ui *stdUI) IsTerminal() bool { + return false +} + +func (ui *stdUI) SetAutoComplete(func(string) string) { +} + +func (ui *stdUI) fprint(f *os.File, args []interface{}) { + text := fmt.Sprint(args...) + if !strings.HasSuffix(text, "\n") { + text += "\n" + } + f.WriteString(text) +} + +// A Fetcher reads and returns the profile named by src. +// It gives up after the given timeout, unless src contains a timeout override +// (as defined by the implementation). +// It can print messages to ui. +type Fetcher func(src string, timeout time.Duration, ui UI) (*profile.Profile, error) + +// A Symbolizer annotates a profile with symbol information. +// The profile was fetch from src. +// The meaning of mode is defined by the implementation. +type Symbolizer func(mode, src string, prof *profile.Profile, obj ObjTool, ui UI) error diff --git a/src/cmd/internal/pprof/profile/encode.go b/src/cmd/internal/pprof/profile/encode.go new file mode 100644 index 0000000000..6b879a84ac --- /dev/null +++ b/src/cmd/internal/pprof/profile/encode.go @@ -0,0 +1,470 @@ +// Copyright 2014 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 profile + +import ( + "errors" + "fmt" + "sort" +) + +func (p *Profile) decoder() []decoder { + return profileDecoder +} + +// preEncode populates the unexported fields to be used by encode +// (with suffix X) from the corresponding exported fields. The +// exported fields are cleared up to facilitate testing. +func (p *Profile) preEncode() { + strings := make(map[string]int) + addString(strings, "") + + for _, st := range p.SampleType { + st.typeX = addString(strings, st.Type) + st.unitX = addString(strings, st.Unit) + } + + for _, s := range p.Sample { + s.labelX = nil + var keys []string + for k := range s.Label { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + vs := s.Label[k] + for _, v := range vs { + s.labelX = append(s.labelX, + Label{ + keyX: addString(strings, k), + strX: addString(strings, v), + }, + ) + } + } + var numKeys []string + for k := range s.NumLabel { + numKeys = append(numKeys, k) + } + sort.Strings(numKeys) + for _, k := range numKeys { + vs := s.NumLabel[k] + for _, v := range vs { + s.labelX = append(s.labelX, + Label{ + keyX: addString(strings, k), + numX: v, + }, + ) + } + } + s.locationIDX = nil + for _, l := range s.Location { + s.locationIDX = append(s.locationIDX, l.ID) + } + } + + for _, m := range p.Mapping { + m.fileX = addString(strings, m.File) + m.buildIDX = addString(strings, m.BuildID) + } + + for _, l := range p.Location { + for i, ln := range l.Line { + if ln.Function != nil { + l.Line[i].functionIDX = ln.Function.ID + } else { + l.Line[i].functionIDX = 0 + } + } + if l.Mapping != nil { + l.mappingIDX = l.Mapping.ID + } else { + l.mappingIDX = 0 + } + } + for _, f := range p.Function { + f.nameX = addString(strings, f.Name) + f.systemNameX = addString(strings, f.SystemName) + f.filenameX = addString(strings, f.Filename) + } + + p.dropFramesX = addString(strings, p.DropFrames) + p.keepFramesX = addString(strings, p.KeepFrames) + + if pt := p.PeriodType; pt != nil { + pt.typeX = addString(strings, pt.Type) + pt.unitX = addString(strings, pt.Unit) + } + + p.stringTable = make([]string, len(strings)) + for s, i := range strings { + p.stringTable[i] = s + } +} + +func (p *Profile) encode(b *buffer) { + for _, x := range p.SampleType { + encodeMessage(b, 1, x) + } + for _, x := range p.Sample { + encodeMessage(b, 2, x) + } + for _, x := range p.Mapping { + encodeMessage(b, 3, x) + } + for _, x := range p.Location { + encodeMessage(b, 4, x) + } + for _, x := range p.Function { + encodeMessage(b, 5, x) + } + encodeStrings(b, 6, p.stringTable) + encodeInt64Opt(b, 7, p.dropFramesX) + encodeInt64Opt(b, 8, p.keepFramesX) + encodeInt64Opt(b, 9, p.TimeNanos) + encodeInt64Opt(b, 10, p.DurationNanos) + if pt := p.PeriodType; pt != nil && (pt.typeX != 0 || pt.unitX != 0) { + encodeMessage(b, 11, p.PeriodType) + } + encodeInt64Opt(b, 12, p.Period) +} + +var profileDecoder = []decoder{ + nil, // 0 + // repeated ValueType sample_type = 1 + func(b *buffer, m message) error { + x := new(ValueType) + pp := m.(*Profile) + pp.SampleType = append(pp.SampleType, x) + return decodeMessage(b, x) + }, + // repeated Sample sample = 2 + func(b *buffer, m message) error { + x := new(Sample) + pp := m.(*Profile) + pp.Sample = append(pp.Sample, x) + return decodeMessage(b, x) + }, + // repeated Mapping mapping = 3 + func(b *buffer, m message) error { + x := new(Mapping) + pp := m.(*Profile) + pp.Mapping = append(pp.Mapping, x) + return decodeMessage(b, x) + }, + // repeated Location location = 4 + func(b *buffer, m message) error { + x := new(Location) + pp := m.(*Profile) + pp.Location = append(pp.Location, x) + return decodeMessage(b, x) + }, + // repeated Function function = 5 + func(b *buffer, m message) error { + x := new(Function) + pp := m.(*Profile) + pp.Function = append(pp.Function, x) + return decodeMessage(b, x) + }, + // repeated string string_table = 6 + func(b *buffer, m message) error { + err := decodeStrings(b, &m.(*Profile).stringTable) + if err != nil { + return err + } + if *&m.(*Profile).stringTable[0] != "" { + return errors.New("string_table[0] must be ''") + } + return nil + }, + // repeated int64 drop_frames = 7 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).dropFramesX) }, + // repeated int64 keep_frames = 8 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).keepFramesX) }, + // repeated int64 time_nanos = 9 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).TimeNanos) }, + // repeated int64 duration_nanos = 10 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).DurationNanos) }, + // optional string period_type = 11 + func(b *buffer, m message) error { + x := new(ValueType) + pp := m.(*Profile) + pp.PeriodType = x + return decodeMessage(b, x) + }, + // repeated int64 period = 12 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).Period) }, +} + +// postDecode takes the unexported fields populated by decode (with +// suffix X) and populates the corresponding exported fields. +// The unexported fields are cleared up to facilitate testing. +func (p *Profile) postDecode() error { + var err error + + mappings := make(map[uint64]*Mapping) + for _, m := range p.Mapping { + m.File, err = getString(p.stringTable, &m.fileX, err) + m.BuildID, err = getString(p.stringTable, &m.buildIDX, err) + mappings[m.ID] = m + } + + functions := make(map[uint64]*Function) + for _, f := range p.Function { + f.Name, err = getString(p.stringTable, &f.nameX, err) + f.SystemName, err = getString(p.stringTable, &f.systemNameX, err) + f.Filename, err = getString(p.stringTable, &f.filenameX, err) + functions[f.ID] = f + } + + locations := make(map[uint64]*Location) + for _, l := range p.Location { + l.Mapping = mappings[l.mappingIDX] + l.mappingIDX = 0 + for i, ln := range l.Line { + if id := ln.functionIDX; id != 0 { + l.Line[i].Function = functions[id] + if l.Line[i].Function == nil { + return fmt.Errorf("Function ID %d not found", id) + } + l.Line[i].functionIDX = 0 + } + } + locations[l.ID] = l + } + + for _, st := range p.SampleType { + st.Type, err = getString(p.stringTable, &st.typeX, err) + st.Unit, err = getString(p.stringTable, &st.unitX, err) + } + + for _, s := range p.Sample { + labels := make(map[string][]string) + numLabels := make(map[string][]int64) + for _, l := range s.labelX { + var key, value string + key, err = getString(p.stringTable, &l.keyX, err) + if l.strX != 0 { + value, err = getString(p.stringTable, &l.strX, err) + labels[key] = append(labels[key], value) + } else { + numLabels[key] = append(numLabels[key], l.numX) + } + } + if len(labels) > 0 { + s.Label = labels + } + if len(numLabels) > 0 { + s.NumLabel = numLabels + } + s.Location = nil + for _, lid := range s.locationIDX { + s.Location = append(s.Location, locations[lid]) + } + s.locationIDX = nil + } + + p.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err) + p.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err) + + if pt := p.PeriodType; pt == nil { + p.PeriodType = &ValueType{} + } + + if pt := p.PeriodType; pt != nil { + pt.Type, err = getString(p.stringTable, &pt.typeX, err) + pt.Unit, err = getString(p.stringTable, &pt.unitX, err) + } + p.stringTable = nil + return nil +} + +func (p *ValueType) decoder() []decoder { + return valueTypeDecoder +} + +func (p *ValueType) encode(b *buffer) { + encodeInt64Opt(b, 1, p.typeX) + encodeInt64Opt(b, 2, p.unitX) +} + +var valueTypeDecoder = []decoder{ + nil, // 0 + // optional int64 type = 1 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) }, + // optional int64 unit = 2 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) }, +} + +func (p *Sample) decoder() []decoder { + return sampleDecoder +} + +func (p *Sample) encode(b *buffer) { + encodeUint64s(b, 1, p.locationIDX) + for _, x := range p.Value { + encodeInt64(b, 2, x) + } + for _, x := range p.labelX { + encodeMessage(b, 3, x) + } +} + +var sampleDecoder = []decoder{ + nil, // 0 + // repeated uint64 location = 1 + func(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) }, + // repeated int64 value = 2 + func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) }, + // repeated Label label = 3 + func(b *buffer, m message) error { + s := m.(*Sample) + n := len(s.labelX) + s.labelX = append(s.labelX, Label{}) + return decodeMessage(b, &s.labelX[n]) + }, +} + +func (p Label) decoder() []decoder { + return labelDecoder +} + +func (p Label) encode(b *buffer) { + encodeInt64Opt(b, 1, p.keyX) + encodeInt64Opt(b, 2, p.strX) + encodeInt64Opt(b, 3, p.numX) +} + +var labelDecoder = []decoder{ + nil, // 0 + // optional int64 key = 1 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).keyX) }, + // optional int64 str = 2 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).strX) }, + // optional int64 num = 3 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).numX) }, +} + +func (p *Mapping) decoder() []decoder { + return mappingDecoder +} + +func (p *Mapping) encode(b *buffer) { + encodeUint64Opt(b, 1, p.ID) + encodeUint64Opt(b, 2, p.Start) + encodeUint64Opt(b, 3, p.Limit) + encodeUint64Opt(b, 4, p.Offset) + encodeInt64Opt(b, 5, p.fileX) + encodeInt64Opt(b, 6, p.buildIDX) + encodeBoolOpt(b, 7, p.HasFunctions) + encodeBoolOpt(b, 8, p.HasFilenames) + encodeBoolOpt(b, 9, p.HasLineNumbers) + encodeBoolOpt(b, 10, p.HasInlineFrames) +} + +var mappingDecoder = []decoder{ + nil, // 0 + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) }, // optional uint64 id = 1 + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) }, // optional uint64 memory_offset = 2 + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) }, // optional uint64 memory_limit = 3 + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) }, // optional uint64 file_offset = 4 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) }, // optional int64 filename = 5 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) }, // optional int64 build_id = 6 + func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) }, // optional bool has_functions = 7 + func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) }, // optional bool has_filenames = 8 + func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) }, // optional bool has_line_numbers = 9 + func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10 +} + +func (p *Location) decoder() []decoder { + return locationDecoder +} + +func (p *Location) encode(b *buffer) { + encodeUint64Opt(b, 1, p.ID) + encodeUint64Opt(b, 2, p.mappingIDX) + encodeUint64Opt(b, 3, p.Address) + for i := range p.Line { + encodeMessage(b, 4, &p.Line[i]) + } +} + +var locationDecoder = []decoder{ + nil, // 0 + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) }, // optional uint64 id = 1; + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2; + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) }, // optional uint64 address = 3; + func(b *buffer, m message) error { // repeated Line line = 4 + pp := m.(*Location) + n := len(pp.Line) + pp.Line = append(pp.Line, Line{}) + return decodeMessage(b, &pp.Line[n]) + }, +} + +func (p *Line) decoder() []decoder { + return lineDecoder +} + +func (p *Line) encode(b *buffer) { + encodeUint64Opt(b, 1, p.functionIDX) + encodeInt64Opt(b, 2, p.Line) +} + +var lineDecoder = []decoder{ + nil, // 0 + // optional uint64 function_id = 1 + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) }, + // optional int64 line = 2 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) }, +} + +func (p *Function) decoder() []decoder { + return functionDecoder +} + +func (p *Function) encode(b *buffer) { + encodeUint64Opt(b, 1, p.ID) + encodeInt64Opt(b, 2, p.nameX) + encodeInt64Opt(b, 3, p.systemNameX) + encodeInt64Opt(b, 4, p.filenameX) + encodeInt64Opt(b, 5, p.StartLine) +} + +var functionDecoder = []decoder{ + nil, // 0 + // optional uint64 id = 1 + func(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) }, + // optional int64 function_name = 2 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) }, + // optional int64 function_system_name = 3 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) }, + // repeated int64 filename = 4 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) }, + // optional int64 start_line = 5 + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) }, +} + +func addString(strings map[string]int, s string) int64 { + i, ok := strings[s] + if !ok { + i = len(strings) + strings[s] = i + } + return int64(i) +} + +func getString(strings []string, strng *int64, err error) (string, error) { + if err != nil { + return "", err + } + s := int(*strng) + if s < 0 || s >= len(strings) { + return "", errMalformed + } + *strng = 0 + return strings[s], nil +} diff --git a/src/cmd/internal/pprof/profile/filter.go b/src/cmd/internal/pprof/profile/filter.go new file mode 100644 index 0000000000..1baa096a49 --- /dev/null +++ b/src/cmd/internal/pprof/profile/filter.go @@ -0,0 +1,158 @@ +// Copyright 2014 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. + +// Implements methods to filter samples from profiles. + +package profile + +import "regexp" + +// FilterSamplesByName filters the samples in a profile and only keeps +// samples where at least one frame matches focus but none match ignore. +// Returns true is the corresponding regexp matched at least one sample. +func (p *Profile) FilterSamplesByName(focus, ignore, hide *regexp.Regexp) (fm, im, hm bool) { + focusOrIgnore := make(map[uint64]bool) + hidden := make(map[uint64]bool) + for _, l := range p.Location { + if ignore != nil && l.matchesName(ignore) { + im = true + focusOrIgnore[l.ID] = false + } else if focus == nil || l.matchesName(focus) { + fm = true + focusOrIgnore[l.ID] = true + } + if hide != nil && l.matchesName(hide) { + hm = true + l.Line = l.unmatchedLines(hide) + if len(l.Line) == 0 { + hidden[l.ID] = true + } + } + } + + s := make([]*Sample, 0, len(p.Sample)) + for _, sample := range p.Sample { + if focusedAndNotIgnored(sample.Location, focusOrIgnore) { + if len(hidden) > 0 { + var locs []*Location + for _, loc := range sample.Location { + if !hidden[loc.ID] { + locs = append(locs, loc) + } + } + if len(locs) == 0 { + // Remove sample with no locations (by not adding it to s). + continue + } + sample.Location = locs + } + s = append(s, sample) + } + } + p.Sample = s + + return +} + +// matchesName returns whether the function name or file in the +// location matches the regular expression. +func (loc *Location) matchesName(re *regexp.Regexp) bool { + for _, ln := range loc.Line { + if fn := ln.Function; fn != nil { + if re.MatchString(fn.Name) { + return true + } + if re.MatchString(fn.Filename) { + return true + } + } + } + return false +} + +// unmatchedLines returns the lines in the location that do not match +// the regular expression. +func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line { + var lines []Line + for _, ln := range loc.Line { + if fn := ln.Function; fn != nil { + if re.MatchString(fn.Name) { + continue + } + if re.MatchString(fn.Filename) { + continue + } + } + lines = append(lines, ln) + } + return lines +} + +// focusedAndNotIgnored looks up a slice of ids against a map of +// focused/ignored locations. The map only contains locations that are +// explicitly focused or ignored. Returns whether there is at least +// one focused location but no ignored locations. +func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool { + var f bool + for _, loc := range locs { + if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore { + if focus { + // Found focused location. Must keep searching in case there + // is an ignored one as well. + f = true + } else { + // Found ignored location. Can return false right away. + return false + } + } + } + return f +} + +// TagMatch selects tags for filtering +type TagMatch func(key, val string, nval int64) bool + +// FilterSamplesByTag removes all samples from the profile, except +// those that match focus and do not match the ignore regular +// expression. +func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) { + samples := make([]*Sample, 0, len(p.Sample)) + for _, s := range p.Sample { + focused, ignored := focusedSample(s, focus, ignore) + fm = fm || focused + im = im || ignored + if focused && !ignored { + samples = append(samples, s) + } + } + p.Sample = samples + return +} + +// focusedTag checks a sample against focus and ignore regexps. +// Returns whether the focus/ignore regexps match any tags +func focusedSample(s *Sample, focus, ignore TagMatch) (fm, im bool) { + fm = focus == nil + for key, vals := range s.Label { + for _, val := range vals { + if ignore != nil && ignore(key, val, 0) { + im = true + } + if !fm && focus(key, val, 0) { + fm = true + } + } + } + for key, vals := range s.NumLabel { + for _, val := range vals { + if ignore != nil && ignore(key, "", val) { + im = true + } + if !fm && focus(key, "", val) { + fm = true + } + } + } + return fm, im +} diff --git a/src/cmd/internal/pprof/profile/legacy_profile.go b/src/cmd/internal/pprof/profile/legacy_profile.go new file mode 100644 index 0000000000..e1f24c4c6d --- /dev/null +++ b/src/cmd/internal/pprof/profile/legacy_profile.go @@ -0,0 +1,1251 @@ +// Copyright 2014 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. + +// This file implements parsers to convert legacy profiles into the +// profile.proto format. + +package profile + +import ( + "bufio" + "bytes" + "fmt" + "io" + "math" + "regexp" + "strconv" + "strings" +) + +var ( + countStartRE = regexp.MustCompile(`\A(\w+) profile: total \d+\n\z`) + countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`) + + heapHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`) + heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`) + + contentionSampleRE = regexp.MustCompile(`(\d+) *(\d+) @([ x0-9a-f]*)`) + + hexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`) + + growthHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`) + + fragmentationHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`) + + threadzStartRE = regexp.MustCompile(`--- threadz \d+ ---`) + threadStartRE = regexp.MustCompile(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`) + + procMapsRE = regexp.MustCompile(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`) + + briefMapsRE = regexp.MustCompile(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`) + + // LegacyHeapAllocated instructs the heapz parsers to use the + // allocated memory stats instead of the default in-use memory. Note + // that tcmalloc doesn't provide all allocated memory, only in-use + // stats. + LegacyHeapAllocated bool +) + +func isSpaceOrComment(line string) bool { + trimmed := strings.TrimSpace(line) + return len(trimmed) == 0 || trimmed[0] == '#' +} + +// parseGoCount parses a Go count profile (e.g., threadcreate or +// goroutine) and returns a new Profile. +func parseGoCount(b []byte) (*Profile, error) { + r := bytes.NewBuffer(b) + + var line string + var err error + for { + // Skip past comments and empty lines seeking a real header. + line, err = r.ReadString('\n') + if err != nil { + return nil, err + } + if !isSpaceOrComment(line) { + break + } + } + + m := countStartRE.FindStringSubmatch(line) + if m == nil { + return nil, errUnrecognized + } + profileType := string(m[1]) + p := &Profile{ + PeriodType: &ValueType{Type: profileType, Unit: "count"}, + Period: 1, + SampleType: []*ValueType{{Type: profileType, Unit: "count"}}, + } + locations := make(map[uint64]*Location) + for { + line, err = r.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + if isSpaceOrComment(line) { + continue + } + if strings.HasPrefix(line, "---") { + break + } + m := countRE.FindStringSubmatch(line) + if m == nil { + return nil, errMalformed + } + n, err := strconv.ParseInt(string(m[1]), 0, 64) + if err != nil { + return nil, errMalformed + } + fields := strings.Fields(string(m[2])) + locs := make([]*Location, 0, len(fields)) + for _, stk := range fields { + addr, err := strconv.ParseUint(stk, 0, 64) + if err != nil { + return nil, errMalformed + } + // Adjust all frames by -1 (except the leaf) to land on top of + // the call instruction. + if len(locs) > 0 { + addr-- + } + loc := locations[addr] + if loc == nil { + loc = &Location{ + Address: addr, + } + locations[addr] = loc + p.Location = append(p.Location, loc) + } + locs = append(locs, loc) + } + p.Sample = append(p.Sample, &Sample{ + Location: locs, + Value: []int64{n}, + }) + } + + if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil { + return nil, err + } + return p, nil +} + +// remapLocationIDs ensures there is a location for each address +// referenced by a sample, and remaps the samples to point to the new +// location ids. +func (p *Profile) remapLocationIDs() { + seen := make(map[*Location]bool, len(p.Location)) + var locs []*Location + + for _, s := range p.Sample { + for _, l := range s.Location { + if seen[l] { + continue + } + l.ID = uint64(len(locs) + 1) + locs = append(locs, l) + seen[l] = true + } + } + p.Location = locs +} + +func (p *Profile) remapFunctionIDs() { + seen := make(map[*Function]bool, len(p.Function)) + var fns []*Function + + for _, l := range p.Location { + for _, ln := range l.Line { + fn := ln.Function + if fn == nil || seen[fn] { + continue + } + fn.ID = uint64(len(fns) + 1) + fns = append(fns, fn) + seen[fn] = true + } + } + p.Function = fns +} + +// remapMappingIDs matches location addresses with existing mappings +// and updates them appropriately. This is O(N*M), if this ever shows +// up as a bottleneck, evaluate sorting the mappings and doing a +// binary search, which would make it O(N*log(M)). +func (p *Profile) remapMappingIDs() { + if len(p.Mapping) == 0 { + return + } + + // Some profile handlers will incorrectly set regions for the main + // executable if its section is remapped. Fix them through heuristics. + + // Remove the initial mapping if named '/anon_hugepage' and has a + // consecutive adjacent mapping. + if m := p.Mapping[0]; strings.HasPrefix(m.File, "/anon_hugepage") { + if len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start { + p.Mapping = p.Mapping[1:] + } + } + + // Subtract the offset from the start of the main mapping if it + // ends up at a recognizable start address. + const expectedStart = 0x400000 + if m := p.Mapping[0]; m.Start-m.Offset == expectedStart { + m.Start = expectedStart + m.Offset = 0 + } + + for _, l := range p.Location { + if a := l.Address; a != 0 { + for _, m := range p.Mapping { + if m.Start <= a && a < m.Limit { + l.Mapping = m + break + } + } + } + } + + // Reset all mapping IDs. + for i, m := range p.Mapping { + m.ID = uint64(i + 1) + } +} + +var cpuInts = []func([]byte) (uint64, []byte){ + get32l, + get32b, + get64l, + get64b, +} + +func get32l(b []byte) (uint64, []byte) { + if len(b) < 4 { + return 0, nil + } + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:] +} + +func get32b(b []byte) (uint64, []byte) { + if len(b) < 4 { + return 0, nil + } + return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:] +} + +func get64l(b []byte) (uint64, []byte) { + if len(b) < 8 { + return 0, nil + } + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:] +} + +func get64b(b []byte) (uint64, []byte) { + if len(b) < 8 { + return 0, nil + } + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:] +} + +// ParseTracebacks parses a set of tracebacks and returns a newly +// populated profile. It will accept any text file and generate a +// Profile out of it with any hex addresses it can identify, including +// a process map if it can recognize one. Each sample will include a +// tag "source" with the addresses recognized in string format. +func ParseTracebacks(b []byte) (*Profile, error) { + r := bytes.NewBuffer(b) + + p := &Profile{ + PeriodType: &ValueType{Type: "trace", Unit: "count"}, + Period: 1, + SampleType: []*ValueType{ + {Type: "trace", Unit: "count"}, + }, + } + + var sources []string + var sloc []*Location + + locs := make(map[uint64]*Location) + for { + l, err := r.ReadString('\n') + if err != nil { + if err != io.EOF { + return nil, err + } + if l == "" { + break + } + } + if sectionTrigger(l) == memoryMapSection { + break + } + if s, addrs := extractHexAddresses(l); len(s) > 0 { + for _, addr := range addrs { + // Addresses from stack traces point to the next instruction after + // each call. Adjust by -1 to land somewhere on the actual call + // (except for the leaf, which is not a call). + if len(sloc) > 0 { + addr-- + } + loc := locs[addr] + if locs[addr] == nil { + loc = &Location{ + Address: addr, + } + p.Location = append(p.Location, loc) + locs[addr] = loc + } + sloc = append(sloc, loc) + } + + sources = append(sources, s...) + } else { + if len(sources) > 0 || len(sloc) > 0 { + addTracebackSample(sloc, sources, p) + sloc, sources = nil, nil + } + } + } + + // Add final sample to save any leftover data. + if len(sources) > 0 || len(sloc) > 0 { + addTracebackSample(sloc, sources, p) + } + + if err := p.ParseMemoryMap(r); err != nil { + return nil, err + } + return p, nil +} + +func addTracebackSample(l []*Location, s []string, p *Profile) { + p.Sample = append(p.Sample, + &Sample{ + Value: []int64{1}, + Location: l, + Label: map[string][]string{"source": s}, + }) +} + +// parseCPU parses a profilez legacy profile and returns a newly +// populated Profile. +// +// The general format for profilez samples is a sequence of words in +// binary format. The first words are a header with the following data: +// 1st word -- 0 +// 2nd word -- 3 +// 3rd word -- 0 if a c++ application, 1 if a java application. +// 4th word -- Sampling period (in microseconds). +// 5th word -- Padding. +func parseCPU(b []byte) (*Profile, error) { + var parse func([]byte) (uint64, []byte) + var n1, n2, n3, n4, n5 uint64 + for _, parse = range cpuInts { + var tmp []byte + n1, tmp = parse(b) + n2, tmp = parse(tmp) + n3, tmp = parse(tmp) + n4, tmp = parse(tmp) + n5, tmp = parse(tmp) + + if tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 { + b = tmp + return cpuProfile(b, int64(n4), parse) + } + } + return nil, errUnrecognized +} + +// cpuProfile returns a new Profile from C++ profilez data. +// b is the profile bytes after the header, period is the profiling +// period, and parse is a function to parse 8-byte chunks from the +// profile in its native endianness. +func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) { + p := &Profile{ + Period: period * 1000, + PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"}, + SampleType: []*ValueType{ + {Type: "samples", Unit: "count"}, + {Type: "cpu", Unit: "nanoseconds"}, + }, + } + var err error + if b, _, err = parseCPUSamples(b, parse, true, p); err != nil { + return nil, err + } + + // If all samples have the same second-to-the-bottom frame, it + // strongly suggests that it is an uninteresting artifact of + // measurement -- a stack frame pushed by the signal handler. The + // bottom frame is always correct as it is picked up from the signal + // structure, not the stack. Check if this is the case and if so, + // remove. + if len(p.Sample) > 1 && len(p.Sample[0].Location) > 1 { + allSame := true + id1 := p.Sample[0].Location[1].Address + for _, s := range p.Sample { + if len(s.Location) < 2 || id1 != s.Location[1].Address { + allSame = false + break + } + } + if allSame { + for _, s := range p.Sample { + s.Location = append(s.Location[:1], s.Location[2:]...) + } + } + } + + if err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil { + return nil, err + } + return p, nil +} + +// parseCPUSamples parses a collection of profilez samples from a +// profile. +// +// profilez samples are a repeated sequence of stack frames of the +// form: +// 1st word -- The number of times this stack was encountered. +// 2nd word -- The size of the stack (StackSize). +// 3rd word -- The first address on the stack. +// ... +// StackSize + 2 -- The last address on the stack +// The last stack trace is of the form: +// 1st word -- 0 +// 2nd word -- 1 +// 3rd word -- 0 +// +// Addresses from stack traces may point to the next instruction after +// each call. Optionally adjust by -1 to land somewhere on the actual +// call (except for the leaf, which is not a call). +func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) { + locs := make(map[uint64]*Location) + for len(b) > 0 { + var count, nstk uint64 + count, b = parse(b) + nstk, b = parse(b) + if b == nil || nstk > uint64(len(b)/4) { + return nil, nil, errUnrecognized + } + var sloc []*Location + addrs := make([]uint64, nstk) + for i := 0; i < int(nstk); i++ { + addrs[i], b = parse(b) + } + + if count == 0 && nstk == 1 && addrs[0] == 0 { + // End of data marker + break + } + for i, addr := range addrs { + if adjust && i > 0 { + addr-- + } + loc := locs[addr] + if loc == nil { + loc = &Location{ + Address: addr, + } + locs[addr] = loc + p.Location = append(p.Location, loc) + } + sloc = append(sloc, loc) + } + p.Sample = append(p.Sample, + &Sample{ + Value: []int64{int64(count), int64(count) * int64(p.Period)}, + Location: sloc, + }) + } + // Reached the end without finding the EOD marker. + return b, locs, nil +} + +// parseHeap parses a heapz legacy or a growthz profile and +// returns a newly populated Profile. +func parseHeap(b []byte) (p *Profile, err error) { + r := bytes.NewBuffer(b) + l, err := r.ReadString('\n') + if err != nil { + return nil, errUnrecognized + } + + sampling := "" + + if header := heapHeaderRE.FindStringSubmatch(l); header != nil { + p = &Profile{ + SampleType: []*ValueType{ + {Type: "objects", Unit: "count"}, + {Type: "space", Unit: "bytes"}, + }, + PeriodType: &ValueType{Type: "objects", Unit: "bytes"}, + } + + var period int64 + if len(header[6]) > 0 { + if period, err = strconv.ParseInt(string(header[6]), 10, 64); err != nil { + return nil, errUnrecognized + } + } + + switch header[5] { + case "heapz_v2", "heap_v2": + sampling, p.Period = "v2", period + case "heapprofile": + sampling, p.Period = "", 1 + case "heap": + sampling, p.Period = "v2", period/2 + default: + return nil, errUnrecognized + } + } else if header = growthHeaderRE.FindStringSubmatch(l); header != nil { + p = &Profile{ + SampleType: []*ValueType{ + {Type: "objects", Unit: "count"}, + {Type: "space", Unit: "bytes"}, + }, + PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"}, + Period: 1, + } + } else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil { + p = &Profile{ + SampleType: []*ValueType{ + {Type: "objects", Unit: "count"}, + {Type: "space", Unit: "bytes"}, + }, + PeriodType: &ValueType{Type: "allocations", Unit: "count"}, + Period: 1, + } + } else { + return nil, errUnrecognized + } + + if LegacyHeapAllocated { + for _, st := range p.SampleType { + st.Type = "alloc_" + st.Type + } + } else { + for _, st := range p.SampleType { + st.Type = "inuse_" + st.Type + } + } + + locs := make(map[uint64]*Location) + for { + l, err = r.ReadString('\n') + if err != nil { + if err != io.EOF { + return nil, err + } + + if l == "" { + break + } + } + + if isSpaceOrComment(l) { + continue + } + l = strings.TrimSpace(l) + + if sectionTrigger(l) != unrecognizedSection { + break + } + + value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling) + if err != nil { + return nil, err + } + var sloc []*Location + for i, addr := range addrs { + // Addresses from stack traces point to the next instruction after + // each call. Adjust by -1 to land somewhere on the actual call + // (except for the leaf, which is not a call). + if i > 0 { + addr-- + } + loc := locs[addr] + if locs[addr] == nil { + loc = &Location{ + Address: addr, + } + p.Location = append(p.Location, loc) + locs[addr] = loc + } + sloc = append(sloc, loc) + } + + p.Sample = append(p.Sample, &Sample{ + Value: value, + Location: sloc, + NumLabel: map[string][]int64{"bytes": {blocksize}}, + }) + } + + if err = parseAdditionalSections(l, r, p); err != nil { + return nil, err + } + return p, nil +} + +// parseHeapSample parses a single row from a heap profile into a new Sample. +func parseHeapSample(line string, rate int64, sampling string) (value []int64, blocksize int64, addrs []uint64, err error) { + sampleData := heapSampleRE.FindStringSubmatch(line) + if len(sampleData) != 6 { + return value, blocksize, addrs, fmt.Errorf("unexpected number of sample values: got %d, want 6", len(sampleData)) + } + + // Use first two values by default; tcmalloc sampling generates the + // same value for both, only the older heap-profile collect separate + // stats for in-use and allocated objects. + valueIndex := 1 + if LegacyHeapAllocated { + valueIndex = 3 + } + + var v1, v2 int64 + if v1, err = strconv.ParseInt(sampleData[valueIndex], 10, 64); err != nil { + return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) + } + if v2, err = strconv.ParseInt(sampleData[valueIndex+1], 10, 64); err != nil { + return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) + } + + if v1 == 0 { + if v2 != 0 { + return value, blocksize, addrs, fmt.Errorf("allocation count was 0 but allocation bytes was %d", v2) + } + } else { + blocksize = v2 / v1 + if sampling == "v2" { + v1, v2 = scaleHeapSample(v1, v2, rate) + } + } + + value = []int64{v1, v2} + addrs = parseHexAddresses(sampleData[5]) + + return value, blocksize, addrs, nil +} + +// extractHexAddresses extracts hex numbers from a string and returns +// them, together with their numeric value, in a slice. +func extractHexAddresses(s string) ([]string, []uint64) { + hexStrings := hexNumberRE.FindAllString(s, -1) + var ids []uint64 + for _, s := range hexStrings { + if id, err := strconv.ParseUint(s, 0, 64); err == nil { + ids = append(ids, id) + } else { + // Do not expect any parsing failures due to the regexp matching. + panic("failed to parse hex value:" + s) + } + } + return hexStrings, ids +} + +// parseHexAddresses parses hex numbers from a string and returns them +// in a slice. +func parseHexAddresses(s string) []uint64 { + _, ids := extractHexAddresses(s) + return ids +} + +// scaleHeapSample adjusts the data from a heapz Sample to +// account for its probability of appearing in the collected +// data. heapz profiles are a sampling of the memory allocations +// requests in a program. We estimate the unsampled value by dividing +// each collected sample by its probability of appearing in the +// profile. heapz v2 profiles rely on a poisson process to determine +// which samples to collect, based on the desired average collection +// rate R. The probability of a sample of size S to appear in that +// profile is 1-exp(-S/R). +func scaleHeapSample(count, size, rate int64) (int64, int64) { + if count == 0 || size == 0 { + return 0, 0 + } + + if rate <= 1 { + // if rate==1 all samples were collected so no adjustment is needed. + // if rate<1 treat as unknown and skip scaling. + return count, size + } + + avgSize := float64(size) / float64(count) + scale := 1 / (1 - math.Exp(-avgSize/float64(rate))) + + return int64(float64(count) * scale), int64(float64(size) * scale) +} + +// parseContention parses a contentionz profile and returns a newly +// populated Profile. +func parseContention(b []byte) (p *Profile, err error) { + r := bytes.NewBuffer(b) + l, err := r.ReadString('\n') + if err != nil { + return nil, errUnrecognized + } + + if !strings.HasPrefix(l, "--- contention") { + return nil, errUnrecognized + } + + p = &Profile{ + PeriodType: &ValueType{Type: "contentions", Unit: "count"}, + Period: 1, + SampleType: []*ValueType{ + {Type: "contentions", Unit: "count"}, + {Type: "delay", Unit: "nanoseconds"}, + }, + } + + var cpuHz int64 + // Parse text of the form "attribute = value" before the samples. + const delimiter = "=" + for { + l, err = r.ReadString('\n') + if err != nil { + if err != io.EOF { + return nil, err + } + + if l == "" { + break + } + } + + if l = strings.TrimSpace(l); l == "" { + continue + } + + if strings.HasPrefix(l, "---") { + break + } + + attr := strings.SplitN(l, delimiter, 2) + if len(attr) != 2 { + break + } + key, val := strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1]) + var err error + switch key { + case "cycles/second": + if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil { + return nil, errUnrecognized + } + case "sampling period": + if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil { + return nil, errUnrecognized + } + case "ms since reset": + ms, err := strconv.ParseInt(val, 0, 64) + if err != nil { + return nil, errUnrecognized + } + p.DurationNanos = ms * 1000 * 1000 + case "format": + // CPP contentionz profiles don't have format. + return nil, errUnrecognized + case "resolution": + // CPP contentionz profiles don't have resolution. + return nil, errUnrecognized + case "discarded samples": + default: + return nil, errUnrecognized + } + } + + locs := make(map[uint64]*Location) + for { + if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") { + break + } + value, addrs, err := parseContentionSample(l, p.Period, cpuHz) + if err != nil { + return nil, err + } + var sloc []*Location + for i, addr := range addrs { + // Addresses from stack traces point to the next instruction after + // each call. Adjust by -1 to land somewhere on the actual call + // (except for the leaf, which is not a call). + if i > 0 { + addr-- + } + loc := locs[addr] + if locs[addr] == nil { + loc = &Location{ + Address: addr, + } + p.Location = append(p.Location, loc) + locs[addr] = loc + } + sloc = append(sloc, loc) + } + p.Sample = append(p.Sample, &Sample{ + Value: value, + Location: sloc, + }) + + if l, err = r.ReadString('\n'); err != nil { + if err != io.EOF { + return nil, err + } + if l == "" { + break + } + } + } + + if err = parseAdditionalSections(l, r, p); err != nil { + return nil, err + } + + return p, nil +} + +// parseContentionSample parses a single row from a contention profile +// into a new Sample. +func parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) { + sampleData := contentionSampleRE.FindStringSubmatch(line) + if sampleData == nil { + return value, addrs, errUnrecognized + } + + v1, err := strconv.ParseInt(sampleData[1], 10, 64) + if err != nil { + return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) + } + v2, err := strconv.ParseInt(sampleData[2], 10, 64) + if err != nil { + return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) + } + + // Unsample values if period and cpuHz are available. + // - Delays are scaled to cycles and then to nanoseconds. + // - Contentions are scaled to cycles. + if period > 0 { + if cpuHz > 0 { + cpuGHz := float64(cpuHz) / 1e9 + v1 = int64(float64(v1) * float64(period) / cpuGHz) + } + v2 = v2 * period + } + + value = []int64{v2, v1} + addrs = parseHexAddresses(sampleData[3]) + + return value, addrs, nil +} + +// parseThread parses a Threadz profile and returns a new Profile. +func parseThread(b []byte) (*Profile, error) { + r := bytes.NewBuffer(b) + + var line string + var err error + for { + // Skip past comments and empty lines seeking a real header. + line, err = r.ReadString('\n') + if err != nil { + return nil, err + } + if !isSpaceOrComment(line) { + break + } + } + + if m := threadzStartRE.FindStringSubmatch(line); m != nil { + // Advance over initial comments until first stack trace. + for { + line, err = r.ReadString('\n') + if err != nil { + if err != io.EOF { + return nil, err + } + + if line == "" { + break + } + } + if sectionTrigger(line) != unrecognizedSection || line[0] == '-' { + break + } + } + } else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { + return nil, errUnrecognized + } + + p := &Profile{ + SampleType: []*ValueType{{Type: "thread", Unit: "count"}}, + PeriodType: &ValueType{Type: "thread", Unit: "count"}, + Period: 1, + } + + locs := make(map[uint64]*Location) + // Recognize each thread and populate profile samples. + for sectionTrigger(line) == unrecognizedSection { + if strings.HasPrefix(line, "---- no stack trace for") { + line = "" + break + } + if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { + return nil, errUnrecognized + } + + var addrs []uint64 + line, addrs, err = parseThreadSample(r) + if err != nil { + return nil, errUnrecognized + } + if len(addrs) == 0 { + // We got a --same as previous threads--. Bump counters. + if len(p.Sample) > 0 { + s := p.Sample[len(p.Sample)-1] + s.Value[0]++ + } + continue + } + + var sloc []*Location + for i, addr := range addrs { + // Addresses from stack traces point to the next instruction after + // each call. Adjust by -1 to land somewhere on the actual call + // (except for the leaf, which is not a call). + if i > 0 { + addr-- + } + loc := locs[addr] + if locs[addr] == nil { + loc = &Location{ + Address: addr, + } + p.Location = append(p.Location, loc) + locs[addr] = loc + } + sloc = append(sloc, loc) + } + + p.Sample = append(p.Sample, &Sample{ + Value: []int64{1}, + Location: sloc, + }) + } + + if err = parseAdditionalSections(line, r, p); err != nil { + return nil, err + } + + return p, nil +} + +// parseThreadSample parses a symbolized or unsymbolized stack trace. +// Returns the first line after the traceback, the sample (or nil if +// it hits a 'same-as-previous' marker) and an error. +func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) { + var l string + sameAsPrevious := false + for { + if l, err = b.ReadString('\n'); err != nil { + if err != io.EOF { + return "", nil, err + } + if l == "" { + break + } + } + if l = strings.TrimSpace(l); l == "" { + continue + } + + if strings.HasPrefix(l, "---") { + break + } + if strings.Contains(l, "same as previous thread") { + sameAsPrevious = true + continue + } + + addrs = append(addrs, parseHexAddresses(l)...) + } + + if sameAsPrevious { + return l, nil, nil + } + return l, addrs, nil +} + +// parseAdditionalSections parses any additional sections in the +// profile, ignoring any unrecognized sections. +func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) (err error) { + for { + if sectionTrigger(l) == memoryMapSection { + break + } + // Ignore any unrecognized sections. + if l, err := b.ReadString('\n'); err != nil { + if err != io.EOF { + return err + } + if l == "" { + break + } + } + } + return p.ParseMemoryMap(b) +} + +// ParseMemoryMap parses a memory map in the format of +// /proc/self/maps, and overrides the mappings in the current profile. +// It renumbers the samples and locations in the profile correspondingly. +func (p *Profile) ParseMemoryMap(rd io.Reader) error { + b := bufio.NewReader(rd) + + var attrs []string + var r *strings.Replacer + const delimiter = "=" + for { + l, err := b.ReadString('\n') + if err != nil { + if err != io.EOF { + return err + } + if l == "" { + break + } + } + if l = strings.TrimSpace(l); l == "" { + continue + } + + if r != nil { + l = r.Replace(l) + } + m, err := parseMappingEntry(l) + if err != nil { + if err == errUnrecognized { + // Recognize assignments of the form: attr=value, and replace + // $attr with value on subsequent mappings. + if attr := strings.SplitN(l, delimiter, 2); len(attr) == 2 { + attrs = append(attrs, "$"+strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1])) + r = strings.NewReplacer(attrs...) + } + // Ignore any unrecognized entries + continue + } + return err + } + if m == nil || (m.File == "" && len(p.Mapping) != 0) { + // In some cases the first entry may include the address range + // but not the name of the file. It should be followed by + // another entry with the name. + continue + } + if len(p.Mapping) == 1 && p.Mapping[0].File == "" { + // Update the name if this is the entry following that empty one. + p.Mapping[0].File = m.File + continue + } + p.Mapping = append(p.Mapping, m) + } + p.remapLocationIDs() + p.remapFunctionIDs() + p.remapMappingIDs() + return nil +} + +func parseMappingEntry(l string) (*Mapping, error) { + mapping := &Mapping{} + var err error + if me := procMapsRE.FindStringSubmatch(l); len(me) == 9 { + if !strings.Contains(me[3], "x") { + // Skip non-executable entries. + return nil, nil + } + if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { + return nil, errUnrecognized + } + if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { + return nil, errUnrecognized + } + if me[4] != "" { + if mapping.Offset, err = strconv.ParseUint(me[4], 16, 64); err != nil { + return nil, errUnrecognized + } + } + mapping.File = me[8] + return mapping, nil + } + + if me := briefMapsRE.FindStringSubmatch(l); len(me) == 6 { + if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { + return nil, errUnrecognized + } + if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { + return nil, errUnrecognized + } + mapping.File = me[3] + if me[5] != "" { + if mapping.Offset, err = strconv.ParseUint(me[5], 16, 64); err != nil { + return nil, errUnrecognized + } + } + return mapping, nil + } + + return nil, errUnrecognized +} + +type sectionType int + +const ( + unrecognizedSection sectionType = iota + memoryMapSection +) + +var memoryMapTriggers = []string{ + "--- Memory map: ---", + "MAPPED_LIBRARIES:", +} + +func sectionTrigger(line string) sectionType { + for _, trigger := range memoryMapTriggers { + if strings.Contains(line, trigger) { + return memoryMapSection + } + } + return unrecognizedSection +} + +func (p *Profile) addLegacyFrameInfo() { + switch { + case isProfileType(p, heapzSampleTypes) || + isProfileType(p, heapzInUseSampleTypes) || + isProfileType(p, heapzAllocSampleTypes): + p.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr + case isProfileType(p, contentionzSampleTypes): + p.DropFrames, p.KeepFrames = lockRxStr, "" + default: + p.DropFrames, p.KeepFrames = cpuProfilerRxStr, "" + } +} + +var heapzSampleTypes = []string{"allocations", "size"} // early Go pprof profiles +var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"} +var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"} +var contentionzSampleTypes = []string{"contentions", "delay"} + +func isProfileType(p *Profile, t []string) bool { + st := p.SampleType + if len(st) != len(t) { + return false + } + + for i := range st { + if st[i].Type != t[i] { + return false + } + } + return true +} + +var allocRxStr = strings.Join([]string{ + // POSIX entry points. + `calloc`, + `cfree`, + `malloc`, + `free`, + `memalign`, + `do_memalign`, + `(__)?posix_memalign`, + `pvalloc`, + `valloc`, + `realloc`, + + // TC malloc. + `tcmalloc::.*`, + `tc_calloc`, + `tc_cfree`, + `tc_malloc`, + `tc_free`, + `tc_memalign`, + `tc_posix_memalign`, + `tc_pvalloc`, + `tc_valloc`, + `tc_realloc`, + `tc_new`, + `tc_delete`, + `tc_newarray`, + `tc_deletearray`, + `tc_new_nothrow`, + `tc_newarray_nothrow`, + + // Memory-allocation routines on OS X. + `malloc_zone_malloc`, + `malloc_zone_calloc`, + `malloc_zone_valloc`, + `malloc_zone_realloc`, + `malloc_zone_memalign`, + `malloc_zone_free`, + + // Go runtime + `runtime\..*`, + + // Other misc. memory allocation routines + `BaseArena::.*`, + `(::)?do_malloc_no_errno`, + `(::)?do_malloc_pages`, + `(::)?do_malloc`, + `DoSampledAllocation`, + `MallocedMemBlock::MallocedMemBlock`, + `_M_allocate`, + `__builtin_(vec_)?delete`, + `__builtin_(vec_)?new`, + `__gnu_cxx::new_allocator::allocate`, + `__libc_malloc`, + `__malloc_alloc_template::allocate`, + `allocate`, + `cpp_alloc`, + `operator new(\[\])?`, + `simple_alloc::allocate`, +}, `|`) + +var allocSkipRxStr = strings.Join([]string{ + // Preserve Go runtime frames that appear in the middle/bottom of + // the stack. + `runtime\.panic`, +}, `|`) + +var cpuProfilerRxStr = strings.Join([]string{ + `ProfileData::Add`, + `ProfileData::prof_handler`, + `CpuProfiler::prof_handler`, + `__pthread_sighandler`, + `__restore`, +}, `|`) + +var lockRxStr = strings.Join([]string{ + `RecordLockProfileData`, + `(base::)?RecordLockProfileData.*`, + `(base::)?SubmitMutexProfileData.*`, + `(base::)?SubmitSpinLockProfileData.*`, + `(Mutex::)?AwaitCommon.*`, + `(Mutex::)?Unlock.*`, + `(Mutex::)?UnlockSlow.*`, + `(Mutex::)?ReaderUnlock.*`, + `(MutexLock::)?~MutexLock.*`, + `(SpinLock::)?Unlock.*`, + `(SpinLock::)?SlowUnlock.*`, + `(SpinLockHolder::)?~SpinLockHolder.*`, +}, `|`) diff --git a/src/cmd/internal/pprof/profile/profile.go b/src/cmd/internal/pprof/profile/profile.go new file mode 100644 index 0000000000..28e713d7be --- /dev/null +++ b/src/cmd/internal/pprof/profile/profile.go @@ -0,0 +1,572 @@ +// Copyright 2014 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 profile provides a representation of profile.proto and +// methods to encode/decode profiles in this format. +package profile + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "regexp" + "strings" + "time" +) + +// Profile is an in-memory representation of profile.proto. +type Profile struct { + SampleType []*ValueType + Sample []*Sample + Mapping []*Mapping + Location []*Location + Function []*Function + + DropFrames string + KeepFrames string + + TimeNanos int64 + DurationNanos int64 + PeriodType *ValueType + Period int64 + + dropFramesX int64 + keepFramesX int64 + stringTable []string +} + +// ValueType corresponds to Profile.ValueType +type ValueType struct { + Type string // cpu, wall, inuse_space, etc + Unit string // seconds, nanoseconds, bytes, etc + + typeX int64 + unitX int64 +} + +// Sample corresponds to Profile.Sample +type Sample struct { + Location []*Location + Value []int64 + Label map[string][]string + NumLabel map[string][]int64 + + locationIDX []uint64 + labelX []Label +} + +// Label corresponds to Profile.Label +type Label struct { + keyX int64 + // Exactly one of the two following values must be set + strX int64 + numX int64 // Integer value for this label +} + +// Mapping corresponds to Profile.Mapping +type Mapping struct { + ID uint64 + Start uint64 + Limit uint64 + Offset uint64 + File string + BuildID string + HasFunctions bool + HasFilenames bool + HasLineNumbers bool + HasInlineFrames bool + + fileX int64 + buildIDX int64 +} + +// Location corresponds to Profile.Location +type Location struct { + ID uint64 + Mapping *Mapping + Address uint64 + Line []Line + + mappingIDX uint64 +} + +// Line corresponds to Profile.Line +type Line struct { + Function *Function + Line int64 + + functionIDX uint64 +} + +// Function corresponds to Profile.Function +type Function struct { + ID uint64 + Name string + SystemName string + Filename string + StartLine int64 + + nameX int64 + systemNameX int64 + filenameX int64 +} + +// Parse parses a profile and checks for its validity. The input +// may be a gzip-compressed encoded protobuf or one of many legacy +// profile formats which may be unsupported in the future. +func Parse(r io.Reader) (*Profile, error) { + orig, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + var p *Profile + if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b { + gz, err := gzip.NewReader(bytes.NewBuffer(orig)) + if err != nil { + return nil, fmt.Errorf("decompressing profile: %v", err) + } + data, err := ioutil.ReadAll(gz) + if err != nil { + return nil, fmt.Errorf("decompressing profile: %v", err) + } + orig = data + } + if p, err = parseUncompressed(orig); err != nil { + if p, err = parseLegacy(orig); err != nil { + return nil, fmt.Errorf("parsing profile: %v", err) + } + } + + if err := p.CheckValid(); err != nil { + return nil, fmt.Errorf("malformed profile: %v", err) + } + return p, nil +} + +var errUnrecognized = fmt.Errorf("unrecognized profile format") +var errMalformed = fmt.Errorf("malformed profile format") + +func parseLegacy(data []byte) (*Profile, error) { + parsers := []func([]byte) (*Profile, error){ + parseCPU, + parseHeap, + parseGoCount, // goroutine, threadcreate + parseThread, + parseContention, + } + + for _, parser := range parsers { + p, err := parser(data) + if err == nil { + p.setMain() + p.addLegacyFrameInfo() + return p, nil + } + if err != errUnrecognized { + return nil, err + } + } + return nil, errUnrecognized +} + +func parseUncompressed(data []byte) (*Profile, error) { + p := &Profile{} + if err := unmarshal(data, p); err != nil { + return nil, err + } + + if err := p.postDecode(); err != nil { + return nil, err + } + + return p, nil +} + +var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`) + +// setMain scans Mapping entries and guesses which entry is main +// because legacy profiles don't obey the convention of putting main +// first. +func (p *Profile) setMain() { + for i := 0; i < len(p.Mapping); i++ { + file := strings.TrimSpace(strings.Replace(p.Mapping[i].File, "(deleted)", "", -1)) + if len(file) == 0 { + continue + } + if len(libRx.FindStringSubmatch(file)) > 0 { + continue + } + if strings.HasPrefix(file, "[") { + continue + } + // Swap what we guess is main to position 0. + tmp := p.Mapping[i] + p.Mapping[i] = p.Mapping[0] + p.Mapping[0] = tmp + break + } +} + +// Write writes the profile as a gzip-compressed marshaled protobuf. +func (p *Profile) Write(w io.Writer) error { + p.preEncode() + b := marshal(p) + zw := gzip.NewWriter(w) + defer zw.Close() + _, err := zw.Write(b) + return err +} + +// CheckValid tests whether the profile is valid. Checks include, but are +// not limited to: +// - len(Profile.Sample[n].value) == len(Profile.value_unit) +// - Sample.id has a corresponding Profile.Location +func (p *Profile) CheckValid() error { + // Check that sample values are consistent + sampleLen := len(p.SampleType) + if sampleLen == 0 && len(p.Sample) != 0 { + return fmt.Errorf("missing sample type information") + } + for _, s := range p.Sample { + if len(s.Value) != sampleLen { + return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType)) + } + } + + // Check that all mappings/locations/functions are in the tables + // Check that there are no duplicate ids + mappings := make(map[uint64]*Mapping, len(p.Mapping)) + for _, m := range p.Mapping { + if m.ID == 0 { + return fmt.Errorf("found mapping with reserved ID=0") + } + if mappings[m.ID] != nil { + return fmt.Errorf("multiple mappings with same id: %d", m.ID) + } + mappings[m.ID] = m + } + functions := make(map[uint64]*Function, len(p.Function)) + for _, f := range p.Function { + if f.ID == 0 { + return fmt.Errorf("found function with reserved ID=0") + } + if functions[f.ID] != nil { + return fmt.Errorf("multiple functions with same id: %d", f.ID) + } + functions[f.ID] = f + } + locations := make(map[uint64]*Location, len(p.Location)) + for _, l := range p.Location { + if l.ID == 0 { + return fmt.Errorf("found location with reserved id=0") + } + if locations[l.ID] != nil { + return fmt.Errorf("multiple locations with same id: %d", l.ID) + } + locations[l.ID] = l + if m := l.Mapping; m != nil { + if m.ID == 0 || mappings[m.ID] != m { + return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID) + } + } + for _, ln := range l.Line { + if f := ln.Function; f != nil { + if f.ID == 0 || functions[f.ID] != f { + return fmt.Errorf("inconsistent function %p: %d", f, f.ID) + } + } + } + } + return nil +} + +// Aggregate merges the locations in the profile into equivalence +// classes preserving the request attributes. It also updates the +// samples to point to the merged locations. +func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error { + for _, m := range p.Mapping { + m.HasInlineFrames = m.HasInlineFrames && inlineFrame + m.HasFunctions = m.HasFunctions && function + m.HasFilenames = m.HasFilenames && filename + m.HasLineNumbers = m.HasLineNumbers && linenumber + } + + // Aggregate functions + if !function || !filename { + for _, f := range p.Function { + if !function { + f.Name = "" + f.SystemName = "" + } + if !filename { + f.Filename = "" + } + } + } + + // Aggregate locations + if !inlineFrame || !address || !linenumber { + for _, l := range p.Location { + if !inlineFrame && len(l.Line) > 1 { + l.Line = l.Line[len(l.Line)-1:] + } + if !linenumber { + for i := range l.Line { + l.Line[i].Line = 0 + } + } + if !address { + l.Address = 0 + } + } + } + + return p.CheckValid() +} + +// Print dumps a text representation of a profile. Intended mainly +// for debugging purposes. +func (p *Profile) String() string { + + ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location)) + if pt := p.PeriodType; pt != nil { + ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit)) + } + ss = append(ss, fmt.Sprintf("Period: %d", p.Period)) + if p.TimeNanos != 0 { + ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos))) + } + if p.DurationNanos != 0 { + ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos))) + } + + ss = append(ss, "Samples:") + var sh1 string + for _, s := range p.SampleType { + sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit) + } + ss = append(ss, strings.TrimSpace(sh1)) + for _, s := range p.Sample { + var sv string + for _, v := range s.Value { + sv = fmt.Sprintf("%s %10d", sv, v) + } + sv = sv + ": " + for _, l := range s.Location { + sv = sv + fmt.Sprintf("%d ", l.ID) + } + ss = append(ss, sv) + const labelHeader = " " + if len(s.Label) > 0 { + ls := labelHeader + for k, v := range s.Label { + ls = ls + fmt.Sprintf("%s:%v ", k, v) + } + ss = append(ss, ls) + } + if len(s.NumLabel) > 0 { + ls := labelHeader + for k, v := range s.NumLabel { + ls = ls + fmt.Sprintf("%s:%v ", k, v) + } + ss = append(ss, ls) + } + } + + ss = append(ss, "Locations") + for _, l := range p.Location { + locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address) + if m := l.Mapping; m != nil { + locStr = locStr + fmt.Sprintf("M=%d ", m.ID) + } + if len(l.Line) == 0 { + ss = append(ss, locStr) + } + for li := range l.Line { + lnStr := "??" + if fn := l.Line[li].Function; fn != nil { + lnStr = fmt.Sprintf("%s %s:%d s=%d", + fn.Name, + fn.Filename, + l.Line[li].Line, + fn.StartLine) + if fn.Name != fn.SystemName { + lnStr = lnStr + "(" + fn.SystemName + ")" + } + } + ss = append(ss, locStr+lnStr) + // Do not print location details past the first line + locStr = " " + } + } + + ss = append(ss, "Mappings") + for _, m := range p.Mapping { + bits := "" + if m.HasFunctions { + bits = bits + "[FN]" + } + if m.HasFilenames { + bits = bits + "[FL]" + } + if m.HasLineNumbers { + bits = bits + "[LN]" + } + if m.HasInlineFrames { + bits = bits + "[IN]" + } + ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s", + m.ID, + m.Start, m.Limit, m.Offset, + m.File, + m.BuildID, + bits)) + } + + return strings.Join(ss, "\n") + "\n" +} + +// Merge adds profile p adjusted by ratio r into profile p. Profiles +// must be compatible (same Type and SampleType). +// TODO(rsilvera): consider normalizing the profiles based on the +// total samples collected. +func (p *Profile) Merge(pb *Profile, r float64) error { + if err := p.Compatible(pb); err != nil { + return err + } + + pb = pb.Copy() + + // Keep the largest of the two periods. + if pb.Period > p.Period { + p.Period = pb.Period + } + + p.DurationNanos += pb.DurationNanos + + p.Mapping = append(p.Mapping, pb.Mapping...) + for i, m := range p.Mapping { + m.ID = uint64(i + 1) + } + p.Location = append(p.Location, pb.Location...) + for i, l := range p.Location { + l.ID = uint64(i + 1) + } + p.Function = append(p.Function, pb.Function...) + for i, f := range p.Function { + f.ID = uint64(i + 1) + } + + if r != 1.0 { + for _, s := range pb.Sample { + for i, v := range s.Value { + s.Value[i] = int64((float64(v) * r)) + } + } + } + p.Sample = append(p.Sample, pb.Sample...) + return p.CheckValid() +} + +// Compatible determines if two profiles can be compared/merged. +// returns nil if the profiles are compatible; otherwise an error with +// details on the incompatibility. +func (p *Profile) Compatible(pb *Profile) error { + if !compatibleValueTypes(p.PeriodType, pb.PeriodType) { + return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType) + } + + if len(p.SampleType) != len(pb.SampleType) { + return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) + } + + for i := range p.SampleType { + if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) { + return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) + } + } + + return nil +} + +// HasFunctions determines if all locations in this profile have +// symbolized function information. +func (p *Profile) HasFunctions() bool { + for _, l := range p.Location { + if l.Mapping == nil || !l.Mapping.HasFunctions { + return false + } + } + return true +} + +// HasFileLines determines if all locations in this profile have +// symbolized file and line number information. +func (p *Profile) HasFileLines() bool { + for _, l := range p.Location { + if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) { + return false + } + } + return true +} + +func compatibleValueTypes(v1, v2 *ValueType) bool { + if v1 == nil || v2 == nil { + return true // No grounds to disqualify. + } + return v1.Type == v2.Type && v1.Unit == v2.Unit +} + +// Copy makes a fully independent copy of a profile. +func (p *Profile) Copy() *Profile { + p.preEncode() + b := marshal(p) + + pp := &Profile{} + if err := unmarshal(b, pp); err != nil { + panic(err) + } + if err := pp.postDecode(); err != nil { + panic(err) + } + + return pp +} + +// Demangler maps symbol names to a human-readable form. This may +// include C++ demangling and additional simplification. Names that +// are not demangled may be missing from the resulting map. +type Demangler func(name []string) (map[string]string, error) + +// Demangle attempts to demangle and optionally simplify any function +// names referenced in the profile. It works on a best-effort basis: +// it will silently preserve the original names in case of any errors. +func (p *Profile) Demangle(d Demangler) error { + // Collect names to demangle. + var names []string + for _, fn := range p.Function { + names = append(names, fn.SystemName) + } + + // Update profile with demangled names. + demangled, err := d(names) + if err != nil { + return err + } + for _, fn := range p.Function { + if dd, ok := demangled[fn.SystemName]; ok { + fn.Name = dd + } + } + return nil +} + +// Empty returns true if the profile contains no samples. +func (p *Profile) Empty() bool { + return len(p.Sample) == 0 +} diff --git a/src/cmd/internal/pprof/profile/profile_test.go b/src/cmd/internal/pprof/profile/profile_test.go new file mode 100644 index 0000000000..09b11a456f --- /dev/null +++ b/src/cmd/internal/pprof/profile/profile_test.go @@ -0,0 +1,24 @@ +// Copyright 2015 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 profile + +import ( + "bytes" + "testing" +) + +func TestEmptyProfile(t *testing.T) { + var buf bytes.Buffer + p, err := Parse(&buf) + if err != nil { + t.Error("Want no error, got", err) + } + if p == nil { + t.Fatal("Want a valid profile, got ") + } + if !p.Empty() { + t.Errorf("Profile should be empty, got %#v", p) + } +} diff --git a/src/cmd/internal/pprof/profile/proto.go b/src/cmd/internal/pprof/profile/proto.go new file mode 100644 index 0000000000..11d7f9ff9b --- /dev/null +++ b/src/cmd/internal/pprof/profile/proto.go @@ -0,0 +1,360 @@ +// Copyright 2014 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. + +// This file is a simple protocol buffer encoder and decoder. +// +// A protocol message must implement the message interface: +// decoder() []decoder +// encode(*buffer) +// +// The decode method returns a slice indexed by field number that gives the +// function to decode that field. +// The encode method encodes its receiver into the given buffer. +// +// The two methods are simple enough to be implemented by hand rather than +// by using a protocol compiler. +// +// See profile.go for examples of messages implementing this interface. +// +// There is no support for groups, message sets, or "has" bits. + +package profile + +import "errors" + +type buffer struct { + field int + typ int + u64 uint64 + data []byte + tmp [16]byte +} + +type decoder func(*buffer, message) error + +type message interface { + decoder() []decoder + encode(*buffer) +} + +func marshal(m message) []byte { + var b buffer + m.encode(&b) + return b.data +} + +func encodeVarint(b *buffer, x uint64) { + for x >= 128 { + b.data = append(b.data, byte(x)|0x80) + x >>= 7 + } + b.data = append(b.data, byte(x)) +} + +func encodeLength(b *buffer, tag int, len int) { + encodeVarint(b, uint64(tag)<<3|2) + encodeVarint(b, uint64(len)) +} + +func encodeUint64(b *buffer, tag int, x uint64) { + // append varint to b.data + encodeVarint(b, uint64(tag)<<3|0) + encodeVarint(b, x) +} + +func encodeUint64s(b *buffer, tag int, x []uint64) { + if len(x) > 2 { + // Use packed encoding + n1 := len(b.data) + for _, u := range x { + encodeVarint(b, u) + } + n2 := len(b.data) + encodeLength(b, tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) + return + } + for _, u := range x { + encodeUint64(b, tag, u) + } +} + +func encodeUint64Opt(b *buffer, tag int, x uint64) { + if x == 0 { + return + } + encodeUint64(b, tag, x) +} + +func encodeInt64(b *buffer, tag int, x int64) { + u := uint64(x) + encodeUint64(b, tag, u) +} + +func encodeInt64Opt(b *buffer, tag int, x int64) { + if x == 0 { + return + } + encodeInt64(b, tag, x) +} + +func encodeInt64s(b *buffer, tag int, x []int64) { + if len(x) > 2 { + // Use packed encoding + n1 := len(b.data) + for _, u := range x { + encodeVarint(b, uint64(u)) + } + n2 := len(b.data) + encodeLength(b, tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) + return + } + for _, u := range x { + encodeInt64(b, tag, u) + } +} + +func encodeString(b *buffer, tag int, x string) { + encodeLength(b, tag, len(x)) + b.data = append(b.data, x...) +} + +func encodeStrings(b *buffer, tag int, x []string) { + for _, s := range x { + encodeString(b, tag, s) + } +} + +func encodeStringOpt(b *buffer, tag int, x string) { + if x == "" { + return + } + encodeString(b, tag, x) +} + +func encodeBool(b *buffer, tag int, x bool) { + if x { + encodeUint64(b, tag, 1) + } else { + encodeUint64(b, tag, 0) + } +} + +func encodeBoolOpt(b *buffer, tag int, x bool) { + if x == false { + return + } + encodeBool(b, tag, x) +} + +func encodeMessage(b *buffer, tag int, m message) { + n1 := len(b.data) + m.encode(b) + n2 := len(b.data) + encodeLength(b, tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) +} + +func unmarshal(data []byte, m message) (err error) { + b := buffer{data: data, typ: 2} + return decodeMessage(&b, m) +} + +func le64(p []byte) uint64 { + return uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 | uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56 +} + +func le32(p []byte) uint32 { + return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24 +} + +func decodeVarint(data []byte) (uint64, []byte, error) { + var i int + var u uint64 + for i = 0; ; i++ { + if i >= 10 || i >= len(data) { + return 0, nil, errors.New("bad varint") + } + u |= uint64(data[i]&0x7F) << uint(7*i) + if data[i]&0x80 == 0 { + return u, data[i+1:], nil + } + } +} + +func decodeField(b *buffer, data []byte) ([]byte, error) { + x, data, err := decodeVarint(data) + if err != nil { + return nil, err + } + b.field = int(x >> 3) + b.typ = int(x & 7) + b.data = nil + b.u64 = 0 + switch b.typ { + case 0: + b.u64, data, err = decodeVarint(data) + if err != nil { + return nil, err + } + case 1: + if len(data) < 8 { + return nil, errors.New("not enough data") + } + b.u64 = le64(data[:8]) + data = data[8:] + case 2: + var n uint64 + n, data, err = decodeVarint(data) + if err != nil { + return nil, err + } + if n > uint64(len(data)) { + return nil, errors.New("too much data") + } + b.data = data[:n] + data = data[n:] + case 5: + if len(data) < 4 { + return nil, errors.New("not enough data") + } + b.u64 = uint64(le32(data[:4])) + data = data[4:] + default: + return nil, errors.New("unknown type: " + string(b.typ)) + } + + return data, nil +} + +func checkType(b *buffer, typ int) error { + if b.typ != typ { + return errors.New("type mismatch") + } + return nil +} + +func decodeMessage(b *buffer, m message) error { + if err := checkType(b, 2); err != nil { + return err + } + dec := m.decoder() + data := b.data + for len(data) > 0 { + // pull varint field# + type + var err error + data, err = decodeField(b, data) + if err != nil { + return err + } + if b.field >= len(dec) || dec[b.field] == nil { + continue + } + if err := dec[b.field](b, m); err != nil { + return err + } + } + return nil +} + +func decodeInt64(b *buffer, x *int64) error { + if err := checkType(b, 0); err != nil { + return err + } + *x = int64(b.u64) + return nil +} + +func decodeInt64s(b *buffer, x *[]int64) error { + if b.typ == 2 { + // Packed encoding + data := b.data + for len(data) > 0 { + var u uint64 + var err error + + if u, data, err = decodeVarint(data); err != nil { + return err + } + *x = append(*x, int64(u)) + } + return nil + } + var i int64 + if err := decodeInt64(b, &i); err != nil { + return err + } + *x = append(*x, i) + return nil +} + +func decodeUint64(b *buffer, x *uint64) error { + if err := checkType(b, 0); err != nil { + return err + } + *x = b.u64 + return nil +} + +func decodeUint64s(b *buffer, x *[]uint64) error { + if b.typ == 2 { + data := b.data + // Packed encoding + for len(data) > 0 { + var u uint64 + var err error + + if u, data, err = decodeVarint(data); err != nil { + return err + } + *x = append(*x, u) + } + return nil + } + var u uint64 + if err := decodeUint64(b, &u); err != nil { + return err + } + *x = append(*x, u) + return nil +} + +func decodeString(b *buffer, x *string) error { + if err := checkType(b, 2); err != nil { + return err + } + *x = string(b.data) + return nil +} + +func decodeStrings(b *buffer, x *[]string) error { + var s string + if err := decodeString(b, &s); err != nil { + return err + } + *x = append(*x, s) + return nil +} + +func decodeBool(b *buffer, x *bool) error { + if err := checkType(b, 0); err != nil { + return err + } + if int64(b.u64) == 0 { + *x = false + } else { + *x = true + } + return nil +} diff --git a/src/cmd/internal/pprof/profile/proto_test.go b/src/cmd/internal/pprof/profile/proto_test.go new file mode 100644 index 0000000000..c2613fc375 --- /dev/null +++ b/src/cmd/internal/pprof/profile/proto_test.go @@ -0,0 +1,67 @@ +package profile + +import ( + "reflect" + "testing" +) + +func TestPackedEncoding(t *testing.T) { + + type testcase struct { + uint64s []uint64 + int64s []int64 + encoded []byte + } + for i, tc := range []testcase{ + { + []uint64{0, 1, 10, 100, 1000, 10000}, + []int64{1000, 0, 1000}, + []byte{10, 8, 0, 1, 10, 100, 232, 7, 144, 78, 18, 5, 232, 7, 0, 232, 7}, + }, + { + []uint64{10000}, + nil, + []byte{8, 144, 78}, + }, + { + nil, + []int64{-10000}, + []byte{16, 240, 177, 255, 255, 255, 255, 255, 255, 255, 1}, + }, + } { + source := &packedInts{tc.uint64s, tc.int64s} + if got, want := marshal(source), tc.encoded; !reflect.DeepEqual(got, want) { + t.Errorf("failed encode %d, got %v, want %v", i, got, want) + } + + dest := new(packedInts) + if err := unmarshal(tc.encoded, dest); err != nil { + t.Errorf("failed decode %d: %v", i, err) + continue + } + if got, want := dest.uint64s, tc.uint64s; !reflect.DeepEqual(got, want) { + t.Errorf("failed decode uint64s %d, got %v, want %v", i, got, want) + } + if got, want := dest.int64s, tc.int64s; !reflect.DeepEqual(got, want) { + t.Errorf("failed decode int64s %d, got %v, want %v", i, got, want) + } + } +} + +type packedInts struct { + uint64s []uint64 + int64s []int64 +} + +func (u *packedInts) decoder() []decoder { + return []decoder{ + nil, + func(b *buffer, m message) error { return decodeUint64s(b, &m.(*packedInts).uint64s) }, + func(b *buffer, m message) error { return decodeInt64s(b, &m.(*packedInts).int64s) }, + } +} + +func (u *packedInts) encode(b *buffer) { + encodeUint64s(b, 1, u.uint64s) + encodeInt64s(b, 2, u.int64s) +} diff --git a/src/cmd/internal/pprof/profile/prune.go b/src/cmd/internal/pprof/profile/prune.go new file mode 100644 index 0000000000..1924fada7a --- /dev/null +++ b/src/cmd/internal/pprof/profile/prune.go @@ -0,0 +1,97 @@ +// Copyright 2014 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. + +// Implements methods to remove frames from profiles. + +package profile + +import ( + "fmt" + "regexp" +) + +// Prune removes all nodes beneath a node matching dropRx, and not +// matching keepRx. If the root node of a Sample matches, the sample +// will have an empty stack. +func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) { + prune := make(map[uint64]bool) + pruneBeneath := make(map[uint64]bool) + + for _, loc := range p.Location { + var i int + for i = len(loc.Line) - 1; i >= 0; i-- { + if fn := loc.Line[i].Function; fn != nil && fn.Name != "" { + funcName := fn.Name + // Account for leading '.' on the PPC ELF v1 ABI. + if funcName[0] == '.' { + funcName = funcName[1:] + } + if dropRx.MatchString(funcName) { + if keepRx == nil || !keepRx.MatchString(funcName) { + break + } + } + } + } + + if i >= 0 { + // Found matching entry to prune. + pruneBeneath[loc.ID] = true + + // Remove the matching location. + if i == len(loc.Line)-1 { + // Matched the top entry: prune the whole location. + prune[loc.ID] = true + } else { + loc.Line = loc.Line[i+1:] + } + } + } + + // Prune locs from each Sample + for _, sample := range p.Sample { + // Scan from the root to the leaves to find the prune location. + // Do not prune frames before the first user frame, to avoid + // pruning everything. + foundUser := false + for i := len(sample.Location) - 1; i >= 0; i-- { + id := sample.Location[i].ID + if !prune[id] && !pruneBeneath[id] { + foundUser = true + continue + } + if !foundUser { + continue + } + if prune[id] { + sample.Location = sample.Location[i+1:] + break + } + if pruneBeneath[id] { + sample.Location = sample.Location[i:] + break + } + } + } +} + +// RemoveUninteresting prunes and elides profiles using built-in +// tables of uninteresting function names. +func (p *Profile) RemoveUninteresting() error { + var keep, drop *regexp.Regexp + var err error + + if p.DropFrames != "" { + if drop, err = regexp.Compile("^(" + p.DropFrames + ")$"); err != nil { + return fmt.Errorf("failed to compile regexp %s: %v", p.DropFrames, err) + } + if p.KeepFrames != "" { + if keep, err = regexp.Compile("^(" + p.KeepFrames + ")$"); err != nil { + return fmt.Errorf("failed to compile regexp %s: %v", p.KeepFrames, err) + } + } + p.Prune(drop, keep) + } + return nil +} diff --git a/src/cmd/internal/pprof/report/report.go b/src/cmd/internal/pprof/report/report.go new file mode 100644 index 0000000000..c492b752b9 --- /dev/null +++ b/src/cmd/internal/pprof/report/report.go @@ -0,0 +1,1682 @@ +// Copyright 2014 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 report summarizes a performance profile into a +// human-readable report. +package report + +import ( + "fmt" + "io" + "math" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "cmd/internal/pprof/plugin" + "cmd/internal/pprof/profile" +) + +// Generate generates a report as directed by the Report. +func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error { + o := rpt.options + + switch o.OutputFormat { + case Dot: + return printDOT(w, rpt) + case Tree: + return printTree(w, rpt) + case Text: + return printText(w, rpt) + case Raw: + fmt.Fprint(w, rpt.prof.String()) + return nil + case Tags: + return printTags(w, rpt) + case Proto: + return rpt.prof.Write(w) + case Dis: + return printAssembly(w, rpt, obj) + case List: + return printSource(w, rpt) + case WebList: + return printWebSource(w, rpt, obj) + case Callgrind: + return printCallgrind(w, rpt) + } + return fmt.Errorf("unexpected output format") +} + +// printAssembly prints an annotated assembly listing. +func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error { + g, err := newGraph(rpt) + if err != nil { + return err + } + + o := rpt.options + prof := rpt.prof + + // If the regexp source can be parsed as an address, also match + // functions that land on that address. + var address *uint64 + if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil { + address = &hex + } + + fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total)) + symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj) + symNodes := nodesPerSymbol(g.ns, symbols) + // Sort function names for printing. + var syms objSymbols + for s := range symNodes { + syms = append(syms, s) + } + sort.Sort(syms) + + // Correlate the symbols from the binary with the profile samples. + for _, s := range syms { + sns := symNodes[s] + + // Gather samples for this symbol. + flatSum, cumSum := sumNodes(sns) + + // Get the function assembly. + insns, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End) + if err != nil { + return err + } + + ns := annotateAssembly(insns, sns, s.base) + + fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0]) + for _, name := range s.sym.Name[1:] { + fmt.Fprintf(w, " AKA ======================== %s\n", name) + } + fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n", + rpt.formatValue(flatSum), rpt.formatValue(cumSum), + percentage(cumSum, rpt.total)) + + for _, n := range ns { + fmt.Fprintf(w, "%10s %10s %10x: %s\n", valueOrDot(n.flat, rpt), valueOrDot(n.cum, rpt), n.info.address, n.info.name) + } + } + return nil +} + +// symbolsFromBinaries examines the binaries listed on the profile +// that have associated samples, and identifies symbols matching rx. +func symbolsFromBinaries(prof *profile.Profile, g graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol { + hasSamples := make(map[string]bool) + // Only examine mappings that have samples that match the + // regexp. This is an optimization to speed up pprof. + for _, n := range g.ns { + if name := n.info.prettyName(); rx.MatchString(name) && n.info.objfile != "" { + hasSamples[n.info.objfile] = true + } + } + + // Walk all mappings looking for matching functions with samples. + var objSyms []*objSymbol + for _, m := range prof.Mapping { + if !hasSamples[filepath.Base(m.File)] { + if address == nil || !(m.Start <= *address && *address <= m.Limit) { + continue + } + } + + f, err := obj.Open(m.File, m.Start) + if err != nil { + fmt.Printf("%v\n", err) + continue + } + + // Find symbols in this binary matching the user regexp. + var addr uint64 + if address != nil { + addr = *address + } + msyms, err := f.Symbols(rx, addr) + base := f.Base() + f.Close() + if err != nil { + continue + } + for _, ms := range msyms { + objSyms = append(objSyms, + &objSymbol{ + sym: ms, + base: base, + }, + ) + } + } + + return objSyms +} + +// objSym represents a symbol identified from a binary. It includes +// the SymbolInfo from the disasm package and the base that must be +// added to correspond to sample addresses +type objSymbol struct { + sym *plugin.Sym + base uint64 +} + +// objSymbols is a wrapper type to enable sorting of []*objSymbol. +type objSymbols []*objSymbol + +func (o objSymbols) Len() int { + return len(o) +} + +func (o objSymbols) Less(i, j int) bool { + if namei, namej := o[i].sym.Name[0], o[j].sym.Name[0]; namei != namej { + return namei < namej + } + return o[i].sym.Start < o[j].sym.Start +} + +func (o objSymbols) Swap(i, j int) { + o[i], o[j] = o[j], o[i] +} + +// nodesPerSymbol classifies nodes into a group of symbols. +func nodesPerSymbol(ns nodes, symbols []*objSymbol) map[*objSymbol]nodes { + symNodes := make(map[*objSymbol]nodes) + for _, s := range symbols { + // Gather samples for this symbol. + for _, n := range ns { + address := n.info.address - s.base + if address >= s.sym.Start && address < s.sym.End { + symNodes[s] = append(symNodes[s], n) + } + } + } + return symNodes +} + +// annotateAssembly annotates a set of assembly instructions with a +// set of samples. It returns a set of nodes to display. base is an +// offset to adjust the sample addresses. +func annotateAssembly(insns []plugin.Inst, samples nodes, base uint64) nodes { + // Add end marker to simplify printing loop. + insns = append(insns, plugin.Inst{^uint64(0), "", "", 0}) + + // Ensure samples are sorted by address. + samples.sort(addressOrder) + + var s int + var asm nodes + for ix, in := range insns[:len(insns)-1] { + n := node{ + info: nodeInfo{ + address: in.Addr, + name: in.Text, + file: trimPath(in.File), + lineno: in.Line, + }, + } + + // Sum all the samples until the next instruction (to account + // for samples attributed to the middle of an instruction). + for next := insns[ix+1].Addr; s < len(samples) && samples[s].info.address-base < next; s++ { + n.flat += samples[s].flat + n.cum += samples[s].cum + if samples[s].info.file != "" { + n.info.file = trimPath(samples[s].info.file) + n.info.lineno = samples[s].info.lineno + } + } + asm = append(asm, &n) + } + + return asm +} + +// valueOrDot formats a value according to a report, intercepting zero +// values. +func valueOrDot(value int64, rpt *Report) string { + if value == 0 { + return "." + } + return rpt.formatValue(value) +} + +// printTags collects all tags referenced in the profile and prints +// them in a sorted table. +func printTags(w io.Writer, rpt *Report) error { + p := rpt.prof + + // Hashtable to keep accumulate tags as key,value,count. + tagMap := make(map[string]map[string]int64) + for _, s := range p.Sample { + for key, vals := range s.Label { + for _, val := range vals { + if valueMap, ok := tagMap[key]; ok { + valueMap[val] = valueMap[val] + s.Value[0] + continue + } + valueMap := make(map[string]int64) + valueMap[val] = s.Value[0] + tagMap[key] = valueMap + } + } + for key, vals := range s.NumLabel { + for _, nval := range vals { + val := scaledValueLabel(nval, key, "auto") + if valueMap, ok := tagMap[key]; ok { + valueMap[val] = valueMap[val] + s.Value[0] + continue + } + valueMap := make(map[string]int64) + valueMap[val] = s.Value[0] + tagMap[key] = valueMap + } + } + } + + tagKeys := make(tags, 0, len(tagMap)) + for key := range tagMap { + tagKeys = append(tagKeys, &tag{name: key}) + } + sort.Sort(tagKeys) + + for _, tagKey := range tagKeys { + var total int64 + key := tagKey.name + tags := make(tags, 0, len(tagMap[key])) + for t, c := range tagMap[key] { + total += c + tags = append(tags, &tag{name: t, weight: c}) + } + + sort.Sort(tags) + fmt.Fprintf(w, "%s: Total %d\n", key, total) + for _, t := range tags { + if total > 0 { + fmt.Fprintf(w, " %8d (%s): %s\n", t.weight, + percentage(t.weight, total), t.name) + } else { + fmt.Fprintf(w, " %8d: %s\n", t.weight, t.name) + } + } + fmt.Fprintln(w) + } + return nil +} + +// printText prints a flat text report for a profile. +func printText(w io.Writer, rpt *Report) error { + g, err := newGraph(rpt) + if err != nil { + return err + } + + origCount, droppedNodes, _ := g.preprocess(rpt) + fmt.Fprintln(w, strings.Join(legendDetailLabels(rpt, g, origCount, droppedNodes, 0), "\n")) + + fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n", + "flat", "flat", "sum", "cum", "cum") + + var flatSum int64 + for _, n := range g.ns { + name, flat, cum := n.info.prettyName(), n.flat, n.cum + + flatSum += flat + fmt.Fprintf(w, "%10s %s %s %10s %s %s\n", + rpt.formatValue(flat), + percentage(flat, rpt.total), + percentage(flatSum, rpt.total), + rpt.formatValue(cum), + percentage(cum, rpt.total), + name) + } + return nil +} + +// printCallgrind prints a graph for a profile on callgrind format. +func printCallgrind(w io.Writer, rpt *Report) error { + g, err := newGraph(rpt) + if err != nil { + return err + } + + o := rpt.options + rpt.options.NodeFraction = 0 + rpt.options.EdgeFraction = 0 + rpt.options.NodeCount = 0 + + g.preprocess(rpt) + + fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")") + + files := make(map[string]int) + names := make(map[string]int) + for _, n := range g.ns { + fmt.Fprintln(w, "fl="+callgrindName(files, n.info.file)) + fmt.Fprintln(w, "fn="+callgrindName(names, n.info.name)) + sv, _ := ScaleValue(n.flat, o.SampleUnit, o.OutputUnit) + fmt.Fprintf(w, "%d %d\n", n.info.lineno, int(sv)) + + // Print outgoing edges. + for _, out := range sortedEdges(n.out) { + c, _ := ScaleValue(out.weight, o.SampleUnit, o.OutputUnit) + count := fmt.Sprintf("%d", int(c)) + callee := out.dest + fmt.Fprintln(w, "cfl="+callgrindName(files, callee.info.file)) + fmt.Fprintln(w, "cfn="+callgrindName(names, callee.info.name)) + fmt.Fprintln(w, "calls="+count, callee.info.lineno) + fmt.Fprintln(w, n.info.lineno, count) + } + fmt.Fprintln(w) + } + + return nil +} + +// callgrindName implements the callgrind naming compression scheme. +// For names not previously seen returns "(N) name", where N is a +// unique index. For names previously seen returns "(N)" where N is +// the index returned the first time. +func callgrindName(names map[string]int, name string) string { + if name == "" { + return "" + } + if id, ok := names[name]; ok { + return fmt.Sprintf("(%d)", id) + } + id := len(names) + 1 + names[name] = id + return fmt.Sprintf("(%d) %s", id, name) +} + +// printTree prints a tree-based report in text form. +func printTree(w io.Writer, rpt *Report) error { + const separator = "----------------------------------------------------------+-------------" + const legend = " flat flat% sum% cum cum% calls calls% + context " + + g, err := newGraph(rpt) + if err != nil { + return err + } + + origCount, droppedNodes, _ := g.preprocess(rpt) + fmt.Fprintln(w, strings.Join(legendDetailLabels(rpt, g, origCount, droppedNodes, 0), "\n")) + + fmt.Fprintln(w, separator) + fmt.Fprintln(w, legend) + var flatSum int64 + + rx := rpt.options.Symbol + for _, n := range g.ns { + name, flat, cum := n.info.prettyName(), n.flat, n.cum + + // Skip any entries that do not match the regexp (for the "peek" command). + if rx != nil && !rx.MatchString(name) { + continue + } + + fmt.Fprintln(w, separator) + // Print incoming edges. + inEdges := sortedEdges(n.in) + inSum := inEdges.sum() + for _, in := range inEdges { + fmt.Fprintf(w, "%50s %s | %s\n", rpt.formatValue(in.weight), + percentage(in.weight, inSum), in.src.info.prettyName()) + } + + // Print current node. + flatSum += flat + fmt.Fprintf(w, "%10s %s %s %10s %s | %s\n", + rpt.formatValue(flat), + percentage(flat, rpt.total), + percentage(flatSum, rpt.total), + rpt.formatValue(cum), + percentage(cum, rpt.total), + name) + + // Print outgoing edges. + outEdges := sortedEdges(n.out) + outSum := outEdges.sum() + for _, out := range outEdges { + fmt.Fprintf(w, "%50s %s | %s\n", rpt.formatValue(out.weight), + percentage(out.weight, outSum), out.dest.info.prettyName()) + } + } + if len(g.ns) > 0 { + fmt.Fprintln(w, separator) + } + return nil +} + +// printDOT prints an annotated callgraph in DOT format. +func printDOT(w io.Writer, rpt *Report) error { + g, err := newGraph(rpt) + if err != nil { + return err + } + + origCount, droppedNodes, droppedEdges := g.preprocess(rpt) + + prof := rpt.prof + graphname := "unnamed" + if len(prof.Mapping) > 0 { + graphname = filepath.Base(prof.Mapping[0].File) + } + fmt.Fprintln(w, `digraph "`+graphname+`" {`) + fmt.Fprintln(w, `node [style=filled fillcolor="#f8f8f8"]`) + fmt.Fprintln(w, dotLegend(rpt, g, origCount, droppedNodes, droppedEdges)) + + if len(g.ns) == 0 { + fmt.Fprintln(w, "}") + return nil + } + + // Make sure nodes have a unique consistent id. + nodeIndex := make(map[*node]int) + maxFlat := float64(g.ns[0].flat) + for i, n := range g.ns { + nodeIndex[n] = i + 1 + if float64(n.flat) > maxFlat { + maxFlat = float64(n.flat) + } + } + var edges edgeList + for _, n := range g.ns { + node := dotNode(rpt, maxFlat, nodeIndex[n], n) + fmt.Fprintln(w, node) + if nodelets := dotNodelets(rpt, nodeIndex[n], n); nodelets != "" { + fmt.Fprint(w, nodelets) + } + + // Collect outgoing edges. + for _, e := range n.out { + edges = append(edges, e) + } + } + // Sort edges by frequency as a hint to the graph layout engine. + sort.Sort(edges) + for _, e := range edges { + fmt.Fprintln(w, dotEdge(rpt, nodeIndex[e.src], nodeIndex[e.dest], e)) + } + fmt.Fprintln(w, "}") + return nil +} + +// percentage computes the percentage of total of a value, and encodes +// it as a string. At least two digits of precision are printed. +func percentage(value, total int64) string { + var ratio float64 + if total != 0 { + ratio = float64(value) / float64(total) * 100 + } + switch { + case ratio >= 99.95: + return " 100%" + case ratio >= 1.0: + return fmt.Sprintf("%5.2f%%", ratio) + default: + return fmt.Sprintf("%5.2g%%", ratio) + } +} + +// dotLegend generates the overall graph label for a report in DOT format. +func dotLegend(rpt *Report, g graph, origCount, droppedNodes, droppedEdges int) string { + label := legendLabels(rpt) + label = append(label, legendDetailLabels(rpt, g, origCount, droppedNodes, droppedEdges)...) + return fmt.Sprintf(`subgraph cluster_L { L [shape=box fontsize=32 label="%s\l"] }`, strings.Join(label, `\l`)) +} + +// legendLabels generates labels exclusive to graph visualization. +func legendLabels(rpt *Report) []string { + prof := rpt.prof + o := rpt.options + var label []string + if len(prof.Mapping) > 0 { + if prof.Mapping[0].File != "" { + label = append(label, "File: "+filepath.Base(prof.Mapping[0].File)) + } + if prof.Mapping[0].BuildID != "" { + label = append(label, "Build ID: "+prof.Mapping[0].BuildID) + } + } + if o.SampleType != "" { + label = append(label, "Type: "+o.SampleType) + } + if prof.TimeNanos != 0 { + const layout = "Jan 2, 2006 at 3:04pm (MST)" + label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout)) + } + if prof.DurationNanos != 0 { + label = append(label, fmt.Sprintf("Duration: %v", time.Duration(prof.DurationNanos))) + } + return label +} + +// legendDetailLabels generates labels common to graph and text visualization. +func legendDetailLabels(rpt *Report, g graph, origCount, droppedNodes, droppedEdges int) []string { + nodeFraction := rpt.options.NodeFraction + edgeFraction := rpt.options.EdgeFraction + nodeCount := rpt.options.NodeCount + + label := []string{} + + var flatSum int64 + for _, n := range g.ns { + flatSum = flatSum + n.flat + } + + label = append(label, fmt.Sprintf("%s of %s total (%s)", rpt.formatValue(flatSum), rpt.formatValue(rpt.total), percentage(flatSum, rpt.total))) + + if rpt.total > 0 { + if droppedNodes > 0 { + label = append(label, genLabel(droppedNodes, "node", "cum", + rpt.formatValue(int64(float64(rpt.total)*nodeFraction)))) + } + if droppedEdges > 0 { + label = append(label, genLabel(droppedEdges, "edge", "freq", + rpt.formatValue(int64(float64(rpt.total)*edgeFraction)))) + } + if nodeCount > 0 && nodeCount < origCount { + label = append(label, fmt.Sprintf("Showing top %d nodes out of %d (cum >= %s)", + nodeCount, origCount, + rpt.formatValue(g.ns[len(g.ns)-1].cum))) + } + } + return label +} + +func genLabel(d int, n, l, f string) string { + if d > 1 { + n = n + "s" + } + return fmt.Sprintf("Dropped %d %s (%s <= %s)", d, n, l, f) +} + +// dotNode generates a graph node in DOT format. +func dotNode(rpt *Report, maxFlat float64, rIndex int, n *node) string { + flat, cum := n.flat, n.cum + + labels := strings.Split(n.info.prettyName(), "::") + label := strings.Join(labels, `\n`) + `\n` + + flatValue := rpt.formatValue(flat) + if flat > 0 { + label = label + fmt.Sprintf(`%s(%s)`, + flatValue, + strings.TrimSpace(percentage(flat, rpt.total))) + } else { + label = label + "0" + } + cumValue := flatValue + if cum != flat { + if flat > 0 { + label = label + `\n` + } else { + label = label + " " + } + cumValue = rpt.formatValue(cum) + label = label + fmt.Sprintf(`of %s(%s)`, + cumValue, + strings.TrimSpace(percentage(cum, rpt.total))) + } + + // Scale font sizes from 8 to 24 based on percentage of flat frequency. + // Use non linear growth to emphasize the size difference. + baseFontSize, maxFontGrowth := 8, 16.0 + fontSize := baseFontSize + if maxFlat > 0 && flat > 0 && float64(flat) <= maxFlat { + fontSize += int(math.Ceil(maxFontGrowth * math.Sqrt(float64(flat)/maxFlat))) + } + return fmt.Sprintf(`N%d [label="%s" fontsize=%d shape=box tooltip="%s (%s)"]`, + rIndex, + label, + fontSize, n.info.prettyName(), cumValue) +} + +// dotEdge generates a graph edge in DOT format. +func dotEdge(rpt *Report, from, to int, e *edgeInfo) string { + w := rpt.formatValue(e.weight) + attr := fmt.Sprintf(`label=" %s"`, w) + if rpt.total > 0 { + if weight := 1 + int(e.weight*100/rpt.total); weight > 1 { + attr = fmt.Sprintf(`%s weight=%d`, attr, weight) + } + if width := 1 + int(e.weight*5/rpt.total); width > 1 { + attr = fmt.Sprintf(`%s penwidth=%d`, attr, width) + } + } + arrow := "->" + if e.residual { + arrow = "..." + } + tooltip := fmt.Sprintf(`"%s %s %s (%s)"`, + e.src.info.prettyName(), arrow, e.dest.info.prettyName(), w) + attr = fmt.Sprintf(`%s tooltip=%s labeltooltip=%s`, + attr, tooltip, tooltip) + + if e.residual { + attr = attr + ` style="dotted"` + } + + if len(e.src.tags) > 0 { + // Separate children further if source has tags. + attr = attr + " minlen=2" + } + return fmt.Sprintf("N%d -> N%d [%s]", from, to, attr) +} + +// dotNodelets generates the DOT boxes for the node tags. +func dotNodelets(rpt *Report, rIndex int, n *node) (dot string) { + const maxNodelets = 4 // Number of nodelets for alphanumeric labels + const maxNumNodelets = 4 // Number of nodelets for numeric labels + + var ts, nts tags + for _, t := range n.tags { + if t.unit == "" { + ts = append(ts, t) + } else { + nts = append(nts, t) + } + } + + // Select the top maxNodelets alphanumeric labels by weight + sort.Sort(ts) + if len(ts) > maxNodelets { + ts = ts[:maxNodelets] + } + for i, t := range ts { + weight := rpt.formatValue(t.weight) + dot += fmt.Sprintf(`N%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", rIndex, i, t.name, weight) + dot += fmt.Sprintf(`N%d -> N%d_%d [label=" %s" weight=100 tooltip="\L" labeltooltip="\L"]`+"\n", rIndex, rIndex, i, weight) + } + + // Collapse numeric labels into maxNumNodelets buckets, of the form: + // 1MB..2MB, 3MB..5MB, ... + nts = collapseTags(nts, maxNumNodelets) + sort.Sort(nts) + for i, t := range nts { + weight := rpt.formatValue(t.weight) + dot += fmt.Sprintf(`NN%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", rIndex, i, t.name, weight) + dot += fmt.Sprintf(`N%d -> NN%d_%d [label=" %s" weight=100 tooltip="\L" labeltooltip="\L"]`+"\n", rIndex, rIndex, i, weight) + } + + return dot +} + +// graph summarizes a performance profile into a format that is +// suitable for visualization. +type graph struct { + ns nodes +} + +// nodes is an ordered collection of graph nodes. +type nodes []*node + +// tags represent sample annotations +type tags []*tag +type tagMap map[string]*tag + +type tag struct { + name string + unit string // Describe the value, "" for non-numeric tags + value int64 + weight int64 +} + +func (t tags) Len() int { return len(t) } +func (t tags) Swap(i, j int) { t[i], t[j] = t[j], t[i] } +func (t tags) Less(i, j int) bool { + if t[i].weight == t[j].weight { + return t[i].name < t[j].name + } + return t[i].weight > t[j].weight +} + +// node is an entry on a profiling report. It represents a unique +// program location. It can include multiple names to represent +// inlined functions. +type node struct { + info nodeInfo // Information associated to this entry. + + // values associated to this node. + // flat is exclusive to this node, cum includes all descendents. + flat, cum int64 + + // in and out contains the nodes immediately reaching or reached by this nodes. + in, out edgeMap + + // tags provide additional information about subsets of a sample. + tags tagMap +} + +type nodeInfo struct { + name string + origName string + address uint64 + file string + startLine, lineno int + inline bool + lowPriority bool + objfile string + parent *node // Used only if creating a calltree +} + +func (n *node) addTags(s *profile.Sample, weight int64) { + // Add a tag with all string labels + var labels []string + for key, vals := range s.Label { + for _, v := range vals { + labels = append(labels, key+":"+v) + } + } + if len(labels) > 0 { + sort.Strings(labels) + l := n.tags.findOrAddTag(strings.Join(labels, `\n`), "", 0) + l.weight += weight + } + + for key, nvals := range s.NumLabel { + for _, v := range nvals { + label := scaledValueLabel(v, key, "auto") + l := n.tags.findOrAddTag(label, key, v) + l.weight += weight + } + } +} + +func (m tagMap) findOrAddTag(label, unit string, value int64) *tag { + if l := m[label]; l != nil { + return l + } + l := &tag{ + name: label, + unit: unit, + value: value, + } + m[label] = l + return l +} + +// collapseTags reduces the number of entries in a tagMap by merging +// adjacent nodes into ranges. It uses a greedy approach to merge +// starting with the entries with the lowest weight. +func collapseTags(ts tags, count int) tags { + if len(ts) <= count { + return ts + } + + sort.Sort(ts) + tagGroups := make([]tags, count) + for i, t := range ts[:count] { + tagGroups[i] = tags{t} + } + for _, t := range ts[count:] { + g, d := 0, tagDistance(t, tagGroups[0][0]) + for i := 1; i < count; i++ { + if nd := tagDistance(t, tagGroups[i][0]); nd < d { + g, d = i, nd + } + } + tagGroups[g] = append(tagGroups[g], t) + } + + var nts tags + for _, g := range tagGroups { + l, w := tagGroupLabel(g) + nts = append(nts, &tag{ + name: l, + weight: w, + }) + } + return nts +} + +func tagDistance(t, u *tag) float64 { + v, _ := ScaleValue(u.value, u.unit, t.unit) + if v < float64(t.value) { + return float64(t.value) - v + } + return v - float64(t.value) +} + +func tagGroupLabel(g tags) (string, int64) { + if len(g) == 1 { + t := g[0] + return scaledValueLabel(t.value, t.unit, "auto"), t.weight + } + min := g[0] + max := g[0] + w := min.weight + for _, t := range g[1:] { + if v, _ := ScaleValue(t.value, t.unit, min.unit); int64(v) < min.value { + min = t + } + if v, _ := ScaleValue(t.value, t.unit, max.unit); int64(v) > max.value { + max = t + } + w += t.weight + } + return scaledValueLabel(min.value, min.unit, "auto") + ".." + + scaledValueLabel(max.value, max.unit, "auto"), w +} + +// sumNodes adds the flat and sum values on a report. +func sumNodes(ns nodes) (flat int64, cum int64) { + for _, n := range ns { + flat += n.flat + cum += n.cum + } + return +} + +type edgeMap map[*node]*edgeInfo + +// edgeInfo contains any attributes to be represented about edges in a graph/ +type edgeInfo struct { + src, dest *node + // The summary weight of the edge + weight int64 + // residual edges connect nodes that were connected through a + // separate node, which has been removed from the report. + residual bool +} + +// bumpWeight increases the weight of an edge. If there isn't such an +// edge in the map one is created. +func bumpWeight(from, to *node, w int64, residual bool) { + if from.out[to] != to.in[from] { + panic(fmt.Errorf("asymmetric edges %v %v", *from, *to)) + } + + if n := from.out[to]; n != nil { + n.weight += w + if n.residual && !residual { + n.residual = false + } + return + } + + info := &edgeInfo{src: from, dest: to, weight: w, residual: residual} + from.out[to] = info + to.in[from] = info +} + +// Output formats. +const ( + Proto = iota + Dot + Tags + Tree + Text + Raw + Dis + List + WebList + Callgrind +) + +// Options are the formatting and filtering options used to generate a +// profile. +type Options struct { + OutputFormat int + + CumSort bool + CallTree bool + PrintAddresses bool + DropNegative bool + Ratio float64 + + NodeCount int + NodeFraction float64 + EdgeFraction float64 + + SampleType string + SampleUnit string // Unit for the sample data from the profile. + OutputUnit string // Units for data formatting in report. + + Symbol *regexp.Regexp // Symbols to include on disassembly report. +} + +// newGraph summarizes performance data from a profile into a graph. +func newGraph(rpt *Report) (g graph, err error) { + prof := rpt.prof + o := rpt.options + + // Generate a tree for graphical output if requested. + buildTree := o.CallTree && o.OutputFormat == Dot + + locations := make(map[uint64][]nodeInfo) + for _, l := range prof.Location { + locations[l.ID] = newLocInfo(l) + } + + nm := make(nodeMap) + for _, sample := range prof.Sample { + if sample.Location == nil { + continue + } + + // Construct list of node names for sample. + var stack []nodeInfo + for _, loc := range sample.Location { + id := loc.ID + stack = append(stack, locations[id]...) + } + + // Upfront pass to update the parent chains, to prevent the + // merging of nodes with different parents. + if buildTree { + var nn *node + for i := len(stack); i > 0; i-- { + n := &stack[i-1] + n.parent = nn + nn = nm.findOrInsertNode(*n) + } + } + + leaf := nm.findOrInsertNode(stack[0]) + weight := rpt.sampleValue(sample) + leaf.addTags(sample, weight) + + // Aggregate counter data. + leaf.flat += weight + seen := make(map[*node]bool) + var nn *node + for _, s := range stack { + n := nm.findOrInsertNode(s) + if !seen[n] { + seen[n] = true + n.cum += weight + + if nn != nil { + bumpWeight(n, nn, weight, false) + } + } + nn = n + } + } + + // Collect new nodes into a report. + ns := make(nodes, 0, len(nm)) + for _, n := range nm { + if rpt.options.DropNegative && n.flat < 0 { + continue + } + ns = append(ns, n) + } + + return graph{ns}, nil +} + +// Create a slice of formatted names for a location. +func newLocInfo(l *profile.Location) []nodeInfo { + var objfile string + + if m := l.Mapping; m != nil { + objfile = filepath.Base(m.File) + } + + if len(l.Line) == 0 { + return []nodeInfo{ + { + address: l.Address, + objfile: objfile, + }, + } + } + var info []nodeInfo + numInlineFrames := len(l.Line) - 1 + for li, line := range l.Line { + ni := nodeInfo{ + address: l.Address, + lineno: int(line.Line), + inline: li < numInlineFrames, + objfile: objfile, + } + + if line.Function != nil { + ni.name = line.Function.Name + ni.origName = line.Function.SystemName + ni.file = line.Function.Filename + ni.startLine = int(line.Function.StartLine) + } + + info = append(info, ni) + } + return info +} + +// nodeMap maps from a node info struct to a node. It is used to merge +// report entries with the same info. +type nodeMap map[nodeInfo]*node + +func (m nodeMap) findOrInsertNode(info nodeInfo) *node { + rr := m[info] + if rr == nil { + rr = &node{ + info: info, + in: make(edgeMap), + out: make(edgeMap), + tags: make(map[string]*tag), + } + m[info] = rr + } + return rr +} + +// preprocess does any required filtering/sorting according to the +// report options. Returns the mapping from each node to any nodes +// removed by path compression and statistics on the nodes/edges removed. +func (g *graph) preprocess(rpt *Report) (origCount, droppedNodes, droppedEdges int) { + o := rpt.options + + // Compute total weight of current set of nodes. + // This is <= rpt.total because of node filtering. + var totalValue int64 + for _, n := range g.ns { + totalValue += n.flat + } + + // Remove nodes with value <= total*nodeFraction + if nodeFraction := o.NodeFraction; nodeFraction > 0 { + var removed nodes + minValue := int64(float64(totalValue) * nodeFraction) + kept := make(nodes, 0, len(g.ns)) + for _, n := range g.ns { + if n.cum < minValue { + removed = append(removed, n) + } else { + kept = append(kept, n) + tagsKept := make(map[string]*tag) + for s, t := range n.tags { + if t.weight >= minValue { + tagsKept[s] = t + } + } + n.tags = tagsKept + } + } + droppedNodes = len(removed) + removeNodes(removed, false, false) + g.ns = kept + } + + // Remove edges below minimum frequency. + if edgeFraction := o.EdgeFraction; edgeFraction > 0 { + minEdge := int64(float64(totalValue) * edgeFraction) + for _, n := range g.ns { + for src, e := range n.in { + if e.weight < minEdge { + delete(n.in, src) + delete(src.out, n) + droppedEdges++ + } + } + } + } + + sortOrder := flatName + if o.CumSort { + // Force cum sorting for graph output, to preserve connectivity. + sortOrder = cumName + } + + // Nodes that have flat==0 and a single in/out do not provide much + // information. Give them first chance to be removed. Do not consider edges + // from/to nodes that are expected to be removed. + maxNodes := o.NodeCount + if o.OutputFormat == Dot { + if maxNodes > 0 && maxNodes < len(g.ns) { + sortOrder = cumName + g.ns.sort(cumName) + cumCutoff := g.ns[maxNodes].cum + for _, n := range g.ns { + if n.flat == 0 { + if count := countEdges(n.out, cumCutoff); count > 1 { + continue + } + if count := countEdges(n.in, cumCutoff); count != 1 { + continue + } + n.info.lowPriority = true + } + } + } + } + + g.ns.sort(sortOrder) + if maxNodes > 0 { + origCount = len(g.ns) + for index, nodes := 0, 0; index < len(g.ns); index++ { + nodes++ + // For DOT output, count the tags as nodes since we will draw + // boxes for them. + if o.OutputFormat == Dot { + nodes += len(g.ns[index].tags) + } + if nodes > maxNodes { + // Trim to the top n nodes. Create dotted edges to bridge any + // broken connections. + removeNodes(g.ns[index:], true, true) + g.ns = g.ns[:index] + break + } + } + } + removeRedundantEdges(g.ns) + + // Select best unit for profile output. + // Find the appropriate units for the smallest non-zero sample + if o.OutputUnit == "minimum" && len(g.ns) > 0 { + var maxValue, minValue int64 + + for _, n := range g.ns { + if n.flat > 0 && (minValue == 0 || n.flat < minValue) { + minValue = n.flat + } + if n.cum > maxValue { + maxValue = n.cum + } + } + if r := o.Ratio; r > 0 && r != 1 { + minValue = int64(float64(minValue) * r) + maxValue = int64(float64(maxValue) * r) + } + + _, minUnit := ScaleValue(minValue, o.SampleUnit, "minimum") + _, maxUnit := ScaleValue(maxValue, o.SampleUnit, "minimum") + + unit := minUnit + if minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind { + // Minimum and maximum values have different units. Scale + // minimum by 100 to use larger units, allowing minimum value to + // be scaled down to 0.01, except for callgrind reports since + // they can only represent integer values. + _, unit = ScaleValue(100*minValue, o.SampleUnit, "minimum") + } + + if unit != "" { + o.OutputUnit = unit + } else { + o.OutputUnit = o.SampleUnit + } + } + return +} + +// countEdges counts the number of edges below the specified cutoff. +func countEdges(el edgeMap, cutoff int64) int { + count := 0 + for _, e := range el { + if e.weight > cutoff { + count++ + } + } + return count +} + +// removeNodes removes nodes from a report, optionally bridging +// connections between in/out edges and spreading out their weights +// proportionally. residual marks new bridge edges as residual +// (dotted). +func removeNodes(toRemove nodes, bridge, residual bool) { + for _, n := range toRemove { + for ei := range n.in { + delete(ei.out, n) + } + if bridge { + for ei, wi := range n.in { + for eo, wo := range n.out { + var weight int64 + if n.cum != 0 { + weight = int64(float64(wo.weight) * (float64(wi.weight) / float64(n.cum))) + } + bumpWeight(ei, eo, weight, residual) + } + } + } + for eo := range n.out { + delete(eo.in, n) + } + } +} + +// removeRedundantEdges removes residual edges if the destination can +// be reached through another path. This is done to simplify the graph +// while preserving connectivity. +func removeRedundantEdges(ns nodes) { + // Walk the nodes and outgoing edges in reverse order to prefer + // removing edges with the lowest weight. + for i := len(ns); i > 0; i-- { + n := ns[i-1] + in := sortedEdges(n.in) + for j := len(in); j > 0; j-- { + if e := in[j-1]; e.residual && isRedundant(e) { + delete(e.src.out, e.dest) + delete(e.dest.in, e.src) + } + } + } +} + +// isRedundant determines if an edge can be removed without impacting +// connectivity of the whole graph. This is implemented by checking if the +// nodes have a common ancestor after removing the edge. +func isRedundant(e *edgeInfo) bool { + destPred := predecessors(e, e.dest) + if len(destPred) == 1 { + return false + } + srcPred := predecessors(e, e.src) + + for n := range srcPred { + if destPred[n] && n != e.dest { + return true + } + } + return false +} + +// predecessors collects all the predecessors to node n, excluding edge e. +func predecessors(e *edgeInfo, n *node) map[*node]bool { + seen := map[*node]bool{n: true} + queue := []*node{n} + for len(queue) > 0 { + n := queue[0] + queue = queue[1:] + for _, ie := range n.in { + if e == ie || seen[ie.src] { + continue + } + seen[ie.src] = true + queue = append(queue, ie.src) + } + } + return seen +} + +// nodeSorter is a mechanism used to allow a report to be sorted +// in different ways. +type nodeSorter struct { + rs nodes + less func(i, j int) bool +} + +func (s nodeSorter) Len() int { return len(s.rs) } +func (s nodeSorter) Swap(i, j int) { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] } +func (s nodeSorter) Less(i, j int) bool { return s.less(i, j) } + +type nodeOrder int + +const ( + flatName nodeOrder = iota + flatCumName + cumName + nameOrder + fileOrder + addressOrder +) + +// sort reorders the entries in a report based on the specified +// ordering criteria. The result is sorted in decreasing order for +// numeric quantities, alphabetically for text, and increasing for +// addresses. +func (ns nodes) sort(o nodeOrder) error { + var s nodeSorter + + switch o { + case flatName: + s = nodeSorter{ns, + func(i, j int) bool { + if iv, jv := ns[i].flat, ns[j].flat; iv != jv { + return iv > jv + } + if ns[i].info.prettyName() != ns[j].info.prettyName() { + return ns[i].info.prettyName() < ns[j].info.prettyName() + } + iv, jv := ns[i].cum, ns[j].cum + return iv > jv + }, + } + case flatCumName: + s = nodeSorter{ns, + func(i, j int) bool { + if iv, jv := ns[i].flat, ns[j].flat; iv != jv { + return iv > jv + } + if iv, jv := ns[i].cum, ns[j].cum; iv != jv { + return iv > jv + } + return ns[i].info.prettyName() < ns[j].info.prettyName() + }, + } + case cumName: + s = nodeSorter{ns, + func(i, j int) bool { + if ns[i].info.lowPriority != ns[j].info.lowPriority { + return ns[j].info.lowPriority + } + if iv, jv := ns[i].cum, ns[j].cum; iv != jv { + return iv > jv + } + if ns[i].info.prettyName() != ns[j].info.prettyName() { + return ns[i].info.prettyName() < ns[j].info.prettyName() + } + iv, jv := ns[i].flat, ns[j].flat + return iv > jv + }, + } + case nameOrder: + s = nodeSorter{ns, + func(i, j int) bool { + return ns[i].info.name < ns[j].info.name + }, + } + case fileOrder: + s = nodeSorter{ns, + func(i, j int) bool { + return ns[i].info.file < ns[j].info.file + }, + } + case addressOrder: + s = nodeSorter{ns, + func(i, j int) bool { + return ns[i].info.address < ns[j].info.address + }, + } + default: + return fmt.Errorf("report: unrecognized sort ordering: %d", o) + } + sort.Sort(s) + return nil +} + +type edgeList []*edgeInfo + +// sortedEdges return a slice of the edges in the map, sorted for +// visualization. The sort order is first based on the edge weight +// (higher-to-lower) and then by the node names to avoid flakiness. +func sortedEdges(edges map[*node]*edgeInfo) edgeList { + el := make(edgeList, 0, len(edges)) + for _, w := range edges { + el = append(el, w) + } + + sort.Sort(el) + return el +} + +func (el edgeList) Len() int { + return len(el) +} + +func (el edgeList) Less(i, j int) bool { + if el[i].weight != el[j].weight { + return el[i].weight > el[j].weight + } + + from1 := el[i].src.info.prettyName() + from2 := el[j].src.info.prettyName() + if from1 != from2 { + return from1 < from2 + } + + to1 := el[i].dest.info.prettyName() + to2 := el[j].dest.info.prettyName() + + return to1 < to2 +} + +func (el edgeList) Swap(i, j int) { + el[i], el[j] = el[j], el[i] +} + +func (el edgeList) sum() int64 { + var ret int64 + for _, e := range el { + ret += e.weight + } + return ret +} + +// ScaleValue reformats a value from a unit to a different unit. +func ScaleValue(value int64, fromUnit, toUnit string) (sv float64, su string) { + // Avoid infinite recursion on overflow. + if value < 0 && -value > 0 { + v, u := ScaleValue(-value, fromUnit, toUnit) + return -v, u + } + if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok { + return m, u + } + if t, u, ok := timeLabel(value, fromUnit, toUnit); ok { + return t, u + } + // Skip non-interesting units. + switch toUnit { + case "count", "sample", "unit", "minimum": + return float64(value), "" + default: + return float64(value), toUnit + } +} + +func scaledValueLabel(value int64, fromUnit, toUnit string) string { + v, u := ScaleValue(value, fromUnit, toUnit) + + sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00") + if sv == "0" || sv == "-0" { + return "0" + } + return sv + u +} + +func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) { + fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s") + toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s") + + switch fromUnit { + case "byte", "b": + case "kilobyte", "kb": + value *= 1024 + case "megabyte", "mb": + value *= 1024 * 1024 + case "gigabyte", "gb": + value *= 1024 * 1024 * 1024 + default: + return 0, "", false + } + + if toUnit == "minimum" || toUnit == "auto" { + switch { + case value < 1024: + toUnit = "b" + case value < 1024*1024: + toUnit = "kb" + case value < 1024*1024*1024: + toUnit = "mb" + default: + toUnit = "gb" + } + } + + var output float64 + switch toUnit { + default: + output, toUnit = float64(value), "B" + case "kb", "kbyte", "kilobyte": + output, toUnit = float64(value)/1024, "kB" + case "mb", "mbyte", "megabyte": + output, toUnit = float64(value)/(1024*1024), "MB" + case "gb", "gbyte", "gigabyte": + output, toUnit = float64(value)/(1024*1024*1024), "GB" + } + return output, toUnit, true +} + +func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) { + fromUnit = strings.ToLower(fromUnit) + if len(fromUnit) > 2 { + fromUnit = strings.TrimSuffix(fromUnit, "s") + } + + toUnit = strings.ToLower(toUnit) + if len(toUnit) > 2 { + toUnit = strings.TrimSuffix(toUnit, "s") + } + + var d time.Duration + switch fromUnit { + case "nanosecond", "ns": + d = time.Duration(value) * time.Nanosecond + case "microsecond": + d = time.Duration(value) * time.Microsecond + case "millisecond", "ms": + d = time.Duration(value) * time.Millisecond + case "second", "sec": + d = time.Duration(value) * time.Second + case "cycle": + return float64(value), "", true + default: + return 0, "", false + } + + if toUnit == "minimum" || toUnit == "auto" { + switch { + case d < 1*time.Microsecond: + toUnit = "ns" + case d < 1*time.Millisecond: + toUnit = "us" + case d < 1*time.Second: + toUnit = "ms" + case d < 1*time.Minute: + toUnit = "sec" + case d < 1*time.Hour: + toUnit = "min" + case d < 24*time.Hour: + toUnit = "hour" + case d < 15*24*time.Hour: + toUnit = "day" + case d < 120*24*time.Hour: + toUnit = "week" + default: + toUnit = "year" + } + } + + var output float64 + dd := float64(d) + switch toUnit { + case "ns", "nanosecond": + output, toUnit = dd/float64(time.Nanosecond), "ns" + case "us", "microsecond": + output, toUnit = dd/float64(time.Microsecond), "us" + case "ms", "millisecond": + output, toUnit = dd/float64(time.Millisecond), "ms" + case "min", "minute": + output, toUnit = dd/float64(time.Minute), "mins" + case "hour", "hr": + output, toUnit = dd/float64(time.Hour), "hrs" + case "day": + output, toUnit = dd/float64(24*time.Hour), "days" + case "week", "wk": + output, toUnit = dd/float64(7*24*time.Hour), "wks" + case "year", "yr": + output, toUnit = dd/float64(365*7*24*time.Hour), "yrs" + default: + fallthrough + case "sec", "second", "s": + output, toUnit = dd/float64(time.Second), "s" + } + return output, toUnit, true +} + +// prettyName determines the printable name to be used for a node. +func (info *nodeInfo) prettyName() string { + var name string + if info.address != 0 { + name = fmt.Sprintf("%016x", info.address) + } + + if info.name != "" { + name = name + " " + info.name + } + + if info.file != "" { + name += " " + trimPath(info.file) + if info.lineno != 0 { + name += fmt.Sprintf(":%d", info.lineno) + } + } + + if info.inline { + name = name + " (inline)" + } + + if name = strings.TrimSpace(name); name == "" && info.objfile != "" { + name = "[" + info.objfile + "]" + } + return name +} + +// New builds a new report indexing the sample values interpreting the +// samples with the provided function. +func New(prof *profile.Profile, options Options, value func(s *profile.Sample) int64, unit string) *Report { + o := &options + if o.SampleUnit == "" { + o.SampleUnit = unit + } + format := func(v int64) string { + if r := o.Ratio; r > 0 && r != 1 { + fv := float64(v) * r + v = int64(fv) + } + return scaledValueLabel(v, o.SampleUnit, o.OutputUnit) + } + return &Report{prof, computeTotal(prof, value), o, value, format} +} + +// NewDefault builds a new report indexing the sample values with the +// last value available. +func NewDefault(prof *profile.Profile, options Options) *Report { + index := len(prof.SampleType) - 1 + o := &options + if o.SampleUnit == "" { + o.SampleUnit = strings.ToLower(prof.SampleType[index].Unit) + } + value := func(s *profile.Sample) int64 { + return s.Value[index] + } + format := func(v int64) string { + if r := o.Ratio; r > 0 && r != 1 { + fv := float64(v) * r + v = int64(fv) + } + return scaledValueLabel(v, o.SampleUnit, o.OutputUnit) + } + return &Report{prof, computeTotal(prof, value), o, value, format} +} + +func computeTotal(prof *profile.Profile, value func(s *profile.Sample) int64) int64 { + var ret int64 + for _, sample := range prof.Sample { + ret += value(sample) + } + return ret +} + +// Report contains the data and associated routines to extract a +// report from a profile. +type Report struct { + prof *profile.Profile + total int64 + options *Options + sampleValue func(*profile.Sample) int64 + formatValue func(int64) string +} diff --git a/src/cmd/internal/pprof/report/source.go b/src/cmd/internal/pprof/report/source.go new file mode 100644 index 0000000000..7beea39562 --- /dev/null +++ b/src/cmd/internal/pprof/report/source.go @@ -0,0 +1,454 @@ +// Copyright 2014 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 report + +// This file contains routines related to the generation of annotated +// source listings. + +import ( + "bufio" + "fmt" + "html/template" + "io" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + + "cmd/internal/pprof/plugin" +) + +// printSource prints an annotated source listing, include all +// functions with samples that match the regexp rpt.options.symbol. +// The sources are sorted by function name and then by filename to +// eliminate potential nondeterminism. +func printSource(w io.Writer, rpt *Report) error { + o := rpt.options + g, err := newGraph(rpt) + if err != nil { + return err + } + + // Identify all the functions that match the regexp provided. + // Group nodes for each matching function. + var functions nodes + functionNodes := make(map[string]nodes) + for _, n := range g.ns { + if !o.Symbol.MatchString(n.info.name) { + continue + } + if functionNodes[n.info.name] == nil { + functions = append(functions, n) + } + functionNodes[n.info.name] = append(functionNodes[n.info.name], n) + } + functions.sort(nameOrder) + + fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total)) + for _, fn := range functions { + name := fn.info.name + + // Identify all the source files associated to this function. + // Group nodes for each source file. + var sourceFiles nodes + fileNodes := make(map[string]nodes) + for _, n := range functionNodes[name] { + if n.info.file == "" { + continue + } + if fileNodes[n.info.file] == nil { + sourceFiles = append(sourceFiles, n) + } + fileNodes[n.info.file] = append(fileNodes[n.info.file], n) + } + + if len(sourceFiles) == 0 { + fmt.Printf("No source information for %s\n", name) + continue + } + + sourceFiles.sort(fileOrder) + + // Print each file associated with this function. + for _, fl := range sourceFiles { + filename := fl.info.file + fns := fileNodes[filename] + flatSum, cumSum := sumNodes(fns) + + fnodes, path, err := getFunctionSource(name, filename, fns, 0, 0) + fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, path) + fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n", + rpt.formatValue(flatSum), rpt.formatValue(cumSum), + percentage(cumSum, rpt.total)) + + if err != nil { + fmt.Fprintf(w, " Error: %v\n", err) + continue + } + + for _, fn := range fnodes { + fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt), fn.info.lineno, fn.info.name) + } + } + } + return nil +} + +// printWebSource prints an annotated source listing, include all +// functions with samples that match the regexp rpt.options.symbol. +func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error { + o := rpt.options + g, err := newGraph(rpt) + if err != nil { + return err + } + + // If the regexp source can be parsed as an address, also match + // functions that land on that address. + var address *uint64 + if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil { + address = &hex + } + + // Extract interesting symbols from binary files in the profile and + // classify samples per symbol. + symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj) + symNodes := nodesPerSymbol(g.ns, symbols) + + // Sort symbols for printing. + var syms objSymbols + for s := range symNodes { + syms = append(syms, s) + } + sort.Sort(syms) + + if len(syms) == 0 { + return fmt.Errorf("no samples found on routines matching: %s", o.Symbol.String()) + } + + printHeader(w, rpt) + for _, s := range syms { + name := s.sym.Name[0] + // Identify sources associated to a symbol by examining + // symbol samples. Classify samples per source file. + var sourceFiles nodes + fileNodes := make(map[string]nodes) + for _, n := range symNodes[s] { + if n.info.file == "" { + continue + } + if fileNodes[n.info.file] == nil { + sourceFiles = append(sourceFiles, n) + } + fileNodes[n.info.file] = append(fileNodes[n.info.file], n) + } + + if len(sourceFiles) == 0 { + fmt.Printf("No source information for %s\n", name) + continue + } + + sourceFiles.sort(fileOrder) + + // Print each file associated with this function. + for _, fl := range sourceFiles { + filename := fl.info.file + fns := fileNodes[filename] + + asm := assemblyPerSourceLine(symbols, fns, filename, obj) + start, end := sourceCoordinates(asm) + + fnodes, path, err := getFunctionSource(name, filename, fns, start, end) + if err != nil { + fnodes, path = getMissingFunctionSource(filename, asm, start, end) + } + + flatSum, cumSum := sumNodes(fnodes) + printFunctionHeader(w, name, path, flatSum, cumSum, rpt) + for _, fn := range fnodes { + printFunctionSourceLine(w, fn, asm[fn.info.lineno], rpt) + } + printFunctionClosing(w) + } + } + printPageClosing(w) + return nil +} + +// sourceCoordinates returns the lowest and highest line numbers from +// a set of assembly statements. +func sourceCoordinates(asm map[int]nodes) (start, end int) { + for l := range asm { + if start == 0 || l < start { + start = l + } + if end == 0 || l > end { + end = l + } + } + return start, end +} + +// assemblyPerSourceLine disassembles the binary containing a symbol +// and classifies the assembly instructions according to its +// corresponding source line, annotating them with a set of samples. +func assemblyPerSourceLine(objSyms []*objSymbol, rs nodes, src string, obj plugin.ObjTool) map[int]nodes { + assembly := make(map[int]nodes) + // Identify symbol to use for this collection of samples. + o := findMatchingSymbol(objSyms, rs) + if o == nil { + return assembly + } + + // Extract assembly for matched symbol + insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End) + if err != nil { + return assembly + } + + srcBase := filepath.Base(src) + anodes := annotateAssembly(insns, rs, o.base) + var lineno = 0 + for _, an := range anodes { + if filepath.Base(an.info.file) == srcBase { + lineno = an.info.lineno + } + if lineno != 0 { + assembly[lineno] = append(assembly[lineno], an) + } + } + + return assembly +} + +// findMatchingSymbol looks for the symbol that corresponds to a set +// of samples, by comparing their addresses. +func findMatchingSymbol(objSyms []*objSymbol, ns nodes) *objSymbol { + for _, n := range ns { + for _, o := range objSyms { + if filepath.Base(o.sym.File) == n.info.objfile && + o.sym.Start <= n.info.address-o.base && + n.info.address-o.base <= o.sym.End { + return o + } + } + } + return nil +} + +// printHeader prints the page header for a weblist report. +func printHeader(w io.Writer, rpt *Report) { + fmt.Fprintln(w, weblistPageHeader) + + var labels []string + for _, l := range legendLabels(rpt) { + labels = append(labels, template.HTMLEscapeString(l)) + } + + fmt.Fprintf(w, `
%s
Total: %s
`, + strings.Join(labels, "
\n"), + rpt.formatValue(rpt.total), + ) +} + +// printFunctionHeader prints a function header for a weblist report. +func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) { + fmt.Fprintf(w, `

%s

%s +
+  Total:  %10s %10s (flat, cum) %s
+`,
+		template.HTMLEscapeString(name), template.HTMLEscapeString(path),
+		rpt.formatValue(flatSum), rpt.formatValue(cumSum),
+		percentage(cumSum, rpt.total))
+}
+
+// printFunctionSourceLine prints a source line and the corresponding assembly.
+func printFunctionSourceLine(w io.Writer, fn *node, assembly nodes, rpt *Report) {
+	if len(assembly) == 0 {
+		fmt.Fprintf(w,
+			" %6d   %10s %10s %s \n",
+			fn.info.lineno,
+			valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
+			template.HTMLEscapeString(fn.info.name))
+		return
+	}
+
+	fmt.Fprintf(w,
+		" %6d   %10s %10s %s ",
+		fn.info.lineno,
+		valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
+		template.HTMLEscapeString(fn.info.name))
+	fmt.Fprint(w, "")
+	for _, an := range assembly {
+		var fileline string
+		class := "disasmloc"
+		if an.info.file != "" {
+			fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.info.file), an.info.lineno)
+			if an.info.lineno != fn.info.lineno {
+				class = "unimportant"
+			}
+		}
+		fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s %s\n", "",
+			valueOrDot(an.flat, rpt), valueOrDot(an.cum, rpt),
+			an.info.address,
+			template.HTMLEscapeString(an.info.name),
+			class,
+			template.HTMLEscapeString(fileline))
+	}
+	fmt.Fprintln(w, "")
+}
+
+// printFunctionClosing prints the end of a function in a weblist report.
+func printFunctionClosing(w io.Writer) {
+	fmt.Fprintln(w, "
") +} + +// printPageClosing prints the end of the page in a weblist report. +func printPageClosing(w io.Writer) { + fmt.Fprintln(w, weblistPageClosing) +} + +// getFunctionSource collects the sources of a function from a source +// file and annotates it with the samples in fns. Returns the sources +// as nodes, using the info.name field to hold the source code. +func getFunctionSource(fun, file string, fns nodes, start, end int) (nodes, string, error) { + f, file, err := adjustSourcePath(file) + if err != nil { + return nil, file, err + } + + lineNodes := make(map[int]nodes) + + // Collect source coordinates from profile. + const margin = 5 // Lines before first/after last sample. + if start == 0 { + if fns[0].info.startLine != 0 { + start = fns[0].info.startLine + } else { + start = fns[0].info.lineno - margin + } + } else { + start -= margin + } + if end == 0 { + end = fns[0].info.lineno + } + end += margin + for _, n := range fns { + lineno := n.info.lineno + nodeStart := n.info.startLine + if nodeStart == 0 { + nodeStart = lineno - margin + } + nodeEnd := lineno + margin + if nodeStart < start { + start = nodeStart + } else if nodeEnd > end { + end = nodeEnd + } + lineNodes[lineno] = append(lineNodes[lineno], n) + } + + var src nodes + buf := bufio.NewReader(f) + lineno := 1 + for { + line, err := buf.ReadString('\n') + if err != nil { + if err != io.EOF { + return nil, file, err + } + if line == "" { + // end was at or past EOF; that's okay + break + } + } + if lineno >= start { + flat, cum := sumNodes(lineNodes[lineno]) + + src = append(src, &node{ + info: nodeInfo{ + name: strings.TrimRight(line, "\n"), + lineno: lineno, + }, + flat: flat, + cum: cum, + }) + } + lineno++ + if lineno > end { + break + } + } + return src, file, nil +} + +// getMissingFunctionSource creates a dummy function body to point to +// the source file and annotates it with the samples in asm. +func getMissingFunctionSource(filename string, asm map[int]nodes, start, end int) (nodes, string) { + var fnodes nodes + for i := start; i <= end; i++ { + lrs := asm[i] + if len(lrs) == 0 { + continue + } + flat, cum := sumNodes(lrs) + fnodes = append(fnodes, &node{ + info: nodeInfo{ + name: "???", + lineno: i, + }, + flat: flat, + cum: cum, + }) + } + return fnodes, filename +} + +// adjustSourcePath adjusts the path for a source file by trimming +// known prefixes and searching for the file on all parents of the +// current working dir. +func adjustSourcePath(path string) (*os.File, string, error) { + path = trimPath(path) + f, err := os.Open(path) + if err == nil { + return f, path, nil + } + + if dir, wderr := os.Getwd(); wderr == nil { + for { + parent := filepath.Dir(dir) + if parent == dir { + break + } + if f, err := os.Open(filepath.Join(parent, path)); err == nil { + return f, filepath.Join(parent, path), nil + } + + dir = parent + } + } + + return nil, path, err +} + +// trimPath cleans up a path by removing prefixes that are commonly +// found on profiles. +func trimPath(path string) string { + basePaths := []string{ + "/proc/self/cwd/./", + "/proc/self/cwd/", + } + + sPath := filepath.ToSlash(path) + + for _, base := range basePaths { + if strings.HasPrefix(sPath, base) { + return filepath.FromSlash(sPath[len(base):]) + } + } + return path +} diff --git a/src/cmd/internal/pprof/report/source_html.go b/src/cmd/internal/pprof/report/source_html.go new file mode 100644 index 0000000000..267fabdc4b --- /dev/null +++ b/src/cmd/internal/pprof/report/source_html.go @@ -0,0 +1,77 @@ +// Copyright 2014 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 report + +const weblistPageHeader = ` + + + +Pprof listing + + + + +` + +const weblistPageClosing = ` + + +` diff --git a/src/cmd/internal/pprof/svg/svg.go b/src/cmd/internal/pprof/svg/svg.go new file mode 100644 index 0000000000..04f6ff1870 --- /dev/null +++ b/src/cmd/internal/pprof/svg/svg.go @@ -0,0 +1,71 @@ +// Copyright 2014 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 svg provides tools related to handling of SVG files +package svg + +import ( + "bytes" + "regexp" + "strings" +) + +var ( + viewBox = regexp.MustCompile(``) +) + +// Massage enhances the SVG output from DOT to provide better +// panning inside a web browser. It uses the SVGPan library, which is +// included directly. +func Massage(in bytes.Buffer) string { + svg := string(in.Bytes()) + + // Work around for dot bug which misses quoting some ampersands, + // resulting on unparsable SVG. + svg = strings.Replace(svg, "&;", "&;", -1) + + //Dot's SVG output is + // + // + // + // ... + // + // + // + // Change it to + // + // + // + // + // + // ... + // + // + // + + if loc := viewBox.FindStringIndex(svg); loc != nil { + svg = svg[:loc[0]] + + `` + svgPanJS + `` + + `` + + svg[loc[0]:] + } + + if loc := svgClose.FindStringIndex(svg); loc != nil { + svg = svg[:loc[0]] + + `` + + svg[loc[0]:] + } + + return svg +} diff --git a/src/cmd/internal/pprof/svg/svgpan.go b/src/cmd/internal/pprof/svg/svgpan.go new file mode 100644 index 0000000000..4975b103e3 --- /dev/null +++ b/src/cmd/internal/pprof/svg/svgpan.go @@ -0,0 +1,291 @@ +// SVG pan and zoom library. +// See copyright notice in string constant below. + +package svg + +// https://www.cyberz.org/projects/SVGPan/SVGPan.js + +const svgPanJS = ` +/** + * SVGPan library 1.2.1 + * ====================== + * + * Given an unique existing element with id "viewport" (or when missing, the first g + * element), including the the library into any SVG adds the following capabilities: + * + * - Mouse panning + * - Mouse zooming (using the wheel) + * - Object dragging + * + * You can configure the behaviour of the pan/zoom/drag with the variables + * listed in the CONFIGURATION section of this file. + * + * Known issues: + * + * - Zooming (while panning) on Safari has still some issues + * + * Releases: + * + * 1.2.1, Mon Jul 4 00:33:18 CEST 2011, Andrea Leofreddi + * - Fixed a regression with mouse wheel (now working on Firefox 5) + * - Working with viewBox attribute (#4) + * - Added "use strict;" and fixed resulting warnings (#5) + * - Added configuration variables, dragging is disabled by default (#3) + * + * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui + * Fixed a bug with browser mouse handler interaction + * + * 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui + * Updated the zoom code to support the mouse wheel on Safari/Chrome + * + * 1.0, Andrea Leofreddi + * First release + * + * This code is licensed under the following BSD license: + * + * Copyright 2009-2010 Andrea Leofreddi . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ` + "``AS IS''" + ` AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Andrea Leofreddi. + */ + +"use strict"; + +/// CONFIGURATION +/// ====> + +var enablePan = 1; // 1 or 0: enable or disable panning (default enabled) +var enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled) +var enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled) + +/// <==== +/// END OF CONFIGURATION + +var root = document.documentElement; + +var state = 'none', svgRoot, stateTarget, stateOrigin, stateTf; + +setupHandlers(root); + +/** + * Register handlers + */ +function setupHandlers(root){ + setAttributes(root, { + "onmouseup" : "handleMouseUp(evt)", + "onmousedown" : "handleMouseDown(evt)", + "onmousemove" : "handleMouseMove(evt)", + //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element + }); + + if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0) + window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari + else + window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others +} + +/** + * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global variable. + */ +function getRoot(root) { + if(typeof(svgRoot) == "undefined") { + var g = null; + + g = root.getElementById("viewport"); + + if(g == null) + g = root.getElementsByTagName('g')[0]; + + if(g == null) + alert('Unable to obtain SVG root element'); + + setCTM(g, g.getCTM()); + + g.removeAttribute("viewBox"); + + svgRoot = g; + } + + return svgRoot; +} + +/** + * Instance an SVGPoint object with given event coordinates. + */ +function getEventPoint(evt) { + var p = root.createSVGPoint(); + + p.x = evt.clientX; + p.y = evt.clientY; + + return p; +} + +/** + * Sets the current transform matrix of an element. + */ +function setCTM(element, matrix) { + var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")"; + + element.setAttribute("transform", s); +} + +/** + * Dumps a matrix to a string (useful for debug). + */ +function dumpMatrix(matrix) { + var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]"; + + return s; +} + +/** + * Sets attributes of an element. + */ +function setAttributes(element, attributes){ + for (var i in attributes) + element.setAttributeNS(null, i, attributes[i]); +} + +/** + * Handle mouse wheel event. + */ +function handleMouseWheel(evt) { + if(!enableZoom) + return; + + if(evt.preventDefault) + evt.preventDefault(); + + evt.returnValue = false; + + var svgDoc = evt.target.ownerDocument; + + var delta; + + if(evt.wheelDelta) + delta = evt.wheelDelta / 3600; // Chrome/Safari + else + delta = evt.detail / -90; // Mozilla + + var z = 1 + delta; // Zoom factor: 0.9/1.1 + + var g = getRoot(svgDoc); + + var p = getEventPoint(evt); + + p = p.matrixTransform(g.getCTM().inverse()); + + // Compute new scale matrix in current mouse position + var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y); + + setCTM(g, g.getCTM().multiply(k)); + + if(typeof(stateTf) == "undefined") + stateTf = g.getCTM().inverse(); + + stateTf = stateTf.multiply(k.inverse()); +} + +/** + * Handle mouse move event. + */ +function handleMouseMove(evt) { + if(evt.preventDefault) + evt.preventDefault(); + + evt.returnValue = false; + + var svgDoc = evt.target.ownerDocument; + + var g = getRoot(svgDoc); + + if(state == 'pan' && enablePan) { + // Pan mode + var p = getEventPoint(evt).matrixTransform(stateTf); + + setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y)); + } else if(state == 'drag' && enableDrag) { + // Drag mode + var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse()); + + setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM())); + + stateOrigin = p; + } +} + +/** + * Handle click event. + */ +function handleMouseDown(evt) { + if(evt.preventDefault) + evt.preventDefault(); + + evt.returnValue = false; + + var svgDoc = evt.target.ownerDocument; + + var g = getRoot(svgDoc); + + if( + evt.target.tagName == "svg" + || !enableDrag // Pan anyway when drag is disabled and the user clicked on an element + ) { + // Pan mode + state = 'pan'; + + stateTf = g.getCTM().inverse(); + + stateOrigin = getEventPoint(evt).matrixTransform(stateTf); + } else { + // Drag mode + state = 'drag'; + + stateTarget = evt.target; + + stateTf = g.getCTM().inverse(); + + stateOrigin = getEventPoint(evt).matrixTransform(stateTf); + } +} + +/** + * Handle mouse button release event. + */ +function handleMouseUp(evt) { + if(evt.preventDefault) + evt.preventDefault(); + + evt.returnValue = false; + + var svgDoc = evt.target.ownerDocument; + + if(state == 'pan' || state == 'drag') { + // Quit pan mode + state = ''; + } +} + +` diff --git a/src/cmd/internal/pprof/symbolizer/symbolizer.go b/src/cmd/internal/pprof/symbolizer/symbolizer.go new file mode 100644 index 0000000000..bc22800530 --- /dev/null +++ b/src/cmd/internal/pprof/symbolizer/symbolizer.go @@ -0,0 +1,195 @@ +// Copyright 2014 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 symbolizer provides a routine to populate a profile with +// symbol, file and line number information. It relies on the +// addr2liner and demangler packages to do the actual work. +package symbolizer + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "cmd/internal/pprof/plugin" + "cmd/internal/pprof/profile" +) + +// Symbolize adds symbol and line number information to all locations +// in a profile. mode enables some options to control +// symbolization. Currently only recognizes "force", which causes it +// to overwrite any existing data. +func Symbolize(mode string, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error { + force := false + // Disable some mechanisms based on mode string. + for _, o := range strings.Split(strings.ToLower(mode), ":") { + switch o { + case "force": + force = true + default: + } + } + + if len(prof.Mapping) == 0 { + return fmt.Errorf("no known mappings") + } + + mt, err := newMapping(prof, obj, ui, force) + if err != nil { + return err + } + defer mt.close() + + functions := make(map[profile.Function]*profile.Function) + for _, l := range mt.prof.Location { + m := l.Mapping + segment := mt.segments[m] + if segment == nil { + // Nothing to do + continue + } + + stack, err := segment.SourceLine(l.Address) + if err != nil || len(stack) == 0 { + // No answers from addr2line + continue + } + + l.Line = make([]profile.Line, len(stack)) + for i, frame := range stack { + if frame.Func != "" { + m.HasFunctions = true + } + if frame.File != "" { + m.HasFilenames = true + } + if frame.Line != 0 { + m.HasLineNumbers = true + } + f := &profile.Function{ + Name: frame.Func, + SystemName: frame.Func, + Filename: frame.File, + } + if fp := functions[*f]; fp != nil { + f = fp + } else { + functions[*f] = f + f.ID = uint64(len(mt.prof.Function)) + 1 + mt.prof.Function = append(mt.prof.Function, f) + } + l.Line[i] = profile.Line{ + Function: f, + Line: int64(frame.Line), + } + } + + if len(stack) > 0 { + m.HasInlineFrames = true + } + } + return nil +} + +// newMapping creates a mappingTable for a profile. +func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) { + mt := &mappingTable{ + prof: prof, + segments: make(map[*profile.Mapping]plugin.ObjFile), + } + + // Identify used mappings + mappings := make(map[*profile.Mapping]bool) + for _, l := range prof.Location { + mappings[l.Mapping] = true + } + + for _, m := range prof.Mapping { + if !mappings[m] { + continue + } + // Do not attempt to re-symbolize a mapping that has already been symbolized. + if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) { + continue + } + + f, err := locateFile(obj, m.File, m.BuildID, m.Start) + if err != nil { + ui.PrintErr("Local symbolization failed for ", filepath.Base(m.File), ": ", err) + // Move on to other mappings + continue + } + + if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID { + // Build ID mismatch - ignore. + f.Close() + continue + } + + mt.segments[m] = f + } + + return mt, nil +} + +// locateFile opens a local file for symbolization on the search path +// at $PPROF_BINARY_PATH. Looks inside these directories for files +// named $BUILDID/$BASENAME and $BASENAME (if build id is available). +func locateFile(obj plugin.ObjTool, file, buildID string, start uint64) (plugin.ObjFile, error) { + // Construct search path to examine + searchPath := os.Getenv("PPROF_BINARY_PATH") + if searchPath == "" { + // Use $HOME/pprof/binaries as default directory for local symbolization binaries + searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries") + } + + // Collect names to search: {buildid/basename, basename} + var fileNames []string + if baseName := filepath.Base(file); buildID != "" { + fileNames = []string{filepath.Join(buildID, baseName), baseName} + } else { + fileNames = []string{baseName} + } + for _, path := range filepath.SplitList(searchPath) { + for nameIndex, name := range fileNames { + file := filepath.Join(path, name) + if f, err := obj.Open(file, start); err == nil { + fileBuildID := f.BuildID() + if buildID == "" || buildID == fileBuildID { + return f, nil + } + f.Close() + if nameIndex == 0 { + // If this is the first name, the path includes the build id. Report inconsistency. + return nil, fmt.Errorf("found file %s with inconsistent build id %s", file, fileBuildID) + } + } + } + } + // Try original file name + f, err := obj.Open(file, start) + if err == nil && buildID != "" { + if fileBuildID := f.BuildID(); fileBuildID != "" && fileBuildID != buildID { + // Mismatched build IDs, ignore + f.Close() + return nil, fmt.Errorf("mismatched build ids %s != %s", fileBuildID, buildID) + } + } + return f, err +} + +// mappingTable contains the mechanisms for symbolization of a +// profile. +type mappingTable struct { + prof *profile.Profile + segments map[*profile.Mapping]plugin.ObjFile +} + +// Close releases any external processes being used for the mapping. +func (mt *mappingTable) close() { + for _, segment := range mt.segments { + segment.Close() + } +} diff --git a/src/cmd/internal/pprof/symbolz/symbolz.go b/src/cmd/internal/pprof/symbolz/symbolz.go new file mode 100644 index 0000000000..2f2850afeb --- /dev/null +++ b/src/cmd/internal/pprof/symbolz/symbolz.go @@ -0,0 +1,111 @@ +// Copyright 2014 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 symbolz symbolizes a profile using the output from the symbolz +// service. +package symbolz + +import ( + "bytes" + "fmt" + "io" + "net/url" + "regexp" + "strconv" + "strings" + + "cmd/internal/pprof/profile" +) + +var ( + symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`) +) + +// Symbolize symbolizes profile p by parsing data returned by a +// symbolz handler. syms receives the symbolz query (hex addresses +// separated by '+') and returns the symbolz output in a string. It +// symbolizes all locations based on their addresses, regardless of +// mapping. +func Symbolize(source string, syms func(string, string) ([]byte, error), p *profile.Profile) error { + if source = symbolz(source, p); source == "" { + // If the source is not a recognizable URL, do nothing. + return nil + } + + // Construct query of addresses to symbolize. + var a []string + for _, l := range p.Location { + if l.Address != 0 && len(l.Line) == 0 { + a = append(a, fmt.Sprintf("%#x", l.Address)) + } + } + + if len(a) == 0 { + // No addresses to symbolize. + return nil + } + lines := make(map[uint64]profile.Line) + functions := make(map[string]*profile.Function) + if b, err := syms(source, strings.Join(a, "+")); err == nil { + buf := bytes.NewBuffer(b) + for { + l, err := buf.ReadString('\n') + + if err != nil { + if err == io.EOF { + break + } + return err + } + + if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 { + addr, err := strconv.ParseUint(symbol[1], 0, 64) + if err != nil { + return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err) + } + + name := symbol[2] + fn := functions[name] + if fn == nil { + fn = &profile.Function{ + ID: uint64(len(p.Function) + 1), + Name: name, + SystemName: name, + } + functions[name] = fn + p.Function = append(p.Function, fn) + } + + lines[addr] = profile.Line{Function: fn} + } + } + } + + for _, l := range p.Location { + if line, ok := lines[l.Address]; ok { + l.Line = []profile.Line{line} + if l.Mapping != nil { + l.Mapping.HasFunctions = true + } + } + } + + return nil +} + +// symbolz returns the corresponding symbolz source for a profile URL. +func symbolz(source string, p *profile.Profile) string { + if url, err := url.Parse(source); err == nil && url.Host != "" { + if last := strings.LastIndex(url.Path, "/"); last != -1 { + if strings.HasSuffix(url.Path[:last], "pprof") { + url.Path = url.Path[:last] + "/symbol" + } else { + url.Path = url.Path[:last] + "/symbolz" + } + return url.String() + } + } + + return "" +} diff --git a/src/cmd/internal/pprof/tempfile/tempfile.go b/src/cmd/internal/pprof/tempfile/tempfile.go new file mode 100644 index 0000000000..31c117690a --- /dev/null +++ b/src/cmd/internal/pprof/tempfile/tempfile.go @@ -0,0 +1,45 @@ +// Copyright 2014 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 tempfile provides tools to create and delete temporary files +package tempfile + +import ( + "fmt" + "os" + "path/filepath" + "sync" +) + +// New returns an unused filename for output files. +func New(dir, prefix, suffix string) (*os.File, error) { + for index := 1; index < 10000; index++ { + path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix)) + if _, err := os.Stat(path); err != nil { + return os.Create(path) + } + } + // Give up + return nil, fmt.Errorf("could not create file of the form %s%03d%s", prefix, 1, suffix) +} + +var tempFiles []string +var tempFilesMu = sync.Mutex{} + +// DeferDelete marks a file to be deleted by next call to Cleanup() +func DeferDelete(path string) { + tempFilesMu.Lock() + tempFiles = append(tempFiles, path) + tempFilesMu.Unlock() +} + +// Cleanup removes any temporary files selected for deferred cleaning. +func Cleanup() { + tempFilesMu.Lock() + for _, f := range tempFiles { + os.Remove(f) + } + tempFiles = nil + tempFilesMu.Unlock() +} diff --git a/src/cmd/pprof/internal/commands/commands.go b/src/cmd/pprof/internal/commands/commands.go deleted file mode 100644 index 9aeee5762e..0000000000 --- a/src/cmd/pprof/internal/commands/commands.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2014 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 commands defines and manages the basic pprof commands -package commands - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "runtime" - "strings" - "time" - - "cmd/pprof/internal/plugin" - "cmd/pprof/internal/report" - "cmd/pprof/internal/svg" - "cmd/pprof/internal/tempfile" -) - -// Commands describes the commands accepted by pprof. -type Commands map[string]*Command - -// Command describes the actions for a pprof command. Includes a -// function for command-line completion, the report format to use -// during report generation, any postprocessing functions, and whether -// the command expects a regexp parameter (typically a function name). -type Command struct { - Complete Completer // autocomplete for interactive mode - Format int // report format to generate - PostProcess PostProcessor // postprocessing to run on report - HasParam bool // Collect a parameter from the CLI - Usage string // Help text -} - -// Completer is a function for command-line autocompletion -type Completer func(prefix string) string - -// PostProcessor is a function that applies post-processing to the report output -type PostProcessor func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error - -// PProf returns the basic pprof report-generation commands -func PProf(c Completer, interactive **bool) Commands { - return Commands{ - // Commands that require no post-processing. - "tags": {nil, report.Tags, nil, false, "Outputs all tags in the profile"}, - "raw": {c, report.Raw, nil, false, "Outputs a text representation of the raw profile"}, - "dot": {c, report.Dot, nil, false, "Outputs a graph in DOT format"}, - "top": {c, report.Text, nil, false, "Outputs top entries in text form"}, - "tree": {c, report.Tree, nil, false, "Outputs a text rendering of call graph"}, - "text": {c, report.Text, nil, false, "Outputs top entries in text form"}, - "disasm": {c, report.Dis, nil, true, "Output annotated assembly for functions matching regexp or address"}, - "list": {c, report.List, nil, true, "Output annotated source for functions matching regexp"}, - "peek": {c, report.Tree, nil, true, "Output callers/callees of functions matching regexp"}, - - // Save binary formats to a file - "callgrind": {c, report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format"}, - "proto": {c, report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format"}, - - // Generate report in DOT format and postprocess with dot - "gif": {c, report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format"}, - "pdf": {c, report.Dot, invokeDot("pdf"), false, "Outputs a graph in PDF format"}, - "png": {c, report.Dot, invokeDot("png"), false, "Outputs a graph image in PNG format"}, - "ps": {c, report.Dot, invokeDot("ps"), false, "Outputs a graph in PS format"}, - - // Save SVG output into a file after including svgpan library - "svg": {c, report.Dot, saveSVGToFile(), false, "Outputs a graph in SVG format"}, - - // Visualize postprocessed dot output - "eog": {c, report.Dot, invokeVisualizer(interactive, invokeDot("svg"), "svg", []string{"eog"}), false, "Visualize graph through eog"}, - "evince": {c, report.Dot, invokeVisualizer(interactive, invokeDot("pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince"}, - "gv": {c, report.Dot, invokeVisualizer(interactive, invokeDot("ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv"}, - "web": {c, report.Dot, invokeVisualizer(interactive, saveSVGToFile(), "svg", browsers()), false, "Visualize graph through web browser"}, - - // Visualize HTML directly generated by report. - "weblist": {c, report.WebList, invokeVisualizer(interactive, awayFromTTY("html"), "html", browsers()), true, "Output annotated source in HTML for functions matching regexp or address"}, - } -} - -// browsers returns a list of commands to attempt for web visualization -// on the current platform -func browsers() []string { - var cmds []string - if exe := os.Getenv("BROWSER"); exe != "" { - cmds = append(cmds, exe) - } - switch runtime.GOOS { - case "darwin": - cmds = append(cmds, "/usr/bin/open") - case "windows": - cmds = append(cmds, "cmd /c start") - default: - cmds = append(cmds, "xdg-open") - } - cmds = append(cmds, "chrome", "google-chrome", "firefox") - return cmds -} - -// NewCompleter creates an autocompletion function for a set of commands. -func NewCompleter(cs Commands) Completer { - return func(line string) string { - switch tokens := strings.Fields(line); len(tokens) { - case 0: - // Nothing to complete - case 1: - // Single token -- complete command name - found := "" - for c := range cs { - if strings.HasPrefix(c, tokens[0]) { - if found != "" { - return line - } - found = c - } - } - if found != "" { - return found - } - default: - // Multiple tokens -- complete using command completer - if c, ok := cs[tokens[0]]; ok { - if c.Complete != nil { - lastTokenIdx := len(tokens) - 1 - lastToken := tokens[lastTokenIdx] - if strings.HasPrefix(lastToken, "-") { - lastToken = "-" + c.Complete(lastToken[1:]) - } else { - lastToken = c.Complete(lastToken) - } - return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ") - } - } - } - return line - } -} - -// awayFromTTY saves the output in a file if it would otherwise go to -// the terminal screen. This is used to avoid dumping binary data on -// the screen. -func awayFromTTY(format string) PostProcessor { - return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { - if output == os.Stdout && ui.IsTerminal() { - tempFile, err := tempfile.New("", "profile", "."+format) - if err != nil { - return err - } - ui.PrintErr("Generating report in ", tempFile.Name()) - _, err = fmt.Fprint(tempFile, input) - return err - } - _, err := fmt.Fprint(output, input) - return err - } -} - -func invokeDot(format string) PostProcessor { - divert := awayFromTTY(format) - return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { - if _, err := exec.LookPath("dot"); err != nil { - ui.PrintErr("Cannot find dot, have you installed Graphviz?") - return err - } - cmd := exec.Command("dot", "-T"+format) - var buf bytes.Buffer - cmd.Stdin, cmd.Stdout, cmd.Stderr = input, &buf, os.Stderr - if err := cmd.Run(); err != nil { - return err - } - return divert(&buf, output, ui) - } -} - -func saveSVGToFile() PostProcessor { - generateSVG := invokeDot("svg") - divert := awayFromTTY("svg") - return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { - baseSVG := &bytes.Buffer{} - generateSVG(input, baseSVG, ui) - massaged := &bytes.Buffer{} - fmt.Fprint(massaged, svg.Massage(*baseSVG)) - return divert(massaged, output, ui) - } -} - -var vizTmpDir string - -func makeVizTmpDir() error { - if vizTmpDir != "" { - return nil - } - name, err := ioutil.TempDir("", "pprof-") - if err != nil { - return err - } - vizTmpDir = name - return nil -} - -func invokeVisualizer(interactive **bool, format PostProcessor, suffix string, visualizers []string) PostProcessor { - return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { - if err := makeVizTmpDir(); err != nil { - return err - } - tempFile, err := tempfile.New(vizTmpDir, "pprof", "."+suffix) - if err != nil { - return err - } - tempfile.DeferDelete(tempFile.Name()) - if err = format(input, tempFile, ui); err != nil { - return err - } - tempFile.Close() // on windows, if the file is Open, start cannot access it. - // Try visualizers until one is successful - for _, v := range visualizers { - // Separate command and arguments for exec.Command. - args := strings.Split(v, " ") - if len(args) == 0 { - continue - } - viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...) - viewer.Stderr = os.Stderr - if err = viewer.Start(); err == nil { - // The viewer might just send a message to another program - // to open the file. Give that program a little time to open the - // file before we remove it. - time.Sleep(1 * time.Second) - - if !**interactive { - // In command-line mode, wait for the viewer to be closed - // before proceeding - return viewer.Wait() - } - return nil - } - } - return err - } -} diff --git a/src/cmd/pprof/internal/driver/driver.go b/src/cmd/pprof/internal/driver/driver.go deleted file mode 100644 index 7cd1ddc928..0000000000 --- a/src/cmd/pprof/internal/driver/driver.go +++ /dev/null @@ -1,1041 +0,0 @@ -// Copyright 2014 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 driver implements the core pprof functionality. It can be -// parameterized with a flag implementation, fetch and symbolize -// mechanisms. -package driver - -import ( - "bytes" - "fmt" - "io" - "net/url" - "os" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "sync" - "time" - - "cmd/pprof/internal/commands" - "cmd/pprof/internal/plugin" - "cmd/pprof/internal/profile" - "cmd/pprof/internal/report" - "cmd/pprof/internal/tempfile" -) - -// PProf acquires a profile, and symbolizes it using a profile -// manager. Then it generates a report formatted according to the -// options selected through the flags package. -func PProf(flagset plugin.FlagSet, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, overrides commands.Commands) error { - // Remove any temporary files created during pprof processing. - defer tempfile.Cleanup() - - f, err := getFlags(flagset, overrides, ui) - if err != nil { - return err - } - - obj.SetConfig(*f.flagTools) - - sources := f.profileSource - if len(sources) > 1 { - source := sources[0] - // If the first argument is a supported object file, treat as executable. - if file, err := obj.Open(source, 0); err == nil { - file.Close() - f.profileExecName = source - sources = sources[1:] - } else if *f.flagBuildID == "" && isBuildID(source) { - f.flagBuildID = &source - sources = sources[1:] - } - } - - // errMu protects concurrent accesses to errset and err. errset is set if an - // error is encountered by one of the goroutines grabbing a profile. - errMu, errset := sync.Mutex{}, false - - // Fetch profiles. - wg := sync.WaitGroup{} - profs := make([]*profile.Profile, len(sources)) - for i, source := range sources { - wg.Add(1) - go func(i int, src string) { - defer wg.Done() - p, grabErr := grabProfile(src, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f) - if grabErr != nil { - errMu.Lock() - defer errMu.Unlock() - errset, err = true, grabErr - return - } - profs[i] = p - }(i, source) - } - wg.Wait() - if errset { - return err - } - - // Merge profiles. - prof := profs[0] - for _, p := range profs[1:] { - if err = prof.Merge(p, 1); err != nil { - return err - } - } - - if *f.flagBase != "" { - // Fetch base profile and subtract from current profile. - base, err := grabProfile(*f.flagBase, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f) - if err != nil { - return err - } - - if err = prof.Merge(base, -1); err != nil { - return err - } - } - - if err := processFlags(prof, ui, f); err != nil { - return err - } - - if !*f.flagRuntime { - prof.RemoveUninteresting() - } - - if *f.flagInteractive { - return interactive(prof, obj, ui, f) - } - - return generate(false, prof, obj, ui, f) -} - -// isBuildID determines if the profile may contain a build ID, by -// checking that it is a string of hex digits. -func isBuildID(id string) bool { - return strings.Trim(id, "0123456789abcdefABCDEF") == "" -} - -// adjustURL updates the profile source URL based on heuristics. It -// will append ?seconds=sec for CPU profiles if not already -// specified. Returns the hostname if the profile is remote. -func adjustURL(source string, sec int, ui plugin.UI) (adjusted, host string, duration time.Duration) { - // If there is a local file with this name, just use it. - if _, err := os.Stat(source); err == nil { - return source, "", 0 - } - - url, err := url.Parse(source) - - // Automatically add http:// to URLs of the form hostname:port/path. - // url.Parse treats "hostname" as the Scheme. - if err != nil || (url.Host == "" && url.Scheme != "" && url.Scheme != "file") { - url, err = url.Parse("http://" + source) - if err != nil { - return source, "", 0 - } - } - if scheme := strings.ToLower(url.Scheme); scheme == "" || scheme == "file" { - url.Scheme = "" - return url.String(), "", 0 - } - - values := url.Query() - if urlSeconds := values.Get("seconds"); urlSeconds != "" { - if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil { - if sec >= 0 { - ui.PrintErr("Overriding -seconds for URL ", source) - } - sec = int(us) - } - } - - switch strings.ToLower(url.Path) { - case "", "/": - // Apply default /profilez. - url.Path = "/profilez" - case "/protoz": - // Rewrite to /profilez?type=proto - url.Path = "/profilez" - values.Set("type", "proto") - } - - if hasDuration(url.Path) { - if sec > 0 { - duration = time.Duration(sec) * time.Second - values.Set("seconds", fmt.Sprintf("%d", sec)) - } else { - // Assume default duration: 30 seconds - duration = 30 * time.Second - } - } - url.RawQuery = values.Encode() - return url.String(), url.Host, duration -} - -func hasDuration(path string) bool { - for _, trigger := range []string{"profilez", "wallz", "/profile"} { - if strings.Contains(path, trigger) { - return true - } - } - return false -} - -// preprocess does filtering and aggregation of a profile based on the -// requested options. -func preprocess(prof *profile.Profile, ui plugin.UI, f *flags) error { - if *f.flagFocus != "" || *f.flagIgnore != "" || *f.flagHide != "" { - focus, ignore, hide, err := compileFocusIgnore(*f.flagFocus, *f.flagIgnore, *f.flagHide) - if err != nil { - return err - } - fm, im, hm := prof.FilterSamplesByName(focus, ignore, hide) - - warnNoMatches(fm, *f.flagFocus, "Focus", ui) - warnNoMatches(im, *f.flagIgnore, "Ignore", ui) - warnNoMatches(hm, *f.flagHide, "Hide", ui) - } - - if *f.flagTagFocus != "" || *f.flagTagIgnore != "" { - focus, err := compileTagFilter(*f.flagTagFocus, ui) - if err != nil { - return err - } - ignore, err := compileTagFilter(*f.flagTagIgnore, ui) - if err != nil { - return err - } - fm, im := prof.FilterSamplesByTag(focus, ignore) - - warnNoMatches(fm, *f.flagTagFocus, "TagFocus", ui) - warnNoMatches(im, *f.flagTagIgnore, "TagIgnore", ui) - } - - return aggregate(prof, f) -} - -func compileFocusIgnore(focus, ignore, hide string) (f, i, h *regexp.Regexp, err error) { - if focus != "" { - if f, err = regexp.Compile(focus); err != nil { - return nil, nil, nil, fmt.Errorf("parsing focus regexp: %v", err) - } - } - - if ignore != "" { - if i, err = regexp.Compile(ignore); err != nil { - return nil, nil, nil, fmt.Errorf("parsing ignore regexp: %v", err) - } - } - - if hide != "" { - if h, err = regexp.Compile(hide); err != nil { - return nil, nil, nil, fmt.Errorf("parsing hide regexp: %v", err) - } - } - return -} - -func compileTagFilter(filter string, ui plugin.UI) (f func(string, string, int64) bool, err error) { - if filter == "" { - return nil, nil - } - if numFilter := parseTagFilterRange(filter); numFilter != nil { - ui.PrintErr("Interpreted '", filter, "' as range, not regexp") - return func(key, val string, num int64) bool { - if val != "" { - return false - } - return numFilter(num, key) - }, nil - } - fx, err := regexp.Compile(filter) - if err != nil { - return nil, err - } - - return func(key, val string, num int64) bool { - if val == "" { - return false - } - return fx.MatchString(key + ":" + val) - }, nil -} - -var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)") - -// parseTagFilterRange returns a function to checks if a value is -// contained on the range described by a string. It can recognize -// strings of the form: -// "32kb" -- matches values == 32kb -// ":64kb" -- matches values <= 64kb -// "4mb:" -- matches values >= 4mb -// "12kb:64mb" -- matches values between 12kb and 64mb (both included). -func parseTagFilterRange(filter string) func(int64, string) bool { - ranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2) - if len(ranges) == 0 { - return nil // No ranges were identified - } - v, err := strconv.ParseInt(ranges[0][1], 10, 64) - if err != nil { - panic(fmt.Errorf("Failed to parse int %s: %v", ranges[0][1], err)) - } - value, unit := report.ScaleValue(v, ranges[0][2], ranges[0][2]) - if len(ranges) == 1 { - switch match := ranges[0][0]; filter { - case match: - return func(v int64, u string) bool { - sv, su := report.ScaleValue(v, u, unit) - return su == unit && sv == value - } - case match + ":": - return func(v int64, u string) bool { - sv, su := report.ScaleValue(v, u, unit) - return su == unit && sv >= value - } - case ":" + match: - return func(v int64, u string) bool { - sv, su := report.ScaleValue(v, u, unit) - return su == unit && sv <= value - } - } - return nil - } - if filter != ranges[0][0]+":"+ranges[1][0] { - return nil - } - if v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil { - panic(fmt.Errorf("Failed to parse int %s: %v", ranges[1][1], err)) - } - value2, unit2 := report.ScaleValue(v, ranges[1][2], unit) - if unit != unit2 { - return nil - } - return func(v int64, u string) bool { - sv, su := report.ScaleValue(v, u, unit) - return su == unit && sv >= value && sv <= value2 - } -} - -func warnNoMatches(match bool, rx, option string, ui plugin.UI) { - if !match && rx != "" && rx != "." { - ui.PrintErr(option + " expression matched no samples: " + rx) - } -} - -// grabProfile fetches and symbolizes a profile. -func grabProfile(source, exec, buildid string, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, f *flags) (*profile.Profile, error) { - source, host, duration := adjustURL(source, *f.flagSeconds, ui) - remote := host != "" - - if remote { - ui.Print("Fetching profile from ", source) - if duration != 0 { - ui.Print("Please wait... (" + duration.String() + ")") - } - } - - now := time.Now() - // Fetch profile from source. - // Give 50% slack on the timeout. - p, err := fetch(source, duration+duration/2, ui) - if err != nil { - return nil, err - } - - // Update the time/duration if the profile source doesn't include it. - // TODO(rsilvera): Remove this when we remove support for legacy profiles. - if remote { - if p.TimeNanos == 0 { - p.TimeNanos = now.UnixNano() - } - if duration != 0 && p.DurationNanos == 0 { - p.DurationNanos = int64(duration) - } - } - - // Replace executable/buildID with the options provided in the - // command line. Assume the executable is the first Mapping entry. - if exec != "" || buildid != "" { - if len(p.Mapping) == 0 { - // Create a fake mapping to hold the user option, and associate - // all samples to it. - m := &profile.Mapping{ - ID: 1, - } - for _, l := range p.Location { - l.Mapping = m - } - p.Mapping = []*profile.Mapping{m} - } - if exec != "" { - p.Mapping[0].File = exec - } - if buildid != "" { - p.Mapping[0].BuildID = buildid - } - } - - if err := sym(*f.flagSymbolize, source, p, obj, ui); err != nil { - return nil, err - } - - // Save a copy of any remote profiles, unless the user is explicitly - // saving it. - if remote && !f.isFormat("proto") { - prefix := "pprof." - if len(p.Mapping) > 0 && p.Mapping[0].File != "" { - prefix = prefix + filepath.Base(p.Mapping[0].File) + "." - } - if !strings.ContainsRune(host, os.PathSeparator) { - prefix = prefix + host + "." - } - for _, s := range p.SampleType { - prefix = prefix + s.Type + "." - } - - dir := os.Getenv("PPROF_TMPDIR") - tempFile, err := tempfile.New(dir, prefix, ".pb.gz") - if err == nil { - if err = p.Write(tempFile); err == nil { - ui.PrintErr("Saved profile in ", tempFile.Name()) - } - } - if err != nil { - ui.PrintErr("Could not save profile: ", err) - } - } - - if err := p.Demangle(obj.Demangle); err != nil { - ui.PrintErr("Failed to demangle profile: ", err) - } - - if err := p.CheckValid(); err != nil { - return nil, fmt.Errorf("Grab %s: %v", source, err) - } - - return p, nil -} - -type flags struct { - flagInteractive *bool // Accept commands interactively - flagCommands map[string]*bool // pprof commands without parameters - flagParamCommands map[string]*string // pprof commands with parameters - - flagOutput *string // Output file name - - flagCum *bool // Sort by cumulative data - flagCallTree *bool // generate a context-sensitive call tree - - flagAddresses *bool // Report at address level - flagLines *bool // Report at source line level - flagFiles *bool // Report at file level - flagFunctions *bool // Report at function level [default] - - flagSymbolize *string // Symbolization options (=none to disable) - flagBuildID *string // Override build if for first mapping - - flagNodeCount *int // Max number of nodes to show - flagNodeFraction *float64 // Hide nodes below *total - flagEdgeFraction *float64 // Hide edges below *total - flagTrim *bool // Set to false to ignore NodeCount/*Fraction - flagRuntime *bool // Show runtime call frames in memory profiles - flagFocus *string // Restricts to paths going through a node matching regexp - flagIgnore *string // Skips paths going through any nodes matching regexp - flagHide *string // Skips sample locations matching regexp - flagTagFocus *string // Restrict to samples tagged with key:value matching regexp - flagTagIgnore *string // Discard samples tagged with key:value matching regexp - flagDropNegative *bool // Skip negative values - - flagBase *string // Source for base profile to user for comparison - - flagSeconds *int // Length of time for dynamic profiles - - flagTotalDelay *bool // Display total delay at each region - flagContentions *bool // Display number of delays at each region - flagMeanDelay *bool // Display mean delay at each region - - flagInUseSpace *bool // Display in-use memory size - flagInUseObjects *bool // Display in-use object counts - flagAllocSpace *bool // Display allocated memory size - flagAllocObjects *bool // Display allocated object counts - flagDisplayUnit *string // Measurement unit to use on reports - flagDivideBy *float64 // Ratio to divide sample values - - flagSampleIndex *int // Sample value to use in reports. - flagMean *bool // Use mean of sample_index over count - - flagTools *string - profileSource []string - profileExecName string - - extraUsage string - commands commands.Commands -} - -func (f *flags) isFormat(format string) bool { - if fl := f.flagCommands[format]; fl != nil { - return *fl - } - if fl := f.flagParamCommands[format]; fl != nil { - return *fl != "" - } - return false -} - -// String provides a printable representation for the current set of flags. -func (f *flags) String(p *profile.Profile) string { - var ret string - - if ix := *f.flagSampleIndex; ix != -1 { - ret += fmt.Sprintf(" %-25s : %d (%s)\n", "sample_index", ix, p.SampleType[ix].Type) - } - if ix := *f.flagMean; ix { - ret += boolFlagString("mean") - } - if *f.flagDisplayUnit != "minimum" { - ret += stringFlagString("unit", *f.flagDisplayUnit) - } - - switch { - case *f.flagInteractive: - ret += boolFlagString("interactive") - } - for name, fl := range f.flagCommands { - if *fl { - ret += boolFlagString(name) - } - } - - if *f.flagCum { - ret += boolFlagString("cum") - } - if *f.flagCallTree { - ret += boolFlagString("call_tree") - } - - switch { - case *f.flagAddresses: - ret += boolFlagString("addresses") - case *f.flagLines: - ret += boolFlagString("lines") - case *f.flagFiles: - ret += boolFlagString("files") - case *f.flagFunctions: - ret += boolFlagString("functions") - } - - if *f.flagNodeCount != -1 { - ret += intFlagString("nodecount", *f.flagNodeCount) - } - - ret += floatFlagString("nodefraction", *f.flagNodeFraction) - ret += floatFlagString("edgefraction", *f.flagEdgeFraction) - - if *f.flagFocus != "" { - ret += stringFlagString("focus", *f.flagFocus) - } - if *f.flagIgnore != "" { - ret += stringFlagString("ignore", *f.flagIgnore) - } - if *f.flagHide != "" { - ret += stringFlagString("hide", *f.flagHide) - } - - if *f.flagTagFocus != "" { - ret += stringFlagString("tagfocus", *f.flagTagFocus) - } - if *f.flagTagIgnore != "" { - ret += stringFlagString("tagignore", *f.flagTagIgnore) - } - - return ret -} - -func boolFlagString(label string) string { - return fmt.Sprintf(" %-25s : true\n", label) -} - -func stringFlagString(label, value string) string { - return fmt.Sprintf(" %-25s : %s\n", label, value) -} - -func intFlagString(label string, value int) string { - return fmt.Sprintf(" %-25s : %d\n", label, value) -} - -func floatFlagString(label string, value float64) string { - return fmt.Sprintf(" %-25s : %f\n", label, value) -} - -// Utility routines to set flag values. -func newBool(b bool) *bool { - return &b -} - -func newString(s string) *string { - return &s -} - -func newFloat64(fl float64) *float64 { - return &fl -} - -func newInt(i int) *int { - return &i -} - -func (f *flags) usage(ui plugin.UI) { - var commandMsg []string - for name, cmd := range f.commands { - if cmd.HasParam { - name = name + "=p" - } - commandMsg = append(commandMsg, - fmt.Sprintf(" -%-16s %s", name, cmd.Usage)) - } - - sort.Strings(commandMsg) - - text := usageMsgHdr + strings.Join(commandMsg, "\n") + "\n" + usageMsg + "\n" - if f.extraUsage != "" { - text += f.extraUsage + "\n" - } - text += usageMsgVars - ui.Print(text) -} - -func getFlags(flag plugin.FlagSet, overrides commands.Commands, ui plugin.UI) (*flags, error) { - f := &flags{ - flagInteractive: flag.Bool("interactive", false, "Accepts commands interactively"), - flagCommands: make(map[string]*bool), - flagParamCommands: make(map[string]*string), - - // Filename for file-based output formats, stdout by default. - flagOutput: flag.String("output", "", "Output filename for file-based outputs "), - // Comparisons. - flagBase: flag.String("base", "", "Source for base profile for comparison"), - flagDropNegative: flag.Bool("drop_negative", false, "Ignore negative differences"), - - // Data sorting criteria. - flagCum: flag.Bool("cum", false, "Sort by cumulative data"), - // Graph handling options. - flagCallTree: flag.Bool("call_tree", false, "Create a context-sensitive call tree"), - // Granularity of output resolution. - flagAddresses: flag.Bool("addresses", false, "Report at address level"), - flagLines: flag.Bool("lines", false, "Report at source line level"), - flagFiles: flag.Bool("files", false, "Report at source file level"), - flagFunctions: flag.Bool("functions", false, "Report at function level [default]"), - // Internal options. - flagSymbolize: flag.String("symbolize", "", "Options for profile symbolization"), - flagBuildID: flag.String("buildid", "", "Override build id for first mapping"), - // Filtering options - flagNodeCount: flag.Int("nodecount", -1, "Max number of nodes to show"), - flagNodeFraction: flag.Float64("nodefraction", 0.005, "Hide nodes below *total"), - flagEdgeFraction: flag.Float64("edgefraction", 0.001, "Hide edges below *total"), - flagTrim: flag.Bool("trim", true, "Honor nodefraction/edgefraction/nodecount defaults"), - flagRuntime: flag.Bool("runtime", false, "Show runtime call frames in memory profiles"), - flagFocus: flag.String("focus", "", "Restricts to paths going through a node matching regexp"), - flagIgnore: flag.String("ignore", "", "Skips paths going through any nodes matching regexp"), - flagHide: flag.String("hide", "", "Skips nodes matching regexp"), - flagTagFocus: flag.String("tagfocus", "", "Restrict to samples with tags in range or matched by regexp"), - flagTagIgnore: flag.String("tagignore", "", "Discard samples with tags in range or matched by regexp"), - // CPU profile options - flagSeconds: flag.Int("seconds", -1, "Length of time for dynamic profiles"), - // Heap profile options - flagInUseSpace: flag.Bool("inuse_space", false, "Display in-use memory size"), - flagInUseObjects: flag.Bool("inuse_objects", false, "Display in-use object counts"), - flagAllocSpace: flag.Bool("alloc_space", false, "Display allocated memory size"), - flagAllocObjects: flag.Bool("alloc_objects", false, "Display allocated object counts"), - flagDisplayUnit: flag.String("unit", "minimum", "Measurement units to display"), - flagDivideBy: flag.Float64("divide_by", 1.0, "Ratio to divide all samples before visualization"), - flagSampleIndex: flag.Int("sample_index", -1, "Index of sample value to report"), - flagMean: flag.Bool("mean", false, "Average sample value over first value (count)"), - // Contention profile options - flagTotalDelay: flag.Bool("total_delay", false, "Display total delay at each region"), - flagContentions: flag.Bool("contentions", false, "Display number of delays at each region"), - flagMeanDelay: flag.Bool("mean_delay", false, "Display mean delay at each region"), - flagTools: flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames"), - extraUsage: flag.ExtraUsage(), - } - - // Flags used during command processing - interactive := &f.flagInteractive - f.commands = commands.PProf(functionCompleter, interactive) - - // Override commands - for name, cmd := range overrides { - f.commands[name] = cmd - } - - for name, cmd := range f.commands { - if cmd.HasParam { - f.flagParamCommands[name] = flag.String(name, "", "Generate a report in "+name+" format, matching regexp") - } else { - f.flagCommands[name] = flag.Bool(name, false, "Generate a report in "+name+" format") - } - } - - args := flag.Parse(func() { f.usage(ui) }) - if len(args) == 0 { - return nil, fmt.Errorf("no profile source specified") - } - - f.profileSource = args - - // Instruct legacy heapz parsers to grab historical allocation data, - // instead of the default in-use data. Not available with tcmalloc. - if *f.flagAllocSpace || *f.flagAllocObjects { - profile.LegacyHeapAllocated = true - } - - if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir == "" { - profileDir = os.Getenv("HOME") + "/pprof" - os.Setenv("PPROF_TMPDIR", profileDir) - if err := os.MkdirAll(profileDir, 0755); err != nil { - return nil, fmt.Errorf("failed to access temp dir %s: %v", profileDir, err) - } - } - - return f, nil -} - -func processFlags(p *profile.Profile, ui plugin.UI, f *flags) error { - flagDis := f.isFormat("disasm") - flagPeek := f.isFormat("peek") - flagWebList := f.isFormat("weblist") - flagList := f.isFormat("list") - - if flagDis || flagWebList { - // Collect all samples at address granularity for assembly - // listing. - f.flagNodeCount = newInt(0) - f.flagAddresses = newBool(true) - f.flagLines = newBool(false) - f.flagFiles = newBool(false) - f.flagFunctions = newBool(false) - } - - if flagPeek { - // Collect all samples at function granularity for peek command - f.flagNodeCount = newInt(0) - f.flagAddresses = newBool(false) - f.flagLines = newBool(false) - f.flagFiles = newBool(false) - f.flagFunctions = newBool(true) - } - - if flagList { - // Collect all samples at fileline granularity for source - // listing. - f.flagNodeCount = newInt(0) - f.flagAddresses = newBool(false) - f.flagLines = newBool(true) - f.flagFiles = newBool(false) - f.flagFunctions = newBool(false) - } - - if !*f.flagTrim { - f.flagNodeCount = newInt(0) - f.flagNodeFraction = newFloat64(0) - f.flagEdgeFraction = newFloat64(0) - } - - if oc := countFlagMap(f.flagCommands, f.flagParamCommands); oc == 0 { - f.flagInteractive = newBool(true) - } else if oc > 1 { - f.usage(ui) - return fmt.Errorf("must set at most one output format") - } - - // Apply nodecount defaults for non-interactive mode. The - // interactive shell will apply defaults for the interactive mode. - if *f.flagNodeCount < 0 && !*f.flagInteractive { - switch { - default: - f.flagNodeCount = newInt(80) - case f.isFormat("text"): - f.flagNodeCount = newInt(0) - } - } - - // Apply legacy options and diagnose conflicts. - if rc := countFlags([]*bool{f.flagAddresses, f.flagLines, f.flagFiles, f.flagFunctions}); rc == 0 { - f.flagFunctions = newBool(true) - } else if rc > 1 { - f.usage(ui) - return fmt.Errorf("must set at most one granularity option") - } - - var err error - si, sm := *f.flagSampleIndex, *f.flagMean || *f.flagMeanDelay - si, err = sampleIndex(p, &f.flagTotalDelay, si, 1, "delay", "-total_delay", err) - si, err = sampleIndex(p, &f.flagMeanDelay, si, 1, "delay", "-mean_delay", err) - si, err = sampleIndex(p, &f.flagContentions, si, 0, "contentions", "-contentions", err) - - si, err = sampleIndex(p, &f.flagInUseSpace, si, 1, "inuse_space", "-inuse_space", err) - si, err = sampleIndex(p, &f.flagInUseObjects, si, 0, "inuse_objects", "-inuse_objects", err) - si, err = sampleIndex(p, &f.flagAllocSpace, si, 1, "alloc_space", "-alloc_space", err) - si, err = sampleIndex(p, &f.flagAllocObjects, si, 0, "alloc_objects", "-alloc_objects", err) - - if si == -1 { - // Use last value if none is requested. - si = len(p.SampleType) - 1 - } else if si < 0 || si >= len(p.SampleType) { - err = fmt.Errorf("sample_index value %d out of range [0..%d]", si, len(p.SampleType)-1) - } - - if err != nil { - f.usage(ui) - return err - } - f.flagSampleIndex, f.flagMean = newInt(si), newBool(sm) - return nil -} - -func sampleIndex(p *profile.Profile, flag **bool, - sampleIndex int, - newSampleIndex int, - sampleType, option string, - err error) (int, error) { - if err != nil || !**flag { - return sampleIndex, err - } - *flag = newBool(false) - if sampleIndex != -1 { - return 0, fmt.Errorf("set at most one sample value selection option") - } - if newSampleIndex >= len(p.SampleType) || - p.SampleType[newSampleIndex].Type != sampleType { - return 0, fmt.Errorf("option %s not valid for this profile", option) - } - return newSampleIndex, nil -} - -func countFlags(bs []*bool) int { - var c int - for _, b := range bs { - if *b { - c++ - } - } - return c -} - -func countFlagMap(bms map[string]*bool, bmrxs map[string]*string) int { - var c int - for _, b := range bms { - if *b { - c++ - } - } - for _, s := range bmrxs { - if *s != "" { - c++ - } - } - return c -} - -var usageMsgHdr = "usage: pprof [options] [binary] ...\n" + - "Output format (only set one):\n" - -var usageMsg = "Output file parameters (for file-based output formats):\n" + - " -output=f Generate output on file f (stdout by default)\n" + - "Output granularity (only set one):\n" + - " -functions Report at function level [default]\n" + - " -files Report at source file level\n" + - " -lines Report at source line level\n" + - " -addresses Report at address level\n" + - "Comparison options:\n" + - " -base Show delta from this profile\n" + - " -drop_negative Ignore negative differences\n" + - "Sorting options:\n" + - " -cum Sort by cumulative data\n\n" + - "Dynamic profile options:\n" + - " -seconds=N Length of time for dynamic profiles\n" + - "Profile trimming options:\n" + - " -nodecount=N Max number of nodes to show\n" + - " -nodefraction=f Hide nodes below *total\n" + - " -edgefraction=f Hide edges below *total\n" + - "Sample value selection option (by index):\n" + - " -sample_index Index of sample value to display\n" + - " -mean Average sample value over first value\n" + - "Sample value selection option (for heap profiles):\n" + - " -inuse_space Display in-use memory size\n" + - " -inuse_objects Display in-use object counts\n" + - " -alloc_space Display allocated memory size\n" + - " -alloc_objects Display allocated object counts\n" + - "Sample value selection option (for contention profiles):\n" + - " -total_delay Display total delay at each region\n" + - " -contentions Display number of delays at each region\n" + - " -mean_delay Display mean delay at each region\n" + - "Filtering options:\n" + - " -runtime Show runtime call frames in memory profiles\n" + - " -focus=r Restricts to paths going through a node matching regexp\n" + - " -ignore=r Skips paths going through any nodes matching regexp\n" + - " -tagfocus=r Restrict to samples tagged with key:value matching regexp\n" + - " Restrict to samples with numeric tags in range (eg \"32kb:1mb\")\n" + - " -tagignore=r Discard samples tagged with key:value matching regexp\n" + - " Avoid samples with numeric tags in range (eg \"1mb:\")\n" + - "Miscellaneous:\n" + - " -call_tree Generate a context-sensitive call tree\n" + - " -unit=u Convert all samples to unit u for display\n" + - " -divide_by=f Scale all samples by dividing them by f\n" + - " -buildid=id Override build id for main binary in profile\n" + - " -tools=path Search path for object-level tools\n" + - " -help This message" - -var usageMsgVars = "Environment Variables:\n" + - " PPROF_TMPDIR Location for saved profiles (default $HOME/pprof)\n" + - " PPROF_TOOLS Search path for object-level tools\n" + - " PPROF_BINARY_PATH Search path for local binary files\n" + - " default: $HOME/pprof/binaries\n" + - " finds binaries by $name and $buildid/$name" - -func aggregate(prof *profile.Profile, f *flags) error { - switch { - case f.isFormat("proto"), f.isFormat("raw"): - // No aggregation for raw profiles. - case f.isFormat("callgrind"): - // Aggregate to file/line for callgrind. - fallthrough - case *f.flagLines: - return prof.Aggregate(true, true, true, true, false) - case *f.flagFiles: - return prof.Aggregate(true, false, true, false, false) - case *f.flagFunctions: - return prof.Aggregate(true, true, false, false, false) - case f.isFormat("weblist"), f.isFormat("disasm"): - return prof.Aggregate(false, true, true, true, true) - } - return nil -} - -// parseOptions parses the options into report.Options -// Returns a function to postprocess the report after generation. -func parseOptions(f *flags) (o *report.Options, p commands.PostProcessor, err error) { - - if *f.flagDivideBy == 0 { - return nil, nil, fmt.Errorf("zero divisor specified") - } - - o = &report.Options{ - CumSort: *f.flagCum, - CallTree: *f.flagCallTree, - PrintAddresses: *f.flagAddresses, - DropNegative: *f.flagDropNegative, - Ratio: 1 / *f.flagDivideBy, - - NodeCount: *f.flagNodeCount, - NodeFraction: *f.flagNodeFraction, - EdgeFraction: *f.flagEdgeFraction, - OutputUnit: *f.flagDisplayUnit, - } - - for cmd, b := range f.flagCommands { - if *b { - pcmd := f.commands[cmd] - o.OutputFormat = pcmd.Format - return o, pcmd.PostProcess, nil - } - } - - for cmd, rx := range f.flagParamCommands { - if *rx != "" { - pcmd := f.commands[cmd] - if o.Symbol, err = regexp.Compile(*rx); err != nil { - return nil, nil, fmt.Errorf("parsing -%s regexp: %v", cmd, err) - } - o.OutputFormat = pcmd.Format - return o, pcmd.PostProcess, nil - } - } - - return nil, nil, fmt.Errorf("no output format selected") -} - -type sampleValueFunc func(*profile.Sample) int64 - -// sampleFormat returns a function to extract values out of a profile.Sample, -// and the type/units of those values. -func sampleFormat(p *profile.Profile, f *flags) (sampleValueFunc, string, string) { - valueIndex := *f.flagSampleIndex - - if *f.flagMean { - return meanExtractor(valueIndex), "mean_" + p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit - } - - return valueExtractor(valueIndex), p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit -} - -func valueExtractor(ix int) sampleValueFunc { - return func(s *profile.Sample) int64 { - return s.Value[ix] - } -} - -func meanExtractor(ix int) sampleValueFunc { - return func(s *profile.Sample) int64 { - if s.Value[0] == 0 { - return 0 - } - return s.Value[ix] / s.Value[0] - } -} - -func generate(interactive bool, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error { - o, postProcess, err := parseOptions(f) - if err != nil { - return err - } - - var w io.Writer - if *f.flagOutput == "" { - w = os.Stdout - } else { - ui.PrintErr("Generating report in ", *f.flagOutput) - outputFile, err := os.Create(*f.flagOutput) - if err != nil { - return err - } - defer outputFile.Close() - w = outputFile - } - - if prof.Empty() { - return fmt.Errorf("profile is empty") - } - - value, stype, unit := sampleFormat(prof, f) - o.SampleType = stype - rpt := report.New(prof, *o, value, unit) - - // Do not apply filters if we're just generating a proto, so we - // still have all the data. - if o.OutputFormat != report.Proto { - // Delay applying focus/ignore until after creating the report so - // the report reflects the total number of samples. - if err := preprocess(prof, ui, f); err != nil { - return err - } - } - - if postProcess == nil { - return report.Generate(w, rpt, obj) - } - - var dot bytes.Buffer - if err = report.Generate(&dot, rpt, obj); err != nil { - return err - } - - return postProcess(&dot, w, ui) -} diff --git a/src/cmd/pprof/internal/driver/interactive.go b/src/cmd/pprof/internal/driver/interactive.go deleted file mode 100644 index 13009bf7e9..0000000000 --- a/src/cmd/pprof/internal/driver/interactive.go +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright 2014 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 driver - -import ( - "fmt" - "io" - "regexp" - "sort" - "strconv" - "strings" - - "cmd/pprof/internal/commands" - "cmd/pprof/internal/plugin" - "cmd/pprof/internal/profile" -) - -var profileFunctionNames = []string{} - -// functionCompleter replaces provided substring with a function -// name retrieved from a profile if a single match exists. Otherwise, -// it returns unchanged substring. It defaults to no-op if the profile -// is not specified. -func functionCompleter(substring string) string { - found := "" - for _, fName := range profileFunctionNames { - if strings.Contains(fName, substring) { - if found != "" { - return substring - } - found = fName - } - } - if found != "" { - return found - } - return substring -} - -// updateAutoComplete enhances autocompletion with information that can be -// retrieved from the profile -func updateAutoComplete(p *profile.Profile) { - profileFunctionNames = nil // remove function names retrieved previously - for _, fn := range p.Function { - profileFunctionNames = append(profileFunctionNames, fn.Name) - } -} - -// splitCommand splits the command line input into tokens separated by -// spaces. Takes care to separate commands of the form 'top10' into -// two tokens: 'top' and '10' -func splitCommand(input string) []string { - fields := strings.Fields(input) - if num := strings.IndexAny(fields[0], "0123456789"); num != -1 { - inputNumber := fields[0][num:] - fields[0] = fields[0][:num] - fields = append([]string{fields[0], inputNumber}, fields[1:]...) - } - return fields -} - -// interactive displays a prompt and reads commands for profile -// manipulation/visualization. -func interactive(p *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error { - updateAutoComplete(p) - - // Enter command processing loop. - ui.Print("Entering interactive mode (type \"help\" for commands)") - ui.SetAutoComplete(commands.NewCompleter(f.commands)) - - for { - input, err := readCommand(p, ui, f) - if err != nil { - if err != io.EOF { - return err - } - if input == "" { - return nil - } - } - // Process simple commands. - switch input { - case "": - continue - case ":": - f.flagFocus = newString("") - f.flagIgnore = newString("") - f.flagTagFocus = newString("") - f.flagTagIgnore = newString("") - f.flagHide = newString("") - continue - } - - fields := splitCommand(input) - // Process report generation commands. - if _, ok := f.commands[fields[0]]; ok { - if err := generateReport(p, fields, obj, ui, f); err != nil { - if err == io.EOF { - return nil - } - ui.PrintErr(err) - } - continue - } - - switch cmd := fields[0]; cmd { - case "help": - commandHelp(fields, ui, f) - continue - case "exit", "quit": - return nil - } - - // Process option settings. - if of, err := optFlags(p, input, f); err == nil { - f = of - } else { - ui.PrintErr("Error: ", err.Error()) - } - } -} - -func generateReport(p *profile.Profile, cmd []string, obj plugin.ObjTool, ui plugin.UI, f *flags) error { - prof := p.Copy() - - cf, err := cmdFlags(prof, cmd, ui, f) - if err != nil { - return err - } - - return generate(true, prof, obj, ui, cf) -} - -// validateRegex checks if a string is a valid regular expression. -func validateRegex(v string) error { - _, err := regexp.Compile(v) - return err -} - -// readCommand prompts for and reads the next command. -func readCommand(p *profile.Profile, ui plugin.UI, f *flags) (string, error) { - //ui.Print("Options:\n", f.String(p)) - s, err := ui.ReadLine() - return strings.TrimSpace(s), err -} - -func commandHelp(_ []string, ui plugin.UI, f *flags) error { - help := ` - Commands: - cmd [n] [--cum] [focus_regex]* [-ignore_regex]* - Produce a text report with the top n entries. - Include samples matching focus_regex, and exclude ignore_regex. - Add --cum to sort using cumulative data. - Available commands: -` - var commands []string - for name, cmd := range f.commands { - commands = append(commands, fmt.Sprintf(" %-12s %s", name, cmd.Usage)) - } - sort.Strings(commands) - - help = help + strings.Join(commands, "\n") + ` - peek func_regex - Display callers and callees of functions matching func_regex. - - dot [n] [focus_regex]* [-ignore_regex]* [>file] - Produce an annotated callgraph with the top n entries. - Include samples matching focus_regex, and exclude ignore_regex. - For other outputs, replace dot with: - - Graphic formats: dot, svg, pdf, ps, gif, png (use > to name output file) - - Graph viewer: gv, web, evince, eog - - callgrind [n] [focus_regex]* [-ignore_regex]* [>file] - Produce a file in callgrind-compatible format. - Include samples matching focus_regex, and exclude ignore_regex. - - weblist func_regex [-ignore_regex]* - Show annotated source with interspersed assembly in a web browser. - - list func_regex [-ignore_regex]* - Print source for routines matching func_regex, and exclude ignore_regex. - - disasm func_regex [-ignore_regex]* - Disassemble routines matching func_regex, and exclude ignore_regex. - - tags tag_regex [-ignore_regex]* - List tags with key:value matching tag_regex and exclude ignore_regex. - - quit/exit/^D - Exit pprof. - - option=value - The following options can be set individually: - cum/flat: Sort entries based on cumulative or flat data - call_tree: Build context-sensitive call trees - nodecount: Max number of entries to display - nodefraction: Min frequency ratio of nodes to display - edgefraction: Min frequency ratio of edges to display - focus/ignore: Regexp to include/exclude samples by name/file - tagfocus/tagignore: Regexp or value range to filter samples by tag - eg "1mb", "1mb:2mb", ":64kb" - - functions: Level of aggregation for sample data - files: - lines: - addresses: - - unit: Measurement unit to use on reports - - Sample value selection by index: - sample_index: Index of sample value to display - mean: Average sample value over first value - - Sample value selection by name: - alloc_space for heap profiles - alloc_objects - inuse_space - inuse_objects - - total_delay for contention profiles - mean_delay - contentions - - : Clear focus/ignore/hide/tagfocus/tagignore` - - ui.Print(help) - return nil -} - -// cmdFlags parses the options of an interactive command and returns -// an updated flags object. -func cmdFlags(prof *profile.Profile, input []string, ui plugin.UI, f *flags) (*flags, error) { - cf := *f - - var focus, ignore string - output := *cf.flagOutput - nodeCount := *cf.flagNodeCount - cmd := input[0] - - // Update output flags based on parameters. - tokens := input[1:] - for p := 0; p < len(tokens); p++ { - t := tokens[p] - if t == "" { - continue - } - if c, err := strconv.ParseInt(t, 10, 32); err == nil { - nodeCount = int(c) - continue - } - switch t[0] { - case '>': - if len(t) > 1 { - output = t[1:] - continue - } - // find next token - for p++; p < len(tokens); p++ { - if tokens[p] != "" { - output = tokens[p] - break - } - } - case '-': - if t == "--cum" || t == "-cum" { - cf.flagCum = newBool(true) - continue - } - ignore = catRegex(ignore, t[1:]) - default: - focus = catRegex(focus, t) - } - } - - pcmd, ok := f.commands[cmd] - if !ok { - return nil, fmt.Errorf("Unexpected parse failure: %v", input) - } - // Reset flags - cf.flagCommands = make(map[string]*bool) - cf.flagParamCommands = make(map[string]*string) - - if !pcmd.HasParam { - cf.flagCommands[cmd] = newBool(true) - - switch cmd { - case "tags": - cf.flagTagFocus = newString(focus) - cf.flagTagIgnore = newString(ignore) - default: - cf.flagFocus = newString(catRegex(*cf.flagFocus, focus)) - cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore)) - } - } else { - if focus == "" { - focus = "." - } - cf.flagParamCommands[cmd] = newString(focus) - cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore)) - } - - if nodeCount < 0 { - switch cmd { - case "text", "top": - // Default text/top to 10 nodes on interactive mode - nodeCount = 10 - default: - nodeCount = 80 - } - } - - cf.flagNodeCount = newInt(nodeCount) - cf.flagOutput = newString(output) - - // Do regular flags processing - if err := processFlags(prof, ui, &cf); err != nil { - cf.usage(ui) - return nil, err - } - - return &cf, nil -} - -func catRegex(a, b string) string { - if a == "" { - return b - } - if b == "" { - return a - } - return a + "|" + b -} - -// optFlags parses an interactive option setting and returns -// an updated flags object. -func optFlags(p *profile.Profile, input string, f *flags) (*flags, error) { - inputs := strings.SplitN(input, "=", 2) - option := strings.ToLower(strings.TrimSpace(inputs[0])) - var value string - if len(inputs) == 2 { - value = strings.TrimSpace(inputs[1]) - } - - of := *f - - var err error - var bv bool - var uv uint64 - var fv float64 - - switch option { - case "cum": - if bv, err = parseBool(value); err != nil { - return nil, err - } - of.flagCum = newBool(bv) - case "flat": - if bv, err = parseBool(value); err != nil { - return nil, err - } - of.flagCum = newBool(!bv) - case "call_tree": - if bv, err = parseBool(value); err != nil { - return nil, err - } - of.flagCallTree = newBool(bv) - case "unit": - of.flagDisplayUnit = newString(value) - case "sample_index": - if uv, err = strconv.ParseUint(value, 10, 32); err != nil { - return nil, err - } - if ix := int(uv); ix < 0 || ix >= len(p.SampleType) { - return nil, fmt.Errorf("sample_index out of range [0..%d]", len(p.SampleType)-1) - } - of.flagSampleIndex = newInt(int(uv)) - case "mean": - if bv, err = parseBool(value); err != nil { - return nil, err - } - of.flagMean = newBool(bv) - case "nodecount": - if uv, err = strconv.ParseUint(value, 10, 32); err != nil { - return nil, err - } - of.flagNodeCount = newInt(int(uv)) - case "nodefraction": - if fv, err = strconv.ParseFloat(value, 64); err != nil { - return nil, err - } - of.flagNodeFraction = newFloat64(fv) - case "edgefraction": - if fv, err = strconv.ParseFloat(value, 64); err != nil { - return nil, err - } - of.flagEdgeFraction = newFloat64(fv) - case "focus": - if err = validateRegex(value); err != nil { - return nil, err - } - of.flagFocus = newString(value) - case "ignore": - if err = validateRegex(value); err != nil { - return nil, err - } - of.flagIgnore = newString(value) - case "tagfocus": - if err = validateRegex(value); err != nil { - return nil, err - } - of.flagTagFocus = newString(value) - case "tagignore": - if err = validateRegex(value); err != nil { - return nil, err - } - of.flagTagIgnore = newString(value) - case "hide": - if err = validateRegex(value); err != nil { - return nil, err - } - of.flagHide = newString(value) - case "addresses", "files", "lines", "functions": - if bv, err = parseBool(value); err != nil { - return nil, err - } - if !bv { - return nil, fmt.Errorf("select one of addresses/files/lines/functions") - } - setGranularityToggle(option, &of) - default: - if ix := findSampleIndex(p, "", option); ix >= 0 { - of.flagSampleIndex = newInt(ix) - } else if ix := findSampleIndex(p, "total_", option); ix >= 0 { - of.flagSampleIndex = newInt(ix) - of.flagMean = newBool(false) - } else if ix := findSampleIndex(p, "mean_", option); ix >= 1 { - of.flagSampleIndex = newInt(ix) - of.flagMean = newBool(true) - } else { - return nil, fmt.Errorf("unrecognized command: %s", input) - } - } - return &of, nil -} - -// parseBool parses a string as a boolean value. -func parseBool(v string) (bool, error) { - switch strings.ToLower(v) { - case "true", "t", "yes", "y", "1", "": - return true, nil - case "false", "f", "no", "n", "0": - return false, nil - } - return false, fmt.Errorf(`illegal input "%s" for bool value`, v) -} - -func findSampleIndex(p *profile.Profile, prefix, sampleType string) int { - if !strings.HasPrefix(sampleType, prefix) { - return -1 - } - sampleType = strings.TrimPrefix(sampleType, prefix) - for i, r := range p.SampleType { - if r.Type == sampleType { - return i - } - } - return -1 -} - -// setGranularityToggle manages the set of granularity options. These -// operate as a toggle; turning one on turns the others off. -func setGranularityToggle(o string, fl *flags) { - t, f := newBool(true), newBool(false) - fl.flagFunctions = f - fl.flagFiles = f - fl.flagLines = f - fl.flagAddresses = f - switch o { - case "functions": - fl.flagFunctions = t - case "files": - fl.flagFiles = t - case "lines": - fl.flagLines = t - case "addresses": - fl.flagAddresses = t - default: - panic(fmt.Errorf("unexpected option %s", o)) - } -} diff --git a/src/cmd/pprof/internal/fetch/fetch.go b/src/cmd/pprof/internal/fetch/fetch.go deleted file mode 100644 index ec4a6383c6..0000000000 --- a/src/cmd/pprof/internal/fetch/fetch.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2014 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 fetch provides an extensible mechanism to fetch a profile -// from a data source. -package fetch - -import ( - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "strings" - "time" - - "cmd/pprof/internal/plugin" - "cmd/pprof/internal/profile" -) - -// FetchProfile reads from a data source (network, file) and generates a -// profile. -func FetchProfile(source string, timeout time.Duration) (*profile.Profile, error) { - return Fetcher(source, timeout, plugin.StandardUI()) -} - -// Fetcher is the plugin.Fetcher version of FetchProfile. -func Fetcher(source string, timeout time.Duration, ui plugin.UI) (*profile.Profile, error) { - var f io.ReadCloser - var err error - - url, err := url.Parse(source) - if err == nil && url.Host != "" { - f, err = FetchURL(source, timeout) - } else { - f, err = os.Open(source) - } - if err != nil { - return nil, err - } - defer f.Close() - return profile.Parse(f) -} - -// FetchURL fetches a profile from a URL using HTTP. -func FetchURL(source string, timeout time.Duration) (io.ReadCloser, error) { - resp, err := httpGet(source, timeout) - if err != nil { - return nil, fmt.Errorf("http fetch %s: %v", source, err) - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("server response: %s", resp.Status) - } - - return resp.Body, nil -} - -// PostURL issues a POST to a URL over HTTP. -func PostURL(source, post string) ([]byte, error) { - resp, err := http.Post(source, "application/octet-stream", strings.NewReader(post)) - if err != nil { - return nil, fmt.Errorf("http post %s: %v", source, err) - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("server response: %s", resp.Status) - } - defer resp.Body.Close() - return ioutil.ReadAll(resp.Body) -} - -// httpGet is a wrapper around http.Get; it is defined as a variable -// so it can be redefined during for testing. -var httpGet = func(url string, timeout time.Duration) (*http.Response, error) { - client := &http.Client{ - Transport: &http.Transport{ - ResponseHeaderTimeout: timeout + 5*time.Second, - }, - } - return client.Get(url) -} diff --git a/src/cmd/pprof/internal/plugin/plugin.go b/src/cmd/pprof/internal/plugin/plugin.go deleted file mode 100644 index a22ec5f3c5..0000000000 --- a/src/cmd/pprof/internal/plugin/plugin.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2014 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 plugin defines the plugin implementations that the main pprof driver requires. -package plugin - -import ( - "bufio" - "fmt" - "os" - "regexp" - "strings" - "time" - - "cmd/pprof/internal/profile" -) - -// A FlagSet creates and parses command-line flags. -// It is similar to the standard flag.FlagSet. -type FlagSet interface { - // Bool, Int, Float64, and String define new flags, - // like the functions of the same name in package flag. - Bool(name string, def bool, usage string) *bool - Int(name string, def int, usage string) *int - Float64(name string, def float64, usage string) *float64 - String(name string, def string, usage string) *string - - // ExtraUsage returns any additional text that should be - // printed after the standard usage message. - // The typical use of ExtraUsage is to show any custom flags - // defined by the specific pprof plugins being used. - ExtraUsage() string - - // Parse initializes the flags with their values for this run - // and returns the non-flag command line arguments. - // If an unknown flag is encountered or there are no arguments, - // Parse should call usage and return nil. - Parse(usage func()) []string -} - -// An ObjTool inspects shared libraries and executable files. -type ObjTool interface { - // Open opens the named object file. - // If the object is a shared library, start is the address where - // it is mapped into memory in the address space being inspected. - Open(file string, start uint64) (ObjFile, error) - - // Demangle translates a batch of symbol names from mangled - // form to human-readable form. - Demangle(names []string) (map[string]string, error) - - // Disasm disassembles the named object file, starting at - // the start address and stopping at (before) the end address. - Disasm(file string, start, end uint64) ([]Inst, error) - - // SetConfig configures the tool. - // The implementation defines the meaning of the string - // and can ignore it entirely. - SetConfig(config string) -} - -// NoObjTool returns a trivial implementation of the ObjTool interface. -// Open returns an error indicating that the requested file does not exist. -// Demangle returns an empty map and a nil error. -// Disasm returns an error. -// SetConfig is a no-op. -func NoObjTool() ObjTool { - return noObjTool{} -} - -type noObjTool struct{} - -func (noObjTool) Open(file string, start uint64) (ObjFile, error) { - return nil, &os.PathError{Op: "open", Path: file, Err: os.ErrNotExist} -} - -func (noObjTool) Demangle(name []string) (map[string]string, error) { - return make(map[string]string), nil -} - -func (noObjTool) Disasm(file string, start, end uint64) ([]Inst, error) { - return nil, fmt.Errorf("disassembly not supported") -} - -func (noObjTool) SetConfig(config string) { -} - -// An ObjFile is a single object file: a shared library or executable. -type ObjFile interface { - // Name returns the underlyinf file name, if available - Name() string - - // Base returns the base address to use when looking up symbols in the file. - Base() uint64 - - // BuildID returns the GNU build ID of the file, or an empty string. - BuildID() string - - // SourceLine reports the source line information for a given - // address in the file. Due to inlining, the source line information - // is in general a list of positions representing a call stack, - // with the leaf function first. - SourceLine(addr uint64) ([]Frame, error) - - // Symbols returns a list of symbols in the object file. - // If r is not nil, Symbols restricts the list to symbols - // with names matching the regular expression. - // If addr is not zero, Symbols restricts the list to symbols - // containing that address. - Symbols(r *regexp.Regexp, addr uint64) ([]*Sym, error) - - // Close closes the file, releasing associated resources. - Close() error -} - -// A Frame describes a single line in a source file. -type Frame struct { - Func string // name of function - File string // source file name - Line int // line in file -} - -// A Sym describes a single symbol in an object file. -type Sym struct { - Name []string // names of symbol (many if symbol was dedup'ed) - File string // object file containing symbol - Start uint64 // start virtual address - End uint64 // virtual address of last byte in sym (Start+size-1) -} - -// An Inst is a single instruction in an assembly listing. -type Inst struct { - Addr uint64 // virtual address of instruction - Text string // instruction text - File string // source file - Line int // source line -} - -// A UI manages user interactions. -type UI interface { - // Read returns a line of text (a command) read from the user. - ReadLine() (string, error) - - // Print shows a message to the user. - // It formats the text as fmt.Print would and adds a final \n if not already present. - // For line-based UI, Print writes to standard error. - // (Standard output is reserved for report data.) - Print(...interface{}) - - // PrintErr shows an error message to the user. - // It formats the text as fmt.Print would and adds a final \n if not already present. - // For line-based UI, PrintErr writes to standard error. - PrintErr(...interface{}) - - // IsTerminal returns whether the UI is known to be tied to an - // interactive terminal (as opposed to being redirected to a file). - IsTerminal() bool - - // SetAutoComplete instructs the UI to call complete(cmd) to obtain - // the auto-completion of cmd, if the UI supports auto-completion at all. - SetAutoComplete(complete func(string) string) -} - -// StandardUI returns a UI that reads from standard input, -// prints messages to standard output, -// prints errors to standard error, and doesn't use auto-completion. -func StandardUI() UI { - return &stdUI{r: bufio.NewReader(os.Stdin)} -} - -type stdUI struct { - r *bufio.Reader -} - -func (ui *stdUI) ReadLine() (string, error) { - os.Stdout.WriteString("(pprof) ") - return ui.r.ReadString('\n') -} - -func (ui *stdUI) Print(args ...interface{}) { - ui.fprint(os.Stderr, args) -} - -func (ui *stdUI) PrintErr(args ...interface{}) { - ui.fprint(os.Stderr, args) -} - -func (ui *stdUI) IsTerminal() bool { - return false -} - -func (ui *stdUI) SetAutoComplete(func(string) string) { -} - -func (ui *stdUI) fprint(f *os.File, args []interface{}) { - text := fmt.Sprint(args...) - if !strings.HasSuffix(text, "\n") { - text += "\n" - } - f.WriteString(text) -} - -// A Fetcher reads and returns the profile named by src. -// It gives up after the given timeout, unless src contains a timeout override -// (as defined by the implementation). -// It can print messages to ui. -type Fetcher func(src string, timeout time.Duration, ui UI) (*profile.Profile, error) - -// A Symbolizer annotates a profile with symbol information. -// The profile was fetch from src. -// The meaning of mode is defined by the implementation. -type Symbolizer func(mode, src string, prof *profile.Profile, obj ObjTool, ui UI) error diff --git a/src/cmd/pprof/internal/profile/encode.go b/src/cmd/pprof/internal/profile/encode.go deleted file mode 100644 index 6b879a84ac..0000000000 --- a/src/cmd/pprof/internal/profile/encode.go +++ /dev/null @@ -1,470 +0,0 @@ -// Copyright 2014 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 profile - -import ( - "errors" - "fmt" - "sort" -) - -func (p *Profile) decoder() []decoder { - return profileDecoder -} - -// preEncode populates the unexported fields to be used by encode -// (with suffix X) from the corresponding exported fields. The -// exported fields are cleared up to facilitate testing. -func (p *Profile) preEncode() { - strings := make(map[string]int) - addString(strings, "") - - for _, st := range p.SampleType { - st.typeX = addString(strings, st.Type) - st.unitX = addString(strings, st.Unit) - } - - for _, s := range p.Sample { - s.labelX = nil - var keys []string - for k := range s.Label { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - vs := s.Label[k] - for _, v := range vs { - s.labelX = append(s.labelX, - Label{ - keyX: addString(strings, k), - strX: addString(strings, v), - }, - ) - } - } - var numKeys []string - for k := range s.NumLabel { - numKeys = append(numKeys, k) - } - sort.Strings(numKeys) - for _, k := range numKeys { - vs := s.NumLabel[k] - for _, v := range vs { - s.labelX = append(s.labelX, - Label{ - keyX: addString(strings, k), - numX: v, - }, - ) - } - } - s.locationIDX = nil - for _, l := range s.Location { - s.locationIDX = append(s.locationIDX, l.ID) - } - } - - for _, m := range p.Mapping { - m.fileX = addString(strings, m.File) - m.buildIDX = addString(strings, m.BuildID) - } - - for _, l := range p.Location { - for i, ln := range l.Line { - if ln.Function != nil { - l.Line[i].functionIDX = ln.Function.ID - } else { - l.Line[i].functionIDX = 0 - } - } - if l.Mapping != nil { - l.mappingIDX = l.Mapping.ID - } else { - l.mappingIDX = 0 - } - } - for _, f := range p.Function { - f.nameX = addString(strings, f.Name) - f.systemNameX = addString(strings, f.SystemName) - f.filenameX = addString(strings, f.Filename) - } - - p.dropFramesX = addString(strings, p.DropFrames) - p.keepFramesX = addString(strings, p.KeepFrames) - - if pt := p.PeriodType; pt != nil { - pt.typeX = addString(strings, pt.Type) - pt.unitX = addString(strings, pt.Unit) - } - - p.stringTable = make([]string, len(strings)) - for s, i := range strings { - p.stringTable[i] = s - } -} - -func (p *Profile) encode(b *buffer) { - for _, x := range p.SampleType { - encodeMessage(b, 1, x) - } - for _, x := range p.Sample { - encodeMessage(b, 2, x) - } - for _, x := range p.Mapping { - encodeMessage(b, 3, x) - } - for _, x := range p.Location { - encodeMessage(b, 4, x) - } - for _, x := range p.Function { - encodeMessage(b, 5, x) - } - encodeStrings(b, 6, p.stringTable) - encodeInt64Opt(b, 7, p.dropFramesX) - encodeInt64Opt(b, 8, p.keepFramesX) - encodeInt64Opt(b, 9, p.TimeNanos) - encodeInt64Opt(b, 10, p.DurationNanos) - if pt := p.PeriodType; pt != nil && (pt.typeX != 0 || pt.unitX != 0) { - encodeMessage(b, 11, p.PeriodType) - } - encodeInt64Opt(b, 12, p.Period) -} - -var profileDecoder = []decoder{ - nil, // 0 - // repeated ValueType sample_type = 1 - func(b *buffer, m message) error { - x := new(ValueType) - pp := m.(*Profile) - pp.SampleType = append(pp.SampleType, x) - return decodeMessage(b, x) - }, - // repeated Sample sample = 2 - func(b *buffer, m message) error { - x := new(Sample) - pp := m.(*Profile) - pp.Sample = append(pp.Sample, x) - return decodeMessage(b, x) - }, - // repeated Mapping mapping = 3 - func(b *buffer, m message) error { - x := new(Mapping) - pp := m.(*Profile) - pp.Mapping = append(pp.Mapping, x) - return decodeMessage(b, x) - }, - // repeated Location location = 4 - func(b *buffer, m message) error { - x := new(Location) - pp := m.(*Profile) - pp.Location = append(pp.Location, x) - return decodeMessage(b, x) - }, - // repeated Function function = 5 - func(b *buffer, m message) error { - x := new(Function) - pp := m.(*Profile) - pp.Function = append(pp.Function, x) - return decodeMessage(b, x) - }, - // repeated string string_table = 6 - func(b *buffer, m message) error { - err := decodeStrings(b, &m.(*Profile).stringTable) - if err != nil { - return err - } - if *&m.(*Profile).stringTable[0] != "" { - return errors.New("string_table[0] must be ''") - } - return nil - }, - // repeated int64 drop_frames = 7 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).dropFramesX) }, - // repeated int64 keep_frames = 8 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).keepFramesX) }, - // repeated int64 time_nanos = 9 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).TimeNanos) }, - // repeated int64 duration_nanos = 10 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).DurationNanos) }, - // optional string period_type = 11 - func(b *buffer, m message) error { - x := new(ValueType) - pp := m.(*Profile) - pp.PeriodType = x - return decodeMessage(b, x) - }, - // repeated int64 period = 12 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).Period) }, -} - -// postDecode takes the unexported fields populated by decode (with -// suffix X) and populates the corresponding exported fields. -// The unexported fields are cleared up to facilitate testing. -func (p *Profile) postDecode() error { - var err error - - mappings := make(map[uint64]*Mapping) - for _, m := range p.Mapping { - m.File, err = getString(p.stringTable, &m.fileX, err) - m.BuildID, err = getString(p.stringTable, &m.buildIDX, err) - mappings[m.ID] = m - } - - functions := make(map[uint64]*Function) - for _, f := range p.Function { - f.Name, err = getString(p.stringTable, &f.nameX, err) - f.SystemName, err = getString(p.stringTable, &f.systemNameX, err) - f.Filename, err = getString(p.stringTable, &f.filenameX, err) - functions[f.ID] = f - } - - locations := make(map[uint64]*Location) - for _, l := range p.Location { - l.Mapping = mappings[l.mappingIDX] - l.mappingIDX = 0 - for i, ln := range l.Line { - if id := ln.functionIDX; id != 0 { - l.Line[i].Function = functions[id] - if l.Line[i].Function == nil { - return fmt.Errorf("Function ID %d not found", id) - } - l.Line[i].functionIDX = 0 - } - } - locations[l.ID] = l - } - - for _, st := range p.SampleType { - st.Type, err = getString(p.stringTable, &st.typeX, err) - st.Unit, err = getString(p.stringTable, &st.unitX, err) - } - - for _, s := range p.Sample { - labels := make(map[string][]string) - numLabels := make(map[string][]int64) - for _, l := range s.labelX { - var key, value string - key, err = getString(p.stringTable, &l.keyX, err) - if l.strX != 0 { - value, err = getString(p.stringTable, &l.strX, err) - labels[key] = append(labels[key], value) - } else { - numLabels[key] = append(numLabels[key], l.numX) - } - } - if len(labels) > 0 { - s.Label = labels - } - if len(numLabels) > 0 { - s.NumLabel = numLabels - } - s.Location = nil - for _, lid := range s.locationIDX { - s.Location = append(s.Location, locations[lid]) - } - s.locationIDX = nil - } - - p.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err) - p.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err) - - if pt := p.PeriodType; pt == nil { - p.PeriodType = &ValueType{} - } - - if pt := p.PeriodType; pt != nil { - pt.Type, err = getString(p.stringTable, &pt.typeX, err) - pt.Unit, err = getString(p.stringTable, &pt.unitX, err) - } - p.stringTable = nil - return nil -} - -func (p *ValueType) decoder() []decoder { - return valueTypeDecoder -} - -func (p *ValueType) encode(b *buffer) { - encodeInt64Opt(b, 1, p.typeX) - encodeInt64Opt(b, 2, p.unitX) -} - -var valueTypeDecoder = []decoder{ - nil, // 0 - // optional int64 type = 1 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) }, - // optional int64 unit = 2 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) }, -} - -func (p *Sample) decoder() []decoder { - return sampleDecoder -} - -func (p *Sample) encode(b *buffer) { - encodeUint64s(b, 1, p.locationIDX) - for _, x := range p.Value { - encodeInt64(b, 2, x) - } - for _, x := range p.labelX { - encodeMessage(b, 3, x) - } -} - -var sampleDecoder = []decoder{ - nil, // 0 - // repeated uint64 location = 1 - func(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) }, - // repeated int64 value = 2 - func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) }, - // repeated Label label = 3 - func(b *buffer, m message) error { - s := m.(*Sample) - n := len(s.labelX) - s.labelX = append(s.labelX, Label{}) - return decodeMessage(b, &s.labelX[n]) - }, -} - -func (p Label) decoder() []decoder { - return labelDecoder -} - -func (p Label) encode(b *buffer) { - encodeInt64Opt(b, 1, p.keyX) - encodeInt64Opt(b, 2, p.strX) - encodeInt64Opt(b, 3, p.numX) -} - -var labelDecoder = []decoder{ - nil, // 0 - // optional int64 key = 1 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).keyX) }, - // optional int64 str = 2 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).strX) }, - // optional int64 num = 3 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).numX) }, -} - -func (p *Mapping) decoder() []decoder { - return mappingDecoder -} - -func (p *Mapping) encode(b *buffer) { - encodeUint64Opt(b, 1, p.ID) - encodeUint64Opt(b, 2, p.Start) - encodeUint64Opt(b, 3, p.Limit) - encodeUint64Opt(b, 4, p.Offset) - encodeInt64Opt(b, 5, p.fileX) - encodeInt64Opt(b, 6, p.buildIDX) - encodeBoolOpt(b, 7, p.HasFunctions) - encodeBoolOpt(b, 8, p.HasFilenames) - encodeBoolOpt(b, 9, p.HasLineNumbers) - encodeBoolOpt(b, 10, p.HasInlineFrames) -} - -var mappingDecoder = []decoder{ - nil, // 0 - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) }, // optional uint64 id = 1 - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) }, // optional uint64 memory_offset = 2 - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) }, // optional uint64 memory_limit = 3 - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) }, // optional uint64 file_offset = 4 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) }, // optional int64 filename = 5 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) }, // optional int64 build_id = 6 - func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) }, // optional bool has_functions = 7 - func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) }, // optional bool has_filenames = 8 - func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) }, // optional bool has_line_numbers = 9 - func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10 -} - -func (p *Location) decoder() []decoder { - return locationDecoder -} - -func (p *Location) encode(b *buffer) { - encodeUint64Opt(b, 1, p.ID) - encodeUint64Opt(b, 2, p.mappingIDX) - encodeUint64Opt(b, 3, p.Address) - for i := range p.Line { - encodeMessage(b, 4, &p.Line[i]) - } -} - -var locationDecoder = []decoder{ - nil, // 0 - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) }, // optional uint64 id = 1; - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2; - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) }, // optional uint64 address = 3; - func(b *buffer, m message) error { // repeated Line line = 4 - pp := m.(*Location) - n := len(pp.Line) - pp.Line = append(pp.Line, Line{}) - return decodeMessage(b, &pp.Line[n]) - }, -} - -func (p *Line) decoder() []decoder { - return lineDecoder -} - -func (p *Line) encode(b *buffer) { - encodeUint64Opt(b, 1, p.functionIDX) - encodeInt64Opt(b, 2, p.Line) -} - -var lineDecoder = []decoder{ - nil, // 0 - // optional uint64 function_id = 1 - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) }, - // optional int64 line = 2 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) }, -} - -func (p *Function) decoder() []decoder { - return functionDecoder -} - -func (p *Function) encode(b *buffer) { - encodeUint64Opt(b, 1, p.ID) - encodeInt64Opt(b, 2, p.nameX) - encodeInt64Opt(b, 3, p.systemNameX) - encodeInt64Opt(b, 4, p.filenameX) - encodeInt64Opt(b, 5, p.StartLine) -} - -var functionDecoder = []decoder{ - nil, // 0 - // optional uint64 id = 1 - func(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) }, - // optional int64 function_name = 2 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) }, - // optional int64 function_system_name = 3 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) }, - // repeated int64 filename = 4 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) }, - // optional int64 start_line = 5 - func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) }, -} - -func addString(strings map[string]int, s string) int64 { - i, ok := strings[s] - if !ok { - i = len(strings) - strings[s] = i - } - return int64(i) -} - -func getString(strings []string, strng *int64, err error) (string, error) { - if err != nil { - return "", err - } - s := int(*strng) - if s < 0 || s >= len(strings) { - return "", errMalformed - } - *strng = 0 - return strings[s], nil -} diff --git a/src/cmd/pprof/internal/profile/filter.go b/src/cmd/pprof/internal/profile/filter.go deleted file mode 100644 index 1baa096a49..0000000000 --- a/src/cmd/pprof/internal/profile/filter.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2014 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. - -// Implements methods to filter samples from profiles. - -package profile - -import "regexp" - -// FilterSamplesByName filters the samples in a profile and only keeps -// samples where at least one frame matches focus but none match ignore. -// Returns true is the corresponding regexp matched at least one sample. -func (p *Profile) FilterSamplesByName(focus, ignore, hide *regexp.Regexp) (fm, im, hm bool) { - focusOrIgnore := make(map[uint64]bool) - hidden := make(map[uint64]bool) - for _, l := range p.Location { - if ignore != nil && l.matchesName(ignore) { - im = true - focusOrIgnore[l.ID] = false - } else if focus == nil || l.matchesName(focus) { - fm = true - focusOrIgnore[l.ID] = true - } - if hide != nil && l.matchesName(hide) { - hm = true - l.Line = l.unmatchedLines(hide) - if len(l.Line) == 0 { - hidden[l.ID] = true - } - } - } - - s := make([]*Sample, 0, len(p.Sample)) - for _, sample := range p.Sample { - if focusedAndNotIgnored(sample.Location, focusOrIgnore) { - if len(hidden) > 0 { - var locs []*Location - for _, loc := range sample.Location { - if !hidden[loc.ID] { - locs = append(locs, loc) - } - } - if len(locs) == 0 { - // Remove sample with no locations (by not adding it to s). - continue - } - sample.Location = locs - } - s = append(s, sample) - } - } - p.Sample = s - - return -} - -// matchesName returns whether the function name or file in the -// location matches the regular expression. -func (loc *Location) matchesName(re *regexp.Regexp) bool { - for _, ln := range loc.Line { - if fn := ln.Function; fn != nil { - if re.MatchString(fn.Name) { - return true - } - if re.MatchString(fn.Filename) { - return true - } - } - } - return false -} - -// unmatchedLines returns the lines in the location that do not match -// the regular expression. -func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line { - var lines []Line - for _, ln := range loc.Line { - if fn := ln.Function; fn != nil { - if re.MatchString(fn.Name) { - continue - } - if re.MatchString(fn.Filename) { - continue - } - } - lines = append(lines, ln) - } - return lines -} - -// focusedAndNotIgnored looks up a slice of ids against a map of -// focused/ignored locations. The map only contains locations that are -// explicitly focused or ignored. Returns whether there is at least -// one focused location but no ignored locations. -func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool { - var f bool - for _, loc := range locs { - if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore { - if focus { - // Found focused location. Must keep searching in case there - // is an ignored one as well. - f = true - } else { - // Found ignored location. Can return false right away. - return false - } - } - } - return f -} - -// TagMatch selects tags for filtering -type TagMatch func(key, val string, nval int64) bool - -// FilterSamplesByTag removes all samples from the profile, except -// those that match focus and do not match the ignore regular -// expression. -func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) { - samples := make([]*Sample, 0, len(p.Sample)) - for _, s := range p.Sample { - focused, ignored := focusedSample(s, focus, ignore) - fm = fm || focused - im = im || ignored - if focused && !ignored { - samples = append(samples, s) - } - } - p.Sample = samples - return -} - -// focusedTag checks a sample against focus and ignore regexps. -// Returns whether the focus/ignore regexps match any tags -func focusedSample(s *Sample, focus, ignore TagMatch) (fm, im bool) { - fm = focus == nil - for key, vals := range s.Label { - for _, val := range vals { - if ignore != nil && ignore(key, val, 0) { - im = true - } - if !fm && focus(key, val, 0) { - fm = true - } - } - } - for key, vals := range s.NumLabel { - for _, val := range vals { - if ignore != nil && ignore(key, "", val) { - im = true - } - if !fm && focus(key, "", val) { - fm = true - } - } - } - return fm, im -} diff --git a/src/cmd/pprof/internal/profile/legacy_profile.go b/src/cmd/pprof/internal/profile/legacy_profile.go deleted file mode 100644 index e1f24c4c6d..0000000000 --- a/src/cmd/pprof/internal/profile/legacy_profile.go +++ /dev/null @@ -1,1251 +0,0 @@ -// Copyright 2014 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. - -// This file implements parsers to convert legacy profiles into the -// profile.proto format. - -package profile - -import ( - "bufio" - "bytes" - "fmt" - "io" - "math" - "regexp" - "strconv" - "strings" -) - -var ( - countStartRE = regexp.MustCompile(`\A(\w+) profile: total \d+\n\z`) - countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`) - - heapHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`) - heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`) - - contentionSampleRE = regexp.MustCompile(`(\d+) *(\d+) @([ x0-9a-f]*)`) - - hexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`) - - growthHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`) - - fragmentationHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`) - - threadzStartRE = regexp.MustCompile(`--- threadz \d+ ---`) - threadStartRE = regexp.MustCompile(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`) - - procMapsRE = regexp.MustCompile(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`) - - briefMapsRE = regexp.MustCompile(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`) - - // LegacyHeapAllocated instructs the heapz parsers to use the - // allocated memory stats instead of the default in-use memory. Note - // that tcmalloc doesn't provide all allocated memory, only in-use - // stats. - LegacyHeapAllocated bool -) - -func isSpaceOrComment(line string) bool { - trimmed := strings.TrimSpace(line) - return len(trimmed) == 0 || trimmed[0] == '#' -} - -// parseGoCount parses a Go count profile (e.g., threadcreate or -// goroutine) and returns a new Profile. -func parseGoCount(b []byte) (*Profile, error) { - r := bytes.NewBuffer(b) - - var line string - var err error - for { - // Skip past comments and empty lines seeking a real header. - line, err = r.ReadString('\n') - if err != nil { - return nil, err - } - if !isSpaceOrComment(line) { - break - } - } - - m := countStartRE.FindStringSubmatch(line) - if m == nil { - return nil, errUnrecognized - } - profileType := string(m[1]) - p := &Profile{ - PeriodType: &ValueType{Type: profileType, Unit: "count"}, - Period: 1, - SampleType: []*ValueType{{Type: profileType, Unit: "count"}}, - } - locations := make(map[uint64]*Location) - for { - line, err = r.ReadString('\n') - if err != nil { - if err == io.EOF { - break - } - return nil, err - } - if isSpaceOrComment(line) { - continue - } - if strings.HasPrefix(line, "---") { - break - } - m := countRE.FindStringSubmatch(line) - if m == nil { - return nil, errMalformed - } - n, err := strconv.ParseInt(string(m[1]), 0, 64) - if err != nil { - return nil, errMalformed - } - fields := strings.Fields(string(m[2])) - locs := make([]*Location, 0, len(fields)) - for _, stk := range fields { - addr, err := strconv.ParseUint(stk, 0, 64) - if err != nil { - return nil, errMalformed - } - // Adjust all frames by -1 (except the leaf) to land on top of - // the call instruction. - if len(locs) > 0 { - addr-- - } - loc := locations[addr] - if loc == nil { - loc = &Location{ - Address: addr, - } - locations[addr] = loc - p.Location = append(p.Location, loc) - } - locs = append(locs, loc) - } - p.Sample = append(p.Sample, &Sample{ - Location: locs, - Value: []int64{n}, - }) - } - - if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil { - return nil, err - } - return p, nil -} - -// remapLocationIDs ensures there is a location for each address -// referenced by a sample, and remaps the samples to point to the new -// location ids. -func (p *Profile) remapLocationIDs() { - seen := make(map[*Location]bool, len(p.Location)) - var locs []*Location - - for _, s := range p.Sample { - for _, l := range s.Location { - if seen[l] { - continue - } - l.ID = uint64(len(locs) + 1) - locs = append(locs, l) - seen[l] = true - } - } - p.Location = locs -} - -func (p *Profile) remapFunctionIDs() { - seen := make(map[*Function]bool, len(p.Function)) - var fns []*Function - - for _, l := range p.Location { - for _, ln := range l.Line { - fn := ln.Function - if fn == nil || seen[fn] { - continue - } - fn.ID = uint64(len(fns) + 1) - fns = append(fns, fn) - seen[fn] = true - } - } - p.Function = fns -} - -// remapMappingIDs matches location addresses with existing mappings -// and updates them appropriately. This is O(N*M), if this ever shows -// up as a bottleneck, evaluate sorting the mappings and doing a -// binary search, which would make it O(N*log(M)). -func (p *Profile) remapMappingIDs() { - if len(p.Mapping) == 0 { - return - } - - // Some profile handlers will incorrectly set regions for the main - // executable if its section is remapped. Fix them through heuristics. - - // Remove the initial mapping if named '/anon_hugepage' and has a - // consecutive adjacent mapping. - if m := p.Mapping[0]; strings.HasPrefix(m.File, "/anon_hugepage") { - if len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start { - p.Mapping = p.Mapping[1:] - } - } - - // Subtract the offset from the start of the main mapping if it - // ends up at a recognizable start address. - const expectedStart = 0x400000 - if m := p.Mapping[0]; m.Start-m.Offset == expectedStart { - m.Start = expectedStart - m.Offset = 0 - } - - for _, l := range p.Location { - if a := l.Address; a != 0 { - for _, m := range p.Mapping { - if m.Start <= a && a < m.Limit { - l.Mapping = m - break - } - } - } - } - - // Reset all mapping IDs. - for i, m := range p.Mapping { - m.ID = uint64(i + 1) - } -} - -var cpuInts = []func([]byte) (uint64, []byte){ - get32l, - get32b, - get64l, - get64b, -} - -func get32l(b []byte) (uint64, []byte) { - if len(b) < 4 { - return 0, nil - } - return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:] -} - -func get32b(b []byte) (uint64, []byte) { - if len(b) < 4 { - return 0, nil - } - return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:] -} - -func get64l(b []byte) (uint64, []byte) { - if len(b) < 8 { - return 0, nil - } - return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:] -} - -func get64b(b []byte) (uint64, []byte) { - if len(b) < 8 { - return 0, nil - } - return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:] -} - -// ParseTracebacks parses a set of tracebacks and returns a newly -// populated profile. It will accept any text file and generate a -// Profile out of it with any hex addresses it can identify, including -// a process map if it can recognize one. Each sample will include a -// tag "source" with the addresses recognized in string format. -func ParseTracebacks(b []byte) (*Profile, error) { - r := bytes.NewBuffer(b) - - p := &Profile{ - PeriodType: &ValueType{Type: "trace", Unit: "count"}, - Period: 1, - SampleType: []*ValueType{ - {Type: "trace", Unit: "count"}, - }, - } - - var sources []string - var sloc []*Location - - locs := make(map[uint64]*Location) - for { - l, err := r.ReadString('\n') - if err != nil { - if err != io.EOF { - return nil, err - } - if l == "" { - break - } - } - if sectionTrigger(l) == memoryMapSection { - break - } - if s, addrs := extractHexAddresses(l); len(s) > 0 { - for _, addr := range addrs { - // Addresses from stack traces point to the next instruction after - // each call. Adjust by -1 to land somewhere on the actual call - // (except for the leaf, which is not a call). - if len(sloc) > 0 { - addr-- - } - loc := locs[addr] - if locs[addr] == nil { - loc = &Location{ - Address: addr, - } - p.Location = append(p.Location, loc) - locs[addr] = loc - } - sloc = append(sloc, loc) - } - - sources = append(sources, s...) - } else { - if len(sources) > 0 || len(sloc) > 0 { - addTracebackSample(sloc, sources, p) - sloc, sources = nil, nil - } - } - } - - // Add final sample to save any leftover data. - if len(sources) > 0 || len(sloc) > 0 { - addTracebackSample(sloc, sources, p) - } - - if err := p.ParseMemoryMap(r); err != nil { - return nil, err - } - return p, nil -} - -func addTracebackSample(l []*Location, s []string, p *Profile) { - p.Sample = append(p.Sample, - &Sample{ - Value: []int64{1}, - Location: l, - Label: map[string][]string{"source": s}, - }) -} - -// parseCPU parses a profilez legacy profile and returns a newly -// populated Profile. -// -// The general format for profilez samples is a sequence of words in -// binary format. The first words are a header with the following data: -// 1st word -- 0 -// 2nd word -- 3 -// 3rd word -- 0 if a c++ application, 1 if a java application. -// 4th word -- Sampling period (in microseconds). -// 5th word -- Padding. -func parseCPU(b []byte) (*Profile, error) { - var parse func([]byte) (uint64, []byte) - var n1, n2, n3, n4, n5 uint64 - for _, parse = range cpuInts { - var tmp []byte - n1, tmp = parse(b) - n2, tmp = parse(tmp) - n3, tmp = parse(tmp) - n4, tmp = parse(tmp) - n5, tmp = parse(tmp) - - if tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 { - b = tmp - return cpuProfile(b, int64(n4), parse) - } - } - return nil, errUnrecognized -} - -// cpuProfile returns a new Profile from C++ profilez data. -// b is the profile bytes after the header, period is the profiling -// period, and parse is a function to parse 8-byte chunks from the -// profile in its native endianness. -func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) { - p := &Profile{ - Period: period * 1000, - PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"}, - SampleType: []*ValueType{ - {Type: "samples", Unit: "count"}, - {Type: "cpu", Unit: "nanoseconds"}, - }, - } - var err error - if b, _, err = parseCPUSamples(b, parse, true, p); err != nil { - return nil, err - } - - // If all samples have the same second-to-the-bottom frame, it - // strongly suggests that it is an uninteresting artifact of - // measurement -- a stack frame pushed by the signal handler. The - // bottom frame is always correct as it is picked up from the signal - // structure, not the stack. Check if this is the case and if so, - // remove. - if len(p.Sample) > 1 && len(p.Sample[0].Location) > 1 { - allSame := true - id1 := p.Sample[0].Location[1].Address - for _, s := range p.Sample { - if len(s.Location) < 2 || id1 != s.Location[1].Address { - allSame = false - break - } - } - if allSame { - for _, s := range p.Sample { - s.Location = append(s.Location[:1], s.Location[2:]...) - } - } - } - - if err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil { - return nil, err - } - return p, nil -} - -// parseCPUSamples parses a collection of profilez samples from a -// profile. -// -// profilez samples are a repeated sequence of stack frames of the -// form: -// 1st word -- The number of times this stack was encountered. -// 2nd word -- The size of the stack (StackSize). -// 3rd word -- The first address on the stack. -// ... -// StackSize + 2 -- The last address on the stack -// The last stack trace is of the form: -// 1st word -- 0 -// 2nd word -- 1 -// 3rd word -- 0 -// -// Addresses from stack traces may point to the next instruction after -// each call. Optionally adjust by -1 to land somewhere on the actual -// call (except for the leaf, which is not a call). -func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) { - locs := make(map[uint64]*Location) - for len(b) > 0 { - var count, nstk uint64 - count, b = parse(b) - nstk, b = parse(b) - if b == nil || nstk > uint64(len(b)/4) { - return nil, nil, errUnrecognized - } - var sloc []*Location - addrs := make([]uint64, nstk) - for i := 0; i < int(nstk); i++ { - addrs[i], b = parse(b) - } - - if count == 0 && nstk == 1 && addrs[0] == 0 { - // End of data marker - break - } - for i, addr := range addrs { - if adjust && i > 0 { - addr-- - } - loc := locs[addr] - if loc == nil { - loc = &Location{ - Address: addr, - } - locs[addr] = loc - p.Location = append(p.Location, loc) - } - sloc = append(sloc, loc) - } - p.Sample = append(p.Sample, - &Sample{ - Value: []int64{int64(count), int64(count) * int64(p.Period)}, - Location: sloc, - }) - } - // Reached the end without finding the EOD marker. - return b, locs, nil -} - -// parseHeap parses a heapz legacy or a growthz profile and -// returns a newly populated Profile. -func parseHeap(b []byte) (p *Profile, err error) { - r := bytes.NewBuffer(b) - l, err := r.ReadString('\n') - if err != nil { - return nil, errUnrecognized - } - - sampling := "" - - if header := heapHeaderRE.FindStringSubmatch(l); header != nil { - p = &Profile{ - SampleType: []*ValueType{ - {Type: "objects", Unit: "count"}, - {Type: "space", Unit: "bytes"}, - }, - PeriodType: &ValueType{Type: "objects", Unit: "bytes"}, - } - - var period int64 - if len(header[6]) > 0 { - if period, err = strconv.ParseInt(string(header[6]), 10, 64); err != nil { - return nil, errUnrecognized - } - } - - switch header[5] { - case "heapz_v2", "heap_v2": - sampling, p.Period = "v2", period - case "heapprofile": - sampling, p.Period = "", 1 - case "heap": - sampling, p.Period = "v2", period/2 - default: - return nil, errUnrecognized - } - } else if header = growthHeaderRE.FindStringSubmatch(l); header != nil { - p = &Profile{ - SampleType: []*ValueType{ - {Type: "objects", Unit: "count"}, - {Type: "space", Unit: "bytes"}, - }, - PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"}, - Period: 1, - } - } else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil { - p = &Profile{ - SampleType: []*ValueType{ - {Type: "objects", Unit: "count"}, - {Type: "space", Unit: "bytes"}, - }, - PeriodType: &ValueType{Type: "allocations", Unit: "count"}, - Period: 1, - } - } else { - return nil, errUnrecognized - } - - if LegacyHeapAllocated { - for _, st := range p.SampleType { - st.Type = "alloc_" + st.Type - } - } else { - for _, st := range p.SampleType { - st.Type = "inuse_" + st.Type - } - } - - locs := make(map[uint64]*Location) - for { - l, err = r.ReadString('\n') - if err != nil { - if err != io.EOF { - return nil, err - } - - if l == "" { - break - } - } - - if isSpaceOrComment(l) { - continue - } - l = strings.TrimSpace(l) - - if sectionTrigger(l) != unrecognizedSection { - break - } - - value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling) - if err != nil { - return nil, err - } - var sloc []*Location - for i, addr := range addrs { - // Addresses from stack traces point to the next instruction after - // each call. Adjust by -1 to land somewhere on the actual call - // (except for the leaf, which is not a call). - if i > 0 { - addr-- - } - loc := locs[addr] - if locs[addr] == nil { - loc = &Location{ - Address: addr, - } - p.Location = append(p.Location, loc) - locs[addr] = loc - } - sloc = append(sloc, loc) - } - - p.Sample = append(p.Sample, &Sample{ - Value: value, - Location: sloc, - NumLabel: map[string][]int64{"bytes": {blocksize}}, - }) - } - - if err = parseAdditionalSections(l, r, p); err != nil { - return nil, err - } - return p, nil -} - -// parseHeapSample parses a single row from a heap profile into a new Sample. -func parseHeapSample(line string, rate int64, sampling string) (value []int64, blocksize int64, addrs []uint64, err error) { - sampleData := heapSampleRE.FindStringSubmatch(line) - if len(sampleData) != 6 { - return value, blocksize, addrs, fmt.Errorf("unexpected number of sample values: got %d, want 6", len(sampleData)) - } - - // Use first two values by default; tcmalloc sampling generates the - // same value for both, only the older heap-profile collect separate - // stats for in-use and allocated objects. - valueIndex := 1 - if LegacyHeapAllocated { - valueIndex = 3 - } - - var v1, v2 int64 - if v1, err = strconv.ParseInt(sampleData[valueIndex], 10, 64); err != nil { - return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) - } - if v2, err = strconv.ParseInt(sampleData[valueIndex+1], 10, 64); err != nil { - return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) - } - - if v1 == 0 { - if v2 != 0 { - return value, blocksize, addrs, fmt.Errorf("allocation count was 0 but allocation bytes was %d", v2) - } - } else { - blocksize = v2 / v1 - if sampling == "v2" { - v1, v2 = scaleHeapSample(v1, v2, rate) - } - } - - value = []int64{v1, v2} - addrs = parseHexAddresses(sampleData[5]) - - return value, blocksize, addrs, nil -} - -// extractHexAddresses extracts hex numbers from a string and returns -// them, together with their numeric value, in a slice. -func extractHexAddresses(s string) ([]string, []uint64) { - hexStrings := hexNumberRE.FindAllString(s, -1) - var ids []uint64 - for _, s := range hexStrings { - if id, err := strconv.ParseUint(s, 0, 64); err == nil { - ids = append(ids, id) - } else { - // Do not expect any parsing failures due to the regexp matching. - panic("failed to parse hex value:" + s) - } - } - return hexStrings, ids -} - -// parseHexAddresses parses hex numbers from a string and returns them -// in a slice. -func parseHexAddresses(s string) []uint64 { - _, ids := extractHexAddresses(s) - return ids -} - -// scaleHeapSample adjusts the data from a heapz Sample to -// account for its probability of appearing in the collected -// data. heapz profiles are a sampling of the memory allocations -// requests in a program. We estimate the unsampled value by dividing -// each collected sample by its probability of appearing in the -// profile. heapz v2 profiles rely on a poisson process to determine -// which samples to collect, based on the desired average collection -// rate R. The probability of a sample of size S to appear in that -// profile is 1-exp(-S/R). -func scaleHeapSample(count, size, rate int64) (int64, int64) { - if count == 0 || size == 0 { - return 0, 0 - } - - if rate <= 1 { - // if rate==1 all samples were collected so no adjustment is needed. - // if rate<1 treat as unknown and skip scaling. - return count, size - } - - avgSize := float64(size) / float64(count) - scale := 1 / (1 - math.Exp(-avgSize/float64(rate))) - - return int64(float64(count) * scale), int64(float64(size) * scale) -} - -// parseContention parses a contentionz profile and returns a newly -// populated Profile. -func parseContention(b []byte) (p *Profile, err error) { - r := bytes.NewBuffer(b) - l, err := r.ReadString('\n') - if err != nil { - return nil, errUnrecognized - } - - if !strings.HasPrefix(l, "--- contention") { - return nil, errUnrecognized - } - - p = &Profile{ - PeriodType: &ValueType{Type: "contentions", Unit: "count"}, - Period: 1, - SampleType: []*ValueType{ - {Type: "contentions", Unit: "count"}, - {Type: "delay", Unit: "nanoseconds"}, - }, - } - - var cpuHz int64 - // Parse text of the form "attribute = value" before the samples. - const delimiter = "=" - for { - l, err = r.ReadString('\n') - if err != nil { - if err != io.EOF { - return nil, err - } - - if l == "" { - break - } - } - - if l = strings.TrimSpace(l); l == "" { - continue - } - - if strings.HasPrefix(l, "---") { - break - } - - attr := strings.SplitN(l, delimiter, 2) - if len(attr) != 2 { - break - } - key, val := strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1]) - var err error - switch key { - case "cycles/second": - if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil { - return nil, errUnrecognized - } - case "sampling period": - if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil { - return nil, errUnrecognized - } - case "ms since reset": - ms, err := strconv.ParseInt(val, 0, 64) - if err != nil { - return nil, errUnrecognized - } - p.DurationNanos = ms * 1000 * 1000 - case "format": - // CPP contentionz profiles don't have format. - return nil, errUnrecognized - case "resolution": - // CPP contentionz profiles don't have resolution. - return nil, errUnrecognized - case "discarded samples": - default: - return nil, errUnrecognized - } - } - - locs := make(map[uint64]*Location) - for { - if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") { - break - } - value, addrs, err := parseContentionSample(l, p.Period, cpuHz) - if err != nil { - return nil, err - } - var sloc []*Location - for i, addr := range addrs { - // Addresses from stack traces point to the next instruction after - // each call. Adjust by -1 to land somewhere on the actual call - // (except for the leaf, which is not a call). - if i > 0 { - addr-- - } - loc := locs[addr] - if locs[addr] == nil { - loc = &Location{ - Address: addr, - } - p.Location = append(p.Location, loc) - locs[addr] = loc - } - sloc = append(sloc, loc) - } - p.Sample = append(p.Sample, &Sample{ - Value: value, - Location: sloc, - }) - - if l, err = r.ReadString('\n'); err != nil { - if err != io.EOF { - return nil, err - } - if l == "" { - break - } - } - } - - if err = parseAdditionalSections(l, r, p); err != nil { - return nil, err - } - - return p, nil -} - -// parseContentionSample parses a single row from a contention profile -// into a new Sample. -func parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) { - sampleData := contentionSampleRE.FindStringSubmatch(line) - if sampleData == nil { - return value, addrs, errUnrecognized - } - - v1, err := strconv.ParseInt(sampleData[1], 10, 64) - if err != nil { - return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) - } - v2, err := strconv.ParseInt(sampleData[2], 10, 64) - if err != nil { - return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) - } - - // Unsample values if period and cpuHz are available. - // - Delays are scaled to cycles and then to nanoseconds. - // - Contentions are scaled to cycles. - if period > 0 { - if cpuHz > 0 { - cpuGHz := float64(cpuHz) / 1e9 - v1 = int64(float64(v1) * float64(period) / cpuGHz) - } - v2 = v2 * period - } - - value = []int64{v2, v1} - addrs = parseHexAddresses(sampleData[3]) - - return value, addrs, nil -} - -// parseThread parses a Threadz profile and returns a new Profile. -func parseThread(b []byte) (*Profile, error) { - r := bytes.NewBuffer(b) - - var line string - var err error - for { - // Skip past comments and empty lines seeking a real header. - line, err = r.ReadString('\n') - if err != nil { - return nil, err - } - if !isSpaceOrComment(line) { - break - } - } - - if m := threadzStartRE.FindStringSubmatch(line); m != nil { - // Advance over initial comments until first stack trace. - for { - line, err = r.ReadString('\n') - if err != nil { - if err != io.EOF { - return nil, err - } - - if line == "" { - break - } - } - if sectionTrigger(line) != unrecognizedSection || line[0] == '-' { - break - } - } - } else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { - return nil, errUnrecognized - } - - p := &Profile{ - SampleType: []*ValueType{{Type: "thread", Unit: "count"}}, - PeriodType: &ValueType{Type: "thread", Unit: "count"}, - Period: 1, - } - - locs := make(map[uint64]*Location) - // Recognize each thread and populate profile samples. - for sectionTrigger(line) == unrecognizedSection { - if strings.HasPrefix(line, "---- no stack trace for") { - line = "" - break - } - if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { - return nil, errUnrecognized - } - - var addrs []uint64 - line, addrs, err = parseThreadSample(r) - if err != nil { - return nil, errUnrecognized - } - if len(addrs) == 0 { - // We got a --same as previous threads--. Bump counters. - if len(p.Sample) > 0 { - s := p.Sample[len(p.Sample)-1] - s.Value[0]++ - } - continue - } - - var sloc []*Location - for i, addr := range addrs { - // Addresses from stack traces point to the next instruction after - // each call. Adjust by -1 to land somewhere on the actual call - // (except for the leaf, which is not a call). - if i > 0 { - addr-- - } - loc := locs[addr] - if locs[addr] == nil { - loc = &Location{ - Address: addr, - } - p.Location = append(p.Location, loc) - locs[addr] = loc - } - sloc = append(sloc, loc) - } - - p.Sample = append(p.Sample, &Sample{ - Value: []int64{1}, - Location: sloc, - }) - } - - if err = parseAdditionalSections(line, r, p); err != nil { - return nil, err - } - - return p, nil -} - -// parseThreadSample parses a symbolized or unsymbolized stack trace. -// Returns the first line after the traceback, the sample (or nil if -// it hits a 'same-as-previous' marker) and an error. -func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) { - var l string - sameAsPrevious := false - for { - if l, err = b.ReadString('\n'); err != nil { - if err != io.EOF { - return "", nil, err - } - if l == "" { - break - } - } - if l = strings.TrimSpace(l); l == "" { - continue - } - - if strings.HasPrefix(l, "---") { - break - } - if strings.Contains(l, "same as previous thread") { - sameAsPrevious = true - continue - } - - addrs = append(addrs, parseHexAddresses(l)...) - } - - if sameAsPrevious { - return l, nil, nil - } - return l, addrs, nil -} - -// parseAdditionalSections parses any additional sections in the -// profile, ignoring any unrecognized sections. -func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) (err error) { - for { - if sectionTrigger(l) == memoryMapSection { - break - } - // Ignore any unrecognized sections. - if l, err := b.ReadString('\n'); err != nil { - if err != io.EOF { - return err - } - if l == "" { - break - } - } - } - return p.ParseMemoryMap(b) -} - -// ParseMemoryMap parses a memory map in the format of -// /proc/self/maps, and overrides the mappings in the current profile. -// It renumbers the samples and locations in the profile correspondingly. -func (p *Profile) ParseMemoryMap(rd io.Reader) error { - b := bufio.NewReader(rd) - - var attrs []string - var r *strings.Replacer - const delimiter = "=" - for { - l, err := b.ReadString('\n') - if err != nil { - if err != io.EOF { - return err - } - if l == "" { - break - } - } - if l = strings.TrimSpace(l); l == "" { - continue - } - - if r != nil { - l = r.Replace(l) - } - m, err := parseMappingEntry(l) - if err != nil { - if err == errUnrecognized { - // Recognize assignments of the form: attr=value, and replace - // $attr with value on subsequent mappings. - if attr := strings.SplitN(l, delimiter, 2); len(attr) == 2 { - attrs = append(attrs, "$"+strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1])) - r = strings.NewReplacer(attrs...) - } - // Ignore any unrecognized entries - continue - } - return err - } - if m == nil || (m.File == "" && len(p.Mapping) != 0) { - // In some cases the first entry may include the address range - // but not the name of the file. It should be followed by - // another entry with the name. - continue - } - if len(p.Mapping) == 1 && p.Mapping[0].File == "" { - // Update the name if this is the entry following that empty one. - p.Mapping[0].File = m.File - continue - } - p.Mapping = append(p.Mapping, m) - } - p.remapLocationIDs() - p.remapFunctionIDs() - p.remapMappingIDs() - return nil -} - -func parseMappingEntry(l string) (*Mapping, error) { - mapping := &Mapping{} - var err error - if me := procMapsRE.FindStringSubmatch(l); len(me) == 9 { - if !strings.Contains(me[3], "x") { - // Skip non-executable entries. - return nil, nil - } - if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { - return nil, errUnrecognized - } - if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { - return nil, errUnrecognized - } - if me[4] != "" { - if mapping.Offset, err = strconv.ParseUint(me[4], 16, 64); err != nil { - return nil, errUnrecognized - } - } - mapping.File = me[8] - return mapping, nil - } - - if me := briefMapsRE.FindStringSubmatch(l); len(me) == 6 { - if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { - return nil, errUnrecognized - } - if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { - return nil, errUnrecognized - } - mapping.File = me[3] - if me[5] != "" { - if mapping.Offset, err = strconv.ParseUint(me[5], 16, 64); err != nil { - return nil, errUnrecognized - } - } - return mapping, nil - } - - return nil, errUnrecognized -} - -type sectionType int - -const ( - unrecognizedSection sectionType = iota - memoryMapSection -) - -var memoryMapTriggers = []string{ - "--- Memory map: ---", - "MAPPED_LIBRARIES:", -} - -func sectionTrigger(line string) sectionType { - for _, trigger := range memoryMapTriggers { - if strings.Contains(line, trigger) { - return memoryMapSection - } - } - return unrecognizedSection -} - -func (p *Profile) addLegacyFrameInfo() { - switch { - case isProfileType(p, heapzSampleTypes) || - isProfileType(p, heapzInUseSampleTypes) || - isProfileType(p, heapzAllocSampleTypes): - p.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr - case isProfileType(p, contentionzSampleTypes): - p.DropFrames, p.KeepFrames = lockRxStr, "" - default: - p.DropFrames, p.KeepFrames = cpuProfilerRxStr, "" - } -} - -var heapzSampleTypes = []string{"allocations", "size"} // early Go pprof profiles -var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"} -var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"} -var contentionzSampleTypes = []string{"contentions", "delay"} - -func isProfileType(p *Profile, t []string) bool { - st := p.SampleType - if len(st) != len(t) { - return false - } - - for i := range st { - if st[i].Type != t[i] { - return false - } - } - return true -} - -var allocRxStr = strings.Join([]string{ - // POSIX entry points. - `calloc`, - `cfree`, - `malloc`, - `free`, - `memalign`, - `do_memalign`, - `(__)?posix_memalign`, - `pvalloc`, - `valloc`, - `realloc`, - - // TC malloc. - `tcmalloc::.*`, - `tc_calloc`, - `tc_cfree`, - `tc_malloc`, - `tc_free`, - `tc_memalign`, - `tc_posix_memalign`, - `tc_pvalloc`, - `tc_valloc`, - `tc_realloc`, - `tc_new`, - `tc_delete`, - `tc_newarray`, - `tc_deletearray`, - `tc_new_nothrow`, - `tc_newarray_nothrow`, - - // Memory-allocation routines on OS X. - `malloc_zone_malloc`, - `malloc_zone_calloc`, - `malloc_zone_valloc`, - `malloc_zone_realloc`, - `malloc_zone_memalign`, - `malloc_zone_free`, - - // Go runtime - `runtime\..*`, - - // Other misc. memory allocation routines - `BaseArena::.*`, - `(::)?do_malloc_no_errno`, - `(::)?do_malloc_pages`, - `(::)?do_malloc`, - `DoSampledAllocation`, - `MallocedMemBlock::MallocedMemBlock`, - `_M_allocate`, - `__builtin_(vec_)?delete`, - `__builtin_(vec_)?new`, - `__gnu_cxx::new_allocator::allocate`, - `__libc_malloc`, - `__malloc_alloc_template::allocate`, - `allocate`, - `cpp_alloc`, - `operator new(\[\])?`, - `simple_alloc::allocate`, -}, `|`) - -var allocSkipRxStr = strings.Join([]string{ - // Preserve Go runtime frames that appear in the middle/bottom of - // the stack. - `runtime\.panic`, -}, `|`) - -var cpuProfilerRxStr = strings.Join([]string{ - `ProfileData::Add`, - `ProfileData::prof_handler`, - `CpuProfiler::prof_handler`, - `__pthread_sighandler`, - `__restore`, -}, `|`) - -var lockRxStr = strings.Join([]string{ - `RecordLockProfileData`, - `(base::)?RecordLockProfileData.*`, - `(base::)?SubmitMutexProfileData.*`, - `(base::)?SubmitSpinLockProfileData.*`, - `(Mutex::)?AwaitCommon.*`, - `(Mutex::)?Unlock.*`, - `(Mutex::)?UnlockSlow.*`, - `(Mutex::)?ReaderUnlock.*`, - `(MutexLock::)?~MutexLock.*`, - `(SpinLock::)?Unlock.*`, - `(SpinLock::)?SlowUnlock.*`, - `(SpinLockHolder::)?~SpinLockHolder.*`, -}, `|`) diff --git a/src/cmd/pprof/internal/profile/profile.go b/src/cmd/pprof/internal/profile/profile.go deleted file mode 100644 index 28e713d7be..0000000000 --- a/src/cmd/pprof/internal/profile/profile.go +++ /dev/null @@ -1,572 +0,0 @@ -// Copyright 2014 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 profile provides a representation of profile.proto and -// methods to encode/decode profiles in this format. -package profile - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "io/ioutil" - "regexp" - "strings" - "time" -) - -// Profile is an in-memory representation of profile.proto. -type Profile struct { - SampleType []*ValueType - Sample []*Sample - Mapping []*Mapping - Location []*Location - Function []*Function - - DropFrames string - KeepFrames string - - TimeNanos int64 - DurationNanos int64 - PeriodType *ValueType - Period int64 - - dropFramesX int64 - keepFramesX int64 - stringTable []string -} - -// ValueType corresponds to Profile.ValueType -type ValueType struct { - Type string // cpu, wall, inuse_space, etc - Unit string // seconds, nanoseconds, bytes, etc - - typeX int64 - unitX int64 -} - -// Sample corresponds to Profile.Sample -type Sample struct { - Location []*Location - Value []int64 - Label map[string][]string - NumLabel map[string][]int64 - - locationIDX []uint64 - labelX []Label -} - -// Label corresponds to Profile.Label -type Label struct { - keyX int64 - // Exactly one of the two following values must be set - strX int64 - numX int64 // Integer value for this label -} - -// Mapping corresponds to Profile.Mapping -type Mapping struct { - ID uint64 - Start uint64 - Limit uint64 - Offset uint64 - File string - BuildID string - HasFunctions bool - HasFilenames bool - HasLineNumbers bool - HasInlineFrames bool - - fileX int64 - buildIDX int64 -} - -// Location corresponds to Profile.Location -type Location struct { - ID uint64 - Mapping *Mapping - Address uint64 - Line []Line - - mappingIDX uint64 -} - -// Line corresponds to Profile.Line -type Line struct { - Function *Function - Line int64 - - functionIDX uint64 -} - -// Function corresponds to Profile.Function -type Function struct { - ID uint64 - Name string - SystemName string - Filename string - StartLine int64 - - nameX int64 - systemNameX int64 - filenameX int64 -} - -// Parse parses a profile and checks for its validity. The input -// may be a gzip-compressed encoded protobuf or one of many legacy -// profile formats which may be unsupported in the future. -func Parse(r io.Reader) (*Profile, error) { - orig, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - - var p *Profile - if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b { - gz, err := gzip.NewReader(bytes.NewBuffer(orig)) - if err != nil { - return nil, fmt.Errorf("decompressing profile: %v", err) - } - data, err := ioutil.ReadAll(gz) - if err != nil { - return nil, fmt.Errorf("decompressing profile: %v", err) - } - orig = data - } - if p, err = parseUncompressed(orig); err != nil { - if p, err = parseLegacy(orig); err != nil { - return nil, fmt.Errorf("parsing profile: %v", err) - } - } - - if err := p.CheckValid(); err != nil { - return nil, fmt.Errorf("malformed profile: %v", err) - } - return p, nil -} - -var errUnrecognized = fmt.Errorf("unrecognized profile format") -var errMalformed = fmt.Errorf("malformed profile format") - -func parseLegacy(data []byte) (*Profile, error) { - parsers := []func([]byte) (*Profile, error){ - parseCPU, - parseHeap, - parseGoCount, // goroutine, threadcreate - parseThread, - parseContention, - } - - for _, parser := range parsers { - p, err := parser(data) - if err == nil { - p.setMain() - p.addLegacyFrameInfo() - return p, nil - } - if err != errUnrecognized { - return nil, err - } - } - return nil, errUnrecognized -} - -func parseUncompressed(data []byte) (*Profile, error) { - p := &Profile{} - if err := unmarshal(data, p); err != nil { - return nil, err - } - - if err := p.postDecode(); err != nil { - return nil, err - } - - return p, nil -} - -var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`) - -// setMain scans Mapping entries and guesses which entry is main -// because legacy profiles don't obey the convention of putting main -// first. -func (p *Profile) setMain() { - for i := 0; i < len(p.Mapping); i++ { - file := strings.TrimSpace(strings.Replace(p.Mapping[i].File, "(deleted)", "", -1)) - if len(file) == 0 { - continue - } - if len(libRx.FindStringSubmatch(file)) > 0 { - continue - } - if strings.HasPrefix(file, "[") { - continue - } - // Swap what we guess is main to position 0. - tmp := p.Mapping[i] - p.Mapping[i] = p.Mapping[0] - p.Mapping[0] = tmp - break - } -} - -// Write writes the profile as a gzip-compressed marshaled protobuf. -func (p *Profile) Write(w io.Writer) error { - p.preEncode() - b := marshal(p) - zw := gzip.NewWriter(w) - defer zw.Close() - _, err := zw.Write(b) - return err -} - -// CheckValid tests whether the profile is valid. Checks include, but are -// not limited to: -// - len(Profile.Sample[n].value) == len(Profile.value_unit) -// - Sample.id has a corresponding Profile.Location -func (p *Profile) CheckValid() error { - // Check that sample values are consistent - sampleLen := len(p.SampleType) - if sampleLen == 0 && len(p.Sample) != 0 { - return fmt.Errorf("missing sample type information") - } - for _, s := range p.Sample { - if len(s.Value) != sampleLen { - return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType)) - } - } - - // Check that all mappings/locations/functions are in the tables - // Check that there are no duplicate ids - mappings := make(map[uint64]*Mapping, len(p.Mapping)) - for _, m := range p.Mapping { - if m.ID == 0 { - return fmt.Errorf("found mapping with reserved ID=0") - } - if mappings[m.ID] != nil { - return fmt.Errorf("multiple mappings with same id: %d", m.ID) - } - mappings[m.ID] = m - } - functions := make(map[uint64]*Function, len(p.Function)) - for _, f := range p.Function { - if f.ID == 0 { - return fmt.Errorf("found function with reserved ID=0") - } - if functions[f.ID] != nil { - return fmt.Errorf("multiple functions with same id: %d", f.ID) - } - functions[f.ID] = f - } - locations := make(map[uint64]*Location, len(p.Location)) - for _, l := range p.Location { - if l.ID == 0 { - return fmt.Errorf("found location with reserved id=0") - } - if locations[l.ID] != nil { - return fmt.Errorf("multiple locations with same id: %d", l.ID) - } - locations[l.ID] = l - if m := l.Mapping; m != nil { - if m.ID == 0 || mappings[m.ID] != m { - return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID) - } - } - for _, ln := range l.Line { - if f := ln.Function; f != nil { - if f.ID == 0 || functions[f.ID] != f { - return fmt.Errorf("inconsistent function %p: %d", f, f.ID) - } - } - } - } - return nil -} - -// Aggregate merges the locations in the profile into equivalence -// classes preserving the request attributes. It also updates the -// samples to point to the merged locations. -func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error { - for _, m := range p.Mapping { - m.HasInlineFrames = m.HasInlineFrames && inlineFrame - m.HasFunctions = m.HasFunctions && function - m.HasFilenames = m.HasFilenames && filename - m.HasLineNumbers = m.HasLineNumbers && linenumber - } - - // Aggregate functions - if !function || !filename { - for _, f := range p.Function { - if !function { - f.Name = "" - f.SystemName = "" - } - if !filename { - f.Filename = "" - } - } - } - - // Aggregate locations - if !inlineFrame || !address || !linenumber { - for _, l := range p.Location { - if !inlineFrame && len(l.Line) > 1 { - l.Line = l.Line[len(l.Line)-1:] - } - if !linenumber { - for i := range l.Line { - l.Line[i].Line = 0 - } - } - if !address { - l.Address = 0 - } - } - } - - return p.CheckValid() -} - -// Print dumps a text representation of a profile. Intended mainly -// for debugging purposes. -func (p *Profile) String() string { - - ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location)) - if pt := p.PeriodType; pt != nil { - ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit)) - } - ss = append(ss, fmt.Sprintf("Period: %d", p.Period)) - if p.TimeNanos != 0 { - ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos))) - } - if p.DurationNanos != 0 { - ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos))) - } - - ss = append(ss, "Samples:") - var sh1 string - for _, s := range p.SampleType { - sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit) - } - ss = append(ss, strings.TrimSpace(sh1)) - for _, s := range p.Sample { - var sv string - for _, v := range s.Value { - sv = fmt.Sprintf("%s %10d", sv, v) - } - sv = sv + ": " - for _, l := range s.Location { - sv = sv + fmt.Sprintf("%d ", l.ID) - } - ss = append(ss, sv) - const labelHeader = " " - if len(s.Label) > 0 { - ls := labelHeader - for k, v := range s.Label { - ls = ls + fmt.Sprintf("%s:%v ", k, v) - } - ss = append(ss, ls) - } - if len(s.NumLabel) > 0 { - ls := labelHeader - for k, v := range s.NumLabel { - ls = ls + fmt.Sprintf("%s:%v ", k, v) - } - ss = append(ss, ls) - } - } - - ss = append(ss, "Locations") - for _, l := range p.Location { - locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address) - if m := l.Mapping; m != nil { - locStr = locStr + fmt.Sprintf("M=%d ", m.ID) - } - if len(l.Line) == 0 { - ss = append(ss, locStr) - } - for li := range l.Line { - lnStr := "??" - if fn := l.Line[li].Function; fn != nil { - lnStr = fmt.Sprintf("%s %s:%d s=%d", - fn.Name, - fn.Filename, - l.Line[li].Line, - fn.StartLine) - if fn.Name != fn.SystemName { - lnStr = lnStr + "(" + fn.SystemName + ")" - } - } - ss = append(ss, locStr+lnStr) - // Do not print location details past the first line - locStr = " " - } - } - - ss = append(ss, "Mappings") - for _, m := range p.Mapping { - bits := "" - if m.HasFunctions { - bits = bits + "[FN]" - } - if m.HasFilenames { - bits = bits + "[FL]" - } - if m.HasLineNumbers { - bits = bits + "[LN]" - } - if m.HasInlineFrames { - bits = bits + "[IN]" - } - ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s", - m.ID, - m.Start, m.Limit, m.Offset, - m.File, - m.BuildID, - bits)) - } - - return strings.Join(ss, "\n") + "\n" -} - -// Merge adds profile p adjusted by ratio r into profile p. Profiles -// must be compatible (same Type and SampleType). -// TODO(rsilvera): consider normalizing the profiles based on the -// total samples collected. -func (p *Profile) Merge(pb *Profile, r float64) error { - if err := p.Compatible(pb); err != nil { - return err - } - - pb = pb.Copy() - - // Keep the largest of the two periods. - if pb.Period > p.Period { - p.Period = pb.Period - } - - p.DurationNanos += pb.DurationNanos - - p.Mapping = append(p.Mapping, pb.Mapping...) - for i, m := range p.Mapping { - m.ID = uint64(i + 1) - } - p.Location = append(p.Location, pb.Location...) - for i, l := range p.Location { - l.ID = uint64(i + 1) - } - p.Function = append(p.Function, pb.Function...) - for i, f := range p.Function { - f.ID = uint64(i + 1) - } - - if r != 1.0 { - for _, s := range pb.Sample { - for i, v := range s.Value { - s.Value[i] = int64((float64(v) * r)) - } - } - } - p.Sample = append(p.Sample, pb.Sample...) - return p.CheckValid() -} - -// Compatible determines if two profiles can be compared/merged. -// returns nil if the profiles are compatible; otherwise an error with -// details on the incompatibility. -func (p *Profile) Compatible(pb *Profile) error { - if !compatibleValueTypes(p.PeriodType, pb.PeriodType) { - return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType) - } - - if len(p.SampleType) != len(pb.SampleType) { - return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) - } - - for i := range p.SampleType { - if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) { - return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) - } - } - - return nil -} - -// HasFunctions determines if all locations in this profile have -// symbolized function information. -func (p *Profile) HasFunctions() bool { - for _, l := range p.Location { - if l.Mapping == nil || !l.Mapping.HasFunctions { - return false - } - } - return true -} - -// HasFileLines determines if all locations in this profile have -// symbolized file and line number information. -func (p *Profile) HasFileLines() bool { - for _, l := range p.Location { - if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) { - return false - } - } - return true -} - -func compatibleValueTypes(v1, v2 *ValueType) bool { - if v1 == nil || v2 == nil { - return true // No grounds to disqualify. - } - return v1.Type == v2.Type && v1.Unit == v2.Unit -} - -// Copy makes a fully independent copy of a profile. -func (p *Profile) Copy() *Profile { - p.preEncode() - b := marshal(p) - - pp := &Profile{} - if err := unmarshal(b, pp); err != nil { - panic(err) - } - if err := pp.postDecode(); err != nil { - panic(err) - } - - return pp -} - -// Demangler maps symbol names to a human-readable form. This may -// include C++ demangling and additional simplification. Names that -// are not demangled may be missing from the resulting map. -type Demangler func(name []string) (map[string]string, error) - -// Demangle attempts to demangle and optionally simplify any function -// names referenced in the profile. It works on a best-effort basis: -// it will silently preserve the original names in case of any errors. -func (p *Profile) Demangle(d Demangler) error { - // Collect names to demangle. - var names []string - for _, fn := range p.Function { - names = append(names, fn.SystemName) - } - - // Update profile with demangled names. - demangled, err := d(names) - if err != nil { - return err - } - for _, fn := range p.Function { - if dd, ok := demangled[fn.SystemName]; ok { - fn.Name = dd - } - } - return nil -} - -// Empty returns true if the profile contains no samples. -func (p *Profile) Empty() bool { - return len(p.Sample) == 0 -} diff --git a/src/cmd/pprof/internal/profile/profile_test.go b/src/cmd/pprof/internal/profile/profile_test.go deleted file mode 100644 index 09b11a456f..0000000000 --- a/src/cmd/pprof/internal/profile/profile_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015 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 profile - -import ( - "bytes" - "testing" -) - -func TestEmptyProfile(t *testing.T) { - var buf bytes.Buffer - p, err := Parse(&buf) - if err != nil { - t.Error("Want no error, got", err) - } - if p == nil { - t.Fatal("Want a valid profile, got ") - } - if !p.Empty() { - t.Errorf("Profile should be empty, got %#v", p) - } -} diff --git a/src/cmd/pprof/internal/profile/proto.go b/src/cmd/pprof/internal/profile/proto.go deleted file mode 100644 index 11d7f9ff9b..0000000000 --- a/src/cmd/pprof/internal/profile/proto.go +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright 2014 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. - -// This file is a simple protocol buffer encoder and decoder. -// -// A protocol message must implement the message interface: -// decoder() []decoder -// encode(*buffer) -// -// The decode method returns a slice indexed by field number that gives the -// function to decode that field. -// The encode method encodes its receiver into the given buffer. -// -// The two methods are simple enough to be implemented by hand rather than -// by using a protocol compiler. -// -// See profile.go for examples of messages implementing this interface. -// -// There is no support for groups, message sets, or "has" bits. - -package profile - -import "errors" - -type buffer struct { - field int - typ int - u64 uint64 - data []byte - tmp [16]byte -} - -type decoder func(*buffer, message) error - -type message interface { - decoder() []decoder - encode(*buffer) -} - -func marshal(m message) []byte { - var b buffer - m.encode(&b) - return b.data -} - -func encodeVarint(b *buffer, x uint64) { - for x >= 128 { - b.data = append(b.data, byte(x)|0x80) - x >>= 7 - } - b.data = append(b.data, byte(x)) -} - -func encodeLength(b *buffer, tag int, len int) { - encodeVarint(b, uint64(tag)<<3|2) - encodeVarint(b, uint64(len)) -} - -func encodeUint64(b *buffer, tag int, x uint64) { - // append varint to b.data - encodeVarint(b, uint64(tag)<<3|0) - encodeVarint(b, x) -} - -func encodeUint64s(b *buffer, tag int, x []uint64) { - if len(x) > 2 { - // Use packed encoding - n1 := len(b.data) - for _, u := range x { - encodeVarint(b, u) - } - n2 := len(b.data) - encodeLength(b, tag, n2-n1) - n3 := len(b.data) - copy(b.tmp[:], b.data[n2:n3]) - copy(b.data[n1+(n3-n2):], b.data[n1:n2]) - copy(b.data[n1:], b.tmp[:n3-n2]) - return - } - for _, u := range x { - encodeUint64(b, tag, u) - } -} - -func encodeUint64Opt(b *buffer, tag int, x uint64) { - if x == 0 { - return - } - encodeUint64(b, tag, x) -} - -func encodeInt64(b *buffer, tag int, x int64) { - u := uint64(x) - encodeUint64(b, tag, u) -} - -func encodeInt64Opt(b *buffer, tag int, x int64) { - if x == 0 { - return - } - encodeInt64(b, tag, x) -} - -func encodeInt64s(b *buffer, tag int, x []int64) { - if len(x) > 2 { - // Use packed encoding - n1 := len(b.data) - for _, u := range x { - encodeVarint(b, uint64(u)) - } - n2 := len(b.data) - encodeLength(b, tag, n2-n1) - n3 := len(b.data) - copy(b.tmp[:], b.data[n2:n3]) - copy(b.data[n1+(n3-n2):], b.data[n1:n2]) - copy(b.data[n1:], b.tmp[:n3-n2]) - return - } - for _, u := range x { - encodeInt64(b, tag, u) - } -} - -func encodeString(b *buffer, tag int, x string) { - encodeLength(b, tag, len(x)) - b.data = append(b.data, x...) -} - -func encodeStrings(b *buffer, tag int, x []string) { - for _, s := range x { - encodeString(b, tag, s) - } -} - -func encodeStringOpt(b *buffer, tag int, x string) { - if x == "" { - return - } - encodeString(b, tag, x) -} - -func encodeBool(b *buffer, tag int, x bool) { - if x { - encodeUint64(b, tag, 1) - } else { - encodeUint64(b, tag, 0) - } -} - -func encodeBoolOpt(b *buffer, tag int, x bool) { - if x == false { - return - } - encodeBool(b, tag, x) -} - -func encodeMessage(b *buffer, tag int, m message) { - n1 := len(b.data) - m.encode(b) - n2 := len(b.data) - encodeLength(b, tag, n2-n1) - n3 := len(b.data) - copy(b.tmp[:], b.data[n2:n3]) - copy(b.data[n1+(n3-n2):], b.data[n1:n2]) - copy(b.data[n1:], b.tmp[:n3-n2]) -} - -func unmarshal(data []byte, m message) (err error) { - b := buffer{data: data, typ: 2} - return decodeMessage(&b, m) -} - -func le64(p []byte) uint64 { - return uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 | uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56 -} - -func le32(p []byte) uint32 { - return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24 -} - -func decodeVarint(data []byte) (uint64, []byte, error) { - var i int - var u uint64 - for i = 0; ; i++ { - if i >= 10 || i >= len(data) { - return 0, nil, errors.New("bad varint") - } - u |= uint64(data[i]&0x7F) << uint(7*i) - if data[i]&0x80 == 0 { - return u, data[i+1:], nil - } - } -} - -func decodeField(b *buffer, data []byte) ([]byte, error) { - x, data, err := decodeVarint(data) - if err != nil { - return nil, err - } - b.field = int(x >> 3) - b.typ = int(x & 7) - b.data = nil - b.u64 = 0 - switch b.typ { - case 0: - b.u64, data, err = decodeVarint(data) - if err != nil { - return nil, err - } - case 1: - if len(data) < 8 { - return nil, errors.New("not enough data") - } - b.u64 = le64(data[:8]) - data = data[8:] - case 2: - var n uint64 - n, data, err = decodeVarint(data) - if err != nil { - return nil, err - } - if n > uint64(len(data)) { - return nil, errors.New("too much data") - } - b.data = data[:n] - data = data[n:] - case 5: - if len(data) < 4 { - return nil, errors.New("not enough data") - } - b.u64 = uint64(le32(data[:4])) - data = data[4:] - default: - return nil, errors.New("unknown type: " + string(b.typ)) - } - - return data, nil -} - -func checkType(b *buffer, typ int) error { - if b.typ != typ { - return errors.New("type mismatch") - } - return nil -} - -func decodeMessage(b *buffer, m message) error { - if err := checkType(b, 2); err != nil { - return err - } - dec := m.decoder() - data := b.data - for len(data) > 0 { - // pull varint field# + type - var err error - data, err = decodeField(b, data) - if err != nil { - return err - } - if b.field >= len(dec) || dec[b.field] == nil { - continue - } - if err := dec[b.field](b, m); err != nil { - return err - } - } - return nil -} - -func decodeInt64(b *buffer, x *int64) error { - if err := checkType(b, 0); err != nil { - return err - } - *x = int64(b.u64) - return nil -} - -func decodeInt64s(b *buffer, x *[]int64) error { - if b.typ == 2 { - // Packed encoding - data := b.data - for len(data) > 0 { - var u uint64 - var err error - - if u, data, err = decodeVarint(data); err != nil { - return err - } - *x = append(*x, int64(u)) - } - return nil - } - var i int64 - if err := decodeInt64(b, &i); err != nil { - return err - } - *x = append(*x, i) - return nil -} - -func decodeUint64(b *buffer, x *uint64) error { - if err := checkType(b, 0); err != nil { - return err - } - *x = b.u64 - return nil -} - -func decodeUint64s(b *buffer, x *[]uint64) error { - if b.typ == 2 { - data := b.data - // Packed encoding - for len(data) > 0 { - var u uint64 - var err error - - if u, data, err = decodeVarint(data); err != nil { - return err - } - *x = append(*x, u) - } - return nil - } - var u uint64 - if err := decodeUint64(b, &u); err != nil { - return err - } - *x = append(*x, u) - return nil -} - -func decodeString(b *buffer, x *string) error { - if err := checkType(b, 2); err != nil { - return err - } - *x = string(b.data) - return nil -} - -func decodeStrings(b *buffer, x *[]string) error { - var s string - if err := decodeString(b, &s); err != nil { - return err - } - *x = append(*x, s) - return nil -} - -func decodeBool(b *buffer, x *bool) error { - if err := checkType(b, 0); err != nil { - return err - } - if int64(b.u64) == 0 { - *x = false - } else { - *x = true - } - return nil -} diff --git a/src/cmd/pprof/internal/profile/proto_test.go b/src/cmd/pprof/internal/profile/proto_test.go deleted file mode 100644 index c2613fc375..0000000000 --- a/src/cmd/pprof/internal/profile/proto_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package profile - -import ( - "reflect" - "testing" -) - -func TestPackedEncoding(t *testing.T) { - - type testcase struct { - uint64s []uint64 - int64s []int64 - encoded []byte - } - for i, tc := range []testcase{ - { - []uint64{0, 1, 10, 100, 1000, 10000}, - []int64{1000, 0, 1000}, - []byte{10, 8, 0, 1, 10, 100, 232, 7, 144, 78, 18, 5, 232, 7, 0, 232, 7}, - }, - { - []uint64{10000}, - nil, - []byte{8, 144, 78}, - }, - { - nil, - []int64{-10000}, - []byte{16, 240, 177, 255, 255, 255, 255, 255, 255, 255, 1}, - }, - } { - source := &packedInts{tc.uint64s, tc.int64s} - if got, want := marshal(source), tc.encoded; !reflect.DeepEqual(got, want) { - t.Errorf("failed encode %d, got %v, want %v", i, got, want) - } - - dest := new(packedInts) - if err := unmarshal(tc.encoded, dest); err != nil { - t.Errorf("failed decode %d: %v", i, err) - continue - } - if got, want := dest.uint64s, tc.uint64s; !reflect.DeepEqual(got, want) { - t.Errorf("failed decode uint64s %d, got %v, want %v", i, got, want) - } - if got, want := dest.int64s, tc.int64s; !reflect.DeepEqual(got, want) { - t.Errorf("failed decode int64s %d, got %v, want %v", i, got, want) - } - } -} - -type packedInts struct { - uint64s []uint64 - int64s []int64 -} - -func (u *packedInts) decoder() []decoder { - return []decoder{ - nil, - func(b *buffer, m message) error { return decodeUint64s(b, &m.(*packedInts).uint64s) }, - func(b *buffer, m message) error { return decodeInt64s(b, &m.(*packedInts).int64s) }, - } -} - -func (u *packedInts) encode(b *buffer) { - encodeUint64s(b, 1, u.uint64s) - encodeInt64s(b, 2, u.int64s) -} diff --git a/src/cmd/pprof/internal/profile/prune.go b/src/cmd/pprof/internal/profile/prune.go deleted file mode 100644 index 1924fada7a..0000000000 --- a/src/cmd/pprof/internal/profile/prune.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2014 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. - -// Implements methods to remove frames from profiles. - -package profile - -import ( - "fmt" - "regexp" -) - -// Prune removes all nodes beneath a node matching dropRx, and not -// matching keepRx. If the root node of a Sample matches, the sample -// will have an empty stack. -func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) { - prune := make(map[uint64]bool) - pruneBeneath := make(map[uint64]bool) - - for _, loc := range p.Location { - var i int - for i = len(loc.Line) - 1; i >= 0; i-- { - if fn := loc.Line[i].Function; fn != nil && fn.Name != "" { - funcName := fn.Name - // Account for leading '.' on the PPC ELF v1 ABI. - if funcName[0] == '.' { - funcName = funcName[1:] - } - if dropRx.MatchString(funcName) { - if keepRx == nil || !keepRx.MatchString(funcName) { - break - } - } - } - } - - if i >= 0 { - // Found matching entry to prune. - pruneBeneath[loc.ID] = true - - // Remove the matching location. - if i == len(loc.Line)-1 { - // Matched the top entry: prune the whole location. - prune[loc.ID] = true - } else { - loc.Line = loc.Line[i+1:] - } - } - } - - // Prune locs from each Sample - for _, sample := range p.Sample { - // Scan from the root to the leaves to find the prune location. - // Do not prune frames before the first user frame, to avoid - // pruning everything. - foundUser := false - for i := len(sample.Location) - 1; i >= 0; i-- { - id := sample.Location[i].ID - if !prune[id] && !pruneBeneath[id] { - foundUser = true - continue - } - if !foundUser { - continue - } - if prune[id] { - sample.Location = sample.Location[i+1:] - break - } - if pruneBeneath[id] { - sample.Location = sample.Location[i:] - break - } - } - } -} - -// RemoveUninteresting prunes and elides profiles using built-in -// tables of uninteresting function names. -func (p *Profile) RemoveUninteresting() error { - var keep, drop *regexp.Regexp - var err error - - if p.DropFrames != "" { - if drop, err = regexp.Compile("^(" + p.DropFrames + ")$"); err != nil { - return fmt.Errorf("failed to compile regexp %s: %v", p.DropFrames, err) - } - if p.KeepFrames != "" { - if keep, err = regexp.Compile("^(" + p.KeepFrames + ")$"); err != nil { - return fmt.Errorf("failed to compile regexp %s: %v", p.KeepFrames, err) - } - } - p.Prune(drop, keep) - } - return nil -} diff --git a/src/cmd/pprof/internal/report/report.go b/src/cmd/pprof/internal/report/report.go deleted file mode 100644 index 86bd4a280b..0000000000 --- a/src/cmd/pprof/internal/report/report.go +++ /dev/null @@ -1,1682 +0,0 @@ -// Copyright 2014 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 report summarizes a performance profile into a -// human-readable report. -package report - -import ( - "fmt" - "io" - "math" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "time" - - "cmd/pprof/internal/plugin" - "cmd/pprof/internal/profile" -) - -// Generate generates a report as directed by the Report. -func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error { - o := rpt.options - - switch o.OutputFormat { - case Dot: - return printDOT(w, rpt) - case Tree: - return printTree(w, rpt) - case Text: - return printText(w, rpt) - case Raw: - fmt.Fprint(w, rpt.prof.String()) - return nil - case Tags: - return printTags(w, rpt) - case Proto: - return rpt.prof.Write(w) - case Dis: - return printAssembly(w, rpt, obj) - case List: - return printSource(w, rpt) - case WebList: - return printWebSource(w, rpt, obj) - case Callgrind: - return printCallgrind(w, rpt) - } - return fmt.Errorf("unexpected output format") -} - -// printAssembly prints an annotated assembly listing. -func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error { - g, err := newGraph(rpt) - if err != nil { - return err - } - - o := rpt.options - prof := rpt.prof - - // If the regexp source can be parsed as an address, also match - // functions that land on that address. - var address *uint64 - if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil { - address = &hex - } - - fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total)) - symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj) - symNodes := nodesPerSymbol(g.ns, symbols) - // Sort function names for printing. - var syms objSymbols - for s := range symNodes { - syms = append(syms, s) - } - sort.Sort(syms) - - // Correlate the symbols from the binary with the profile samples. - for _, s := range syms { - sns := symNodes[s] - - // Gather samples for this symbol. - flatSum, cumSum := sumNodes(sns) - - // Get the function assembly. - insns, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End) - if err != nil { - return err - } - - ns := annotateAssembly(insns, sns, s.base) - - fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0]) - for _, name := range s.sym.Name[1:] { - fmt.Fprintf(w, " AKA ======================== %s\n", name) - } - fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n", - rpt.formatValue(flatSum), rpt.formatValue(cumSum), - percentage(cumSum, rpt.total)) - - for _, n := range ns { - fmt.Fprintf(w, "%10s %10s %10x: %s\n", valueOrDot(n.flat, rpt), valueOrDot(n.cum, rpt), n.info.address, n.info.name) - } - } - return nil -} - -// symbolsFromBinaries examines the binaries listed on the profile -// that have associated samples, and identifies symbols matching rx. -func symbolsFromBinaries(prof *profile.Profile, g graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol { - hasSamples := make(map[string]bool) - // Only examine mappings that have samples that match the - // regexp. This is an optimization to speed up pprof. - for _, n := range g.ns { - if name := n.info.prettyName(); rx.MatchString(name) && n.info.objfile != "" { - hasSamples[n.info.objfile] = true - } - } - - // Walk all mappings looking for matching functions with samples. - var objSyms []*objSymbol - for _, m := range prof.Mapping { - if !hasSamples[filepath.Base(m.File)] { - if address == nil || !(m.Start <= *address && *address <= m.Limit) { - continue - } - } - - f, err := obj.Open(m.File, m.Start) - if err != nil { - fmt.Printf("%v\n", err) - continue - } - - // Find symbols in this binary matching the user regexp. - var addr uint64 - if address != nil { - addr = *address - } - msyms, err := f.Symbols(rx, addr) - base := f.Base() - f.Close() - if err != nil { - continue - } - for _, ms := range msyms { - objSyms = append(objSyms, - &objSymbol{ - sym: ms, - base: base, - }, - ) - } - } - - return objSyms -} - -// objSym represents a symbol identified from a binary. It includes -// the SymbolInfo from the disasm package and the base that must be -// added to correspond to sample addresses -type objSymbol struct { - sym *plugin.Sym - base uint64 -} - -// objSymbols is a wrapper type to enable sorting of []*objSymbol. -type objSymbols []*objSymbol - -func (o objSymbols) Len() int { - return len(o) -} - -func (o objSymbols) Less(i, j int) bool { - if namei, namej := o[i].sym.Name[0], o[j].sym.Name[0]; namei != namej { - return namei < namej - } - return o[i].sym.Start < o[j].sym.Start -} - -func (o objSymbols) Swap(i, j int) { - o[i], o[j] = o[j], o[i] -} - -// nodesPerSymbol classifies nodes into a group of symbols. -func nodesPerSymbol(ns nodes, symbols []*objSymbol) map[*objSymbol]nodes { - symNodes := make(map[*objSymbol]nodes) - for _, s := range symbols { - // Gather samples for this symbol. - for _, n := range ns { - address := n.info.address - s.base - if address >= s.sym.Start && address < s.sym.End { - symNodes[s] = append(symNodes[s], n) - } - } - } - return symNodes -} - -// annotateAssembly annotates a set of assembly instructions with a -// set of samples. It returns a set of nodes to display. base is an -// offset to adjust the sample addresses. -func annotateAssembly(insns []plugin.Inst, samples nodes, base uint64) nodes { - // Add end marker to simplify printing loop. - insns = append(insns, plugin.Inst{^uint64(0), "", "", 0}) - - // Ensure samples are sorted by address. - samples.sort(addressOrder) - - var s int - var asm nodes - for ix, in := range insns[:len(insns)-1] { - n := node{ - info: nodeInfo{ - address: in.Addr, - name: in.Text, - file: trimPath(in.File), - lineno: in.Line, - }, - } - - // Sum all the samples until the next instruction (to account - // for samples attributed to the middle of an instruction). - for next := insns[ix+1].Addr; s < len(samples) && samples[s].info.address-base < next; s++ { - n.flat += samples[s].flat - n.cum += samples[s].cum - if samples[s].info.file != "" { - n.info.file = trimPath(samples[s].info.file) - n.info.lineno = samples[s].info.lineno - } - } - asm = append(asm, &n) - } - - return asm -} - -// valueOrDot formats a value according to a report, intercepting zero -// values. -func valueOrDot(value int64, rpt *Report) string { - if value == 0 { - return "." - } - return rpt.formatValue(value) -} - -// printTags collects all tags referenced in the profile and prints -// them in a sorted table. -func printTags(w io.Writer, rpt *Report) error { - p := rpt.prof - - // Hashtable to keep accumulate tags as key,value,count. - tagMap := make(map[string]map[string]int64) - for _, s := range p.Sample { - for key, vals := range s.Label { - for _, val := range vals { - if valueMap, ok := tagMap[key]; ok { - valueMap[val] = valueMap[val] + s.Value[0] - continue - } - valueMap := make(map[string]int64) - valueMap[val] = s.Value[0] - tagMap[key] = valueMap - } - } - for key, vals := range s.NumLabel { - for _, nval := range vals { - val := scaledValueLabel(nval, key, "auto") - if valueMap, ok := tagMap[key]; ok { - valueMap[val] = valueMap[val] + s.Value[0] - continue - } - valueMap := make(map[string]int64) - valueMap[val] = s.Value[0] - tagMap[key] = valueMap - } - } - } - - tagKeys := make(tags, 0, len(tagMap)) - for key := range tagMap { - tagKeys = append(tagKeys, &tag{name: key}) - } - sort.Sort(tagKeys) - - for _, tagKey := range tagKeys { - var total int64 - key := tagKey.name - tags := make(tags, 0, len(tagMap[key])) - for t, c := range tagMap[key] { - total += c - tags = append(tags, &tag{name: t, weight: c}) - } - - sort.Sort(tags) - fmt.Fprintf(w, "%s: Total %d\n", key, total) - for _, t := range tags { - if total > 0 { - fmt.Fprintf(w, " %8d (%s): %s\n", t.weight, - percentage(t.weight, total), t.name) - } else { - fmt.Fprintf(w, " %8d: %s\n", t.weight, t.name) - } - } - fmt.Fprintln(w) - } - return nil -} - -// printText prints a flat text report for a profile. -func printText(w io.Writer, rpt *Report) error { - g, err := newGraph(rpt) - if err != nil { - return err - } - - origCount, droppedNodes, _ := g.preprocess(rpt) - fmt.Fprintln(w, strings.Join(legendDetailLabels(rpt, g, origCount, droppedNodes, 0), "\n")) - - fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n", - "flat", "flat", "sum", "cum", "cum") - - var flatSum int64 - for _, n := range g.ns { - name, flat, cum := n.info.prettyName(), n.flat, n.cum - - flatSum += flat - fmt.Fprintf(w, "%10s %s %s %10s %s %s\n", - rpt.formatValue(flat), - percentage(flat, rpt.total), - percentage(flatSum, rpt.total), - rpt.formatValue(cum), - percentage(cum, rpt.total), - name) - } - return nil -} - -// printCallgrind prints a graph for a profile on callgrind format. -func printCallgrind(w io.Writer, rpt *Report) error { - g, err := newGraph(rpt) - if err != nil { - return err - } - - o := rpt.options - rpt.options.NodeFraction = 0 - rpt.options.EdgeFraction = 0 - rpt.options.NodeCount = 0 - - g.preprocess(rpt) - - fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")") - - files := make(map[string]int) - names := make(map[string]int) - for _, n := range g.ns { - fmt.Fprintln(w, "fl="+callgrindName(files, n.info.file)) - fmt.Fprintln(w, "fn="+callgrindName(names, n.info.name)) - sv, _ := ScaleValue(n.flat, o.SampleUnit, o.OutputUnit) - fmt.Fprintf(w, "%d %d\n", n.info.lineno, int(sv)) - - // Print outgoing edges. - for _, out := range sortedEdges(n.out) { - c, _ := ScaleValue(out.weight, o.SampleUnit, o.OutputUnit) - count := fmt.Sprintf("%d", int(c)) - callee := out.dest - fmt.Fprintln(w, "cfl="+callgrindName(files, callee.info.file)) - fmt.Fprintln(w, "cfn="+callgrindName(names, callee.info.name)) - fmt.Fprintln(w, "calls="+count, callee.info.lineno) - fmt.Fprintln(w, n.info.lineno, count) - } - fmt.Fprintln(w) - } - - return nil -} - -// callgrindName implements the callgrind naming compression scheme. -// For names not previously seen returns "(N) name", where N is a -// unique index. For names previously seen returns "(N)" where N is -// the index returned the first time. -func callgrindName(names map[string]int, name string) string { - if name == "" { - return "" - } - if id, ok := names[name]; ok { - return fmt.Sprintf("(%d)", id) - } - id := len(names) + 1 - names[name] = id - return fmt.Sprintf("(%d) %s", id, name) -} - -// printTree prints a tree-based report in text form. -func printTree(w io.Writer, rpt *Report) error { - const separator = "----------------------------------------------------------+-------------" - const legend = " flat flat% sum% cum cum% calls calls% + context " - - g, err := newGraph(rpt) - if err != nil { - return err - } - - origCount, droppedNodes, _ := g.preprocess(rpt) - fmt.Fprintln(w, strings.Join(legendDetailLabels(rpt, g, origCount, droppedNodes, 0), "\n")) - - fmt.Fprintln(w, separator) - fmt.Fprintln(w, legend) - var flatSum int64 - - rx := rpt.options.Symbol - for _, n := range g.ns { - name, flat, cum := n.info.prettyName(), n.flat, n.cum - - // Skip any entries that do not match the regexp (for the "peek" command). - if rx != nil && !rx.MatchString(name) { - continue - } - - fmt.Fprintln(w, separator) - // Print incoming edges. - inEdges := sortedEdges(n.in) - inSum := inEdges.sum() - for _, in := range inEdges { - fmt.Fprintf(w, "%50s %s | %s\n", rpt.formatValue(in.weight), - percentage(in.weight, inSum), in.src.info.prettyName()) - } - - // Print current node. - flatSum += flat - fmt.Fprintf(w, "%10s %s %s %10s %s | %s\n", - rpt.formatValue(flat), - percentage(flat, rpt.total), - percentage(flatSum, rpt.total), - rpt.formatValue(cum), - percentage(cum, rpt.total), - name) - - // Print outgoing edges. - outEdges := sortedEdges(n.out) - outSum := outEdges.sum() - for _, out := range outEdges { - fmt.Fprintf(w, "%50s %s | %s\n", rpt.formatValue(out.weight), - percentage(out.weight, outSum), out.dest.info.prettyName()) - } - } - if len(g.ns) > 0 { - fmt.Fprintln(w, separator) - } - return nil -} - -// printDOT prints an annotated callgraph in DOT format. -func printDOT(w io.Writer, rpt *Report) error { - g, err := newGraph(rpt) - if err != nil { - return err - } - - origCount, droppedNodes, droppedEdges := g.preprocess(rpt) - - prof := rpt.prof - graphname := "unnamed" - if len(prof.Mapping) > 0 { - graphname = filepath.Base(prof.Mapping[0].File) - } - fmt.Fprintln(w, `digraph "`+graphname+`" {`) - fmt.Fprintln(w, `node [style=filled fillcolor="#f8f8f8"]`) - fmt.Fprintln(w, dotLegend(rpt, g, origCount, droppedNodes, droppedEdges)) - - if len(g.ns) == 0 { - fmt.Fprintln(w, "}") - return nil - } - - // Make sure nodes have a unique consistent id. - nodeIndex := make(map[*node]int) - maxFlat := float64(g.ns[0].flat) - for i, n := range g.ns { - nodeIndex[n] = i + 1 - if float64(n.flat) > maxFlat { - maxFlat = float64(n.flat) - } - } - var edges edgeList - for _, n := range g.ns { - node := dotNode(rpt, maxFlat, nodeIndex[n], n) - fmt.Fprintln(w, node) - if nodelets := dotNodelets(rpt, nodeIndex[n], n); nodelets != "" { - fmt.Fprint(w, nodelets) - } - - // Collect outgoing edges. - for _, e := range n.out { - edges = append(edges, e) - } - } - // Sort edges by frequency as a hint to the graph layout engine. - sort.Sort(edges) - for _, e := range edges { - fmt.Fprintln(w, dotEdge(rpt, nodeIndex[e.src], nodeIndex[e.dest], e)) - } - fmt.Fprintln(w, "}") - return nil -} - -// percentage computes the percentage of total of a value, and encodes -// it as a string. At least two digits of precision are printed. -func percentage(value, total int64) string { - var ratio float64 - if total != 0 { - ratio = float64(value) / float64(total) * 100 - } - switch { - case ratio >= 99.95: - return " 100%" - case ratio >= 1.0: - return fmt.Sprintf("%5.2f%%", ratio) - default: - return fmt.Sprintf("%5.2g%%", ratio) - } -} - -// dotLegend generates the overall graph label for a report in DOT format. -func dotLegend(rpt *Report, g graph, origCount, droppedNodes, droppedEdges int) string { - label := legendLabels(rpt) - label = append(label, legendDetailLabels(rpt, g, origCount, droppedNodes, droppedEdges)...) - return fmt.Sprintf(`subgraph cluster_L { L [shape=box fontsize=32 label="%s\l"] }`, strings.Join(label, `\l`)) -} - -// legendLabels generates labels exclusive to graph visualization. -func legendLabels(rpt *Report) []string { - prof := rpt.prof - o := rpt.options - var label []string - if len(prof.Mapping) > 0 { - if prof.Mapping[0].File != "" { - label = append(label, "File: "+filepath.Base(prof.Mapping[0].File)) - } - if prof.Mapping[0].BuildID != "" { - label = append(label, "Build ID: "+prof.Mapping[0].BuildID) - } - } - if o.SampleType != "" { - label = append(label, "Type: "+o.SampleType) - } - if prof.TimeNanos != 0 { - const layout = "Jan 2, 2006 at 3:04pm (MST)" - label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout)) - } - if prof.DurationNanos != 0 { - label = append(label, fmt.Sprintf("Duration: %v", time.Duration(prof.DurationNanos))) - } - return label -} - -// legendDetailLabels generates labels common to graph and text visualization. -func legendDetailLabels(rpt *Report, g graph, origCount, droppedNodes, droppedEdges int) []string { - nodeFraction := rpt.options.NodeFraction - edgeFraction := rpt.options.EdgeFraction - nodeCount := rpt.options.NodeCount - - label := []string{} - - var flatSum int64 - for _, n := range g.ns { - flatSum = flatSum + n.flat - } - - label = append(label, fmt.Sprintf("%s of %s total (%s)", rpt.formatValue(flatSum), rpt.formatValue(rpt.total), percentage(flatSum, rpt.total))) - - if rpt.total > 0 { - if droppedNodes > 0 { - label = append(label, genLabel(droppedNodes, "node", "cum", - rpt.formatValue(int64(float64(rpt.total)*nodeFraction)))) - } - if droppedEdges > 0 { - label = append(label, genLabel(droppedEdges, "edge", "freq", - rpt.formatValue(int64(float64(rpt.total)*edgeFraction)))) - } - if nodeCount > 0 && nodeCount < origCount { - label = append(label, fmt.Sprintf("Showing top %d nodes out of %d (cum >= %s)", - nodeCount, origCount, - rpt.formatValue(g.ns[len(g.ns)-1].cum))) - } - } - return label -} - -func genLabel(d int, n, l, f string) string { - if d > 1 { - n = n + "s" - } - return fmt.Sprintf("Dropped %d %s (%s <= %s)", d, n, l, f) -} - -// dotNode generates a graph node in DOT format. -func dotNode(rpt *Report, maxFlat float64, rIndex int, n *node) string { - flat, cum := n.flat, n.cum - - labels := strings.Split(n.info.prettyName(), "::") - label := strings.Join(labels, `\n`) + `\n` - - flatValue := rpt.formatValue(flat) - if flat > 0 { - label = label + fmt.Sprintf(`%s(%s)`, - flatValue, - strings.TrimSpace(percentage(flat, rpt.total))) - } else { - label = label + "0" - } - cumValue := flatValue - if cum != flat { - if flat > 0 { - label = label + `\n` - } else { - label = label + " " - } - cumValue = rpt.formatValue(cum) - label = label + fmt.Sprintf(`of %s(%s)`, - cumValue, - strings.TrimSpace(percentage(cum, rpt.total))) - } - - // Scale font sizes from 8 to 24 based on percentage of flat frequency. - // Use non linear growth to emphasize the size difference. - baseFontSize, maxFontGrowth := 8, 16.0 - fontSize := baseFontSize - if maxFlat > 0 && flat > 0 && float64(flat) <= maxFlat { - fontSize += int(math.Ceil(maxFontGrowth * math.Sqrt(float64(flat)/maxFlat))) - } - return fmt.Sprintf(`N%d [label="%s" fontsize=%d shape=box tooltip="%s (%s)"]`, - rIndex, - label, - fontSize, n.info.prettyName(), cumValue) -} - -// dotEdge generates a graph edge in DOT format. -func dotEdge(rpt *Report, from, to int, e *edgeInfo) string { - w := rpt.formatValue(e.weight) - attr := fmt.Sprintf(`label=" %s"`, w) - if rpt.total > 0 { - if weight := 1 + int(e.weight*100/rpt.total); weight > 1 { - attr = fmt.Sprintf(`%s weight=%d`, attr, weight) - } - if width := 1 + int(e.weight*5/rpt.total); width > 1 { - attr = fmt.Sprintf(`%s penwidth=%d`, attr, width) - } - } - arrow := "->" - if e.residual { - arrow = "..." - } - tooltip := fmt.Sprintf(`"%s %s %s (%s)"`, - e.src.info.prettyName(), arrow, e.dest.info.prettyName(), w) - attr = fmt.Sprintf(`%s tooltip=%s labeltooltip=%s`, - attr, tooltip, tooltip) - - if e.residual { - attr = attr + ` style="dotted"` - } - - if len(e.src.tags) > 0 { - // Separate children further if source has tags. - attr = attr + " minlen=2" - } - return fmt.Sprintf("N%d -> N%d [%s]", from, to, attr) -} - -// dotNodelets generates the DOT boxes for the node tags. -func dotNodelets(rpt *Report, rIndex int, n *node) (dot string) { - const maxNodelets = 4 // Number of nodelets for alphanumeric labels - const maxNumNodelets = 4 // Number of nodelets for numeric labels - - var ts, nts tags - for _, t := range n.tags { - if t.unit == "" { - ts = append(ts, t) - } else { - nts = append(nts, t) - } - } - - // Select the top maxNodelets alphanumeric labels by weight - sort.Sort(ts) - if len(ts) > maxNodelets { - ts = ts[:maxNodelets] - } - for i, t := range ts { - weight := rpt.formatValue(t.weight) - dot += fmt.Sprintf(`N%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", rIndex, i, t.name, weight) - dot += fmt.Sprintf(`N%d -> N%d_%d [label=" %s" weight=100 tooltip="\L" labeltooltip="\L"]`+"\n", rIndex, rIndex, i, weight) - } - - // Collapse numeric labels into maxNumNodelets buckets, of the form: - // 1MB..2MB, 3MB..5MB, ... - nts = collapseTags(nts, maxNumNodelets) - sort.Sort(nts) - for i, t := range nts { - weight := rpt.formatValue(t.weight) - dot += fmt.Sprintf(`NN%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", rIndex, i, t.name, weight) - dot += fmt.Sprintf(`N%d -> NN%d_%d [label=" %s" weight=100 tooltip="\L" labeltooltip="\L"]`+"\n", rIndex, rIndex, i, weight) - } - - return dot -} - -// graph summarizes a performance profile into a format that is -// suitable for visualization. -type graph struct { - ns nodes -} - -// nodes is an ordered collection of graph nodes. -type nodes []*node - -// tags represent sample annotations -type tags []*tag -type tagMap map[string]*tag - -type tag struct { - name string - unit string // Describe the value, "" for non-numeric tags - value int64 - weight int64 -} - -func (t tags) Len() int { return len(t) } -func (t tags) Swap(i, j int) { t[i], t[j] = t[j], t[i] } -func (t tags) Less(i, j int) bool { - if t[i].weight == t[j].weight { - return t[i].name < t[j].name - } - return t[i].weight > t[j].weight -} - -// node is an entry on a profiling report. It represents a unique -// program location. It can include multiple names to represent -// inlined functions. -type node struct { - info nodeInfo // Information associated to this entry. - - // values associated to this node. - // flat is exclusive to this node, cum includes all descendents. - flat, cum int64 - - // in and out contains the nodes immediately reaching or reached by this nodes. - in, out edgeMap - - // tags provide additional information about subsets of a sample. - tags tagMap -} - -type nodeInfo struct { - name string - origName string - address uint64 - file string - startLine, lineno int - inline bool - lowPriority bool - objfile string - parent *node // Used only if creating a calltree -} - -func (n *node) addTags(s *profile.Sample, weight int64) { - // Add a tag with all string labels - var labels []string - for key, vals := range s.Label { - for _, v := range vals { - labels = append(labels, key+":"+v) - } - } - if len(labels) > 0 { - sort.Strings(labels) - l := n.tags.findOrAddTag(strings.Join(labels, `\n`), "", 0) - l.weight += weight - } - - for key, nvals := range s.NumLabel { - for _, v := range nvals { - label := scaledValueLabel(v, key, "auto") - l := n.tags.findOrAddTag(label, key, v) - l.weight += weight - } - } -} - -func (m tagMap) findOrAddTag(label, unit string, value int64) *tag { - if l := m[label]; l != nil { - return l - } - l := &tag{ - name: label, - unit: unit, - value: value, - } - m[label] = l - return l -} - -// collapseTags reduces the number of entries in a tagMap by merging -// adjacent nodes into ranges. It uses a greedy approach to merge -// starting with the entries with the lowest weight. -func collapseTags(ts tags, count int) tags { - if len(ts) <= count { - return ts - } - - sort.Sort(ts) - tagGroups := make([]tags, count) - for i, t := range ts[:count] { - tagGroups[i] = tags{t} - } - for _, t := range ts[count:] { - g, d := 0, tagDistance(t, tagGroups[0][0]) - for i := 1; i < count; i++ { - if nd := tagDistance(t, tagGroups[i][0]); nd < d { - g, d = i, nd - } - } - tagGroups[g] = append(tagGroups[g], t) - } - - var nts tags - for _, g := range tagGroups { - l, w := tagGroupLabel(g) - nts = append(nts, &tag{ - name: l, - weight: w, - }) - } - return nts -} - -func tagDistance(t, u *tag) float64 { - v, _ := ScaleValue(u.value, u.unit, t.unit) - if v < float64(t.value) { - return float64(t.value) - v - } - return v - float64(t.value) -} - -func tagGroupLabel(g tags) (string, int64) { - if len(g) == 1 { - t := g[0] - return scaledValueLabel(t.value, t.unit, "auto"), t.weight - } - min := g[0] - max := g[0] - w := min.weight - for _, t := range g[1:] { - if v, _ := ScaleValue(t.value, t.unit, min.unit); int64(v) < min.value { - min = t - } - if v, _ := ScaleValue(t.value, t.unit, max.unit); int64(v) > max.value { - max = t - } - w += t.weight - } - return scaledValueLabel(min.value, min.unit, "auto") + ".." + - scaledValueLabel(max.value, max.unit, "auto"), w -} - -// sumNodes adds the flat and sum values on a report. -func sumNodes(ns nodes) (flat int64, cum int64) { - for _, n := range ns { - flat += n.flat - cum += n.cum - } - return -} - -type edgeMap map[*node]*edgeInfo - -// edgeInfo contains any attributes to be represented about edges in a graph/ -type edgeInfo struct { - src, dest *node - // The summary weight of the edge - weight int64 - // residual edges connect nodes that were connected through a - // separate node, which has been removed from the report. - residual bool -} - -// bumpWeight increases the weight of an edge. If there isn't such an -// edge in the map one is created. -func bumpWeight(from, to *node, w int64, residual bool) { - if from.out[to] != to.in[from] { - panic(fmt.Errorf("asymmetric edges %v %v", *from, *to)) - } - - if n := from.out[to]; n != nil { - n.weight += w - if n.residual && !residual { - n.residual = false - } - return - } - - info := &edgeInfo{src: from, dest: to, weight: w, residual: residual} - from.out[to] = info - to.in[from] = info -} - -// Output formats. -const ( - Proto = iota - Dot - Tags - Tree - Text - Raw - Dis - List - WebList - Callgrind -) - -// Options are the formatting and filtering options used to generate a -// profile. -type Options struct { - OutputFormat int - - CumSort bool - CallTree bool - PrintAddresses bool - DropNegative bool - Ratio float64 - - NodeCount int - NodeFraction float64 - EdgeFraction float64 - - SampleType string - SampleUnit string // Unit for the sample data from the profile. - OutputUnit string // Units for data formatting in report. - - Symbol *regexp.Regexp // Symbols to include on disassembly report. -} - -// newGraph summarizes performance data from a profile into a graph. -func newGraph(rpt *Report) (g graph, err error) { - prof := rpt.prof - o := rpt.options - - // Generate a tree for graphical output if requested. - buildTree := o.CallTree && o.OutputFormat == Dot - - locations := make(map[uint64][]nodeInfo) - for _, l := range prof.Location { - locations[l.ID] = newLocInfo(l) - } - - nm := make(nodeMap) - for _, sample := range prof.Sample { - if sample.Location == nil { - continue - } - - // Construct list of node names for sample. - var stack []nodeInfo - for _, loc := range sample.Location { - id := loc.ID - stack = append(stack, locations[id]...) - } - - // Upfront pass to update the parent chains, to prevent the - // merging of nodes with different parents. - if buildTree { - var nn *node - for i := len(stack); i > 0; i-- { - n := &stack[i-1] - n.parent = nn - nn = nm.findOrInsertNode(*n) - } - } - - leaf := nm.findOrInsertNode(stack[0]) - weight := rpt.sampleValue(sample) - leaf.addTags(sample, weight) - - // Aggregate counter data. - leaf.flat += weight - seen := make(map[*node]bool) - var nn *node - for _, s := range stack { - n := nm.findOrInsertNode(s) - if !seen[n] { - seen[n] = true - n.cum += weight - - if nn != nil { - bumpWeight(n, nn, weight, false) - } - } - nn = n - } - } - - // Collect new nodes into a report. - ns := make(nodes, 0, len(nm)) - for _, n := range nm { - if rpt.options.DropNegative && n.flat < 0 { - continue - } - ns = append(ns, n) - } - - return graph{ns}, nil -} - -// Create a slice of formatted names for a location. -func newLocInfo(l *profile.Location) []nodeInfo { - var objfile string - - if m := l.Mapping; m != nil { - objfile = filepath.Base(m.File) - } - - if len(l.Line) == 0 { - return []nodeInfo{ - { - address: l.Address, - objfile: objfile, - }, - } - } - var info []nodeInfo - numInlineFrames := len(l.Line) - 1 - for li, line := range l.Line { - ni := nodeInfo{ - address: l.Address, - lineno: int(line.Line), - inline: li < numInlineFrames, - objfile: objfile, - } - - if line.Function != nil { - ni.name = line.Function.Name - ni.origName = line.Function.SystemName - ni.file = line.Function.Filename - ni.startLine = int(line.Function.StartLine) - } - - info = append(info, ni) - } - return info -} - -// nodeMap maps from a node info struct to a node. It is used to merge -// report entries with the same info. -type nodeMap map[nodeInfo]*node - -func (m nodeMap) findOrInsertNode(info nodeInfo) *node { - rr := m[info] - if rr == nil { - rr = &node{ - info: info, - in: make(edgeMap), - out: make(edgeMap), - tags: make(map[string]*tag), - } - m[info] = rr - } - return rr -} - -// preprocess does any required filtering/sorting according to the -// report options. Returns the mapping from each node to any nodes -// removed by path compression and statistics on the nodes/edges removed. -func (g *graph) preprocess(rpt *Report) (origCount, droppedNodes, droppedEdges int) { - o := rpt.options - - // Compute total weight of current set of nodes. - // This is <= rpt.total because of node filtering. - var totalValue int64 - for _, n := range g.ns { - totalValue += n.flat - } - - // Remove nodes with value <= total*nodeFraction - if nodeFraction := o.NodeFraction; nodeFraction > 0 { - var removed nodes - minValue := int64(float64(totalValue) * nodeFraction) - kept := make(nodes, 0, len(g.ns)) - for _, n := range g.ns { - if n.cum < minValue { - removed = append(removed, n) - } else { - kept = append(kept, n) - tagsKept := make(map[string]*tag) - for s, t := range n.tags { - if t.weight >= minValue { - tagsKept[s] = t - } - } - n.tags = tagsKept - } - } - droppedNodes = len(removed) - removeNodes(removed, false, false) - g.ns = kept - } - - // Remove edges below minimum frequency. - if edgeFraction := o.EdgeFraction; edgeFraction > 0 { - minEdge := int64(float64(totalValue) * edgeFraction) - for _, n := range g.ns { - for src, e := range n.in { - if e.weight < minEdge { - delete(n.in, src) - delete(src.out, n) - droppedEdges++ - } - } - } - } - - sortOrder := flatName - if o.CumSort { - // Force cum sorting for graph output, to preserve connectivity. - sortOrder = cumName - } - - // Nodes that have flat==0 and a single in/out do not provide much - // information. Give them first chance to be removed. Do not consider edges - // from/to nodes that are expected to be removed. - maxNodes := o.NodeCount - if o.OutputFormat == Dot { - if maxNodes > 0 && maxNodes < len(g.ns) { - sortOrder = cumName - g.ns.sort(cumName) - cumCutoff := g.ns[maxNodes].cum - for _, n := range g.ns { - if n.flat == 0 { - if count := countEdges(n.out, cumCutoff); count > 1 { - continue - } - if count := countEdges(n.in, cumCutoff); count != 1 { - continue - } - n.info.lowPriority = true - } - } - } - } - - g.ns.sort(sortOrder) - if maxNodes > 0 { - origCount = len(g.ns) - for index, nodes := 0, 0; index < len(g.ns); index++ { - nodes++ - // For DOT output, count the tags as nodes since we will draw - // boxes for them. - if o.OutputFormat == Dot { - nodes += len(g.ns[index].tags) - } - if nodes > maxNodes { - // Trim to the top n nodes. Create dotted edges to bridge any - // broken connections. - removeNodes(g.ns[index:], true, true) - g.ns = g.ns[:index] - break - } - } - } - removeRedundantEdges(g.ns) - - // Select best unit for profile output. - // Find the appropriate units for the smallest non-zero sample - if o.OutputUnit == "minimum" && len(g.ns) > 0 { - var maxValue, minValue int64 - - for _, n := range g.ns { - if n.flat > 0 && (minValue == 0 || n.flat < minValue) { - minValue = n.flat - } - if n.cum > maxValue { - maxValue = n.cum - } - } - if r := o.Ratio; r > 0 && r != 1 { - minValue = int64(float64(minValue) * r) - maxValue = int64(float64(maxValue) * r) - } - - _, minUnit := ScaleValue(minValue, o.SampleUnit, "minimum") - _, maxUnit := ScaleValue(maxValue, o.SampleUnit, "minimum") - - unit := minUnit - if minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind { - // Minimum and maximum values have different units. Scale - // minimum by 100 to use larger units, allowing minimum value to - // be scaled down to 0.01, except for callgrind reports since - // they can only represent integer values. - _, unit = ScaleValue(100*minValue, o.SampleUnit, "minimum") - } - - if unit != "" { - o.OutputUnit = unit - } else { - o.OutputUnit = o.SampleUnit - } - } - return -} - -// countEdges counts the number of edges below the specified cutoff. -func countEdges(el edgeMap, cutoff int64) int { - count := 0 - for _, e := range el { - if e.weight > cutoff { - count++ - } - } - return count -} - -// removeNodes removes nodes from a report, optionally bridging -// connections between in/out edges and spreading out their weights -// proportionally. residual marks new bridge edges as residual -// (dotted). -func removeNodes(toRemove nodes, bridge, residual bool) { - for _, n := range toRemove { - for ei := range n.in { - delete(ei.out, n) - } - if bridge { - for ei, wi := range n.in { - for eo, wo := range n.out { - var weight int64 - if n.cum != 0 { - weight = int64(float64(wo.weight) * (float64(wi.weight) / float64(n.cum))) - } - bumpWeight(ei, eo, weight, residual) - } - } - } - for eo := range n.out { - delete(eo.in, n) - } - } -} - -// removeRedundantEdges removes residual edges if the destination can -// be reached through another path. This is done to simplify the graph -// while preserving connectivity. -func removeRedundantEdges(ns nodes) { - // Walk the nodes and outgoing edges in reverse order to prefer - // removing edges with the lowest weight. - for i := len(ns); i > 0; i-- { - n := ns[i-1] - in := sortedEdges(n.in) - for j := len(in); j > 0; j-- { - if e := in[j-1]; e.residual && isRedundant(e) { - delete(e.src.out, e.dest) - delete(e.dest.in, e.src) - } - } - } -} - -// isRedundant determines if an edge can be removed without impacting -// connectivity of the whole graph. This is implemented by checking if the -// nodes have a common ancestor after removing the edge. -func isRedundant(e *edgeInfo) bool { - destPred := predecessors(e, e.dest) - if len(destPred) == 1 { - return false - } - srcPred := predecessors(e, e.src) - - for n := range srcPred { - if destPred[n] && n != e.dest { - return true - } - } - return false -} - -// predecessors collects all the predecessors to node n, excluding edge e. -func predecessors(e *edgeInfo, n *node) map[*node]bool { - seen := map[*node]bool{n: true} - queue := []*node{n} - for len(queue) > 0 { - n := queue[0] - queue = queue[1:] - for _, ie := range n.in { - if e == ie || seen[ie.src] { - continue - } - seen[ie.src] = true - queue = append(queue, ie.src) - } - } - return seen -} - -// nodeSorter is a mechanism used to allow a report to be sorted -// in different ways. -type nodeSorter struct { - rs nodes - less func(i, j int) bool -} - -func (s nodeSorter) Len() int { return len(s.rs) } -func (s nodeSorter) Swap(i, j int) { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] } -func (s nodeSorter) Less(i, j int) bool { return s.less(i, j) } - -type nodeOrder int - -const ( - flatName nodeOrder = iota - flatCumName - cumName - nameOrder - fileOrder - addressOrder -) - -// sort reorders the entries in a report based on the specified -// ordering criteria. The result is sorted in decreasing order for -// numeric quantities, alphabetically for text, and increasing for -// addresses. -func (ns nodes) sort(o nodeOrder) error { - var s nodeSorter - - switch o { - case flatName: - s = nodeSorter{ns, - func(i, j int) bool { - if iv, jv := ns[i].flat, ns[j].flat; iv != jv { - return iv > jv - } - if ns[i].info.prettyName() != ns[j].info.prettyName() { - return ns[i].info.prettyName() < ns[j].info.prettyName() - } - iv, jv := ns[i].cum, ns[j].cum - return iv > jv - }, - } - case flatCumName: - s = nodeSorter{ns, - func(i, j int) bool { - if iv, jv := ns[i].flat, ns[j].flat; iv != jv { - return iv > jv - } - if iv, jv := ns[i].cum, ns[j].cum; iv != jv { - return iv > jv - } - return ns[i].info.prettyName() < ns[j].info.prettyName() - }, - } - case cumName: - s = nodeSorter{ns, - func(i, j int) bool { - if ns[i].info.lowPriority != ns[j].info.lowPriority { - return ns[j].info.lowPriority - } - if iv, jv := ns[i].cum, ns[j].cum; iv != jv { - return iv > jv - } - if ns[i].info.prettyName() != ns[j].info.prettyName() { - return ns[i].info.prettyName() < ns[j].info.prettyName() - } - iv, jv := ns[i].flat, ns[j].flat - return iv > jv - }, - } - case nameOrder: - s = nodeSorter{ns, - func(i, j int) bool { - return ns[i].info.name < ns[j].info.name - }, - } - case fileOrder: - s = nodeSorter{ns, - func(i, j int) bool { - return ns[i].info.file < ns[j].info.file - }, - } - case addressOrder: - s = nodeSorter{ns, - func(i, j int) bool { - return ns[i].info.address < ns[j].info.address - }, - } - default: - return fmt.Errorf("report: unrecognized sort ordering: %d", o) - } - sort.Sort(s) - return nil -} - -type edgeList []*edgeInfo - -// sortedEdges return a slice of the edges in the map, sorted for -// visualization. The sort order is first based on the edge weight -// (higher-to-lower) and then by the node names to avoid flakiness. -func sortedEdges(edges map[*node]*edgeInfo) edgeList { - el := make(edgeList, 0, len(edges)) - for _, w := range edges { - el = append(el, w) - } - - sort.Sort(el) - return el -} - -func (el edgeList) Len() int { - return len(el) -} - -func (el edgeList) Less(i, j int) bool { - if el[i].weight != el[j].weight { - return el[i].weight > el[j].weight - } - - from1 := el[i].src.info.prettyName() - from2 := el[j].src.info.prettyName() - if from1 != from2 { - return from1 < from2 - } - - to1 := el[i].dest.info.prettyName() - to2 := el[j].dest.info.prettyName() - - return to1 < to2 -} - -func (el edgeList) Swap(i, j int) { - el[i], el[j] = el[j], el[i] -} - -func (el edgeList) sum() int64 { - var ret int64 - for _, e := range el { - ret += e.weight - } - return ret -} - -// ScaleValue reformats a value from a unit to a different unit. -func ScaleValue(value int64, fromUnit, toUnit string) (sv float64, su string) { - // Avoid infinite recursion on overflow. - if value < 0 && -value > 0 { - v, u := ScaleValue(-value, fromUnit, toUnit) - return -v, u - } - if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok { - return m, u - } - if t, u, ok := timeLabel(value, fromUnit, toUnit); ok { - return t, u - } - // Skip non-interesting units. - switch toUnit { - case "count", "sample", "unit", "minimum": - return float64(value), "" - default: - return float64(value), toUnit - } -} - -func scaledValueLabel(value int64, fromUnit, toUnit string) string { - v, u := ScaleValue(value, fromUnit, toUnit) - - sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00") - if sv == "0" || sv == "-0" { - return "0" - } - return sv + u -} - -func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) { - fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s") - toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s") - - switch fromUnit { - case "byte", "b": - case "kilobyte", "kb": - value *= 1024 - case "megabyte", "mb": - value *= 1024 * 1024 - case "gigabyte", "gb": - value *= 1024 * 1024 * 1024 - default: - return 0, "", false - } - - if toUnit == "minimum" || toUnit == "auto" { - switch { - case value < 1024: - toUnit = "b" - case value < 1024*1024: - toUnit = "kb" - case value < 1024*1024*1024: - toUnit = "mb" - default: - toUnit = "gb" - } - } - - var output float64 - switch toUnit { - default: - output, toUnit = float64(value), "B" - case "kb", "kbyte", "kilobyte": - output, toUnit = float64(value)/1024, "kB" - case "mb", "mbyte", "megabyte": - output, toUnit = float64(value)/(1024*1024), "MB" - case "gb", "gbyte", "gigabyte": - output, toUnit = float64(value)/(1024*1024*1024), "GB" - } - return output, toUnit, true -} - -func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) { - fromUnit = strings.ToLower(fromUnit) - if len(fromUnit) > 2 { - fromUnit = strings.TrimSuffix(fromUnit, "s") - } - - toUnit = strings.ToLower(toUnit) - if len(toUnit) > 2 { - toUnit = strings.TrimSuffix(toUnit, "s") - } - - var d time.Duration - switch fromUnit { - case "nanosecond", "ns": - d = time.Duration(value) * time.Nanosecond - case "microsecond": - d = time.Duration(value) * time.Microsecond - case "millisecond", "ms": - d = time.Duration(value) * time.Millisecond - case "second", "sec": - d = time.Duration(value) * time.Second - case "cycle": - return float64(value), "", true - default: - return 0, "", false - } - - if toUnit == "minimum" || toUnit == "auto" { - switch { - case d < 1*time.Microsecond: - toUnit = "ns" - case d < 1*time.Millisecond: - toUnit = "us" - case d < 1*time.Second: - toUnit = "ms" - case d < 1*time.Minute: - toUnit = "sec" - case d < 1*time.Hour: - toUnit = "min" - case d < 24*time.Hour: - toUnit = "hour" - case d < 15*24*time.Hour: - toUnit = "day" - case d < 120*24*time.Hour: - toUnit = "week" - default: - toUnit = "year" - } - } - - var output float64 - dd := float64(d) - switch toUnit { - case "ns", "nanosecond": - output, toUnit = dd/float64(time.Nanosecond), "ns" - case "us", "microsecond": - output, toUnit = dd/float64(time.Microsecond), "us" - case "ms", "millisecond": - output, toUnit = dd/float64(time.Millisecond), "ms" - case "min", "minute": - output, toUnit = dd/float64(time.Minute), "mins" - case "hour", "hr": - output, toUnit = dd/float64(time.Hour), "hrs" - case "day": - output, toUnit = dd/float64(24*time.Hour), "days" - case "week", "wk": - output, toUnit = dd/float64(7*24*time.Hour), "wks" - case "year", "yr": - output, toUnit = dd/float64(365*7*24*time.Hour), "yrs" - default: - fallthrough - case "sec", "second", "s": - output, toUnit = dd/float64(time.Second), "s" - } - return output, toUnit, true -} - -// prettyName determines the printable name to be used for a node. -func (info *nodeInfo) prettyName() string { - var name string - if info.address != 0 { - name = fmt.Sprintf("%016x", info.address) - } - - if info.name != "" { - name = name + " " + info.name - } - - if info.file != "" { - name += " " + trimPath(info.file) - if info.lineno != 0 { - name += fmt.Sprintf(":%d", info.lineno) - } - } - - if info.inline { - name = name + " (inline)" - } - - if name = strings.TrimSpace(name); name == "" && info.objfile != "" { - name = "[" + info.objfile + "]" - } - return name -} - -// New builds a new report indexing the sample values interpreting the -// samples with the provided function. -func New(prof *profile.Profile, options Options, value func(s *profile.Sample) int64, unit string) *Report { - o := &options - if o.SampleUnit == "" { - o.SampleUnit = unit - } - format := func(v int64) string { - if r := o.Ratio; r > 0 && r != 1 { - fv := float64(v) * r - v = int64(fv) - } - return scaledValueLabel(v, o.SampleUnit, o.OutputUnit) - } - return &Report{prof, computeTotal(prof, value), o, value, format} -} - -// NewDefault builds a new report indexing the sample values with the -// last value available. -func NewDefault(prof *profile.Profile, options Options) *Report { - index := len(prof.SampleType) - 1 - o := &options - if o.SampleUnit == "" { - o.SampleUnit = strings.ToLower(prof.SampleType[index].Unit) - } - value := func(s *profile.Sample) int64 { - return s.Value[index] - } - format := func(v int64) string { - if r := o.Ratio; r > 0 && r != 1 { - fv := float64(v) * r - v = int64(fv) - } - return scaledValueLabel(v, o.SampleUnit, o.OutputUnit) - } - return &Report{prof, computeTotal(prof, value), o, value, format} -} - -func computeTotal(prof *profile.Profile, value func(s *profile.Sample) int64) int64 { - var ret int64 - for _, sample := range prof.Sample { - ret += value(sample) - } - return ret -} - -// Report contains the data and associated routines to extract a -// report from a profile. -type Report struct { - prof *profile.Profile - total int64 - options *Options - sampleValue func(*profile.Sample) int64 - formatValue func(int64) string -} diff --git a/src/cmd/pprof/internal/report/source.go b/src/cmd/pprof/internal/report/source.go deleted file mode 100644 index 908be21424..0000000000 --- a/src/cmd/pprof/internal/report/source.go +++ /dev/null @@ -1,454 +0,0 @@ -// Copyright 2014 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 report - -// This file contains routines related to the generation of annotated -// source listings. - -import ( - "bufio" - "fmt" - "html/template" - "io" - "os" - "path/filepath" - "sort" - "strconv" - "strings" - - "cmd/pprof/internal/plugin" -) - -// printSource prints an annotated source listing, include all -// functions with samples that match the regexp rpt.options.symbol. -// The sources are sorted by function name and then by filename to -// eliminate potential nondeterminism. -func printSource(w io.Writer, rpt *Report) error { - o := rpt.options - g, err := newGraph(rpt) - if err != nil { - return err - } - - // Identify all the functions that match the regexp provided. - // Group nodes for each matching function. - var functions nodes - functionNodes := make(map[string]nodes) - for _, n := range g.ns { - if !o.Symbol.MatchString(n.info.name) { - continue - } - if functionNodes[n.info.name] == nil { - functions = append(functions, n) - } - functionNodes[n.info.name] = append(functionNodes[n.info.name], n) - } - functions.sort(nameOrder) - - fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total)) - for _, fn := range functions { - name := fn.info.name - - // Identify all the source files associated to this function. - // Group nodes for each source file. - var sourceFiles nodes - fileNodes := make(map[string]nodes) - for _, n := range functionNodes[name] { - if n.info.file == "" { - continue - } - if fileNodes[n.info.file] == nil { - sourceFiles = append(sourceFiles, n) - } - fileNodes[n.info.file] = append(fileNodes[n.info.file], n) - } - - if len(sourceFiles) == 0 { - fmt.Printf("No source information for %s\n", name) - continue - } - - sourceFiles.sort(fileOrder) - - // Print each file associated with this function. - for _, fl := range sourceFiles { - filename := fl.info.file - fns := fileNodes[filename] - flatSum, cumSum := sumNodes(fns) - - fnodes, path, err := getFunctionSource(name, filename, fns, 0, 0) - fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, path) - fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n", - rpt.formatValue(flatSum), rpt.formatValue(cumSum), - percentage(cumSum, rpt.total)) - - if err != nil { - fmt.Fprintf(w, " Error: %v\n", err) - continue - } - - for _, fn := range fnodes { - fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt), fn.info.lineno, fn.info.name) - } - } - } - return nil -} - -// printWebSource prints an annotated source listing, include all -// functions with samples that match the regexp rpt.options.symbol. -func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error { - o := rpt.options - g, err := newGraph(rpt) - if err != nil { - return err - } - - // If the regexp source can be parsed as an address, also match - // functions that land on that address. - var address *uint64 - if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil { - address = &hex - } - - // Extract interesting symbols from binary files in the profile and - // classify samples per symbol. - symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj) - symNodes := nodesPerSymbol(g.ns, symbols) - - // Sort symbols for printing. - var syms objSymbols - for s := range symNodes { - syms = append(syms, s) - } - sort.Sort(syms) - - if len(syms) == 0 { - return fmt.Errorf("no samples found on routines matching: %s", o.Symbol.String()) - } - - printHeader(w, rpt) - for _, s := range syms { - name := s.sym.Name[0] - // Identify sources associated to a symbol by examining - // symbol samples. Classify samples per source file. - var sourceFiles nodes - fileNodes := make(map[string]nodes) - for _, n := range symNodes[s] { - if n.info.file == "" { - continue - } - if fileNodes[n.info.file] == nil { - sourceFiles = append(sourceFiles, n) - } - fileNodes[n.info.file] = append(fileNodes[n.info.file], n) - } - - if len(sourceFiles) == 0 { - fmt.Printf("No source information for %s\n", name) - continue - } - - sourceFiles.sort(fileOrder) - - // Print each file associated with this function. - for _, fl := range sourceFiles { - filename := fl.info.file - fns := fileNodes[filename] - - asm := assemblyPerSourceLine(symbols, fns, filename, obj) - start, end := sourceCoordinates(asm) - - fnodes, path, err := getFunctionSource(name, filename, fns, start, end) - if err != nil { - fnodes, path = getMissingFunctionSource(filename, asm, start, end) - } - - flatSum, cumSum := sumNodes(fnodes) - printFunctionHeader(w, name, path, flatSum, cumSum, rpt) - for _, fn := range fnodes { - printFunctionSourceLine(w, fn, asm[fn.info.lineno], rpt) - } - printFunctionClosing(w) - } - } - printPageClosing(w) - return nil -} - -// sourceCoordinates returns the lowest and highest line numbers from -// a set of assembly statements. -func sourceCoordinates(asm map[int]nodes) (start, end int) { - for l := range asm { - if start == 0 || l < start { - start = l - } - if end == 0 || l > end { - end = l - } - } - return start, end -} - -// assemblyPerSourceLine disassembles the binary containing a symbol -// and classifies the assembly instructions according to its -// corresponding source line, annotating them with a set of samples. -func assemblyPerSourceLine(objSyms []*objSymbol, rs nodes, src string, obj plugin.ObjTool) map[int]nodes { - assembly := make(map[int]nodes) - // Identify symbol to use for this collection of samples. - o := findMatchingSymbol(objSyms, rs) - if o == nil { - return assembly - } - - // Extract assembly for matched symbol - insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End) - if err != nil { - return assembly - } - - srcBase := filepath.Base(src) - anodes := annotateAssembly(insns, rs, o.base) - var lineno = 0 - for _, an := range anodes { - if filepath.Base(an.info.file) == srcBase { - lineno = an.info.lineno - } - if lineno != 0 { - assembly[lineno] = append(assembly[lineno], an) - } - } - - return assembly -} - -// findMatchingSymbol looks for the symbol that corresponds to a set -// of samples, by comparing their addresses. -func findMatchingSymbol(objSyms []*objSymbol, ns nodes) *objSymbol { - for _, n := range ns { - for _, o := range objSyms { - if filepath.Base(o.sym.File) == n.info.objfile && - o.sym.Start <= n.info.address-o.base && - n.info.address-o.base <= o.sym.End { - return o - } - } - } - return nil -} - -// printHeader prints the page header for a weblist report. -func printHeader(w io.Writer, rpt *Report) { - fmt.Fprintln(w, weblistPageHeader) - - var labels []string - for _, l := range legendLabels(rpt) { - labels = append(labels, template.HTMLEscapeString(l)) - } - - fmt.Fprintf(w, `
%s
Total: %s
`, - strings.Join(labels, "
\n"), - rpt.formatValue(rpt.total), - ) -} - -// printFunctionHeader prints a function header for a weblist report. -func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) { - fmt.Fprintf(w, `

%s

%s -
-  Total:  %10s %10s (flat, cum) %s
-`,
-		template.HTMLEscapeString(name), template.HTMLEscapeString(path),
-		rpt.formatValue(flatSum), rpt.formatValue(cumSum),
-		percentage(cumSum, rpt.total))
-}
-
-// printFunctionSourceLine prints a source line and the corresponding assembly.
-func printFunctionSourceLine(w io.Writer, fn *node, assembly nodes, rpt *Report) {
-	if len(assembly) == 0 {
-		fmt.Fprintf(w,
-			" %6d   %10s %10s %s \n",
-			fn.info.lineno,
-			valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
-			template.HTMLEscapeString(fn.info.name))
-		return
-	}
-
-	fmt.Fprintf(w,
-		" %6d   %10s %10s %s ",
-		fn.info.lineno,
-		valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
-		template.HTMLEscapeString(fn.info.name))
-	fmt.Fprint(w, "")
-	for _, an := range assembly {
-		var fileline string
-		class := "disasmloc"
-		if an.info.file != "" {
-			fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.info.file), an.info.lineno)
-			if an.info.lineno != fn.info.lineno {
-				class = "unimportant"
-			}
-		}
-		fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s %s\n", "",
-			valueOrDot(an.flat, rpt), valueOrDot(an.cum, rpt),
-			an.info.address,
-			template.HTMLEscapeString(an.info.name),
-			class,
-			template.HTMLEscapeString(fileline))
-	}
-	fmt.Fprintln(w, "")
-}
-
-// printFunctionClosing prints the end of a function in a weblist report.
-func printFunctionClosing(w io.Writer) {
-	fmt.Fprintln(w, "
") -} - -// printPageClosing prints the end of the page in a weblist report. -func printPageClosing(w io.Writer) { - fmt.Fprintln(w, weblistPageClosing) -} - -// getFunctionSource collects the sources of a function from a source -// file and annotates it with the samples in fns. Returns the sources -// as nodes, using the info.name field to hold the source code. -func getFunctionSource(fun, file string, fns nodes, start, end int) (nodes, string, error) { - f, file, err := adjustSourcePath(file) - if err != nil { - return nil, file, err - } - - lineNodes := make(map[int]nodes) - - // Collect source coordinates from profile. - const margin = 5 // Lines before first/after last sample. - if start == 0 { - if fns[0].info.startLine != 0 { - start = fns[0].info.startLine - } else { - start = fns[0].info.lineno - margin - } - } else { - start -= margin - } - if end == 0 { - end = fns[0].info.lineno - } - end += margin - for _, n := range fns { - lineno := n.info.lineno - nodeStart := n.info.startLine - if nodeStart == 0 { - nodeStart = lineno - margin - } - nodeEnd := lineno + margin - if nodeStart < start { - start = nodeStart - } else if nodeEnd > end { - end = nodeEnd - } - lineNodes[lineno] = append(lineNodes[lineno], n) - } - - var src nodes - buf := bufio.NewReader(f) - lineno := 1 - for { - line, err := buf.ReadString('\n') - if err != nil { - if err != io.EOF { - return nil, file, err - } - if line == "" { - // end was at or past EOF; that's okay - break - } - } - if lineno >= start { - flat, cum := sumNodes(lineNodes[lineno]) - - src = append(src, &node{ - info: nodeInfo{ - name: strings.TrimRight(line, "\n"), - lineno: lineno, - }, - flat: flat, - cum: cum, - }) - } - lineno++ - if lineno > end { - break - } - } - return src, file, nil -} - -// getMissingFunctionSource creates a dummy function body to point to -// the source file and annotates it with the samples in asm. -func getMissingFunctionSource(filename string, asm map[int]nodes, start, end int) (nodes, string) { - var fnodes nodes - for i := start; i <= end; i++ { - lrs := asm[i] - if len(lrs) == 0 { - continue - } - flat, cum := sumNodes(lrs) - fnodes = append(fnodes, &node{ - info: nodeInfo{ - name: "???", - lineno: i, - }, - flat: flat, - cum: cum, - }) - } - return fnodes, filename -} - -// adjustSourcePath adjusts the path for a source file by trimming -// known prefixes and searching for the file on all parents of the -// current working dir. -func adjustSourcePath(path string) (*os.File, string, error) { - path = trimPath(path) - f, err := os.Open(path) - if err == nil { - return f, path, nil - } - - if dir, wderr := os.Getwd(); wderr == nil { - for { - parent := filepath.Dir(dir) - if parent == dir { - break - } - if f, err := os.Open(filepath.Join(parent, path)); err == nil { - return f, filepath.Join(parent, path), nil - } - - dir = parent - } - } - - return nil, path, err -} - -// trimPath cleans up a path by removing prefixes that are commonly -// found on profiles. -func trimPath(path string) string { - basePaths := []string{ - "/proc/self/cwd/./", - "/proc/self/cwd/", - } - - sPath := filepath.ToSlash(path) - - for _, base := range basePaths { - if strings.HasPrefix(sPath, base) { - return filepath.FromSlash(sPath[len(base):]) - } - } - return path -} diff --git a/src/cmd/pprof/internal/report/source_html.go b/src/cmd/pprof/internal/report/source_html.go deleted file mode 100644 index 267fabdc4b..0000000000 --- a/src/cmd/pprof/internal/report/source_html.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2014 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 report - -const weblistPageHeader = ` - - - -Pprof listing - - - - -` - -const weblistPageClosing = ` - - -` diff --git a/src/cmd/pprof/internal/svg/svg.go b/src/cmd/pprof/internal/svg/svg.go deleted file mode 100644 index 04f6ff1870..0000000000 --- a/src/cmd/pprof/internal/svg/svg.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2014 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 svg provides tools related to handling of SVG files -package svg - -import ( - "bytes" - "regexp" - "strings" -) - -var ( - viewBox = regexp.MustCompile(``) -) - -// Massage enhances the SVG output from DOT to provide better -// panning inside a web browser. It uses the SVGPan library, which is -// included directly. -func Massage(in bytes.Buffer) string { - svg := string(in.Bytes()) - - // Work around for dot bug which misses quoting some ampersands, - // resulting on unparsable SVG. - svg = strings.Replace(svg, "&;", "&;", -1) - - //Dot's SVG output is - // - // - // - // ... - // - // - // - // Change it to - // - // - // - // - // - // ... - // - // - // - - if loc := viewBox.FindStringIndex(svg); loc != nil { - svg = svg[:loc[0]] + - `` + svgPanJS + `` + - `` + - svg[loc[0]:] - } - - if loc := svgClose.FindStringIndex(svg); loc != nil { - svg = svg[:loc[0]] + - `` + - svg[loc[0]:] - } - - return svg -} diff --git a/src/cmd/pprof/internal/svg/svgpan.go b/src/cmd/pprof/internal/svg/svgpan.go deleted file mode 100644 index 4975b103e3..0000000000 --- a/src/cmd/pprof/internal/svg/svgpan.go +++ /dev/null @@ -1,291 +0,0 @@ -// SVG pan and zoom library. -// See copyright notice in string constant below. - -package svg - -// https://www.cyberz.org/projects/SVGPan/SVGPan.js - -const svgPanJS = ` -/** - * SVGPan library 1.2.1 - * ====================== - * - * Given an unique existing element with id "viewport" (or when missing, the first g - * element), including the the library into any SVG adds the following capabilities: - * - * - Mouse panning - * - Mouse zooming (using the wheel) - * - Object dragging - * - * You can configure the behaviour of the pan/zoom/drag with the variables - * listed in the CONFIGURATION section of this file. - * - * Known issues: - * - * - Zooming (while panning) on Safari has still some issues - * - * Releases: - * - * 1.2.1, Mon Jul 4 00:33:18 CEST 2011, Andrea Leofreddi - * - Fixed a regression with mouse wheel (now working on Firefox 5) - * - Working with viewBox attribute (#4) - * - Added "use strict;" and fixed resulting warnings (#5) - * - Added configuration variables, dragging is disabled by default (#3) - * - * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui - * Fixed a bug with browser mouse handler interaction - * - * 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui - * Updated the zoom code to support the mouse wheel on Safari/Chrome - * - * 1.0, Andrea Leofreddi - * First release - * - * This code is licensed under the following BSD license: - * - * Copyright 2009-2010 Andrea Leofreddi . All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ` + "``AS IS''" + ` AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of Andrea Leofreddi. - */ - -"use strict"; - -/// CONFIGURATION -/// ====> - -var enablePan = 1; // 1 or 0: enable or disable panning (default enabled) -var enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled) -var enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled) - -/// <==== -/// END OF CONFIGURATION - -var root = document.documentElement; - -var state = 'none', svgRoot, stateTarget, stateOrigin, stateTf; - -setupHandlers(root); - -/** - * Register handlers - */ -function setupHandlers(root){ - setAttributes(root, { - "onmouseup" : "handleMouseUp(evt)", - "onmousedown" : "handleMouseDown(evt)", - "onmousemove" : "handleMouseMove(evt)", - //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element - }); - - if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0) - window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari - else - window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others -} - -/** - * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global variable. - */ -function getRoot(root) { - if(typeof(svgRoot) == "undefined") { - var g = null; - - g = root.getElementById("viewport"); - - if(g == null) - g = root.getElementsByTagName('g')[0]; - - if(g == null) - alert('Unable to obtain SVG root element'); - - setCTM(g, g.getCTM()); - - g.removeAttribute("viewBox"); - - svgRoot = g; - } - - return svgRoot; -} - -/** - * Instance an SVGPoint object with given event coordinates. - */ -function getEventPoint(evt) { - var p = root.createSVGPoint(); - - p.x = evt.clientX; - p.y = evt.clientY; - - return p; -} - -/** - * Sets the current transform matrix of an element. - */ -function setCTM(element, matrix) { - var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")"; - - element.setAttribute("transform", s); -} - -/** - * Dumps a matrix to a string (useful for debug). - */ -function dumpMatrix(matrix) { - var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]"; - - return s; -} - -/** - * Sets attributes of an element. - */ -function setAttributes(element, attributes){ - for (var i in attributes) - element.setAttributeNS(null, i, attributes[i]); -} - -/** - * Handle mouse wheel event. - */ -function handleMouseWheel(evt) { - if(!enableZoom) - return; - - if(evt.preventDefault) - evt.preventDefault(); - - evt.returnValue = false; - - var svgDoc = evt.target.ownerDocument; - - var delta; - - if(evt.wheelDelta) - delta = evt.wheelDelta / 3600; // Chrome/Safari - else - delta = evt.detail / -90; // Mozilla - - var z = 1 + delta; // Zoom factor: 0.9/1.1 - - var g = getRoot(svgDoc); - - var p = getEventPoint(evt); - - p = p.matrixTransform(g.getCTM().inverse()); - - // Compute new scale matrix in current mouse position - var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y); - - setCTM(g, g.getCTM().multiply(k)); - - if(typeof(stateTf) == "undefined") - stateTf = g.getCTM().inverse(); - - stateTf = stateTf.multiply(k.inverse()); -} - -/** - * Handle mouse move event. - */ -function handleMouseMove(evt) { - if(evt.preventDefault) - evt.preventDefault(); - - evt.returnValue = false; - - var svgDoc = evt.target.ownerDocument; - - var g = getRoot(svgDoc); - - if(state == 'pan' && enablePan) { - // Pan mode - var p = getEventPoint(evt).matrixTransform(stateTf); - - setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y)); - } else if(state == 'drag' && enableDrag) { - // Drag mode - var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse()); - - setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM())); - - stateOrigin = p; - } -} - -/** - * Handle click event. - */ -function handleMouseDown(evt) { - if(evt.preventDefault) - evt.preventDefault(); - - evt.returnValue = false; - - var svgDoc = evt.target.ownerDocument; - - var g = getRoot(svgDoc); - - if( - evt.target.tagName == "svg" - || !enableDrag // Pan anyway when drag is disabled and the user clicked on an element - ) { - // Pan mode - state = 'pan'; - - stateTf = g.getCTM().inverse(); - - stateOrigin = getEventPoint(evt).matrixTransform(stateTf); - } else { - // Drag mode - state = 'drag'; - - stateTarget = evt.target; - - stateTf = g.getCTM().inverse(); - - stateOrigin = getEventPoint(evt).matrixTransform(stateTf); - } -} - -/** - * Handle mouse button release event. - */ -function handleMouseUp(evt) { - if(evt.preventDefault) - evt.preventDefault(); - - evt.returnValue = false; - - var svgDoc = evt.target.ownerDocument; - - if(state == 'pan' || state == 'drag') { - // Quit pan mode - state = ''; - } -} - -` diff --git a/src/cmd/pprof/internal/symbolizer/symbolizer.go b/src/cmd/pprof/internal/symbolizer/symbolizer.go deleted file mode 100644 index 86de5640d2..0000000000 --- a/src/cmd/pprof/internal/symbolizer/symbolizer.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2014 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 symbolizer provides a routine to populate a profile with -// symbol, file and line number information. It relies on the -// addr2liner and demangler packages to do the actual work. -package symbolizer - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "cmd/pprof/internal/plugin" - "cmd/pprof/internal/profile" -) - -// Symbolize adds symbol and line number information to all locations -// in a profile. mode enables some options to control -// symbolization. Currently only recognizes "force", which causes it -// to overwrite any existing data. -func Symbolize(mode string, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error { - force := false - // Disable some mechanisms based on mode string. - for _, o := range strings.Split(strings.ToLower(mode), ":") { - switch o { - case "force": - force = true - default: - } - } - - if len(prof.Mapping) == 0 { - return fmt.Errorf("no known mappings") - } - - mt, err := newMapping(prof, obj, ui, force) - if err != nil { - return err - } - defer mt.close() - - functions := make(map[profile.Function]*profile.Function) - for _, l := range mt.prof.Location { - m := l.Mapping - segment := mt.segments[m] - if segment == nil { - // Nothing to do - continue - } - - stack, err := segment.SourceLine(l.Address) - if err != nil || len(stack) == 0 { - // No answers from addr2line - continue - } - - l.Line = make([]profile.Line, len(stack)) - for i, frame := range stack { - if frame.Func != "" { - m.HasFunctions = true - } - if frame.File != "" { - m.HasFilenames = true - } - if frame.Line != 0 { - m.HasLineNumbers = true - } - f := &profile.Function{ - Name: frame.Func, - SystemName: frame.Func, - Filename: frame.File, - } - if fp := functions[*f]; fp != nil { - f = fp - } else { - functions[*f] = f - f.ID = uint64(len(mt.prof.Function)) + 1 - mt.prof.Function = append(mt.prof.Function, f) - } - l.Line[i] = profile.Line{ - Function: f, - Line: int64(frame.Line), - } - } - - if len(stack) > 0 { - m.HasInlineFrames = true - } - } - return nil -} - -// newMapping creates a mappingTable for a profile. -func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) { - mt := &mappingTable{ - prof: prof, - segments: make(map[*profile.Mapping]plugin.ObjFile), - } - - // Identify used mappings - mappings := make(map[*profile.Mapping]bool) - for _, l := range prof.Location { - mappings[l.Mapping] = true - } - - for _, m := range prof.Mapping { - if !mappings[m] { - continue - } - // Do not attempt to re-symbolize a mapping that has already been symbolized. - if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) { - continue - } - - f, err := locateFile(obj, m.File, m.BuildID, m.Start) - if err != nil { - ui.PrintErr("Local symbolization failed for ", filepath.Base(m.File), ": ", err) - // Move on to other mappings - continue - } - - if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID { - // Build ID mismatch - ignore. - f.Close() - continue - } - - mt.segments[m] = f - } - - return mt, nil -} - -// locateFile opens a local file for symbolization on the search path -// at $PPROF_BINARY_PATH. Looks inside these directories for files -// named $BUILDID/$BASENAME and $BASENAME (if build id is available). -func locateFile(obj plugin.ObjTool, file, buildID string, start uint64) (plugin.ObjFile, error) { - // Construct search path to examine - searchPath := os.Getenv("PPROF_BINARY_PATH") - if searchPath == "" { - // Use $HOME/pprof/binaries as default directory for local symbolization binaries - searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries") - } - - // Collect names to search: {buildid/basename, basename} - var fileNames []string - if baseName := filepath.Base(file); buildID != "" { - fileNames = []string{filepath.Join(buildID, baseName), baseName} - } else { - fileNames = []string{baseName} - } - for _, path := range filepath.SplitList(searchPath) { - for nameIndex, name := range fileNames { - file := filepath.Join(path, name) - if f, err := obj.Open(file, start); err == nil { - fileBuildID := f.BuildID() - if buildID == "" || buildID == fileBuildID { - return f, nil - } - f.Close() - if nameIndex == 0 { - // If this is the first name, the path includes the build id. Report inconsistency. - return nil, fmt.Errorf("found file %s with inconsistent build id %s", file, fileBuildID) - } - } - } - } - // Try original file name - f, err := obj.Open(file, start) - if err == nil && buildID != "" { - if fileBuildID := f.BuildID(); fileBuildID != "" && fileBuildID != buildID { - // Mismatched build IDs, ignore - f.Close() - return nil, fmt.Errorf("mismatched build ids %s != %s", fileBuildID, buildID) - } - } - return f, err -} - -// mappingTable contains the mechanisms for symbolization of a -// profile. -type mappingTable struct { - prof *profile.Profile - segments map[*profile.Mapping]plugin.ObjFile -} - -// Close releases any external processes being used for the mapping. -func (mt *mappingTable) close() { - for _, segment := range mt.segments { - segment.Close() - } -} diff --git a/src/cmd/pprof/internal/symbolz/symbolz.go b/src/cmd/pprof/internal/symbolz/symbolz.go deleted file mode 100644 index 15b3b6df26..0000000000 --- a/src/cmd/pprof/internal/symbolz/symbolz.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2014 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 symbolz symbolizes a profile using the output from the symbolz -// service. -package symbolz - -import ( - "bytes" - "fmt" - "io" - "net/url" - "regexp" - "strconv" - "strings" - - "cmd/pprof/internal/profile" -) - -var ( - symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`) -) - -// Symbolize symbolizes profile p by parsing data returned by a -// symbolz handler. syms receives the symbolz query (hex addresses -// separated by '+') and returns the symbolz output in a string. It -// symbolizes all locations based on their addresses, regardless of -// mapping. -func Symbolize(source string, syms func(string, string) ([]byte, error), p *profile.Profile) error { - if source = symbolz(source, p); source == "" { - // If the source is not a recognizable URL, do nothing. - return nil - } - - // Construct query of addresses to symbolize. - var a []string - for _, l := range p.Location { - if l.Address != 0 && len(l.Line) == 0 { - a = append(a, fmt.Sprintf("%#x", l.Address)) - } - } - - if len(a) == 0 { - // No addresses to symbolize. - return nil - } - lines := make(map[uint64]profile.Line) - functions := make(map[string]*profile.Function) - if b, err := syms(source, strings.Join(a, "+")); err == nil { - buf := bytes.NewBuffer(b) - for { - l, err := buf.ReadString('\n') - - if err != nil { - if err == io.EOF { - break - } - return err - } - - if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 { - addr, err := strconv.ParseUint(symbol[1], 0, 64) - if err != nil { - return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err) - } - - name := symbol[2] - fn := functions[name] - if fn == nil { - fn = &profile.Function{ - ID: uint64(len(p.Function) + 1), - Name: name, - SystemName: name, - } - functions[name] = fn - p.Function = append(p.Function, fn) - } - - lines[addr] = profile.Line{Function: fn} - } - } - } - - for _, l := range p.Location { - if line, ok := lines[l.Address]; ok { - l.Line = []profile.Line{line} - if l.Mapping != nil { - l.Mapping.HasFunctions = true - } - } - } - - return nil -} - -// symbolz returns the corresponding symbolz source for a profile URL. -func symbolz(source string, p *profile.Profile) string { - if url, err := url.Parse(source); err == nil && url.Host != "" { - if last := strings.LastIndex(url.Path, "/"); last != -1 { - if strings.HasSuffix(url.Path[:last], "pprof") { - url.Path = url.Path[:last] + "/symbol" - } else { - url.Path = url.Path[:last] + "/symbolz" - } - return url.String() - } - } - - return "" -} diff --git a/src/cmd/pprof/internal/tempfile/tempfile.go b/src/cmd/pprof/internal/tempfile/tempfile.go deleted file mode 100644 index 31c117690a..0000000000 --- a/src/cmd/pprof/internal/tempfile/tempfile.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2014 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 tempfile provides tools to create and delete temporary files -package tempfile - -import ( - "fmt" - "os" - "path/filepath" - "sync" -) - -// New returns an unused filename for output files. -func New(dir, prefix, suffix string) (*os.File, error) { - for index := 1; index < 10000; index++ { - path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix)) - if _, err := os.Stat(path); err != nil { - return os.Create(path) - } - } - // Give up - return nil, fmt.Errorf("could not create file of the form %s%03d%s", prefix, 1, suffix) -} - -var tempFiles []string -var tempFilesMu = sync.Mutex{} - -// DeferDelete marks a file to be deleted by next call to Cleanup() -func DeferDelete(path string) { - tempFilesMu.Lock() - tempFiles = append(tempFiles, path) - tempFilesMu.Unlock() -} - -// Cleanup removes any temporary files selected for deferred cleaning. -func Cleanup() { - tempFilesMu.Lock() - for _, f := range tempFiles { - os.Remove(f) - } - tempFiles = nil - tempFilesMu.Unlock() -} diff --git a/src/cmd/pprof/pprof.go b/src/cmd/pprof/pprof.go index 1c55d05d5d..bce37dcb97 100644 --- a/src/cmd/pprof/pprof.go +++ b/src/cmd/pprof/pprof.go @@ -15,13 +15,13 @@ import ( "sync" "cmd/internal/objfile" - "cmd/pprof/internal/commands" - "cmd/pprof/internal/driver" - "cmd/pprof/internal/fetch" - "cmd/pprof/internal/plugin" - "cmd/pprof/internal/profile" - "cmd/pprof/internal/symbolizer" - "cmd/pprof/internal/symbolz" + "cmd/internal/pprof/commands" + "cmd/internal/pprof/driver" + "cmd/internal/pprof/fetch" + "cmd/internal/pprof/plugin" + "cmd/internal/pprof/profile" + "cmd/internal/pprof/symbolizer" + "cmd/internal/pprof/symbolz" ) func main() { -- cgit v1.3 From cd85f711c0b6847cbfe4e05f4402df075ea936de Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 11 Apr 2016 21:23:11 -0700 Subject: cmd/compile: add x.Uses==1 test to load combiners We need to make sure that when we combine loads, we only do so if there are no other uses of the load. We can't split one load into two because that can then lead to inconsistent loaded values in the presence of races. Add some aggressive copy removal code so that phantom "dead copy" uses of values are cleaned up promptly. This lets us use x.Uses==1 conditions reliably. Change-Id: I9037311db85665f3868dbeb3adb3de5c20728b38 Reviewed-on: https://go-review.googlesource.com/21853 Reviewed-by: Todd Neal --- src/cmd/compile/internal/gc/ssa_test.go | 2 + src/cmd/compile/internal/gc/testdata/dupLoad.go | 46 +++++++++++++++++ .../compile/internal/gc/testdata/namedReturn.go | 4 ++ src/cmd/compile/internal/ssa/gen/AMD64.rules | 12 ++--- src/cmd/compile/internal/ssa/nilcheck_test.go | 2 +- src/cmd/compile/internal/ssa/rewrite.go | 60 +++++++++++++++++++++- src/cmd/compile/internal/ssa/rewriteAMD64.go | 24 ++++----- 7 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 src/cmd/compile/internal/gc/testdata/dupLoad.go (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/ssa_test.go b/src/cmd/compile/internal/gc/ssa_test.go index 0fb0f17778..46e1b0a7d3 100644 --- a/src/cmd/compile/internal/gc/ssa_test.go +++ b/src/cmd/compile/internal/gc/ssa_test.go @@ -101,3 +101,5 @@ func TestPhi(t *testing.T) { runTest(t, "phi_ssa.go") } func TestSlice(t *testing.T) { runTest(t, "slice.go") } func TestNamedReturn(t *testing.T) { runTest(t, "namedReturn.go") } + +func TestDuplicateLoad(t *testing.T) { runTest(t, "dupLoad.go") } diff --git a/src/cmd/compile/internal/gc/testdata/dupLoad.go b/src/cmd/compile/internal/gc/testdata/dupLoad.go new file mode 100644 index 0000000000..d12c26355a --- /dev/null +++ b/src/cmd/compile/internal/gc/testdata/dupLoad.go @@ -0,0 +1,46 @@ +// run + +// 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. + +// This test makes sure that we don't split a single +// load up into two separate loads. + +package main + +import "fmt" + +//go:noinline +func read(b []byte) (uint16, uint16) { + // There is only a single read of b[0]. The two + // returned values must have the same low byte. + v := b[0] + return uint16(v), uint16(v) | uint16(b[1])<<8 +} + +const N = 100000 + +func main() { + done := make(chan struct{}) + b := make([]byte, 2) + go func() { + for i := 0; i < N; i++ { + b[0] = byte(i) + b[1] = byte(i) + } + done <- struct{}{} + }() + go func() { + for i := 0; i < N; i++ { + x, y := read(b) + if byte(x) != byte(y) { + fmt.Printf("x=%x y=%x\n", x, y) + panic("bad") + } + } + done <- struct{}{} + }() + <-done + <-done +} diff --git a/src/cmd/compile/internal/gc/testdata/namedReturn.go b/src/cmd/compile/internal/gc/testdata/namedReturn.go index dafb5d719f..19ef8a7e43 100644 --- a/src/cmd/compile/internal/gc/testdata/namedReturn.go +++ b/src/cmd/compile/internal/gc/testdata/namedReturn.go @@ -1,5 +1,9 @@ // run +// 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. + // This test makes sure that naming named // return variables in a return statement works. // See issue #14904. diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index dcd5e6a5e1..21c74a9c1c 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -1369,13 +1369,13 @@ // There are many ways these combinations could occur. This is // designed to match the way encoding/binary.LittleEndian does it. (ORW x0:(MOVBload [i] {s} p mem) - (SHLWconst [8] x1:(MOVBload [i+1] {s} p mem))) && mergePoint(b,x0,x1) != nil -> @mergePoint(b,x0,x1) (MOVWload [i] {s} p mem) + (SHLWconst [8] x1:(MOVBload [i+1] {s} p mem))) && x0.Uses == 1 && x1.Uses == 1 && mergePoint(b,x0,x1) != nil -> @mergePoint(b,x0,x1) (MOVWload [i] {s} p mem) (ORL (ORL (ORL x0:(MOVBload [i] {s} p mem) (SHLLconst [8] x1:(MOVBload [i+1] {s} p mem))) (SHLLconst [16] x2:(MOVBload [i+2] {s} p mem))) - (SHLLconst [24] x3:(MOVBload [i+3] {s} p mem))) && mergePoint(b,x0,x1,x2,x3) != nil -> @mergePoint(b,x0,x1,x2,x3) (MOVLload [i] {s} p mem) + (SHLLconst [24] x3:(MOVBload [i+3] {s} p mem))) && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && mergePoint(b,x0,x1,x2,x3) != nil -> @mergePoint(b,x0,x1,x2,x3) (MOVLload [i] {s} p mem) (ORQ (ORQ (ORQ (ORQ (ORQ (ORQ (ORQ x0:(MOVBload [i] {s} p mem) @@ -1385,16 +1385,16 @@ (SHLQconst [32] x4:(MOVBload [i+4] {s} p mem))) (SHLQconst [40] x5:(MOVBload [i+5] {s} p mem))) (SHLQconst [48] x6:(MOVBload [i+6] {s} p mem))) - (SHLQconst [56] x7:(MOVBload [i+7] {s} p mem))) && mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) != nil -> @mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) (MOVQload [i] {s} p mem) + (SHLQconst [56] x7:(MOVBload [i+7] {s} p mem))) && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses == 1 && x7.Uses == 1 && mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) != nil -> @mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) (MOVQload [i] {s} p mem) (ORW x0:(MOVBloadidx1 [i] {s} p idx mem) - (SHLWconst [8] x1:(MOVBloadidx1 [i+1] {s} p idx mem))) && mergePoint(b,x0,x1) != nil -> @mergePoint(b,x0,x1) (MOVWloadidx1 [i] {s} p idx mem) + (SHLWconst [8] x1:(MOVBloadidx1 [i+1] {s} p idx mem))) && x0.Uses == 1 && x1.Uses == 1 && mergePoint(b,x0,x1) != nil -> @mergePoint(b,x0,x1) (MOVWloadidx1 [i] {s} p idx mem) (ORL (ORL (ORL x0:(MOVBloadidx1 [i] {s} p idx mem) (SHLLconst [8] x1:(MOVBloadidx1 [i+1] {s} p idx mem))) (SHLLconst [16] x2:(MOVBloadidx1 [i+2] {s} p idx mem))) - (SHLLconst [24] x3:(MOVBloadidx1 [i+3] {s} p idx mem))) && mergePoint(b,x0,x1,x2,x3) != nil -> @mergePoint(b,x0,x1,x2,x3) (MOVLloadidx1 [i] {s} p idx mem) + (SHLLconst [24] x3:(MOVBloadidx1 [i+3] {s} p idx mem))) && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && mergePoint(b,x0,x1,x2,x3) != nil -> @mergePoint(b,x0,x1,x2,x3) (MOVLloadidx1 [i] {s} p idx mem) (ORQ (ORQ (ORQ (ORQ (ORQ (ORQ (ORQ x0:(MOVBloadidx1 [i] {s} p idx mem) @@ -1404,4 +1404,4 @@ (SHLQconst [32] x4:(MOVBloadidx1 [i+4] {s} p idx mem))) (SHLQconst [40] x5:(MOVBloadidx1 [i+5] {s} p idx mem))) (SHLQconst [48] x6:(MOVBloadidx1 [i+6] {s} p idx mem))) - (SHLQconst [56] x7:(MOVBloadidx1 [i+7] {s} p idx mem))) && mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) != nil -> @mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) (MOVQloadidx1 [i] {s} p idx mem) + (SHLQconst [56] x7:(MOVBloadidx1 [i+7] {s} p idx mem))) && x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses == 1 && x7.Uses == 1 && mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) != nil -> @mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) (MOVQloadidx1 [i] {s} p idx mem) diff --git a/src/cmd/compile/internal/ssa/nilcheck_test.go b/src/cmd/compile/internal/ssa/nilcheck_test.go index c1c8f94767..af6cbe864a 100644 --- a/src/cmd/compile/internal/ssa/nilcheck_test.go +++ b/src/cmd/compile/internal/ssa/nilcheck_test.go @@ -418,7 +418,7 @@ func TestNilcheckBug(t *testing.T) { Goto("exit")), Bloc("exit", Valu("phi", OpPhi, TypeMem, 0, nil, "mem", "store"), - Exit("mem"))) + Exit("phi"))) CheckFunc(fun.f) // we need the opt here to rewrite the user nilcheck diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index e0cb7f517b..c2f8ceadaf 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -40,9 +40,44 @@ func applyRewrite(f *Func, rb func(*Block) bool, rv func(*Value, *Config) bool) } curb = nil for _, v := range b.Values { - change = copyelimValue(v) || change change = phielimValue(v) || change + // Eliminate copy inputs. + // If any copy input becomes unused, mark it + // as invalid and discard its argument. Repeat + // recursively on the discarded argument. + // This phase helps remove phantom "dead copy" uses + // of a value so that a x.Uses==1 rule condition + // fires reliably. + for i, a := range v.Args { + if a.Op != OpCopy { + continue + } + x := a.Args[0] + // Rewriting can generate OpCopy loops. + // They are harmless (see removePredecessor), + // but take care to stop if we find a cycle. + slow := x // advances every other iteration + var advance bool + for x.Op == OpCopy { + x = x.Args[0] + if slow == x { + break + } + if advance { + slow = slow.Args[0] + } + advance = !advance + } + v.SetArg(i, x) + change = true + for a.Uses == 0 { + b := a.Args[0] + a.reset(OpInvalid) + a = b + } + } + // apply rewrite function curv = v if rv(v, config) { @@ -52,7 +87,28 @@ func applyRewrite(f *Func, rb func(*Block) bool, rv func(*Value, *Config) bool) } } if !change { - return + break + } + } + // remove clobbered copies + for _, b := range f.Blocks { + j := 0 + for i, v := range b.Values { + if v.Op == OpInvalid { + f.freeValue(v) + continue + } + if i != j { + b.Values[j] = v + } + j++ + } + if j != len(b.Values) { + tail := b.Values[j:] + for j := range tail { + tail[j] = nil + } + b.Values = b.Values[:j] } } } diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index a6600513fa..d1793ad8c0 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -12666,7 +12666,7 @@ func rewriteValueAMD64_OpAMD64ORL(v *Value, config *Config) bool { return true } // match: (ORL (ORL (ORL x0:(MOVBload [i] {s} p mem) (SHLLconst [8] x1:(MOVBload [i+1] {s} p mem))) (SHLLconst [16] x2:(MOVBload [i+2] {s} p mem))) (SHLLconst [24] x3:(MOVBload [i+3] {s} p mem))) - // cond: mergePoint(b,x0,x1,x2,x3) != nil + // cond: x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && mergePoint(b,x0,x1,x2,x3) != nil // result: @mergePoint(b,x0,x1,x2,x3) (MOVLload [i] {s} p mem) for { v_0 := v.Args[0] @@ -12754,7 +12754,7 @@ func rewriteValueAMD64_OpAMD64ORL(v *Value, config *Config) bool { if mem != x3.Args[1] { break } - if !(mergePoint(b, x0, x1, x2, x3) != nil) { + if !(x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && mergePoint(b, x0, x1, x2, x3) != nil) { break } b = mergePoint(b, x0, x1, x2, x3) @@ -12768,7 +12768,7 @@ func rewriteValueAMD64_OpAMD64ORL(v *Value, config *Config) bool { return true } // match: (ORL (ORL (ORL x0:(MOVBloadidx1 [i] {s} p idx mem) (SHLLconst [8] x1:(MOVBloadidx1 [i+1] {s} p idx mem))) (SHLLconst [16] x2:(MOVBloadidx1 [i+2] {s} p idx mem))) (SHLLconst [24] x3:(MOVBloadidx1 [i+3] {s} p idx mem))) - // cond: mergePoint(b,x0,x1,x2,x3) != nil + // cond: x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && mergePoint(b,x0,x1,x2,x3) != nil // result: @mergePoint(b,x0,x1,x2,x3) (MOVLloadidx1 [i] {s} p idx mem) for { v_0 := v.Args[0] @@ -12866,7 +12866,7 @@ func rewriteValueAMD64_OpAMD64ORL(v *Value, config *Config) bool { if mem != x3.Args[2] { break } - if !(mergePoint(b, x0, x1, x2, x3) != nil) { + if !(x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && mergePoint(b, x0, x1, x2, x3) != nil) { break } b = mergePoint(b, x0, x1, x2, x3) @@ -12980,7 +12980,7 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value, config *Config) bool { return true } // match: (ORQ (ORQ (ORQ (ORQ (ORQ (ORQ (ORQ x0:(MOVBload [i] {s} p mem) (SHLQconst [8] x1:(MOVBload [i+1] {s} p mem))) (SHLQconst [16] x2:(MOVBload [i+2] {s} p mem))) (SHLQconst [24] x3:(MOVBload [i+3] {s} p mem))) (SHLQconst [32] x4:(MOVBload [i+4] {s} p mem))) (SHLQconst [40] x5:(MOVBload [i+5] {s} p mem))) (SHLQconst [48] x6:(MOVBload [i+6] {s} p mem))) (SHLQconst [56] x7:(MOVBload [i+7] {s} p mem))) - // cond: mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) != nil + // cond: x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses == 1 && x7.Uses == 1 && mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) != nil // result: @mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) (MOVQload [i] {s} p mem) for { v_0 := v.Args[0] @@ -13176,7 +13176,7 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value, config *Config) bool { if mem != x7.Args[1] { break } - if !(mergePoint(b, x0, x1, x2, x3, x4, x5, x6, x7) != nil) { + if !(x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses == 1 && x7.Uses == 1 && mergePoint(b, x0, x1, x2, x3, x4, x5, x6, x7) != nil) { break } b = mergePoint(b, x0, x1, x2, x3, x4, x5, x6, x7) @@ -13190,7 +13190,7 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value, config *Config) bool { return true } // match: (ORQ (ORQ (ORQ (ORQ (ORQ (ORQ (ORQ x0:(MOVBloadidx1 [i] {s} p idx mem) (SHLQconst [8] x1:(MOVBloadidx1 [i+1] {s} p idx mem))) (SHLQconst [16] x2:(MOVBloadidx1 [i+2] {s} p idx mem))) (SHLQconst [24] x3:(MOVBloadidx1 [i+3] {s} p idx mem))) (SHLQconst [32] x4:(MOVBloadidx1 [i+4] {s} p idx mem))) (SHLQconst [40] x5:(MOVBloadidx1 [i+5] {s} p idx mem))) (SHLQconst [48] x6:(MOVBloadidx1 [i+6] {s} p idx mem))) (SHLQconst [56] x7:(MOVBloadidx1 [i+7] {s} p idx mem))) - // cond: mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) != nil + // cond: x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses == 1 && x7.Uses == 1 && mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) != nil // result: @mergePoint(b,x0,x1,x2,x3,x4,x5,x6,x7) (MOVQloadidx1 [i] {s} p idx mem) for { v_0 := v.Args[0] @@ -13408,7 +13408,7 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value, config *Config) bool { if mem != x7.Args[2] { break } - if !(mergePoint(b, x0, x1, x2, x3, x4, x5, x6, x7) != nil) { + if !(x0.Uses == 1 && x1.Uses == 1 && x2.Uses == 1 && x3.Uses == 1 && x4.Uses == 1 && x5.Uses == 1 && x6.Uses == 1 && x7.Uses == 1 && mergePoint(b, x0, x1, x2, x3, x4, x5, x6, x7) != nil) { break } b = mergePoint(b, x0, x1, x2, x3, x4, x5, x6, x7) @@ -13514,7 +13514,7 @@ func rewriteValueAMD64_OpAMD64ORW(v *Value, config *Config) bool { return true } // match: (ORW x0:(MOVBload [i] {s} p mem) (SHLWconst [8] x1:(MOVBload [i+1] {s} p mem))) - // cond: mergePoint(b,x0,x1) != nil + // cond: x0.Uses == 1 && x1.Uses == 1 && mergePoint(b,x0,x1) != nil // result: @mergePoint(b,x0,x1) (MOVWload [i] {s} p mem) for { x0 := v.Args[0] @@ -13548,7 +13548,7 @@ func rewriteValueAMD64_OpAMD64ORW(v *Value, config *Config) bool { if mem != x1.Args[1] { break } - if !(mergePoint(b, x0, x1) != nil) { + if !(x0.Uses == 1 && x1.Uses == 1 && mergePoint(b, x0, x1) != nil) { break } b = mergePoint(b, x0, x1) @@ -13562,7 +13562,7 @@ func rewriteValueAMD64_OpAMD64ORW(v *Value, config *Config) bool { return true } // match: (ORW x0:(MOVBloadidx1 [i] {s} p idx mem) (SHLWconst [8] x1:(MOVBloadidx1 [i+1] {s} p idx mem))) - // cond: mergePoint(b,x0,x1) != nil + // cond: x0.Uses == 1 && x1.Uses == 1 && mergePoint(b,x0,x1) != nil // result: @mergePoint(b,x0,x1) (MOVWloadidx1 [i] {s} p idx mem) for { x0 := v.Args[0] @@ -13600,7 +13600,7 @@ func rewriteValueAMD64_OpAMD64ORW(v *Value, config *Config) bool { if mem != x1.Args[2] { break } - if !(mergePoint(b, x0, x1) != nil) { + if !(x0.Uses == 1 && x1.Uses == 1 && mergePoint(b, x0, x1) != nil) { break } b = mergePoint(b, x0, x1) -- cgit v1.3 From 7f5a063d157c777d8e78a567fc9538929bfd38f5 Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Tue, 12 Apr 2016 10:27:16 -0400 Subject: cmd/compile/internal/gc: minor Cgen_checknil cleanup Most architectures can only generate nil checks when the the address to check is in a register. Currently only amd64 and 386 can generate checks for addresses that reside in memory. This is unlikely to change so the architecture check has been inverted. Change-Id: I73697488a183406c79a9039c62823712b510bb6a Reviewed-on: https://go-review.googlesource.com/21861 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/gc/pgen.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index bfb65ade38..f6e9ab3b06 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -324,7 +324,12 @@ func Cgen_checknil(n *Node) { Fatalf("bad checknil") } - if (Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64) && n.Op != OREGISTER) || !n.Addable || n.Op == OLITERAL { + // Most architectures require that the address to be checked is + // in a register (it could be in memory). + needsReg := !Thearch.LinkArch.InFamily(sys.AMD64, sys.I386) + + // Move the address to be checked into a register if necessary. + if (needsReg && n.Op != OREGISTER) || !n.Addable || n.Op == OLITERAL { var reg Node Regalloc(®, Types[Tptr], n) Cgen(n, ®) -- cgit v1.3 From 811ebb6ac961162b815f4fd50976df81ba4c47b0 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 12 Apr 2016 09:22:26 -0700 Subject: cmd/compile: temporarily disable inplace append special case Fixes #15246 Re-opens #14969 Change-Id: Ic0b41c5aa42bbb229a0d62b7f3e5888c6b29293d Reviewed-on: https://go-review.googlesource.com/21891 Run-TryBot: Josh Bleecher Snyder TryBot-Result: Gobot Gobot Reviewed-by: Keith Randall --- src/cmd/compile/internal/gc/ssa.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index beb68b0385..fdd14953e6 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -699,7 +699,8 @@ func (s *state) stmt(n *Node) { // If the slice can be SSA'd, it'll be on the stack, // so there will be no write barriers, // so there's no need to attempt to prevent them. - if samesafeexpr(n.Left, rhs.List.First()) && !s.canSSA(n.Left) { + const doInPlaceAppend = false // issue 15246 + if doInPlaceAppend && samesafeexpr(n.Left, rhs.List.First()) && !s.canSSA(n.Left) { s.append(rhs, true) return } -- cgit v1.3 From 613ba6cda845fef442995d705027a622984c6b3a Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Tue, 12 Apr 2016 12:26:17 -0400 Subject: cmd/compile/internal/gc: add s390x support Allows instructions with a From3 field to be used in regopt so long as From3 represents a constant. This is needed because the storage-to-storage instructions on s390x place the length of the data into From3. Change-Id: I12cd32d4f997baf2fe97937bb7d45bbf716dfcb5 Reviewed-on: https://go-review.googlesource.com/20875 Reviewed-by: Matthew Dempsky Run-TryBot: Matthew Dempsky --- src/cmd/compile/internal/gc/cgen.go | 4 ++-- src/cmd/compile/internal/gc/gsubr.go | 8 +++++--- src/cmd/compile/internal/gc/pgen.go | 2 +- src/cmd/compile/internal/gc/reg.go | 2 +- src/cmd/compile/internal/gc/walk.go | 7 ++++++- 5 files changed, 15 insertions(+), 8 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/cgen.go b/src/cmd/compile/internal/gc/cgen.go index eacbc30f87..9de2a19f68 100644 --- a/src/cmd/compile/internal/gc/cgen.go +++ b/src/cmd/compile/internal/gc/cgen.go @@ -247,7 +247,7 @@ func cgen_wb(n, res *Node, wb bool) { return } - if Ctxt.Arch.InFamily(sys.AMD64, sys.I386) && n.Addable { + if Ctxt.Arch.InFamily(sys.AMD64, sys.I386, sys.S390X) && n.Addable { Thearch.Gmove(n, res) return } @@ -1829,7 +1829,7 @@ func bgenx(n, res *Node, wantTrue bool, likely int, to *obj.Prog) { // Some architectures might need a temporary or other help here, // but they don't support direct generation of a bool value yet. // We can fix that as we go. - mayNeedTemp := Ctxt.Arch.InFamily(sys.ARM, sys.ARM64, sys.MIPS64, sys.PPC64) + mayNeedTemp := Ctxt.Arch.InFamily(sys.ARM, sys.ARM64, sys.MIPS64, sys.PPC64, sys.S390X) if genval { if mayNeedTemp { diff --git a/src/cmd/compile/internal/gc/gsubr.go b/src/cmd/compile/internal/gc/gsubr.go index 63a8e969c3..f1316db8d8 100644 --- a/src/cmd/compile/internal/gc/gsubr.go +++ b/src/cmd/compile/internal/gc/gsubr.go @@ -58,7 +58,9 @@ func Ismem(n *Node) bool { return true case OADDR: - return Thearch.LinkArch.InFamily(sys.AMD64, sys.PPC64) // because 6g uses PC-relative addressing; TODO(rsc): not sure why 9g too + // amd64 and s390x use PC relative addressing. + // TODO(rsc): not sure why ppc64 needs this too. + return Thearch.LinkArch.InFamily(sys.AMD64, sys.PPC64, sys.S390X) } return false @@ -84,7 +86,7 @@ func Gbranch(as obj.As, t *Type, likely int) *obj.Prog { p := Prog(as) p.To.Type = obj.TYPE_BRANCH p.To.Val = nil - if as != obj.AJMP && likely != 0 && Thearch.LinkArch.Family != sys.PPC64 && Thearch.LinkArch.Family != sys.ARM64 && Thearch.LinkArch.Family != sys.MIPS64 { + if as != obj.AJMP && likely != 0 && !Thearch.LinkArch.InFamily(sys.PPC64, sys.ARM64, sys.MIPS64, sys.S390X) { p.From.Type = obj.TYPE_CONST if likely > 0 { p.From.Offset = 1 @@ -458,7 +460,7 @@ func Naddr(a *obj.Addr, n *Node) { case OADDR: Naddr(a, n.Left) a.Etype = uint8(Tptr) - if !Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64) { // TODO(rsc): Do this even for arm, ppc64. + if !Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64, sys.S390X) { // TODO(rsc): Do this even for these architectures. a.Width = int64(Widthptr) } if a.Type != obj.TYPE_MEM { diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index f6e9ab3b06..baa960bf75 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -287,7 +287,7 @@ func allocauto(ptxt *obj.Prog) { if haspointers(n.Type) { stkptrsize = Stksize } - if Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64) { + if Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64, sys.S390X) { Stksize = Rnd(Stksize, int64(Widthptr)) } if Stksize >= 1<<31 { diff --git a/src/cmd/compile/internal/gc/reg.go b/src/cmd/compile/internal/gc/reg.go index 8705d6dfa4..138ad683c5 100644 --- a/src/cmd/compile/internal/gc/reg.go +++ b/src/cmd/compile/internal/gc/reg.go @@ -1115,7 +1115,7 @@ func regopt(firstp *obj.Prog) { // Currently we never generate three register forms. // If we do, this will need to change. - if p.From3Type() != obj.TYPE_NONE { + if p.From3Type() != obj.TYPE_NONE && p.From3Type() != obj.TYPE_CONST { Fatalf("regopt not implemented for from3") } diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index 586a8e9c4f..3e5f5161db 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -673,7 +673,7 @@ opswitch: walkexprlist(n.List.Slice(), init) if n.Left.Op == ONAME && n.Left.Sym.Name == "Sqrt" && n.Left.Sym.Pkg.Path == "math" { - if Thearch.LinkArch.InFamily(sys.AMD64, sys.ARM, sys.ARM64, sys.PPC64) { + if Thearch.LinkArch.InFamily(sys.AMD64, sys.ARM, sys.ARM64, sys.PPC64, sys.S390X) { n.Op = OSQRT n.Left = n.List.First() n.List.Set(nil) @@ -3294,6 +3294,11 @@ func walkrotate(n *Node) *Node { // Constants adding to width? w := int(l.Type.Width * 8) + if Thearch.LinkArch.Family == sys.S390X && w != 32 && w != 64 { + // only supports 32-bit and 64-bit rotates + return n + } + if Smallintconst(l.Right) && Smallintconst(r.Right) { sl := int(l.Right.Int64()) if sl >= 0 { -- cgit v1.3 From be7c786dd04db51076012618ea29ee528a654978 Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Tue, 12 Apr 2016 13:38:24 -0400 Subject: cmd/objdump: skip TestDisasm* on s390x The disassembler is not yet implemented on s390x. Updates #15255. Change-Id: Ibab319c8c087b1a619baa1529398305c1e721877 Reviewed-on: https://go-review.googlesource.com/21894 Reviewed-by: Brad Fitzpatrick --- src/cmd/objdump/objdump_test.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/cmd') diff --git a/src/cmd/objdump/objdump_test.go b/src/cmd/objdump/objdump_test.go index 8ceaba078c..899db06324 100644 --- a/src/cmd/objdump/objdump_test.go +++ b/src/cmd/objdump/objdump_test.go @@ -107,6 +107,8 @@ func TestDisasm(t *testing.T) { t.Skipf("skipping on %s, issue 10106", runtime.GOARCH) case "mips64", "mips64le": t.Skipf("skipping on %s, issue 12559", runtime.GOARCH) + case "s390x": + t.Skipf("skipping on %s, issue 15255", runtime.GOARCH) } testDisasm(t) } @@ -123,6 +125,8 @@ func TestDisasmExtld(t *testing.T) { t.Skipf("skipping on %s, issue 10106", runtime.GOARCH) case "mips64", "mips64le": t.Skipf("skipping on %s, issue 12559 and 12560", runtime.GOARCH) + case "s390x": + t.Skipf("skipping on %s, issue 15255", runtime.GOARCH) } // TODO(jsing): Reenable once openbsd/arm has external linking support. if runtime.GOOS == "openbsd" && runtime.GOARCH == "arm" { -- cgit v1.3 From b1851a3c11a179d4eb55f9d0dd25ef81668a9f81 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 12 Apr 2016 11:31:16 -0700 Subject: cmd/compile: move compiler-specific flags into compiler-spec. export data section Also: Adjust go/importer accordingly. Change-Id: Ia6669563793e218946af45b9fba1cf986a21c031 Reviewed-on: https://go-review.googlesource.com/21896 Reviewed-by: Alan Donovan --- src/cmd/compile/internal/gc/bexport.go | 18 +++++++----------- src/cmd/compile/internal/gc/bimport.go | 12 ++++++------ src/go/internal/gcimporter/bimport.go | 5 +---- 3 files changed, 14 insertions(+), 21 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index 15e5e3ada6..cb438d7573 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -182,22 +182,12 @@ func export(out *bufio.Writer, trace bool) int { Fatalf("exporter: local package path not empty: %q", localpkg.Path) } p.pkg(localpkg) - - // write compiler-specific flags - // TODO(gri) move this into the compiler-specific export data section - { - var flags string - if safemode != 0 { - flags = "safe" - } - p.string(flags) - } if p.trace { p.tracef("\n") } // export objects - + // // First, export all exported (package-level) objects; i.e., all objects // in the current exportlist. These objects represent all information // required to import this package and type-check against it; i.e., this @@ -270,6 +260,12 @@ func export(out *bufio.Writer, trace bool) int { } } + // write compiler-specific flags + p.bool(safemode != 0) + if p.trace { + p.tracef("\n") + } + // Phase 2: Export objects added to exportlist during phase 1. // Don't use range since exportlist may grow during this phase // and we want to export all remaining objects. diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go index 7ad4d9dbb0..9cebafcaef 100644 --- a/src/cmd/compile/internal/gc/bimport.go +++ b/src/cmd/compile/internal/gc/bimport.go @@ -62,9 +62,6 @@ func Import(in *bufio.Reader) { Fatalf("importer: imported package not found in pkgList[0]") } - // read compiler-specific flags - importpkg.Safe = p.string() == "safe" - // defer some type-checking until all types are read in completely // (parser.go:import_package) tcok := typecheckok @@ -73,7 +70,7 @@ func Import(in *bufio.Reader) { // read objects - // Phase 1 + // phase 1 objcount := 0 for { tag := p.tagOrIndex() @@ -91,7 +88,10 @@ func Import(in *bufio.Reader) { // --- compiler-specific export data --- - // Phase 2 + // read compiler-specific flags + importpkg.Safe = p.bool() + + // phase 2 objcount = 0 for { tag := p.tagOrIndex() @@ -264,7 +264,7 @@ func (p *importer) obj(tag int) { } default: - Fatalf("importer: unexpected object tag") + Fatalf("importer: unexpected object (tag = %d)", tag) } } diff --git a/src/go/internal/gcimporter/bimport.go b/src/go/internal/gcimporter/bimport.go index aa9569de52..a9d678b021 100644 --- a/src/go/internal/gcimporter/bimport.go +++ b/src/go/internal/gcimporter/bimport.go @@ -78,9 +78,6 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i panic("imported packaged not found in pkgList[0]") } - // read compiler-specific flags - p.string() // discard - // read objects of phase 1 only (see cmd/compiler/internal/gc/bexport.go) objcount := 0 for { @@ -193,7 +190,7 @@ func (p *importer) obj(tag int) { p.declare(types.NewFunc(token.NoPos, pkg, name, sig)) default: - panic("unexpected object tag") + panic(fmt.Sprintf("unexpected object tag %d", tag)) } } -- cgit v1.3 From e07a4459a155789fb57bbf4e2c8eaca5b369fd17 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Tue, 12 Apr 2016 12:16:20 -0700 Subject: cmd: replace x[i:][0] expressions with x[i] Passes toolstash -cmp. Change-Id: Id504e71ed1f23900e24a9aed25143c94f4d7d50c Reviewed-on: https://go-review.googlesource.com/21899 Run-TryBot: Matthew Dempsky Reviewed-by: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/cmd/internal/obj/arm64/asm7.go | 2 +- src/cmd/internal/obj/mips/asm0.go | 2 +- src/cmd/internal/obj/s390x/asmz.go | 2 +- src/cmd/internal/obj/x86/asm6.go | 3 +-- src/cmd/link/internal/ld/decodesym.go | 4 ++-- src/cmd/link/internal/ld/ldelf.go | 2 +- 6 files changed, 7 insertions(+), 8 deletions(-) (limited to 'src/cmd') diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go index ff8d4fdf60..d0ae6115cb 100644 --- a/src/cmd/internal/obj/arm64/asm7.go +++ b/src/cmd/internal/obj/arm64/asm7.go @@ -1087,7 +1087,7 @@ func aclass(ctxt *obj.Link, a *obj.Addr) int { func oplook(ctxt *obj.Link, p *obj.Prog) *Optab { a1 := int(p.Optab) if a1 != 0 { - return &optab[a1-1:][0] + return &optab[a1-1] } a1 = int(p.From.Class) if a1 == 0 { diff --git a/src/cmd/internal/obj/mips/asm0.go b/src/cmd/internal/obj/mips/asm0.go index 5cb5d1cfd9..13e7600c21 100644 --- a/src/cmd/internal/obj/mips/asm0.go +++ b/src/cmd/internal/obj/mips/asm0.go @@ -611,7 +611,7 @@ func oplook(ctxt *obj.Link, p *obj.Prog) *Optab { a1 := int(p.Optab) if a1 != 0 { - return &optab[a1-1:][0] + return &optab[a1-1] } a1 = int(p.From.Class) if a1 == 0 { diff --git a/src/cmd/internal/obj/s390x/asmz.go b/src/cmd/internal/obj/s390x/asmz.go index cf3b11424b..bae4dc3ce7 100644 --- a/src/cmd/internal/obj/s390x/asmz.go +++ b/src/cmd/internal/obj/s390x/asmz.go @@ -606,7 +606,7 @@ func aclass(ctxt *obj.Link, a *obj.Addr) int { func oplook(ctxt *obj.Link, p *obj.Prog) *Optab { a1 := int(p.Optab) if a1 != 0 { - return &optab[a1-1:][0] + return &optab[a1-1] } a1 = int(p.From.Class) if a1 == 0 { diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go index c15b59b5e8..c563a7a48d 100644 --- a/src/cmd/internal/obj/x86/asm6.go +++ b/src/cmd/internal/obj/x86/asm6.go @@ -4452,9 +4452,8 @@ func asmins(ctxt *obj.Link, p *obj.Prog) { } n := ctxt.AsmBuf.Len() - var r *obj.Reloc for i := len(ctxt.Cursym.R) - 1; i >= 0; i-- { - r = &ctxt.Cursym.R[i:][0] + r := &ctxt.Cursym.R[i] if int64(r.Off) < p.Pc { break } diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go index 1066d220f7..7daa8bc812 100644 --- a/src/cmd/link/internal/ld/decodesym.go +++ b/src/cmd/link/internal/ld/decodesym.go @@ -17,9 +17,9 @@ import ( // ../gc/reflect.c stuffs in these. func decode_reloc(s *LSym, off int32) *Reloc { - for i := 0; i < len(s.R); i++ { + for i := range s.R { if s.R[i].Off == off { - return &s.R[i:][0] + return &s.R[i] } } return nil diff --git a/src/cmd/link/internal/ld/ldelf.go b/src/cmd/link/internal/ld/ldelf.go index d9581a5189..d07a2a2c34 100644 --- a/src/cmd/link/internal/ld/ldelf.go +++ b/src/cmd/link/internal/ld/ldelf.go @@ -774,7 +774,7 @@ func ldelf(f *bio.Reader, pkg string, length int64, pn string) { if sym.sym == nil { continue } - sect = &elfobj.sect[sym.shndx:][0] + sect = &elfobj.sect[sym.shndx] if sect.sym == nil { if strings.HasPrefix(sym.name, ".Linfo_string") { // clang does this continue -- cgit v1.3 From da224a5c42e7fce7f1d190a86962b1c46be454ef Mon Sep 17 00:00:00 2001 From: Alberto Donizetti Date: Sun, 10 Apr 2016 20:14:27 +0200 Subject: cmd/pprof: pass the event to pprof_toggle_asm for the weblist command Fixes #15225 Change-Id: I1f85590b2c3293463c6476beebcd3256adc1bf23 Reviewed-on: https://go-review.googlesource.com/21802 Reviewed-by: Brad Fitzpatrick --- src/cmd/internal/pprof/report/source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/cmd') diff --git a/src/cmd/internal/pprof/report/source.go b/src/cmd/internal/pprof/report/source.go index 7beea39562..608e4d561d 100644 --- a/src/cmd/internal/pprof/report/source.go +++ b/src/cmd/internal/pprof/report/source.go @@ -257,7 +257,7 @@ func printHeader(w io.Writer, rpt *Report) { // printFunctionHeader prints a function header for a weblist report. func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) { fmt.Fprintf(w, `

%s

%s -
+
   Total:  %10s %10s (flat, cum) %s
 `,
 		template.HTMLEscapeString(name), template.HTMLEscapeString(path),
-- 
cgit v1.3


From f028b9f9e2433662502283850d06e9e07e72a6bb Mon Sep 17 00:00:00 2001
From: David Crawshaw 
Date: Sun, 27 Mar 2016 10:21:48 -0400
Subject: cmd/link, etc: store typelinks as offsets

This is the first in a series of CLs to replace the use of pointers
in binary read-only data with offsets.

In standard Go binaries these CLs have a small effect, shrinking
8-byte pointers to 4-bytes. In position-independent code, it also
saves the dynamic relocation for the pointer. This has a significant
effect on the binary size when building as PIE, c-archive, or
c-shared.

darwin/amd64:
	cmd/go: -12KB (0.1%)
	jujud:  -82KB (0.1%)

linux/amd64 PIE:
	cmd/go:  -86KB (0.7%)
	jujud:  -569KB (0.7%)

For #6853.

Change-Id: Iad5625bbeba58dabfd4d334dbee3fcbfe04b2dcf
Reviewed-on: https://go-review.googlesource.com/21284
Reviewed-by: Ian Lance Taylor 
Run-TryBot: David Crawshaw 
TryBot-Result: Gobot Gobot 
---
 src/cmd/compile/internal/gc/go.go      |  2 --
 src/cmd/compile/internal/gc/main.go    |  4 ---
 src/cmd/compile/internal/gc/obj.go     |  6 +++++
 src/cmd/compile/internal/gc/reflect.go | 17 +++++-------
 src/cmd/internal/obj/data.go           | 21 ++++++++++++++-
 src/cmd/internal/obj/link.go           |  3 +++
 src/cmd/link/internal/ld/data.go       | 18 +++++++++++++
 src/cmd/link/internal/ld/symtab.go     |  4 +++
 src/reflect/export_test.go             |  8 +++---
 src/reflect/type.go                    | 47 ++++++++++++++++++++++------------
 src/runtime/runtime1.go                |  8 +++---
 src/runtime/symtab.go                  |  3 ++-
 12 files changed, 99 insertions(+), 42 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go
index d9b28ff8e6..5df49b56d6 100644
--- a/src/cmd/compile/internal/gc/go.go
+++ b/src/cmd/compile/internal/gc/go.go
@@ -171,8 +171,6 @@ var msanpkg *Pkg // package runtime/msan
 
 var typepkg *Pkg // fake package for runtime type info (headers)
 
-var typelinkpkg *Pkg // fake package for runtime type info (data)
-
 var unsafepkg *Pkg // package unsafe
 
 var trackpkg *Pkg // fake package for field tracking
diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index 26acf8861f..45a510d577 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -126,10 +126,6 @@ func Main() {
 	itabpkg.Name = "go.itab"
 	itabpkg.Prefix = "go.itab" // not go%2eitab
 
-	typelinkpkg = mkpkg("go.typelink")
-	typelinkpkg.Name = "go.typelink"
-	typelinkpkg.Prefix = "go.typelink" // not go%2etypelink
-
 	itablinkpkg = mkpkg("go.itablink")
 	itablinkpkg.Name = "go.itablink"
 	itablinkpkg.Prefix = "go.itablink" // not go%2eitablink
diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go
index 23c8be645c..eed0ed6e24 100644
--- a/src/cmd/compile/internal/gc/obj.go
+++ b/src/cmd/compile/internal/gc/obj.go
@@ -321,6 +321,12 @@ func dsymptrLSym(s *obj.LSym, off int, x *obj.LSym, xoff int) int {
 	return off
 }
 
+func dsymptrOffLSym(s *obj.LSym, off int, x *obj.LSym, xoff int) int {
+	s.WriteOff(Ctxt, int64(off), x, int64(xoff))
+	off += 4
+	return off
+}
+
 func gdata(nam *Node, nr *Node, wid int) {
 	if nam.Op != ONAME {
 		Fatalf("gdata nam op %v", opnames[nam.Op])
diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go
index df9ef27b7a..ea67634260 100644
--- a/src/cmd/compile/internal/gc/reflect.go
+++ b/src/cmd/compile/internal/gc/reflect.go
@@ -879,7 +879,7 @@ func tracksym(t *Type, f *Field) *Sym {
 	return Pkglookup(Tconv(t, FmtLeft)+"."+f.Sym.Name, trackpkg)
 }
 
-func typelinksym(t *Type) *Sym {
+func typelinkLSym(t *Type) *obj.LSym {
 	// %-uT is what the generated Type's string field says.
 	// It uses (ambiguous) package names instead of import paths.
 	// %-T is the complete, unambiguous type name.
@@ -889,13 +889,8 @@ func typelinksym(t *Type) *Sym {
 	// ensure the types appear sorted by their string field. The
 	// names are a little long but they are discarded by the linker
 	// and do not end up in the symbol table of the final binary.
-	p := Tconv(t, FmtLeft|FmtUnsigned) + "\t" + Tconv(t, FmtLeft)
-
-	s := Pkglookup(p, typelinkpkg)
-
-	//print("typelinksym: %s -> %+S\n", p, s);
-
-	return s
+	name := "go.typelink." + Tconv(t, FmtLeft|FmtUnsigned) + "\t" + Tconv(t, FmtLeft)
+	return obj.Linklookup(Ctxt, name, 0)
 }
 
 func typesymprefix(prefix string, t *Type) *Sym {
@@ -1298,9 +1293,9 @@ ok:
 	if t.Sym == nil {
 		switch t.Etype {
 		case TPTR32, TPTR64, TARRAY, TCHAN, TFUNC, TMAP, TSTRUCT:
-			slink := typelinksym(t)
-			dsymptr(slink, 0, s, 0)
-			ggloblsym(slink, int32(Widthptr), int16(dupok|obj.RODATA))
+			slink := typelinkLSym(t)
+			dsymptrOffLSym(slink, 0, Linksym(s), 0)
+			ggloblLSym(slink, 4, int16(dupok|obj.RODATA))
 		}
 	}
 
diff --git a/src/cmd/internal/obj/data.go b/src/cmd/internal/obj/data.go
index 37ab70bb0e..546ff37269 100644
--- a/src/cmd/internal/obj/data.go
+++ b/src/cmd/internal/obj/data.go
@@ -111,17 +111,36 @@ func (s *LSym) WriteInt(ctxt *Link, off int64, siz int, i int64) {
 // rsym and roff specify the relocation for the address.
 func (s *LSym) WriteAddr(ctxt *Link, off int64, siz int, rsym *LSym, roff int64) {
 	if siz != ctxt.Arch.PtrSize {
-		ctxt.Diag("WriteAddr: bad address size: %d", siz)
+		ctxt.Diag("WriteAddr: bad address size %d in %s", siz, s.Name)
 	}
 	s.prepwrite(ctxt, off, siz)
 	r := Addrel(s)
 	r.Off = int32(off)
+	if int64(r.Off) != off {
+		ctxt.Diag("WriteAddr: off overflow %d in %s", off, s.Name)
+	}
 	r.Siz = uint8(siz)
 	r.Sym = rsym
 	r.Type = R_ADDR
 	r.Add = roff
 }
 
+// WriteOff writes a 4 byte offset to rsym+roff into s at offset off.
+// After linking the 4 bytes stored at s+off will be
+// rsym+roff-(start of section that s is in).
+func (s *LSym) WriteOff(ctxt *Link, off int64, rsym *LSym, roff int64) {
+	s.prepwrite(ctxt, off, 4)
+	r := Addrel(s)
+	r.Off = int32(off)
+	if int64(r.Off) != off {
+		ctxt.Diag("WriteOff: off overflow %d in %s", off, s.Name)
+	}
+	r.Siz = 4
+	r.Sym = rsym
+	r.Type = R_ADDROFF
+	r.Add = roff
+}
+
 // WriteString writes a string of size siz into s at offset off.
 func (s *LSym) WriteString(ctxt *Link, off int64, siz int, str string) {
 	if siz < len(str) {
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 62175f9ed8..d44d4398b1 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -457,6 +457,9 @@ const (
 	// R_ADDRMIPS (only used on mips64) resolves to a 32-bit external address,
 	// by loading the address into a register with two instructions (lui, ori).
 	R_ADDRMIPS
+	// R_ADDROFF resolves to an offset from the beginning of the section holding
+	// the data being relocated to the referenced symbol.
+	R_ADDROFF
 	R_SIZE
 	R_CALL
 	R_CALLARM
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index ae7c287f59..cf51b0a908 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -525,6 +525,9 @@ func relocsym(s *LSym) {
 			}
 			o = Symaddr(r.Sym) + r.Add - int64(r.Sym.Sect.Vaddr)
 
+		case obj.R_ADDROFF:
+			o = Symaddr(r.Sym) - int64(r.Sym.Sect.Vaddr) + r.Add
+
 			// r->sym can be null when CALL $(constant) is transformed from absolute PC to relative PC call.
 		case obj.R_CALL, obj.R_GOTPCREL, obj.R_PCREL:
 			if Linkmode == LinkExternal && r.Sym != nil && r.Sym.Type != obj.SCONST && (r.Sym.Sect != Ctxt.Cursym.Sect || r.Type == obj.R_GOTPCREL) {
@@ -1599,6 +1602,10 @@ func dodata() {
 	sect.Vaddr = 0
 	Linklookup(Ctxt, "runtime.rodata", 0).Sect = sect
 	Linklookup(Ctxt, "runtime.erodata", 0).Sect = sect
+	if !UseRelro() {
+		Linklookup(Ctxt, "runtime.types", 0).Sect = sect
+		Linklookup(Ctxt, "runtime.etypes", 0).Sect = sect
+	}
 	for ; s != nil && s.Type < obj.STYPERELRO; s = s.Next {
 		datsize = aligndatsize(datsize, s)
 		s.Sect = sect
@@ -1631,6 +1638,8 @@ func dodata() {
 		sect.Align = maxalign(s, obj.STYPELINK-1)
 		datsize = Rnd(datsize, int64(sect.Align))
 		sect.Vaddr = 0
+		Linklookup(Ctxt, "runtime.types", 0).Sect = sect
+		Linklookup(Ctxt, "runtime.etypes", 0).Sect = sect
 		for ; s != nil && s.Type < obj.STYPELINK; s = s.Next {
 			datsize = aligndatsize(datsize, s)
 			if s.Outer != nil && s.Outer.Sect != nil && s.Outer.Sect != sect {
@@ -1970,10 +1979,12 @@ func address() {
 	} else {
 		rodata = text.Next
 	}
+	var relrodata *Section
 	typelink := rodata.Next
 	if UseRelro() {
 		// There is another section (.data.rel.ro) when building a shared
 		// object on elf systems.
+		relrodata = typelink
 		typelink = typelink.Next
 	}
 	itablink := typelink.Next
@@ -2007,6 +2018,11 @@ func address() {
 		s.Value = int64(sectSym.Sect.Vaddr + 16)
 	}
 
+	types := relrodata
+	if types == nil {
+		types = rodata
+	}
+
 	xdefine("runtime.text", obj.STEXT, int64(text.Vaddr))
 	xdefine("runtime.etext", obj.STEXT, int64(text.Vaddr+text.Length))
 	if HEADTYPE == obj.Hwindows {
@@ -2014,6 +2030,8 @@ func address() {
 	}
 	xdefine("runtime.rodata", obj.SRODATA, int64(rodata.Vaddr))
 	xdefine("runtime.erodata", obj.SRODATA, int64(rodata.Vaddr+rodata.Length))
+	xdefine("runtime.types", obj.SRODATA, int64(types.Vaddr))
+	xdefine("runtime.etypes", obj.SRODATA, int64(types.Vaddr+types.Length))
 	xdefine("runtime.typelink", obj.SRODATA, int64(typelink.Vaddr))
 	xdefine("runtime.etypelink", obj.SRODATA, int64(typelink.Vaddr+typelink.Length))
 	xdefine("runtime.itablink", obj.SRODATA, int64(itablink.Vaddr))
diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go
index ae0b17c259..678ed38730 100644
--- a/src/cmd/link/internal/ld/symtab.go
+++ b/src/cmd/link/internal/ld/symtab.go
@@ -329,6 +329,8 @@ func symtab() {
 	xdefine("runtime.eitablink", obj.SRODATA, 0)
 	xdefine("runtime.rodata", obj.SRODATA, 0)
 	xdefine("runtime.erodata", obj.SRODATA, 0)
+	xdefine("runtime.types", obj.SRODATA, 0)
+	xdefine("runtime.etypes", obj.SRODATA, 0)
 	xdefine("runtime.noptrdata", obj.SNOPTRDATA, 0)
 	xdefine("runtime.enoptrdata", obj.SNOPTRDATA, 0)
 	xdefine("runtime.data", obj.SDATA, 0)
@@ -537,6 +539,8 @@ func symtab() {
 	Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.end", 0))
 	Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.gcdata", 0))
 	Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.gcbss", 0))
+	Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.types", 0))
+	Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.etypes", 0))
 	// The typelinks slice
 	Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.typelink", 0))
 	adduint(Ctxt, moduledata, uint64(ntypelinks))
diff --git a/src/reflect/export_test.go b/src/reflect/export_test.go
index ddc64b46be..037c953718 100644
--- a/src/reflect/export_test.go
+++ b/src/reflect/export_test.go
@@ -46,9 +46,11 @@ func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr,
 
 func TypeLinks() []string {
 	var r []string
-	for _, m := range typelinks() {
-		for _, t := range m {
-			r = append(r, t.string)
+	sections, offset := typelinks()
+	for i, offs := range offset {
+		rodata := sections[i]
+		for _, off := range offs {
+			r = append(r, rtypeOff(rodata, off).string)
 		}
 	}
 	return r
diff --git a/src/reflect/type.go b/src/reflect/type.go
index 8f13acf26e..7104fde60a 100644
--- a/src/reflect/type.go
+++ b/src/reflect/type.go
@@ -1558,30 +1558,48 @@ func haveIdenticalUnderlyingType(T, V *rtype) bool {
 }
 
 // typelinks is implemented in package runtime.
-// It returns a slice of all the 'typelink' information in the binary,
-// which is to say a slice of known types, sorted by string.
+// It returns a slice of the sections in each module,
+// and a slice of *rtype offsets in each module.
+//
+// The types in each module are sorted by string. That is, the first
+// two linked types of the first module are:
+//
+//	d0 := sections[0]
+//	t1 := (*rtype)(add(d0, offset[0][0]))
+//	t2 := (*rtype)(add(d0, offset[0][1]))
+//
+// and
+//
+//	t1.string < t2.string
+//
 // Note that strings are not unique identifiers for types:
 // there can be more than one with a given string.
 // Only types we might want to look up are included:
 // pointers, channels, maps, slices, and arrays.
-func typelinks() [][]*rtype
+func typelinks() (sections []unsafe.Pointer, offset [][]int32)
+
+func rtypeOff(section unsafe.Pointer, off int32) *rtype {
+	return (*rtype)(add(section, uintptr(off)))
+}
 
 // typesByString returns the subslice of typelinks() whose elements have
 // the given string representation.
 // It may be empty (no known types with that string) or may have
 // multiple elements (multiple types with that string).
 func typesByString(s string) []*rtype {
-	typs := typelinks()
+	sections, offset := typelinks()
 	var ret []*rtype
 
-	for _, typ := range typs {
+	for offsI, offs := range offset {
+		section := sections[offsI]
+
 		// We are looking for the first index i where the string becomes >= s.
 		// This is a copy of sort.Search, with f(h) replaced by (*typ[h].string >= s).
-		i, j := 0, len(typ)
+		i, j := 0, len(offs)
 		for i < j {
 			h := i + (j-i)/2 // avoid overflow when computing h
 			// i ≤ h < j
-			if !(typ[h].string >= s) {
+			if !(rtypeOff(section, offs[h]).string >= s) {
 				i = h + 1 // preserves f(i-1) == false
 			} else {
 				j = h // preserves f(j) == true
@@ -1592,17 +1610,12 @@ func typesByString(s string) []*rtype {
 		// Having found the first, linear scan forward to find the last.
 		// We could do a second binary search, but the caller is going
 		// to do a linear scan anyway.
-		j = i
-		for j < len(typ) && typ[j].string == s {
-			j++
-		}
-
-		if j > i {
-			if ret == nil {
-				ret = typ[i:j:j]
-			} else {
-				ret = append(ret, typ[i:j]...)
+		for j := i; j < len(offs); j++ {
+			typ := rtypeOff(section, offs[j])
+			if typ.string != s {
+				break
 			}
+			ret = append(ret, typ)
 		}
 	}
 	return ret
diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go
index 95bebac593..e1956569fd 100644
--- a/src/runtime/runtime1.go
+++ b/src/runtime/runtime1.go
@@ -477,10 +477,12 @@ func gomcache() *mcache {
 }
 
 //go:linkname reflect_typelinks reflect.typelinks
-func reflect_typelinks() [][]*_type {
-	ret := [][]*_type{firstmoduledata.typelinks}
+func reflect_typelinks() ([]unsafe.Pointer, [][]int32) {
+	sections := []unsafe.Pointer{unsafe.Pointer(firstmoduledata.types)}
+	ret := [][]int32{firstmoduledata.typelinks}
 	for datap := firstmoduledata.next; datap != nil; datap = datap.next {
+		sections = append(sections, unsafe.Pointer(datap.types))
 		ret = append(ret, datap.typelinks)
 	}
-	return ret
+	return sections, ret
 }
diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go
index 158bdcea0d..8c70f22c1f 100644
--- a/src/runtime/symtab.go
+++ b/src/runtime/symtab.go
@@ -127,8 +127,9 @@ type moduledata struct {
 	bss, ebss             uintptr
 	noptrbss, enoptrbss   uintptr
 	end, gcdata, gcbss    uintptr
+	types, etypes         uintptr
 
-	typelinks []*_type
+	typelinks []int32 // offsets from types
 	itablinks []*itab
 
 	modulename   string
-- 
cgit v1.3


From 24fc3234428e138e693584185fab4146de6088db Mon Sep 17 00:00:00 2001
From: Shahar Kohanim 
Date: Mon, 11 Apr 2016 17:35:55 +0300
Subject: cmd/internal/obj: split plist flushing from object writing

Only splits into separate files, no other changes.

Change-Id: Icc0da2c5f18e03e9ed7c0043bd7c790f741900f2
Reviewed-on: https://go-review.googlesource.com/21804
Reviewed-by: Matthew Dempsky 
Run-TryBot: Matthew Dempsky 
TryBot-Result: Gobot Gobot 
---
 src/cmd/internal/obj/link.go    |  21 ----
 src/cmd/internal/obj/objfile.go | 187 ----------------------------------
 src/cmd/internal/obj/plist.go   | 218 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 218 insertions(+), 208 deletions(-)
 create mode 100644 src/cmd/internal/obj/plist.go

(limited to 'src/cmd')

diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index d44d4398b1..146be6f98f 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -730,27 +730,6 @@ const (
 	Hwindows
 )
 
-type Plist struct {
-	Name    *LSym
-	Firstpc *Prog
-	Recur   int
-	Link    *Plist
-}
-
-/*
- * start a new Prog list.
- */
-func Linknewplist(ctxt *Link) *Plist {
-	pl := new(Plist)
-	if ctxt.Plist == nil {
-		ctxt.Plist = pl
-	} else {
-		ctxt.Plast.Link = pl
-	}
-	ctxt.Plast = pl
-	return pl
-}
-
 // AsmBuf is a simple buffer to assemble variable-length x86 instructions into.
 type AsmBuf struct {
 	buf [100]byte
diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go
index ee21f39d10..7d88db2bcc 100644
--- a/src/cmd/internal/obj/objfile.go
+++ b/src/cmd/internal/obj/objfile.go
@@ -115,7 +115,6 @@ import (
 	"log"
 	"path/filepath"
 	"sort"
-	"strings"
 )
 
 // The Go and C compilers, and the assembler, call writeobj to write
@@ -126,192 +125,6 @@ func Writeobjdirect(ctxt *Link, b *bio.Writer) {
 	WriteObjFile(ctxt, b)
 }
 
-func Flushplist(ctxt *Link) {
-	flushplist(ctxt, ctxt.Debugasm == 0)
-}
-func FlushplistNoFree(ctxt *Link) {
-	flushplist(ctxt, false)
-}
-func flushplist(ctxt *Link, freeProgs bool) {
-	// Build list of symbols, and assign instructions to lists.
-	// Ignore ctxt->plist boundaries. There are no guarantees there,
-	// and the assemblers just use one big list.
-	var curtext *LSym
-	var etext *Prog
-	var text []*LSym
-
-	for pl := ctxt.Plist; pl != nil; pl = pl.Link {
-		var plink *Prog
-		for p := pl.Firstpc; p != nil; p = plink {
-			if ctxt.Debugasm != 0 && ctxt.Debugvlog != 0 {
-				fmt.Printf("obj: %v\n", p)
-			}
-			plink = p.Link
-			p.Link = nil
-
-			switch p.As {
-			case AEND:
-				continue
-
-			case ATYPE:
-				// Assume each TYPE instruction describes
-				// a different local variable or parameter,
-				// so no dedup.
-				// Using only the TYPE instructions means
-				// that we discard location information about local variables
-				// in C and assembly functions; that information is inferred
-				// from ordinary references, because there are no TYPE
-				// instructions there. Without the type information, gdb can't
-				// use the locations, so we don't bother to save them.
-				// If something else could use them, we could arrange to
-				// preserve them.
-				if curtext == nil {
-					continue
-				}
-				a := new(Auto)
-				a.Asym = p.From.Sym
-				a.Aoffset = int32(p.From.Offset)
-				a.Name = int16(p.From.Name)
-				a.Gotype = p.From.Gotype
-				a.Link = curtext.Autom
-				curtext.Autom = a
-				continue
-
-			case AGLOBL:
-				s := p.From.Sym
-				if s.Seenglobl {
-					fmt.Printf("duplicate %v\n", p)
-				}
-				s.Seenglobl = true
-				if s.Onlist {
-					log.Fatalf("symbol %s listed multiple times", s.Name)
-				}
-				s.Onlist = true
-				ctxt.Data = append(ctxt.Data, s)
-				s.Size = p.To.Offset
-				if s.Type == 0 || s.Type == SXREF {
-					s.Type = SBSS
-				}
-				flag := int(p.From3.Offset)
-				if flag&DUPOK != 0 {
-					s.Dupok = true
-				}
-				if flag&RODATA != 0 {
-					s.Type = SRODATA
-				} else if flag&NOPTR != 0 {
-					s.Type = SNOPTRBSS
-				} else if flag&TLSBSS != 0 {
-					s.Type = STLSBSS
-				}
-				continue
-
-			case ATEXT:
-				s := p.From.Sym
-				if s == nil {
-					// func _() { }
-					curtext = nil
-
-					continue
-				}
-
-				if s.Text != nil {
-					log.Fatalf("duplicate TEXT for %s", s.Name)
-				}
-				if s.Onlist {
-					log.Fatalf("symbol %s listed multiple times", s.Name)
-				}
-				s.Onlist = true
-				text = append(text, s)
-				flag := int(p.From3Offset())
-				if flag&DUPOK != 0 {
-					s.Dupok = true
-				}
-				if flag&NOSPLIT != 0 {
-					s.Nosplit = true
-				}
-				if flag&REFLECTMETHOD != 0 {
-					s.ReflectMethod = true
-				}
-				s.Type = STEXT
-				s.Text = p
-				etext = p
-				curtext = s
-				continue
-
-			case AFUNCDATA:
-				// Rewrite reference to go_args_stackmap(SB) to the Go-provided declaration information.
-				if curtext == nil { // func _() {}
-					continue
-				}
-				if p.To.Sym.Name == "go_args_stackmap" {
-					if p.From.Type != TYPE_CONST || p.From.Offset != FUNCDATA_ArgsPointerMaps {
-						ctxt.Diag("FUNCDATA use of go_args_stackmap(SB) without FUNCDATA_ArgsPointerMaps")
-					}
-					p.To.Sym = Linklookup(ctxt, fmt.Sprintf("%s.args_stackmap", curtext.Name), int(curtext.Version))
-				}
-
-			}
-
-			if curtext == nil {
-				etext = nil
-				continue
-			}
-			etext.Link = p
-			etext = p
-		}
-	}
-
-	// Add reference to Go arguments for C or assembly functions without them.
-	for _, s := range text {
-		if !strings.HasPrefix(s.Name, "\"\".") {
-			continue
-		}
-		found := false
-		var p *Prog
-		for p = s.Text; p != nil; p = p.Link {
-			if p.As == AFUNCDATA && p.From.Type == TYPE_CONST && p.From.Offset == FUNCDATA_ArgsPointerMaps {
-				found = true
-				break
-			}
-		}
-
-		if !found {
-			p = Appendp(ctxt, s.Text)
-			p.As = AFUNCDATA
-			p.From.Type = TYPE_CONST
-			p.From.Offset = FUNCDATA_ArgsPointerMaps
-			p.To.Type = TYPE_MEM
-			p.To.Name = NAME_EXTERN
-			p.To.Sym = Linklookup(ctxt, fmt.Sprintf("%s.args_stackmap", s.Name), int(s.Version))
-		}
-	}
-
-	// Turn functions into machine code images.
-	for _, s := range text {
-		mkfwd(s)
-		linkpatch(ctxt, s)
-		if ctxt.Flag_optimize {
-			ctxt.Arch.Follow(ctxt, s)
-		}
-		ctxt.Arch.Preprocess(ctxt, s)
-		ctxt.Arch.Assemble(ctxt, s)
-		fieldtrack(ctxt, s)
-		linkpcln(ctxt, s)
-		if freeProgs {
-			s.Text = nil
-		}
-	}
-
-	// Add to running list in ctxt.
-	ctxt.Text = append(ctxt.Text, text...)
-	ctxt.Plist = nil
-	ctxt.Plast = nil
-	ctxt.Curp = nil
-	if freeProgs {
-		ctxt.freeProgs()
-	}
-}
-
 // objWriter writes Go object files.
 type objWriter struct {
 	wr   *bufio.Writer
diff --git a/src/cmd/internal/obj/plist.go b/src/cmd/internal/obj/plist.go
new file mode 100644
index 0000000000..e55dbeca1e
--- /dev/null
+++ b/src/cmd/internal/obj/plist.go
@@ -0,0 +1,218 @@
+// Copyright 2013 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 obj
+
+import (
+	"fmt"
+	"log"
+	"strings"
+)
+
+type Plist struct {
+	Name    *LSym
+	Firstpc *Prog
+	Recur   int
+	Link    *Plist
+}
+
+/*
+ * start a new Prog list.
+ */
+func Linknewplist(ctxt *Link) *Plist {
+	pl := new(Plist)
+	if ctxt.Plist == nil {
+		ctxt.Plist = pl
+	} else {
+		ctxt.Plast.Link = pl
+	}
+	ctxt.Plast = pl
+	return pl
+}
+
+func Flushplist(ctxt *Link) {
+	flushplist(ctxt, ctxt.Debugasm == 0)
+}
+func FlushplistNoFree(ctxt *Link) {
+	flushplist(ctxt, false)
+}
+func flushplist(ctxt *Link, freeProgs bool) {
+	// Build list of symbols, and assign instructions to lists.
+	// Ignore ctxt->plist boundaries. There are no guarantees there,
+	// and the assemblers just use one big list.
+	var curtext *LSym
+	var etext *Prog
+	var text []*LSym
+
+	for pl := ctxt.Plist; pl != nil; pl = pl.Link {
+		var plink *Prog
+		for p := pl.Firstpc; p != nil; p = plink {
+			if ctxt.Debugasm != 0 && ctxt.Debugvlog != 0 {
+				fmt.Printf("obj: %v\n", p)
+			}
+			plink = p.Link
+			p.Link = nil
+
+			switch p.As {
+			case AEND:
+				continue
+
+			case ATYPE:
+				// Assume each TYPE instruction describes
+				// a different local variable or parameter,
+				// so no dedup.
+				// Using only the TYPE instructions means
+				// that we discard location information about local variables
+				// in C and assembly functions; that information is inferred
+				// from ordinary references, because there are no TYPE
+				// instructions there. Without the type information, gdb can't
+				// use the locations, so we don't bother to save them.
+				// If something else could use them, we could arrange to
+				// preserve them.
+				if curtext == nil {
+					continue
+				}
+				a := new(Auto)
+				a.Asym = p.From.Sym
+				a.Aoffset = int32(p.From.Offset)
+				a.Name = int16(p.From.Name)
+				a.Gotype = p.From.Gotype
+				a.Link = curtext.Autom
+				curtext.Autom = a
+				continue
+
+			case AGLOBL:
+				s := p.From.Sym
+				if s.Seenglobl {
+					fmt.Printf("duplicate %v\n", p)
+				}
+				s.Seenglobl = true
+				if s.Onlist {
+					log.Fatalf("symbol %s listed multiple times", s.Name)
+				}
+				s.Onlist = true
+				ctxt.Data = append(ctxt.Data, s)
+				s.Size = p.To.Offset
+				if s.Type == 0 || s.Type == SXREF {
+					s.Type = SBSS
+				}
+				flag := int(p.From3.Offset)
+				if flag&DUPOK != 0 {
+					s.Dupok = true
+				}
+				if flag&RODATA != 0 {
+					s.Type = SRODATA
+				} else if flag&NOPTR != 0 {
+					s.Type = SNOPTRBSS
+				} else if flag&TLSBSS != 0 {
+					s.Type = STLSBSS
+				}
+				continue
+
+			case ATEXT:
+				s := p.From.Sym
+				if s == nil {
+					// func _() { }
+					curtext = nil
+
+					continue
+				}
+
+				if s.Text != nil {
+					log.Fatalf("duplicate TEXT for %s", s.Name)
+				}
+				if s.Onlist {
+					log.Fatalf("symbol %s listed multiple times", s.Name)
+				}
+				s.Onlist = true
+				text = append(text, s)
+				flag := int(p.From3Offset())
+				if flag&DUPOK != 0 {
+					s.Dupok = true
+				}
+				if flag&NOSPLIT != 0 {
+					s.Nosplit = true
+				}
+				if flag&REFLECTMETHOD != 0 {
+					s.ReflectMethod = true
+				}
+				s.Type = STEXT
+				s.Text = p
+				etext = p
+				curtext = s
+				continue
+
+			case AFUNCDATA:
+				// Rewrite reference to go_args_stackmap(SB) to the Go-provided declaration information.
+				if curtext == nil { // func _() {}
+					continue
+				}
+				if p.To.Sym.Name == "go_args_stackmap" {
+					if p.From.Type != TYPE_CONST || p.From.Offset != FUNCDATA_ArgsPointerMaps {
+						ctxt.Diag("FUNCDATA use of go_args_stackmap(SB) without FUNCDATA_ArgsPointerMaps")
+					}
+					p.To.Sym = Linklookup(ctxt, fmt.Sprintf("%s.args_stackmap", curtext.Name), int(curtext.Version))
+				}
+
+			}
+
+			if curtext == nil {
+				etext = nil
+				continue
+			}
+			etext.Link = p
+			etext = p
+		}
+	}
+
+	// Add reference to Go arguments for C or assembly functions without them.
+	for _, s := range text {
+		if !strings.HasPrefix(s.Name, "\"\".") {
+			continue
+		}
+		found := false
+		var p *Prog
+		for p = s.Text; p != nil; p = p.Link {
+			if p.As == AFUNCDATA && p.From.Type == TYPE_CONST && p.From.Offset == FUNCDATA_ArgsPointerMaps {
+				found = true
+				break
+			}
+		}
+
+		if !found {
+			p = Appendp(ctxt, s.Text)
+			p.As = AFUNCDATA
+			p.From.Type = TYPE_CONST
+			p.From.Offset = FUNCDATA_ArgsPointerMaps
+			p.To.Type = TYPE_MEM
+			p.To.Name = NAME_EXTERN
+			p.To.Sym = Linklookup(ctxt, fmt.Sprintf("%s.args_stackmap", s.Name), int(s.Version))
+		}
+	}
+
+	// Turn functions into machine code images.
+	for _, s := range text {
+		mkfwd(s)
+		linkpatch(ctxt, s)
+		if ctxt.Flag_optimize {
+			ctxt.Arch.Follow(ctxt, s)
+		}
+		ctxt.Arch.Preprocess(ctxt, s)
+		ctxt.Arch.Assemble(ctxt, s)
+		fieldtrack(ctxt, s)
+		linkpcln(ctxt, s)
+		if freeProgs {
+			s.Text = nil
+		}
+	}
+
+	// Add to running list in ctxt.
+	ctxt.Text = append(ctxt.Text, text...)
+	ctxt.Plist = nil
+	ctxt.Plast = nil
+	ctxt.Curp = nil
+	if freeProgs {
+		ctxt.freeProgs()
+	}
+}
-- 
cgit v1.3


From e46b00a43b62bd67ec13ca75c51037db3b312043 Mon Sep 17 00:00:00 2001
From: Matthew Dempsky 
Date: Tue, 12 Apr 2016 14:44:49 -0700
Subject: cmd/internal/obj: remove unused Pciter type

Change-Id: Ie8323cfcd1193f390729d0d3dd67863aedf47d13
Reviewed-on: https://go-review.googlesource.com/21906
Run-TryBot: Matthew Dempsky 
Reviewed-by: Brad Fitzpatrick 
TryBot-Result: Gobot Gobot 
---
 src/cmd/internal/obj/link.go | 13 -------------
 1 file changed, 13 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 146be6f98f..42aaa5f4f0 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -600,19 +600,6 @@ type Pcdata struct {
 	P []byte
 }
 
-// Pcdata iterator.
-//      for(pciterinit(ctxt, &it, &pcd); !it.done; pciternext(&it)) { it.value holds in [it.pc, it.nextpc) }
-type Pciter struct {
-	d       Pcdata
-	p       []byte
-	pc      uint32
-	nextpc  uint32
-	pcscale uint32
-	value   int32
-	start   int
-	done    int
-}
-
 // symbol version, incremented each time a file is loaded.
 // version==1 is reserved for savehist.
 const (
-- 
cgit v1.3


From b0cbe158da10aac1876680e825a902d58a9d1bac Mon Sep 17 00:00:00 2001
From: Shahar Kohanim 
Date: Mon, 11 Apr 2016 22:19:34 +0300
Subject: cmd/link: move function only lsym fields to pcln struct
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

name       old secs    new secs    delta
LinkCmdGo   0.53 ± 9%   0.53 ±10%  -1.30%  (p=0.022 n=100+99)

name       old MaxRSS  new MaxRSS  delta
LinkCmdGo   151k ± 4%   142k ± 6%  -5.92%  (p=0.000 n=98+100)

Change-Id: Ic30e63a948f8e626b3396f458a0163f7234810c1
Reviewed-on: https://go-review.googlesource.com/21920
Run-TryBot: Ian Lance Taylor 
TryBot-Result: Gobot Gobot 
Reviewed-by: Ian Lance Taylor 
---
 src/cmd/link/internal/ld/deadcode.go |  7 +++++--
 src/cmd/link/internal/ld/dwarf.go    |  2 +-
 src/cmd/link/internal/ld/lib.go      | 17 ++++++++++++++---
 src/cmd/link/internal/ld/link.go     |  6 +++---
 src/cmd/link/internal/ld/objfile.go  | 13 +++++++------
 src/cmd/link/internal/ld/pcln.go     |  6 +++++-
 6 files changed, 35 insertions(+), 16 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go
index 8b2d0d447e..83e4cdc077 100644
--- a/src/cmd/link/internal/ld/deadcode.go
+++ b/src/cmd/link/internal/ld/deadcode.go
@@ -272,9 +272,12 @@ func (d *deadcodepass) flood() {
 			if Debug['v'] > 1 {
 				fmt.Fprintf(d.ctxt.Bso, "marktext %s\n", s.Name)
 			}
-			for _, a := range s.Autom {
-				d.mark(a.Gotype, s)
+			if s.Pcln != nil {
+				for _, a := range s.Pcln.Autom {
+					d.mark(a.Gotype, s)
+				}
 			}
+
 		}
 
 		if strings.HasPrefix(s.Name, "type.") && s.Name[5] != '.' {
diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go
index a3a931f94c..82689988c5 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -1556,7 +1556,7 @@ func writelines(prev *LSym) *LSym {
 			dt, da int
 			offs   int64
 		)
-		for _, a := range s.Autom {
+		for _, a := range s.Pcln.Autom {
 			switch a.Name {
 			case obj.A_AUTO:
 				dt = DW_ABRV_AUTO
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index 1f2df8b9c5..db34e68404 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -1747,7 +1747,11 @@ func stkcheck(up *Chain, depth int) int {
 			return 0
 		}
 		// Raise limit to allow frame.
-		limit = int(obj.StackLimit+s.Locals) + int(Ctxt.FixedFrameSize())
+		locals := int32(0)
+		if s.Pcln != nil {
+			locals = s.Pcln.Locals
+		}
+		limit = int(obj.StackLimit+locals) + int(Ctxt.FixedFrameSize())
 	}
 
 	// Walk through sp adjustments in function, consuming relocs.
@@ -1978,10 +1982,17 @@ func genasmsym(put func(*LSym, string, int, int64, int64, int, *LSym)) {
 	for s := Ctxt.Textp; s != nil; s = s.Next {
 		put(s, s.Name, 'T', s.Value, s.Size, int(s.Version), s.Gotype)
 
+		locals := int32(0)
+		if s.Pcln != nil {
+			locals = s.Pcln.Locals
+		}
 		// NOTE(ality): acid can't produce a stack trace without .frame symbols
-		put(nil, ".frame", 'm', int64(s.Locals)+int64(SysArch.PtrSize), 0, 0, nil)
+		put(nil, ".frame", 'm', int64(locals)+int64(SysArch.PtrSize), 0, 0, nil)
 
-		for _, a := range s.Autom {
+		if s.Pcln == nil {
+			continue
+		}
+		for _, a := range s.Pcln.Autom {
 			// Emit a or p according to actual offset, even if label is wrong.
 			// This avoids negative offsets, which cannot be encoded.
 			if a.Name != obj.A_AUTO && a.Name != obj.A_PARAM {
diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go
index 52b52f1cc0..93454fb4b2 100644
--- a/src/cmd/link/internal/ld/link.go
+++ b/src/cmd/link/internal/ld/link.go
@@ -50,8 +50,6 @@ type LSym struct {
 	Align       int32
 	Elfsym      int32
 	LocalElfsym int32
-	Args        int32
-	Locals      int32
 	Value       int64
 	Size        int64
 	// ElfType is set for symbols read from shared libraries by ldshlibsyms. It
@@ -67,7 +65,6 @@ type LSym struct {
 	Dynimplib   string
 	Dynimpvers  string
 	Sect        *Section
-	Autom       []Auto
 	Pcln        *Pcln
 	P           []byte
 	R           []Reloc
@@ -221,6 +218,9 @@ type Library struct {
 }
 
 type Pcln struct {
+	Args        int32
+	Locals      int32
+	Autom       []Auto
 	Pcsp        Pcdata
 	Pcfile      Pcdata
 	Pcline      Pcdata
diff --git a/src/cmd/link/internal/ld/objfile.go b/src/cmd/link/internal/ld/objfile.go
index 578afd4c74..eacccb59fb 100644
--- a/src/cmd/link/internal/ld/objfile.go
+++ b/src/cmd/link/internal/ld/objfile.go
@@ -331,8 +331,11 @@ overwrite:
 	}
 
 	if s.Type == obj.STEXT {
-		s.Args = r.readInt32()
-		s.Locals = r.readInt32()
+		s.Pcln = new(Pcln)
+		pc := s.Pcln
+
+		pc.Args = r.readInt32()
+		pc.Locals = r.readInt32()
 		if r.readUint8() != 0 {
 			s.Attr |= AttrNoSplit
 		}
@@ -341,13 +344,13 @@ overwrite:
 			s.Attr |= AttrReflectMethod
 		}
 		n := r.readInt()
-		s.Autom = r.autom[:n:n]
+		pc.Autom = r.autom[:n:n]
 		if !isdup {
 			r.autom = r.autom[n:]
 		}
 
 		for i := 0; i < n; i++ {
-			s.Autom[i] = Auto{
+			pc.Autom[i] = Auto{
 				Asym:    r.readSymIndex(),
 				Aoffset: r.readInt32(),
 				Name:    r.readInt16(),
@@ -355,8 +358,6 @@ overwrite:
 			}
 		}
 
-		s.Pcln = new(Pcln)
-		pc := s.Pcln
 		pc.Pcsp.P = r.readData()
 		pc.Pcfile.P = r.readData()
 		pc.Pcline.P = r.readData()
diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go
index 9a947c7c07..3ef52444af 100644
--- a/src/cmd/link/internal/ld/pcln.go
+++ b/src/cmd/link/internal/ld/pcln.go
@@ -293,7 +293,11 @@ func pclntab() {
 
 		// args int32
 		// TODO: Move into funcinfo.
-		off = int32(setuint32(Ctxt, ftab, int64(off), uint32(Ctxt.Cursym.Args)))
+		args := uint32(0)
+		if Ctxt.Cursym.Pcln != nil {
+			args = uint32(Ctxt.Cursym.Pcln.Args)
+		}
+		off = int32(setuint32(Ctxt, ftab, int64(off), args))
 
 		// frame int32
 		// This has been removed (it was never set quite correctly anyway).
-- 
cgit v1.3


From db5338f87982086a19310ad6e25c046280644b98 Mon Sep 17 00:00:00 2001
From: Josh Bleecher Snyder 
Date: Tue, 12 Apr 2016 17:12:26 -0700
Subject: cmd/compile: teach CSE that new objects are bespoke
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

runtime.newobject never returns the same thing twice,
so the resulting value will never be a common subexpression.

This helps when compiling large static data structures
that include pointers, such as maps and slices.
No clear performance impact on other code. (See below.)

For the code in issue #15112:

Before:
  real	1m14.238s
  user	1m18.985s
  sys	0m0.787s

After:
  real	0m47.172s
  user	0m52.248s
  sys	0m0.767s

For the code in issue #15235, size 10k:

Before:
  real	0m44.916s
  user	0m46.577s
  sys	0m0.304s

After:
  real	0m7.703s
  user	0m9.041s
  sys	0m0.316s

Still more work to be done, particularly for #15112.

Updates #15112
Updates #15235


name       old time/op      new time/op      delta
Template        330ms ±11%       333ms ±13%    ~           (p=0.749 n=20+19)
Unicode         148ms ± 6%       152ms ± 8%    ~           (p=0.072 n=18+20)
GoTypes         1.01s ± 7%       1.01s ± 3%    ~           (p=0.583 n=20+20)
Compiler        5.04s ± 2%       5.06s ± 2%    ~           (p=0.314 n=20+20)

name       old user-ns/op   new user-ns/op   delta
Template   444user-ms ±11%  445user-ms ±10%    ~           (p=0.738 n=20+20)
Unicode    215user-ms ± 5%  218user-ms ± 5%    ~           (p=0.239 n=18+18)
GoTypes    1.45user-s ± 3%  1.45user-s ± 4%    ~           (p=0.620 n=20+20)
Compiler   7.23user-s ± 2%  7.22user-s ± 2%    ~           (p=0.901 n=20+19)

name       old alloc/op     new alloc/op     delta
Template       55.0MB ± 0%      55.0MB ± 0%    ~           (p=0.547 n=20+20)
Unicode        37.6MB ± 0%      37.6MB ± 0%    ~           (p=0.301 n=20+20)
GoTypes         177MB ± 0%       177MB ± 0%    ~           (p=0.065 n=20+19)
Compiler        798MB ± 0%       797MB ± 0%  -0.05%        (p=0.000 n=19+20)

name       old allocs/op    new allocs/op    delta
Template         492k ± 0%        493k ± 0%  +0.03%        (p=0.030 n=20+20)
Unicode          377k ± 0%        377k ± 0%    ~           (p=0.423 n=20+19)
GoTypes         1.40M ± 0%       1.40M ± 0%    ~           (p=0.102 n=20+20)
Compiler        5.53M ± 0%       5.53M ± 0%    ~           (p=0.094 n=17+18)

name       old text-bytes   new text-bytes   delta
HelloSize        561k ± 0%        561k ± 0%    ~     (all samples are equal)
CmdGoSize       6.13M ± 0%       6.13M ± 0%    ~     (all samples are equal)

name       old data-bytes   new data-bytes   delta
HelloSize        128k ± 0%        128k ± 0%    ~     (all samples are equal)
CmdGoSize        306k ± 0%        306k ± 0%    ~     (all samples are equal)

name       old exe-bytes    new exe-bytes    delta
HelloSize        905k ± 0%        905k ± 0%    ~     (all samples are equal)
CmdGoSize       9.64M ± 0%       9.64M ± 0%    ~     (all samples are equal)

Change-Id: Id774e2901d7701a3ec7a1c1d1cf1d9327a4107fc
Reviewed-on: https://go-review.googlesource.com/21937
Run-TryBot: Josh Bleecher Snyder 
TryBot-Result: Gobot Gobot 
Reviewed-by: Todd Neal 
---
 src/cmd/compile/internal/gc/subr.go    | 4 ++++
 src/cmd/compile/internal/ssa/config.go | 6 ++++++
 src/cmd/compile/internal/ssa/cse.go    | 8 ++++++++
 3 files changed, 18 insertions(+)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index 035bd815c2..091762f496 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -1081,6 +1081,10 @@ func syslook(name string) *Node {
 	return s.Def
 }
 
+func (s *Sym) IsRuntimeCall(name string) bool {
+	return s.Pkg == Runtimepkg && s.Name == name
+}
+
 // typehash computes a hash value for type t to use in type switch
 // statements.
 func typehash(t *Type) uint32 {
diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go
index 2a676e39b3..a60291ea53 100644
--- a/src/cmd/compile/internal/ssa/config.go
+++ b/src/cmd/compile/internal/ssa/config.go
@@ -116,6 +116,12 @@ type GCNode interface {
 	String() string
 }
 
+// GCSym is an interface that *gc.Sym implements.
+// Using *gc.Sym directly would lead to import cycles.
+type GCSym interface {
+	IsRuntimeCall(name string) bool
+}
+
 // NewConfig returns a new configuration object for the given architecture.
 func NewConfig(arch string, fe Frontend, ctxt *obj.Link, optimize bool) *Config {
 	c := &Config{arch: arch, fe: fe}
diff --git a/src/cmd/compile/internal/ssa/cse.go b/src/cmd/compile/internal/ssa/cse.go
index 1ec5712be0..9853ff06d0 100644
--- a/src/cmd/compile/internal/ssa/cse.go
+++ b/src/cmd/compile/internal/ssa/cse.go
@@ -255,6 +255,14 @@ func cmpVal(v, w *Value, auxIDs auxmap, depth int) Cmp {
 		return lt2Cmp(v.Block.ID < w.Block.ID)
 	}
 
+	switch v.Op {
+	case OpStaticCall, OpAMD64CALLstatic, OpARMCALLstatic:
+		sym := v.Aux.(GCSym)
+		if sym.IsRuntimeCall("newobject") {
+			return lt2Cmp(v.ID < w.ID)
+		}
+	}
+
 	if tc := v.Type.Compare(w.Type); tc != CMPeq {
 		return tc
 	}
-- 
cgit v1.3


From 6531fab06fc4667b7d167c7e3ee936f28bac68e2 Mon Sep 17 00:00:00 2001
From: Tal Shprecher 
Date: Tue, 12 Apr 2016 22:29:34 -0700
Subject: cmd/compile: remove unnecessary assignments while type checking.

Change-Id: Ica0ec84714d7f01d800d62fa10cdb08321d43cf3
Reviewed-on: https://go-review.googlesource.com/21967
Reviewed-by: Brad Fitzpatrick 
Run-TryBot: Brad Fitzpatrick 
TryBot-Result: Gobot Gobot 
Reviewed-by: Matthew Dempsky 
---
 src/cmd/compile/internal/gc/typecheck.go | 6 ------
 1 file changed, 6 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go
index a20f87d940..f676b9dd09 100644
--- a/src/cmd/compile/internal/gc/typecheck.go
+++ b/src/cmd/compile/internal/gc/typecheck.go
@@ -338,7 +338,6 @@ OpSwitch:
 		ok |= Etype
 
 		if n.Type == nil {
-			n.Type = nil
 			return n
 		}
 
@@ -449,7 +448,6 @@ OpSwitch:
 		n.Op = OTYPE
 		n.Type = tointerface(n.List.Slice())
 		if n.Type == nil {
-			n.Type = nil
 			return n
 		}
 
@@ -458,7 +456,6 @@ OpSwitch:
 		n.Op = OTYPE
 		n.Type = functype(n.Left, n.List.Slice(), n.Rlist.Slice())
 		if n.Type == nil {
-			n.Type = nil
 			return n
 		}
 		n.Left = nil
@@ -822,7 +819,6 @@ OpSwitch:
 		ok |= Erv
 		n = typecheckcomplit(n)
 		if n.Type == nil {
-			n.Type = nil
 			return n
 		}
 		break OpSwitch
@@ -864,7 +860,6 @@ OpSwitch:
 			if n.Type.Etype != TFUNC || n.Type.Recv() == nil {
 				Yyerror("type %v has no method %v", n.Left.Type, Sconv(n.Right.Sym, FmtShort))
 				n.Type = nil
-				n.Type = nil
 				return n
 			}
 
@@ -1961,7 +1956,6 @@ OpSwitch:
 		ok |= Erv
 		typecheckclosure(n, top)
 		if n.Type == nil {
-			n.Type = nil
 			return n
 		}
 		break OpSwitch
-- 
cgit v1.3


From 0e01db4b8d6ac64e6661508bc6876fa41c799208 Mon Sep 17 00:00:00 2001
From: Matthew Dempsky 
Date: Tue, 12 Apr 2016 17:46:41 -0700
Subject: cmd/compile: fix crash on bare package name in constant declarations

Fixes #11361.

Change-Id: I70b8808f97f0e07de680e7e6ede1322ea0fdbbc0
Reviewed-on: https://go-review.googlesource.com/21936
Reviewed-by: Brad Fitzpatrick 
---
 src/cmd/compile/internal/gc/subr.go |  7 +++++++
 test/fixedbugs/issue11361.go        | 11 +++++++++++
 2 files changed, 18 insertions(+)
 create mode 100644 test/fixedbugs/issue11361.go

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index 091762f496..ea2db8721a 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -540,8 +540,15 @@ func treecopy(n *Node, lineno int32) *Node {
 		}
 		return n
 
+	case OPACK:
+		// OPACK nodes are never valid in const value declarations,
+		// but allow them like any other declared symbol to avoid
+		// crashing (golang.org/issue/11361).
+		fallthrough
+
 	case ONAME, OLITERAL, OTYPE:
 		return n
+
 	}
 }
 
diff --git a/test/fixedbugs/issue11361.go b/test/fixedbugs/issue11361.go
new file mode 100644
index 0000000000..d01776b47c
--- /dev/null
+++ b/test/fixedbugs/issue11361.go
@@ -0,0 +1,11 @@
+// errorcheck
+
+// 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 a
+
+import "fmt"  // ERROR "imported and not used"
+
+const n = fmt // ERROR "fmt without selector" "fmt is not a constant"
-- 
cgit v1.3


From 24967ec122710e73b35893925fd9a8390d7524ab Mon Sep 17 00:00:00 2001
From: Tal Shprecher 
Date: Sun, 10 Apr 2016 18:12:41 -0700
Subject: cmd/compile: make enqueued map keys fail validation on forward types

Map keys are currently validated in multiple locations but share
a common validation routine. The problem is that early validations
should be lenient enough to allow for forward types while the final
validations should not. The final validations should fail on forward
types since they've already settled.

This change also separates the key type checking from the creation
of the map via typMap. Instead of the mapqueue being populated in
copytype() by checking the map line number, it's populated in the
same block that validates the key type. This isolates key validation
logic while type checking.

Fixes #14988

Change-Id: Ia47cf6213585d6c63b3a35249104c0439feae658
Reviewed-on: https://go-review.googlesource.com/21830
Reviewed-by: Matthew Dempsky 
Run-TryBot: Matthew Dempsky 
TryBot-Result: Gobot Gobot 
---
 src/cmd/compile/internal/gc/subr.go      | 24 ------------------
 src/cmd/compile/internal/gc/type.go      |  4 ---
 src/cmd/compile/internal/gc/typecheck.go | 42 +++++++++++++++++++-------------
 test/fixedbugs/issue14988.go             | 13 ++++++++++
 4 files changed, 38 insertions(+), 45 deletions(-)
 create mode 100644 test/fixedbugs/issue14988.go

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index ea2db8721a..776eb9c64e 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -372,30 +372,6 @@ func saveorignode(n *Node) {
 	n.Orig = norig
 }
 
-// checkMapKeyType checks that Type key is valid for use as a map key.
-func checkMapKeyType(key *Type) {
-	alg, bad := algtype1(key)
-	if alg != ANOEQ {
-		return
-	}
-	switch bad.Etype {
-	default:
-		Yyerror("invalid map key type %v", key)
-	case TANY:
-		// Will be resolved later.
-	case TFORW:
-		// map[key] used during definition of key.
-		// postpone check until key is fully defined.
-		// if there are multiple uses of map[key]
-		// before key is fully defined, the error
-		// will only be printed for the first one.
-		// good enough.
-		if maplineno[key] == 0 {
-			maplineno[key] = lineno
-		}
-	}
-}
-
 // methcmp sorts by symbol, then by package path for unexported symbols.
 type methcmp []*Field
 
diff --git a/src/cmd/compile/internal/gc/type.go b/src/cmd/compile/internal/gc/type.go
index 25c1bcc203..a44a85bed8 100644
--- a/src/cmd/compile/internal/gc/type.go
+++ b/src/cmd/compile/internal/gc/type.go
@@ -429,10 +429,6 @@ func typChan(elem *Type, dir ChanDir) *Type {
 
 // typMap returns a new map Type with key type k and element (aka value) type v.
 func typMap(k, v *Type) *Type {
-	if k != nil {
-		checkMapKeyType(k)
-	}
-
 	t := typ(TMAP)
 	mt := t.MapType()
 	mt.Key = k
diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go
index f676b9dd09..7089d7de72 100644
--- a/src/cmd/compile/internal/gc/typecheck.go
+++ b/src/cmd/compile/internal/gc/typecheck.go
@@ -416,6 +416,18 @@ OpSwitch:
 		}
 		n.Op = OTYPE
 		n.Type = typMap(l.Type, r.Type)
+
+		// map key validation
+		alg, bad := algtype1(l.Type)
+		if alg == ANOEQ {
+			if bad.Etype == TFORW {
+				// queue check for map until all the types are done settling.
+				mapqueue = append(mapqueue, mapqueueval{l, n.Lineno})
+			} else if bad.Etype != TANY {
+				// no need to queue, key is already bad
+				Yyerror("invalid map key type %v", l.Type)
+			}
+		}
 		n.Left = nil
 		n.Right = nil
 
@@ -3507,11 +3519,13 @@ func domethod(n *Node) {
 	checkwidth(n.Type)
 }
 
-var (
-	mapqueue []*Node
-	// maplineno tracks the line numbers at which types are first used as map keys
-	maplineno = map[*Type]int32{}
-)
+type mapqueueval struct {
+	n   *Node
+	lno int32
+}
+
+// tracks the line numbers at which forward types are first used as map keys
+var mapqueue []mapqueueval
 
 func copytype(n *Node, t *Type) {
 	if t.Etype == TFORW {
@@ -3520,7 +3534,6 @@ func copytype(n *Node, t *Type) {
 		return
 	}
 
-	mapline := maplineno[n.Type]
 	embedlineno := n.Type.ForwardType().Embedlineno
 	l := n.Type.ForwardType().Copyto
 
@@ -3555,12 +3568,6 @@ func copytype(n *Node, t *Type) {
 	}
 
 	lineno = lno
-
-	// Queue check for map until all the types are done settling.
-	if mapline != 0 {
-		maplineno[t] = mapline
-		mapqueue = append(mapqueue, n)
-	}
 }
 
 func typecheckdeftype(n *Node) {
@@ -3605,12 +3612,13 @@ ret:
 				domethod(n)
 			}
 		}
-
-		for _, n := range mapqueue {
-			lineno = maplineno[n.Type]
-			checkMapKeyType(n.Type)
+		for _, e := range mapqueue {
+			lineno = e.lno
+			if !e.n.Type.IsComparable() {
+				Yyerror("invalid map key type %v", e.n.Type)
+			}
 		}
-
+		mapqueue = nil
 		lineno = lno
 	}
 
diff --git a/test/fixedbugs/issue14988.go b/test/fixedbugs/issue14988.go
new file mode 100644
index 0000000000..4ddc7e728f
--- /dev/null
+++ b/test/fixedbugs/issue14988.go
@@ -0,0 +1,13 @@
+// errorcheck
+
+// 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.
+
+// Issue 14988: defining a map with an invalid forward declaration array
+//              key doesn't cause a fatal.
+
+package main
+
+type m map[k]int // ERROR "invalid map key type"
+type k [1]m
-- 
cgit v1.3


From 61b7a9c57bb6b9c259360239001b2d5be4876abd Mon Sep 17 00:00:00 2001
From: Shahar Kohanim 
Date: Tue, 12 Apr 2016 23:18:47 +0300
Subject: cmd/link: rename Pcln to FuncInfo

After non pcln fields were added to it in a previous commit.

Change-Id: Icf92c0774d157c61399a6fc2a3c4d2cd47a634d2
Reviewed-on: https://go-review.googlesource.com/21921
Run-TryBot: Shahar Kohanim 
TryBot-Result: Gobot Gobot 
Reviewed-by: David Crawshaw 
---
 src/cmd/link/internal/ld/deadcode.go | 10 +++++-----
 src/cmd/link/internal/ld/dwarf.go    | 16 ++++++++--------
 src/cmd/link/internal/ld/lib.go      | 16 ++++++++--------
 src/cmd/link/internal/ld/link.go     |  4 ++--
 src/cmd/link/internal/ld/objfile.go  |  4 ++--
 src/cmd/link/internal/ld/pcln.go     | 10 +++++-----
 6 files changed, 30 insertions(+), 30 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go
index 83e4cdc077..51fae02ef0 100644
--- a/src/cmd/link/internal/ld/deadcode.go
+++ b/src/cmd/link/internal/ld/deadcode.go
@@ -272,8 +272,8 @@ func (d *deadcodepass) flood() {
 			if Debug['v'] > 1 {
 				fmt.Fprintf(d.ctxt.Bso, "marktext %s\n", s.Name)
 			}
-			if s.Pcln != nil {
-				for _, a := range s.Pcln.Autom {
+			if s.FuncInfo != nil {
+				for _, a := range s.FuncInfo.Autom {
 					d.mark(a.Gotype, s)
 				}
 			}
@@ -335,9 +335,9 @@ func (d *deadcodepass) flood() {
 			d.markableMethods = append(d.markableMethods, methods...)
 		}
 
-		if s.Pcln != nil {
-			for i := range s.Pcln.Funcdata {
-				d.mark(s.Pcln.Funcdata[i], s)
+		if s.FuncInfo != nil {
+			for i := range s.FuncInfo.Funcdata {
+				d.mark(s.FuncInfo.Funcdata[i], s)
 			}
 		}
 		d.mark(s.Gotype, s)
diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go
index 82689988c5..b1208b63a8 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -1345,8 +1345,8 @@ func finddebugruntimepath(s *LSym) {
 		return
 	}
 
-	for i := range s.Pcln.File {
-		f := s.Pcln.File[i]
+	for i := range s.FuncInfo.File {
+		f := s.FuncInfo.File[i]
 		if i := strings.Index(f.Name, "runtime/runtime.go"); i >= 0 {
 			gdbscript = f.Name[:i] + "runtime/runtime-gdb.py"
 			break
@@ -1514,14 +1514,14 @@ func writelines(prev *LSym) *LSym {
 			newattr(dwfunc, DW_AT_external, DW_CLS_FLAG, 1, 0)
 		}
 
-		if s.Pcln == nil {
+		if s.FuncInfo == nil {
 			continue
 		}
 
 		finddebugruntimepath(s)
 
-		pciterinit(Ctxt, &pcfile, &s.Pcln.Pcfile)
-		pciterinit(Ctxt, &pcline, &s.Pcln.Pcline)
+		pciterinit(Ctxt, &pcfile, &s.FuncInfo.Pcfile)
+		pciterinit(Ctxt, &pcline, &s.FuncInfo.Pcline)
 		epc = pc
 		for pcfile.done == 0 && pcline.done == 0 {
 			if epc-s.Value >= int64(pcfile.nextpc) {
@@ -1556,7 +1556,7 @@ func writelines(prev *LSym) *LSym {
 			dt, da int
 			offs   int64
 		)
-		for _, a := range s.Pcln.Autom {
+		for _, a := range s.FuncInfo.Autom {
 			switch a.Name {
 			case obj.A_AUTO:
 				dt = DW_ABRV_AUTO
@@ -1698,14 +1698,14 @@ func writeframes(prev *LSym) *LSym {
 	var pcsp Pciter
 	for Ctxt.Cursym = Ctxt.Textp; Ctxt.Cursym != nil; Ctxt.Cursym = Ctxt.Cursym.Next {
 		s := Ctxt.Cursym
-		if s.Pcln == nil {
+		if s.FuncInfo == nil {
 			continue
 		}
 
 		// Emit a FDE, Section 6.4.1.
 		// First build the section contents into a byte buffer.
 		deltaBuf = deltaBuf[:0]
-		for pciterinit(Ctxt, &pcsp, &s.Pcln.Pcsp); pcsp.done == 0; pciternext(&pcsp) {
+		for pciterinit(Ctxt, &pcsp, &s.FuncInfo.Pcsp); pcsp.done == 0; pciternext(&pcsp) {
 			nextpc := pcsp.nextpc
 
 			// pciterinit goes up to the end of the function,
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index db34e68404..bdcc84a129 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -1710,7 +1710,7 @@ func stkcheck(up *Chain, depth int) int {
 		return -1
 	}
 
-	if s.Attr.External() || s.Pcln == nil {
+	if s.Attr.External() || s.FuncInfo == nil {
 		// external function.
 		// should never be called directly.
 		// only diagnose the direct caller.
@@ -1748,8 +1748,8 @@ func stkcheck(up *Chain, depth int) int {
 		}
 		// Raise limit to allow frame.
 		locals := int32(0)
-		if s.Pcln != nil {
-			locals = s.Pcln.Locals
+		if s.FuncInfo != nil {
+			locals = s.FuncInfo.Locals
 		}
 		limit = int(obj.StackLimit+locals) + int(Ctxt.FixedFrameSize())
 	}
@@ -1761,7 +1761,7 @@ func stkcheck(up *Chain, depth int) int {
 	var ch1 Chain
 	var pcsp Pciter
 	var r *Reloc
-	for pciterinit(Ctxt, &pcsp, &s.Pcln.Pcsp); pcsp.done == 0; pciternext(&pcsp) {
+	for pciterinit(Ctxt, &pcsp, &s.FuncInfo.Pcsp); pcsp.done == 0; pciternext(&pcsp) {
 		// pcsp.value is in effect for [pcsp.pc, pcsp.nextpc).
 
 		// Check stack size in effect for this span.
@@ -1983,16 +1983,16 @@ func genasmsym(put func(*LSym, string, int, int64, int64, int, *LSym)) {
 		put(s, s.Name, 'T', s.Value, s.Size, int(s.Version), s.Gotype)
 
 		locals := int32(0)
-		if s.Pcln != nil {
-			locals = s.Pcln.Locals
+		if s.FuncInfo != nil {
+			locals = s.FuncInfo.Locals
 		}
 		// NOTE(ality): acid can't produce a stack trace without .frame symbols
 		put(nil, ".frame", 'm', int64(locals)+int64(SysArch.PtrSize), 0, 0, nil)
 
-		if s.Pcln == nil {
+		if s.FuncInfo == nil {
 			continue
 		}
-		for _, a := range s.Pcln.Autom {
+		for _, a := range s.FuncInfo.Autom {
 			// Emit a or p according to actual offset, even if label is wrong.
 			// This avoids negative offsets, which cannot be encoded.
 			if a.Name != obj.A_AUTO && a.Name != obj.A_PARAM {
diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go
index 93454fb4b2..b0bca4300f 100644
--- a/src/cmd/link/internal/ld/link.go
+++ b/src/cmd/link/internal/ld/link.go
@@ -65,7 +65,7 @@ type LSym struct {
 	Dynimplib   string
 	Dynimpvers  string
 	Sect        *Section
-	Pcln        *Pcln
+	FuncInfo    *FuncInfo
 	P           []byte
 	R           []Reloc
 }
@@ -217,7 +217,7 @@ type Library struct {
 	hash   []byte
 }
 
-type Pcln struct {
+type FuncInfo struct {
 	Args        int32
 	Locals      int32
 	Autom       []Auto
diff --git a/src/cmd/link/internal/ld/objfile.go b/src/cmd/link/internal/ld/objfile.go
index eacccb59fb..6826737cae 100644
--- a/src/cmd/link/internal/ld/objfile.go
+++ b/src/cmd/link/internal/ld/objfile.go
@@ -331,8 +331,8 @@ overwrite:
 	}
 
 	if s.Type == obj.STEXT {
-		s.Pcln = new(Pcln)
-		pc := s.Pcln
+		s.FuncInfo = new(FuncInfo)
+		pc := s.FuncInfo
 
 		pc.Args = r.readInt32()
 		pc.Locals = r.readInt32()
diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go
index 3ef52444af..74ef8c2929 100644
--- a/src/cmd/link/internal/ld/pcln.go
+++ b/src/cmd/link/internal/ld/pcln.go
@@ -204,7 +204,7 @@ func container(s *LSym) int {
 // pclntab initializes the pclntab symbol with
 // runtime function and file name information.
 
-var pclntab_zpcln Pcln
+var pclntab_zpcln FuncInfo
 
 // These variables are used to initialize runtime.firstmoduledata, see symtab.go:symtab.
 var pclntabNfunc int32
@@ -255,13 +255,13 @@ func pclntab() {
 	var i int32
 	var it Pciter
 	var off int32
-	var pcln *Pcln
+	var pcln *FuncInfo
 	for Ctxt.Cursym = Ctxt.Textp; Ctxt.Cursym != nil; Ctxt.Cursym = Ctxt.Cursym.Next {
 		last = Ctxt.Cursym
 		if container(Ctxt.Cursym) != 0 {
 			continue
 		}
-		pcln = Ctxt.Cursym.Pcln
+		pcln = Ctxt.Cursym.FuncInfo
 		if pcln == nil {
 			pcln = &pclntab_zpcln
 		}
@@ -294,8 +294,8 @@ func pclntab() {
 		// args int32
 		// TODO: Move into funcinfo.
 		args := uint32(0)
-		if Ctxt.Cursym.Pcln != nil {
-			args = uint32(Ctxt.Cursym.Pcln.Args)
+		if Ctxt.Cursym.FuncInfo != nil {
+			args = uint32(Ctxt.Cursym.FuncInfo.Args)
 		}
 		off = int32(setuint32(Ctxt, ftab, int64(off), args))
 
-- 
cgit v1.3


From e0611b16645dba6768cab405f1ec1b3fce83334a Mon Sep 17 00:00:00 2001
From: Alexandru Moșoi 
Date: Wed, 13 Apr 2016 10:58:38 +0200
Subject: cmd/compile: use shared dom tree for cse, too
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Missed this in the previous CL where the shared
dom tree was introduced.

Change-Id: If0bd85d4b4567d7e87814ed511603b1303ab3903
Reviewed-on: https://go-review.googlesource.com/21970
Run-TryBot: Alexandru Moșoi 
TryBot-Result: Gobot Gobot 
Reviewed-by: David Chase 
---
 src/cmd/compile/internal/ssa/compile.go  | 5 +++--
 src/cmd/compile/internal/ssa/cse.go      | 8 +++-----
 src/cmd/compile/internal/ssa/cse_test.go | 1 +
 3 files changed, 7 insertions(+), 7 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/ssa/compile.go b/src/cmd/compile/internal/ssa/compile.go
index f4f0d8cab2..a0b5ff71cf 100644
--- a/src/cmd/compile/internal/ssa/compile.go
+++ b/src/cmd/compile/internal/ssa/compile.go
@@ -233,8 +233,8 @@ var passes = [...]pass{
 	{name: "opt", fn: opt, required: true},               // TODO: split required rules and optimizing rules
 	{name: "zero arg cse", fn: zcse, required: true},     // required to merge OpSB values
 	{name: "opt deadcode", fn: deadcode, required: true}, // remove any blocks orphaned during opt
-	{name: "generic cse", fn: cse},
 	{name: "generic domtree", fn: domTree},
+	{name: "generic cse", fn: cse},
 	{name: "phiopt", fn: phiopt},
 	{name: "nilcheckelim", fn: nilcheckelim},
 	{name: "prove", fn: prove},
@@ -289,7 +289,8 @@ var passOrder = [...]constraint{
 	{"opt", "nilcheckelim"},
 	// tighten should happen before lowering to avoid splitting naturally paired instructions such as CMP/SET
 	{"tighten", "lower"},
-	// nilcheckelim, prove and loopbce share idom.
+	// cse, nilcheckelim, prove and loopbce share idom.
+	{"generic domtree", "generic cse"},
 	{"generic domtree", "nilcheckelim"},
 	{"generic domtree", "prove"},
 	{"generic domtree", "loopbce"},
diff --git a/src/cmd/compile/internal/ssa/cse.go b/src/cmd/compile/internal/ssa/cse.go
index 9853ff06d0..c12d51e50c 100644
--- a/src/cmd/compile/internal/ssa/cse.go
+++ b/src/cmd/compile/internal/ssa/cse.go
@@ -131,9 +131,7 @@ func cse(f *Func) {
 		}
 	}
 
-	// Compute dominator tree
-	idom := dominators(f)
-	sdom := newSparseTree(f, idom)
+	// Dominator tree (f.sdom) is computed by the generic domtree pass.
 
 	// Compute substitutions we would like to do. We substitute v for w
 	// if v and w are in the same equivalence class and v dominates w.
@@ -143,7 +141,7 @@ func cse(f *Func) {
 			// Find a maximal dominant element in e
 			v := e[0]
 			for _, w := range e[1:] {
-				if sdom.isAncestorEq(w.Block, v.Block) {
+				if f.sdom.isAncestorEq(w.Block, v.Block) {
 					v = w
 				}
 			}
@@ -153,7 +151,7 @@ func cse(f *Func) {
 				w := e[i]
 				if w == v {
 					e, e[i] = e[:len(e)-1], e[len(e)-1]
-				} else if sdom.isAncestorEq(v.Block, w.Block) {
+				} else if f.sdom.isAncestorEq(v.Block, w.Block) {
 					rewrite[w.ID] = v
 					e, e[i] = e[:len(e)-1], e[len(e)-1]
 				} else {
diff --git a/src/cmd/compile/internal/ssa/cse_test.go b/src/cmd/compile/internal/ssa/cse_test.go
index 905939fc32..d5be2b52ec 100644
--- a/src/cmd/compile/internal/ssa/cse_test.go
+++ b/src/cmd/compile/internal/ssa/cse_test.go
@@ -44,6 +44,7 @@ func TestCSEAuxPartitionBug(t *testing.T) {
 			Exit("rstore")))
 
 	CheckFunc(fun.f)
+	domTree(fun.f)
 	cse(fun.f)
 	deadcode(fun.f)
 	CheckFunc(fun.f)
-- 
cgit v1.3


From 7d469179e6e3dafe16700b7fc1cf8683ad9453fa Mon Sep 17 00:00:00 2001
From: David Crawshaw 
Date: Mon, 28 Mar 2016 10:32:27 -0400
Subject: cmd/compile, etc: store method tables as offsets

This CL introduces the typeOff type and a lookup method of the same
name that can turn a typeOff offset into an *rtype.

In a typical Go binary (built with buildmode=exe, pie, c-archive, or
c-shared), there is one moduledata and all typeOff values are offsets
relative to firstmoduledata.types. This makes computing the pointer
cheap in typical programs.

With buildmode=shared (and one day, buildmode=plugin) there are
multiple modules whose relative offset is determined at runtime.
We identify a type in the general case by the pair of the original
*rtype that references it and its typeOff value. We determine
the module from the original pointer, and then use the typeOff from
there to compute the final *rtype.

To ensure there is only one *rtype representing each type, the
runtime initializes a typemap for each module, using any identical
type from an earlier module when resolving that offset. This means
that types computed from an offset match the type mapped by the
pointer dynamic relocations.

A series of followup CLs will replace other *rtype values with typeOff
(and name/*string with nameOff).

For types created at runtime by reflect, type offsets are treated as
global IDs and reference into a reflect offset map kept by the runtime.

darwin/amd64:
	cmd/go:  -57KB (0.6%)
	jujud:  -557KB (0.8%)

linux/amd64 PIE:
	cmd/go: -361KB (3.0%)
	jujud:  -3.5MB (4.2%)

For #6853.

Change-Id: Icf096fd884a0a0cb9f280f46f7a26c70a9006c96
Reviewed-on: https://go-review.googlesource.com/21285
Reviewed-by: Ian Lance Taylor 
Run-TryBot: David Crawshaw 
TryBot-Result: Gobot Gobot 
---
 src/cmd/compile/internal/gc/reflect.go |  75 +++++---
 src/cmd/internal/obj/link.go           |  15 +-
 src/cmd/link/internal/ld/deadcode.go   |  14 +-
 src/cmd/link/internal/ld/decodesym.go  |  22 +--
 src/reflect/export_test.go             |   2 +-
 src/reflect/type.go                    | 267 +++++++++++++++++++++-------
 src/reflect/value.go                   |  15 +-
 src/runtime/iface.go                   |  10 +-
 src/runtime/proc.go                    |   3 +-
 src/runtime/runtime1.go                |  33 ++++
 src/runtime/symtab.go                  |   2 +
 src/runtime/type.go                    | 307 ++++++++++++++++++++++++++++++++-
 12 files changed, 637 insertions(+), 128 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go
index ea67634260..2bd50b4665 100644
--- a/src/cmd/compile/internal/gc/reflect.go
+++ b/src/cmd/compile/internal/gc/reflect.go
@@ -75,7 +75,7 @@ func uncommonSize(t *Type) int { // Sizeof(runtime.uncommontype{})
 	if t.Sym == nil && len(methods(t)) == 0 {
 		return 0
 	}
-	return 2*Widthptr + 2*Widthint
+	return 2 * Widthptr
 }
 
 func makefield(name string, t *Type) *Field {
@@ -580,13 +580,23 @@ func dextratype(s *Sym, ot int, t *Type, dataAdd int) int {
 
 	ot = dgopkgpath(s, ot, typePkg(t))
 
-	// slice header
-	ot = dsymptr(s, ot, s, ot+Widthptr+2*Widthint+dataAdd)
-
-	n := len(m)
-	ot = duintxx(s, ot, uint64(n), Widthint)
-	ot = duintxx(s, ot, uint64(n), Widthint)
+	dataAdd += Widthptr + 2 + 2
+	if Widthptr == 8 {
+		dataAdd += 4
+	}
+	mcount := len(m)
+	if mcount != int(uint16(mcount)) {
+		Fatalf("too many methods on %s: %d", t, mcount)
+	}
+	if dataAdd != int(uint16(dataAdd)) {
+		Fatalf("methods are too far away on %s: %d", t, dataAdd)
+	}
 
+	ot = duint16(s, ot, uint16(mcount))
+	ot = duint16(s, ot, uint16(dataAdd))
+	if Widthptr == 8 {
+		ot = duint32(s, ot, 0) // align for following pointers
+	}
 	return ot
 }
 
@@ -609,6 +619,7 @@ func typePkg(t *Type) *Pkg {
 // dextratypeData dumps the backing array for the []method field of
 // runtime.uncommontype.
 func dextratypeData(s *Sym, ot int, t *Type) int {
+	lsym := Linksym(s)
 	for _, a := range methods(t) {
 		// ../../../../runtime/type.go:/method
 		exported := exportname(a.name)
@@ -617,21 +628,24 @@ func dextratypeData(s *Sym, ot int, t *Type) int {
 			pkg = a.pkg
 		}
 		ot = dname(s, ot, a.name, "", pkg, exported)
-		ot = dmethodptr(s, ot, dtypesym(a.mtype))
-		ot = dmethodptr(s, ot, a.isym)
-		ot = dmethodptr(s, ot, a.tsym)
+		ot = dmethodptrOffLSym(lsym, ot, Linksym(dtypesym(a.mtype)))
+		ot = dmethodptrOffLSym(lsym, ot, Linksym(a.isym))
+		ot = dmethodptrOffLSym(lsym, ot, Linksym(a.tsym))
+		if Widthptr == 8 {
+			ot = duintxxLSym(lsym, ot, 0, 4) // pad to reflect.method size
+		}
 	}
 	return ot
 }
 
-func dmethodptr(s *Sym, off int, x *Sym) int {
-	duintptr(s, off, 0)
-	r := obj.Addrel(Linksym(s))
-	r.Off = int32(off)
-	r.Siz = uint8(Widthptr)
-	r.Sym = Linksym(x)
-	r.Type = obj.R_METHOD
-	return off + Widthptr
+func dmethodptrOffLSym(s *obj.LSym, ot int, x *obj.LSym) int {
+	duintxxLSym(s, ot, 0, 4)
+	r := obj.Addrel(s)
+	r.Off = int32(ot)
+	r.Siz = 4
+	r.Sym = x
+	r.Type = obj.R_METHODOFF
+	return ot + 4
 }
 
 var kinds = []int{
@@ -1286,18 +1300,29 @@ ok:
 	ggloblsym(s, int32(ot), int16(dupok|obj.RODATA))
 
 	// generate typelink.foo pointing at s = type.foo.
+	//
 	// The linker will leave a table of all the typelinks for
-	// types in the binary, so reflect can find them.
-	// We only need the link for unnamed composites that
-	// we want be able to find.
-	if t.Sym == nil {
+	// types in the binary, so the runtime can find them.
+	//
+	// When buildmode=shared, all types are in typelinks so the
+	// runtime can deduplicate type pointers.
+	keep := Ctxt.Flag_dynlink
+	if !keep && t.Sym == nil {
+		// For an unnamed type, we only need the link if the type can
+		// be created at run time by reflect.PtrTo and similar
+		// functions. If the type exists in the program, those
+		// functions must return the existing type structure rather
+		// than creating a new one.
 		switch t.Etype {
 		case TPTR32, TPTR64, TARRAY, TCHAN, TFUNC, TMAP, TSTRUCT:
-			slink := typelinkLSym(t)
-			dsymptrOffLSym(slink, 0, Linksym(s), 0)
-			ggloblLSym(slink, 4, int16(dupok|obj.RODATA))
+			keep = true
 		}
 	}
+	if keep {
+		slink := typelinkLSym(t)
+		dsymptrOffLSym(slink, 0, Linksym(s), 0)
+		ggloblLSym(slink, 4, int16(dupok|obj.RODATA))
+	}
 
 	return s
 }
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 42aaa5f4f0..55c9f4f9e2 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -457,8 +457,8 @@ const (
 	// R_ADDRMIPS (only used on mips64) resolves to a 32-bit external address,
 	// by loading the address into a register with two instructions (lui, ori).
 	R_ADDRMIPS
-	// R_ADDROFF resolves to an offset from the beginning of the section holding
-	// the data being relocated to the referenced symbol.
+	// R_ADDROFF resolves to a 32-bit offset from the beginning of the section
+	// holding the data being relocated to the referenced symbol.
 	R_ADDROFF
 	R_SIZE
 	R_CALL
@@ -492,11 +492,12 @@ const (
 	// should be linked into the final binary, even if there are no other
 	// direct references. (This is used for types reachable by reflection.)
 	R_USETYPE
-	// R_METHOD resolves to an *rtype for a method.
-	// It is used when linking from the uncommonType of another *rtype, and
-	// may be set to zero by the linker if it determines the method text is
-	// unreachable by the linked program.
-	R_METHOD
+	// R_METHODOFF resolves to a 32-bit offset from the beginning of the section
+	// holding the data being relocated to the referenced symbol.
+	// It is a variant of R_ADDROFF used when linking from the uncommonType of a
+	// *rtype, and may be set to zero by the linker if it determines the method
+	// text is unreachable by the linked program.
+	R_METHODOFF
 	R_POWER_TOC
 	R_GOTPCREL
 	// R_JMPMIPS (only used on mips64) resolves to non-PC-relative target address
diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go
index 51fae02ef0..c83a104a54 100644
--- a/src/cmd/link/internal/ld/deadcode.go
+++ b/src/cmd/link/internal/ld/deadcode.go
@@ -19,7 +19,7 @@ import (
 //
 // This flood fill is wrapped in logic for pruning unused methods.
 // All methods are mentioned by relocations on their receiver's *rtype.
-// These relocations are specially defined as R_METHOD by the compiler
+// These relocations are specially defined as R_METHODOFF by the compiler
 // so we can detect and manipulated them here.
 //
 // There are three ways a method of a reachable type can be invoked:
@@ -100,7 +100,7 @@ func deadcode(ctxt *Link) {
 		d.flood()
 	}
 
-	// Remove all remaining unreached R_METHOD relocations.
+	// Remove all remaining unreached R_METHODOFF relocations.
 	for _, m := range d.markableMethods {
 		for _, r := range m.r {
 			d.cleanupReloc(r)
@@ -167,7 +167,7 @@ var markextra = []string{
 type methodref struct {
 	m   methodsig
 	src *LSym     // receiver type symbol
-	r   [3]*Reloc // R_METHOD relocations to fields of runtime.method
+	r   [3]*Reloc // R_METHODOFF relocations to fields of runtime.method
 }
 
 func (m methodref) ifn() *LSym { return m.r[1].Sym }
@@ -190,7 +190,7 @@ type deadcodepass struct {
 
 func (d *deadcodepass) cleanupReloc(r *Reloc) {
 	if r.Sym.Attr.Reachable() {
-		r.Type = obj.R_ADDR
+		r.Type = obj.R_ADDROFF
 	} else {
 		if Debug['v'] > 1 {
 			fmt.Fprintf(d.ctxt.Bso, "removing method %s\n", r.Sym.Name)
@@ -217,7 +217,7 @@ func (d *deadcodepass) mark(s, parent *LSym) {
 func (d *deadcodepass) markMethod(m methodref) {
 	for _, r := range m.r {
 		d.mark(r.Sym, m.src)
-		r.Type = obj.R_ADDR
+		r.Type = obj.R_ADDROFF
 	}
 }
 
@@ -291,14 +291,14 @@ func (d *deadcodepass) flood() {
 			}
 		}
 
-		mpos := 0 // 0-3, the R_METHOD relocs of runtime.uncommontype
+		mpos := 0 // 0-3, the R_METHODOFF relocs of runtime.uncommontype
 		var methods []methodref
 		for i := 0; i < len(s.R); i++ {
 			r := &s.R[i]
 			if r.Sym == nil {
 				continue
 			}
-			if r.Type != obj.R_METHOD {
+			if r.Type != obj.R_METHODOFF {
 				d.mark(r.Sym, s)
 				continue
 			}
diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go
index 7daa8bc812..5fa8b4c81f 100644
--- a/src/cmd/link/internal/ld/decodesym.go
+++ b/src/cmd/link/internal/ld/decodesym.go
@@ -47,9 +47,9 @@ func decode_inuxi(p []byte, sz int) uint64 {
 	}
 }
 
-func commonsize() int      { return 6*SysArch.PtrSize + 8 }                 // runtime._type
-func structfieldSize() int { return 3 * SysArch.PtrSize }                   // runtime.structfield
-func uncommonSize() int    { return 2*SysArch.PtrSize + 2*SysArch.IntSize } // runtime.uncommontype
+func commonsize() int      { return 6*SysArch.PtrSize + 8 } // runtime._type
+func structfieldSize() int { return 3 * SysArch.PtrSize }   // runtime.structfield
+func uncommonSize() int    { return 2 * SysArch.PtrSize }   // runtime.uncommontype
 
 // Type.commonType.kind
 func decodetype_kind(s *LSym) uint8 {
@@ -341,12 +341,14 @@ func decodetype_methods(s *LSym) []methodsig {
 		// just Sizeof(rtype)
 	}
 
-	numMethods := int(decode_inuxi(s.P[off+2*SysArch.PtrSize:], SysArch.IntSize))
-	r := decode_reloc(s, int32(off+SysArch.PtrSize))
-	if r.Sym != s {
-		panic(fmt.Sprintf("method slice pointer in %s leads to a different symbol %s", s, r.Sym))
+	mcount := int(decode_inuxi(s.P[off+SysArch.PtrSize:], 2))
+	moff := int(decode_inuxi(s.P[off+SysArch.PtrSize+2:], 2))
+	off += moff          // offset to array of reflect.method values
+	var sizeofMethod int // sizeof reflect.method in program
+	if SysArch.PtrSize == 4 {
+		sizeofMethod = 4 * SysArch.PtrSize
+	} else {
+		sizeofMethod = 3 * SysArch.PtrSize
 	}
-	off = int(r.Add)                    // array of reflect.method values
-	sizeofMethod := 4 * SysArch.PtrSize // sizeof reflect.method in program
-	return decode_methodsig(s, off, sizeofMethod, numMethods)
+	return decode_methodsig(s, off, sizeofMethod, mcount)
 }
diff --git a/src/reflect/export_test.go b/src/reflect/export_test.go
index 037c953718..2769e0db40 100644
--- a/src/reflect/export_test.go
+++ b/src/reflect/export_test.go
@@ -90,7 +90,7 @@ func FirstMethodNameBytes(t Type) *byte {
 	if ut == nil {
 		panic("type has no methods")
 	}
-	m := ut.methods[0]
+	m := ut.methods()[0]
 	if *m.name.data(0)&(1<<2) == 0 {
 		panic("method name does not have pkgPath *string")
 	}
diff --git a/src/reflect/type.go b/src/reflect/type.go
index 7104fde60a..c7ed402be2 100644
--- a/src/reflect/type.go
+++ b/src/reflect/type.go
@@ -288,10 +288,10 @@ type typeAlg struct {
 
 // Method on non-interface type
 type method struct {
-	name name           // name of method
-	mtyp *rtype         // method type (without receiver)
-	ifn  unsafe.Pointer // fn used in interface call (one-word receiver)
-	tfn  unsafe.Pointer // fn used for normal method call
+	name name    // name of method
+	mtyp typeOff // method type (without receiver)
+	ifn  textOff // fn used in interface call (one-word receiver)
+	tfn  textOff // fn used for normal method call
 }
 
 // uncommonType is present only for types with names or methods
@@ -299,8 +299,9 @@ type method struct {
 // Using a pointer to this struct reduces the overall size required
 // to describe an unnamed type with no methods.
 type uncommonType struct {
-	pkgPath *string  // import path; nil for built-in types like int, string
-	methods []method // methods associated with type
+	pkgPath *string // import path; nil for built-in types like int, string
+	mcount  uint16  // number of methods
+	moff    uint16  // offset from this uncommontype to [mcount]method
 }
 
 // ChanDir represents a channel type's direction.
@@ -589,6 +590,10 @@ var kindNames = []string{
 	UnsafePointer: "unsafe.Pointer",
 }
 
+func (t *uncommonType) methods() []method {
+	return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff)))[:t.mcount:t.mcount]
+}
+
 func (t *uncommonType) PkgPath() string {
 	if t == nil || t.pkgPath == nil {
 		return ""
@@ -596,13 +601,55 @@ func (t *uncommonType) PkgPath() string {
 	return *t.pkgPath
 }
 
+// resolveTypeOff resolves an *rtype offset from a base type.
+// The (*rtype).typeOff method is a convenience wrapper for this function.
+// Implemented in the runtime package.
+func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer
+
+// resolveTextOff resolves an function pointer offset from a base type.
+// The (*rtype).textOff method is a convenience wrapper for this function.
+// Implemented in the runtime package.
+func resolveTextOff(rtype unsafe.Pointer, off int32) unsafe.Pointer
+
+// addReflectOff adds a pointer to the reflection lookup map in the runtime.
+// It returns a new ID that can be used as a typeOff or textOff, and will
+// be resolved correctly. Implemented in the runtime package.
+func addReflectOff(ptr unsafe.Pointer) int32
+
+// resolveReflectType adds a *rtype to the reflection lookup map in the runtime.
+// It returns a new typeOff that can be used to refer to the pointer.
+func resolveReflectType(t *rtype) typeOff {
+	return typeOff(addReflectOff(unsafe.Pointer(t)))
+}
+
+// resolveReflectText adds a function pointer to the reflection lookup map in
+// the runtime. It returns a new textOff that can be used to refer to the
+// pointer.
+func resolveReflectText(ptr unsafe.Pointer) textOff {
+	return textOff(addReflectOff(ptr))
+}
+
+type typeOff int32 // offset to an *rtype
+type textOff int32 // offset from top of text section
+
+func (t *rtype) typeOff(off typeOff) *rtype {
+	if off == 0 {
+		return nil
+	}
+	return (*rtype)(resolveTypeOff(unsafe.Pointer(t), int32(off)))
+}
+
+func (t *rtype) textOff(off textOff) unsafe.Pointer {
+	return resolveTextOff(unsafe.Pointer(t), int32(off))
+}
+
 func (t *rtype) uncommon() *uncommonType {
 	if t.tflag&tflagUncommon == 0 {
 		return nil
 	}
 	switch t.Kind() {
 	case Struct:
-		return &(*structTypeWithMethods)(unsafe.Pointer(t)).u
+		return &(*structTypeUncommon)(unsafe.Pointer(t)).u
 	case Ptr:
 		type u struct {
 			ptrType
@@ -688,7 +735,7 @@ func (t *rtype) NumMethod() int {
 	if ut == nil {
 		return 0
 	}
-	return len(ut.methods)
+	return int(ut.mcount)
 }
 
 func (t *rtype) Method(i int) (m Method) {
@@ -698,10 +745,10 @@ func (t *rtype) Method(i int) (m Method) {
 	}
 	ut := t.uncommon()
 
-	if ut == nil || i < 0 || i >= len(ut.methods) {
+	if ut == nil || i < 0 || i >= int(ut.mcount) {
 		panic("reflect: Method index out of range")
 	}
-	p := &ut.methods[i]
+	p := ut.methods()[i]
 	m.Name = p.name.name()
 	fl := flag(Func)
 	if !p.name.isExported() {
@@ -712,8 +759,9 @@ func (t *rtype) Method(i int) (m Method) {
 		m.PkgPath = *pkgPath
 		fl |= flagStickyRO
 	}
-	if p.mtyp != nil {
-		ft := (*funcType)(unsafe.Pointer(p.mtyp))
+	if p.mtyp != 0 {
+		mtyp := t.typeOff(p.mtyp)
+		ft := (*funcType)(unsafe.Pointer(mtyp))
 		in := make([]Type, 0, 1+len(ft.in()))
 		in = append(in, t)
 		for _, arg := range ft.in() {
@@ -723,9 +771,10 @@ func (t *rtype) Method(i int) (m Method) {
 		for _, ret := range ft.out() {
 			out = append(out, ret)
 		}
-		mt := FuncOf(in, out, p.mtyp.IsVariadic())
+		mt := FuncOf(in, out, ft.IsVariadic())
 		m.Type = mt
-		fn := unsafe.Pointer(&p.tfn)
+		tfn := t.textOff(p.tfn)
+		fn := unsafe.Pointer(&tfn)
 		m.Func = Value{mt.(*rtype), fn, fl}
 	}
 	m.Index = i
@@ -741,8 +790,9 @@ func (t *rtype) MethodByName(name string) (m Method, ok bool) {
 	if ut == nil {
 		return Method{}, false
 	}
-	for i := range ut.methods {
-		p := &ut.methods[i]
+	utmethods := ut.methods()
+	for i := 0; i < int(ut.mcount); i++ {
+		p := utmethods[i]
 		if p.name.name() == name {
 			return t.Method(i), true
 		}
@@ -1430,10 +1480,11 @@ func implements(T, V *rtype) bool {
 		return false
 	}
 	i := 0
-	for j := 0; j < len(v.methods); j++ {
+	vmethods := v.methods()
+	for j := 0; j < int(v.mcount); j++ {
 		tm := &t.methods[i]
-		vm := &v.methods[j]
-		if vm.name.name() == tm.name.name() && vm.mtyp == tm.typ {
+		vm := vmethods[j]
+		if vm.name.name() == tm.name.name() && V.typeOff(vm.mtyp) == tm.typ {
 			if i++; i >= len(t.methods) {
 				return true
 			}
@@ -2161,21 +2212,55 @@ func SliceOf(t Type) Type {
 	return cachePut(ckey, &slice.rtype)
 }
 
-// structTypeWithMethods is a structType created at runtime with StructOf.
-// It is needed to pin the []method slice from its associated uncommonType struct.
-// Keep in sync with the memory layout of structType.
-type structTypeWithMethods struct {
-	structType
-	u uncommonType
-}
-
 // The structLookupCache caches StructOf lookups.
 // StructOf does not share the common lookupCache since we need to pin
-// the *structType and its associated *uncommonType (especially the
-// []method slice field of that uncommonType.)
+// the memory associated with *structTypeFixedN.
 var structLookupCache struct {
 	sync.RWMutex
-	m map[uint32][]*structTypeWithMethods // keyed by hash calculated in StructOf
+	m map[uint32][]interface {
+		common() *rtype
+	} // keyed by hash calculated in StructOf
+}
+
+type structTypeUncommon struct {
+	structType
+	u uncommonType
+}
+
+// A *rtype representing a struct is followed directly in memory by an
+// array of method objects representing the methods attached to the
+// struct. To get the same layout for a run time generated type, we
+// need an array directly following the uncommonType memory. The types
+// structTypeFixed4, ...structTypeFixedN are used to do this.
+//
+// A similar strategy is used for funcTypeFixed4, ...funcTypeFixedN.
+
+// TODO(crawshaw): as these structTypeFixedN and funcTypeFixedN structs
+// have no methods, they could be defined at runtime using the StructOf
+// function.
+
+type structTypeFixed4 struct {
+	structType
+	u uncommonType
+	m [4]method
+}
+
+type structTypeFixed8 struct {
+	structType
+	u uncommonType
+	m [8]method
+}
+
+type structTypeFixed16 struct {
+	structType
+	u uncommonType
+	m [16]method
+}
+
+type structTypeFixed32 struct {
+	structType
+	u uncommonType
+	m [32]method
 }
 
 // StructOf returns the struct type containing fields.
@@ -2192,7 +2277,7 @@ func StructOf(fields []StructField) Type {
 		typalign   uint8
 		comparable = true
 		hashable   = true
-		typ        = new(structTypeWithMethods)
+		methods    []method
 
 		fs   = make([]structField, len(fields))
 		repr = make([]byte, 0, 64)
@@ -2269,7 +2354,6 @@ func StructOf(fields []StructField) Type {
 							}
 							return recv.Field(ifield).Method(imethod).Call(args)
 						})
-
 					} else {
 						tfn = MakeFunc(m.typ, func(in []Value) []Value {
 							var args []Value
@@ -2287,47 +2371,59 @@ func StructOf(fields []StructField) Type {
 							}
 							return recv.Field(ifield).Method(imethod).Call(args)
 						})
-
 					}
 
-					typ.u.methods = append(
-						typ.u.methods,
-						method{
-							name: m.name,
-							mtyp: m.typ,
-							ifn:  unsafe.Pointer(&ifn),
-							tfn:  unsafe.Pointer(&tfn),
-						},
-					)
+					methods = append(methods, method{
+						name: m.name,
+						mtyp: resolveReflectType(m.typ),
+						ifn:  resolveReflectText(unsafe.Pointer(&ifn)),
+						tfn:  resolveReflectText(unsafe.Pointer(&tfn)),
+					})
 				}
 			case Ptr:
 				ptr := (*ptrType)(unsafe.Pointer(ft))
 				if unt := ptr.uncommon(); unt != nil {
-					for _, m := range unt.methods {
+					for _, m := range unt.methods() {
 						if m.name.pkgPath() != nil {
 							// TODO(sbinet)
 							panic("reflect: embedded interface with unexported method(s) not implemented")
 						}
-						typ.u.methods = append(typ.u.methods, m)
+						methods = append(methods, method{
+							name: m.name,
+							mtyp: resolveReflectType(ptr.typeOff(m.mtyp)),
+							ifn:  resolveReflectText(ptr.textOff(m.ifn)),
+							tfn:  resolveReflectText(ptr.textOff(m.tfn)),
+						})
 					}
 				}
 				if unt := ptr.elem.uncommon(); unt != nil {
-					for _, m := range unt.methods {
+					for _, m := range unt.methods() {
 						if m.name.pkgPath() != nil {
 							// TODO(sbinet)
 							panic("reflect: embedded interface with unexported method(s) not implemented")
 						}
-						typ.u.methods = append(typ.u.methods, m)
+						methods = append(methods, method{
+							name: m.name,
+							mtyp: resolveReflectType(ptr.elem.typeOff(m.mtyp)),
+							ifn:  resolveReflectText(ptr.elem.textOff(m.ifn)),
+							tfn:  resolveReflectText(ptr.elem.textOff(m.tfn)),
+						})
 					}
 				}
 			default:
 				if unt := ft.uncommon(); unt != nil {
-					for _, m := range unt.methods {
+					for _, m := range unt.methods() {
 						if m.name.pkgPath() != nil {
 							// TODO(sbinet)
 							panic("reflect: embedded interface with unexported method(s) not implemented")
 						}
-						typ.u.methods = append(typ.u.methods, m)
+						methods = append(methods, method{
+							name: m.name,
+							mtyp: resolveReflectType(ft.typeOff(m.mtyp)),
+							ifn:  resolveReflectText(ft.textOff(m.ifn)),
+							tfn:  resolveReflectText(ft.textOff(m.tfn)),
+						})
+
 					}
 				}
 			}
@@ -2359,6 +2455,49 @@ func StructOf(fields []StructField) Type {
 
 		fs[i] = f
 	}
+
+	var typ *structType
+	var ut *uncommonType
+	var typPin interface {
+		common() *rtype
+	} // structTypeFixedN
+
+	switch {
+	case len(methods) == 0:
+		t := new(structTypeUncommon)
+		typ = &t.structType
+		ut = &t.u
+		typPin = t
+	case len(methods) <= 4:
+		t := new(structTypeFixed4)
+		typ = &t.structType
+		ut = &t.u
+		copy(t.m[:], methods)
+		typPin = t
+	case len(methods) <= 8:
+		t := new(structTypeFixed8)
+		typ = &t.structType
+		ut = &t.u
+		copy(t.m[:], methods)
+		typPin = t
+	case len(methods) <= 16:
+		t := new(structTypeFixed16)
+		typ = &t.structType
+		ut = &t.u
+		copy(t.m[:], methods)
+		typPin = t
+	case len(methods) <= 32:
+		t := new(structTypeFixed32)
+		typ = &t.structType
+		ut = &t.u
+		copy(t.m[:], methods)
+		typPin = t
+	default:
+		panic("reflect.StructOf: too many methods")
+	}
+	ut.mcount = uint16(len(methods))
+	ut.moff = uint16(unsafe.Sizeof(uncommonType{}))
+
 	if len(fs) > 0 {
 		repr = append(repr, ' ')
 	}
@@ -2372,15 +2511,16 @@ func StructOf(fields []StructField) Type {
 	// Make the struct type.
 	var istruct interface{} = struct{}{}
 	prototype := *(**structType)(unsafe.Pointer(&istruct))
-	typ.structType = *prototype
-	typ.structType.fields = fs
+	*typ = *prototype
+	typ.fields = fs
 
 	// Look in cache
 	structLookupCache.RLock()
-	for _, t := range structLookupCache.m[hash] {
-		if haveIdenticalUnderlyingType(&typ.rtype, &t.rtype) {
+	for _, st := range structLookupCache.m[hash] {
+		t := st.common()
+		if haveIdenticalUnderlyingType(&typ.rtype, t) {
 			structLookupCache.RUnlock()
-			return &t.rtype
+			return t
 		}
 	}
 	structLookupCache.RUnlock()
@@ -2389,11 +2529,14 @@ func StructOf(fields []StructField) Type {
 	structLookupCache.Lock()
 	defer structLookupCache.Unlock()
 	if structLookupCache.m == nil {
-		structLookupCache.m = make(map[uint32][]*structTypeWithMethods)
+		structLookupCache.m = make(map[uint32][]interface {
+			common() *rtype
+		})
 	}
-	for _, t := range structLookupCache.m[hash] {
-		if haveIdenticalUnderlyingType(&typ.rtype, &t.rtype) {
-			return &t.rtype
+	for _, st := range structLookupCache.m[hash] {
+		t := st.common()
+		if haveIdenticalUnderlyingType(&typ.rtype, t) {
+			return t
 		}
 	}
 
@@ -2403,9 +2546,8 @@ func StructOf(fields []StructField) Type {
 			// even if 't' wasn't a structType with methods, we should be ok
 			// as the 'u uncommonType' field won't be accessed except when
 			// tflag&tflagUncommon is set.
-			tt := (*structTypeWithMethods)(unsafe.Pointer(t))
-			structLookupCache.m[hash] = append(structLookupCache.m[hash], tt)
-			return &tt.rtype
+			structLookupCache.m[hash] = append(structLookupCache.m[hash], t)
+			return t
 		}
 	}
 
@@ -2414,7 +2556,7 @@ func StructOf(fields []StructField) Type {
 	typ.size = size
 	typ.align = typalign
 	typ.fieldAlign = typalign
-	if len(typ.u.methods) > 0 {
+	if len(methods) > 0 {
 		typ.tflag |= tflagUncommon
 	}
 	if !hasPtr {
@@ -2514,7 +2656,7 @@ func StructOf(fields []StructField) Type {
 		typ.kind &^= kindDirectIface
 	}
 
-	structLookupCache.m[hash] = append(structLookupCache.m[hash], typ)
+	structLookupCache.m[hash] = append(structLookupCache.m[hash], typPin)
 	return &typ.rtype
 }
 
@@ -2533,6 +2675,7 @@ func runtimeStructField(field StructField) structField {
 		}
 	}
 
+	_ = resolveReflectType(field.Type.common())
 	return structField{
 		name:   newName(field.Name, string(field.Tag), field.PkgPath, exported),
 		typ:    field.Type.common(),
diff --git a/src/reflect/value.go b/src/reflect/value.go
index 262545d973..d72c14e9e1 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -566,15 +566,16 @@ func methodReceiver(op string, v Value, methodIndex int) (rcvrtype, t *rtype, fn
 	} else {
 		rcvrtype = v.typ
 		ut := v.typ.uncommon()
-		if ut == nil || uint(i) >= uint(len(ut.methods)) {
+		if ut == nil || uint(i) >= uint(ut.mcount) {
 			panic("reflect: internal error: invalid method index")
 		}
-		m := &ut.methods[i]
+		m := ut.methods()[i]
 		if !m.name.isExported() {
 			panic("reflect: " + op + " of unexported method")
 		}
-		fn = unsafe.Pointer(&m.ifn)
-		t = m.mtyp
+		ifn := v.typ.textOff(m.ifn)
+		fn = unsafe.Pointer(&ifn)
+		t = v.typ.typeOff(m.mtyp)
 	}
 	return
 }
@@ -1687,11 +1688,11 @@ func (v Value) Type() Type {
 	}
 	// Method on concrete type.
 	ut := v.typ.uncommon()
-	if ut == nil || uint(i) >= uint(len(ut.methods)) {
+	if ut == nil || uint(i) >= uint(ut.mcount) {
 		panic("reflect: internal error: invalid method index")
 	}
-	m := &ut.methods[i]
-	return m.mtyp
+	m := ut.methods()[i]
+	return v.typ.typeOff(m.mtyp)
 }
 
 // Uint returns v's underlying value, as a uint64.
diff --git a/src/runtime/iface.go b/src/runtime/iface.go
index a4c962fb7a..700bdc2f48 100644
--- a/src/runtime/iface.go
+++ b/src/runtime/iface.go
@@ -93,7 +93,8 @@ func additab(m *itab, locked, canfail bool) {
 	// so can iterate over both in lock step;
 	// the loop is O(ni+nt) not O(ni*nt).
 	ni := len(inter.mhdr)
-	nt := len(x.mhdr)
+	nt := int(x.mcount)
+	xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
 	j := 0
 	for k := 0; k < ni; k++ {
 		i := &inter.mhdr[k]
@@ -104,15 +105,16 @@ func additab(m *itab, locked, canfail bool) {
 			ipkg = inter.pkgpath
 		}
 		for ; j < nt; j++ {
-			t := &x.mhdr[j]
-			if t.mtyp == itype && t.name.name() == iname {
+			t := &xmhdr[j]
+			if typ.typeOff(t.mtyp) == itype && t.name.name() == iname {
 				pkgPath := t.name.pkgPath()
 				if pkgPath == nil {
 					pkgPath = x.pkgpath
 				}
 				if t.name.isExported() || pkgPath == ipkg {
 					if m != nil {
-						*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = t.ifn
+						ifn := typ.textOff(t.ifn)
+						*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
 					}
 					goto nextimethod
 				}
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index 1a9dbd6c53..98a986cd63 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -435,9 +435,10 @@ func schedinit() {
 	tracebackinit()
 	moduledataverify()
 	stackinit()
-	itabsinit()
 	mallocinit()
 	mcommoninit(_g_.m)
+	typelinksinit()
+	itabsinit()
 
 	msigsave(_g_.m)
 	initSigmask = _g_.m.sigmask
diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go
index e1956569fd..02aeedaf75 100644
--- a/src/runtime/runtime1.go
+++ b/src/runtime/runtime1.go
@@ -486,3 +486,36 @@ func reflect_typelinks() ([]unsafe.Pointer, [][]int32) {
 	}
 	return sections, ret
 }
+
+// reflect_resolveTypeOff resolves an *rtype offset from a base type.
+//go:linkname reflect_resolveTypeOff reflect.resolveTypeOff
+func reflect_resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer {
+	return unsafe.Pointer((*_type)(rtype).typeOff(typeOff(off)))
+}
+
+// reflect_resolveTextOff resolves an function pointer offset from a base type.
+//go:linkname reflect_resolveTextOff reflect.resolveTextOff
+func reflect_resolveTextOff(rtype unsafe.Pointer, off int32) unsafe.Pointer {
+	return (*_type)(rtype).textOff(textOff(off))
+
+}
+
+// reflect_addReflectOff adds a pointer to the reflection offset lookup map.
+//go:linkname reflect_addReflectOff reflect.addReflectOff
+func reflect_addReflectOff(ptr unsafe.Pointer) int32 {
+	lock(&reflectOffs.lock)
+	if reflectOffs.m == nil {
+		reflectOffs.m = make(map[int32]unsafe.Pointer)
+		reflectOffs.minv = make(map[unsafe.Pointer]int32)
+		reflectOffs.next = -1
+	}
+	id, found := reflectOffs.minv[ptr]
+	if !found {
+		id = reflectOffs.next
+		reflectOffs.next-- // use negative offsets as IDs to aid debugging
+		reflectOffs.m[id] = ptr
+		reflectOffs.minv[ptr] = id
+	}
+	unlock(&reflectOffs.lock)
+	return id
+}
diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go
index 8c70f22c1f..2df390253a 100644
--- a/src/runtime/symtab.go
+++ b/src/runtime/symtab.go
@@ -137,6 +137,8 @@ type moduledata struct {
 
 	gcdatamask, gcbssmask bitvector
 
+	typemap map[typeOff]*_type // offset to *_rtype in previous module
+
 	next *moduledata
 }
 
diff --git a/src/runtime/type.go b/src/runtime/type.go
index fbf6f9973c..86131d3ff3 100644
--- a/src/runtime/type.go
+++ b/src/runtime/type.go
@@ -131,6 +131,92 @@ func (t *_type) name() string {
 	return t._string[i+1:]
 }
 
+// reflectOffs holds type offsets defined at run time by the reflect package.
+//
+// When a type is defined at run time, its *rtype data lives on the heap.
+// There are a wide range of possible addresses the heap may use, that
+// may not be representable as a 32-bit offset. Moreover the GC may
+// one day start moving heap memory, in which case there is no stable
+// offset that can be defined.
+//
+// To provide stable offsets, we add pin *rtype objects in a global map
+// and treat the offset as an identifier. We use negative offsets that
+// do not overlap with any compile-time module offsets.
+//
+// Entries are created by reflect.addReflectOff.
+var reflectOffs struct {
+	lock mutex
+	next int32
+	m    map[int32]unsafe.Pointer
+	minv map[unsafe.Pointer]int32
+}
+
+func (t *_type) typeOff(off typeOff) *_type {
+	if off == 0 {
+		return nil
+	}
+	base := uintptr(unsafe.Pointer(t))
+	var md *moduledata
+	for next := &firstmoduledata; next != nil; next = next.next {
+		if base >= next.types && base < next.etypes {
+			md = next
+			break
+		}
+	}
+	if md == nil {
+		lock(&reflectOffs.lock)
+		res := reflectOffs.m[int32(off)]
+		unlock(&reflectOffs.lock)
+		if res == nil {
+			println("runtime: typeOff", hex(off), "base", hex(base), "not in ranges:")
+			for next := &firstmoduledata; next != nil; next = next.next {
+				println("\ttypes", hex(next.types), "etypes", hex(next.etypes))
+			}
+			throw("runtime: type offset base pointer out of range")
+		}
+		return (*_type)(res)
+	}
+	if t := md.typemap[off]; t != nil {
+		return t
+	}
+	res := md.types + uintptr(off)
+	if res > md.etypes {
+		println("runtime: typeOff", hex(off), "out of range", hex(md.types), "-", hex(md.etypes))
+		throw("runtime: type offset out of range")
+	}
+	return (*_type)(unsafe.Pointer(res))
+}
+
+func (t *_type) textOff(off textOff) unsafe.Pointer {
+	base := uintptr(unsafe.Pointer(t))
+	var md *moduledata
+	for next := &firstmoduledata; next != nil; next = next.next {
+		if base >= next.types && base < next.etypes {
+			md = next
+			break
+		}
+	}
+	if md == nil {
+		lock(&reflectOffs.lock)
+		res := reflectOffs.m[int32(off)]
+		unlock(&reflectOffs.lock)
+		if res == nil {
+			println("runtime: textOff", hex(off), "base", hex(base), "not in ranges:")
+			for next := &firstmoduledata; next != nil; next = next.next {
+				println("\ttypes", hex(next.types), "etypes", hex(next.etypes))
+			}
+			throw("runtime: text offset base pointer out of range")
+		}
+		return res
+	}
+	res := md.text + uintptr(off)
+	if res > md.etext {
+		println("runtime: textOff", hex(off), "out of range", hex(md.text), "-", hex(md.etext))
+		throw("runtime: text offset out of range")
+	}
+	return unsafe.Pointer(res)
+}
+
 func (t *functype) in() []*_type {
 	// See funcType in reflect/type.go for details on data layout.
 	uadd := uintptr(unsafe.Sizeof(functype{}))
@@ -154,16 +240,20 @@ func (t *functype) dotdotdot() bool {
 	return t.outCount&(1<<15) != 0
 }
 
+type typeOff int32
+type textOff int32
+
 type method struct {
 	name name
-	mtyp *_type
-	ifn  unsafe.Pointer
-	tfn  unsafe.Pointer
+	mtyp typeOff
+	ifn  textOff
+	tfn  textOff
 }
 
 type uncommontype struct {
 	pkgpath *string
-	mhdr    []method
+	mcount  uint16 // number of methods
+	moff    uint16 // offset from this uncommontype to [mcount]method
 }
 
 type imethod struct {
@@ -270,6 +360,18 @@ func (n *name) name() (s string) {
 	return s
 }
 
+func (n *name) tag() (s string) {
+	tl := n.tagLen()
+	if tl == 0 {
+		return ""
+	}
+	nl := n.nameLen()
+	hdr := (*stringStruct)(unsafe.Pointer(&s))
+	hdr.str = unsafe.Pointer(n.data(3 + nl + 2))
+	hdr.len = tl
+	return s
+}
+
 func (n *name) pkgPath() *string {
 	if *n.data(0)&(1<<2) == 0 {
 		return nil
@@ -281,3 +383,200 @@ func (n *name) pkgPath() *string {
 	off = int(round(uintptr(off), sys.PtrSize))
 	return *(**string)(unsafe.Pointer(n.data(off)))
 }
+
+// typelinksinit scans the types from extra modules and builds the
+// moduledata typemap used to de-duplicate type pointers.
+func typelinksinit() {
+	if firstmoduledata.next == nil {
+		return
+	}
+	typehash := make(map[uint32][]*_type)
+
+	modules := []*moduledata{}
+	for md := &firstmoduledata; md != nil; md = md.next {
+		modules = append(modules, md)
+	}
+	prev, modules := modules[len(modules)-1], modules[:len(modules)-1]
+	for len(modules) > 0 {
+		// Collect types from the previous module into typehash.
+	collect:
+		for _, tl := range prev.typelinks {
+			var t *_type
+			if prev.typemap == nil {
+				t = (*_type)(unsafe.Pointer(prev.types + uintptr(tl)))
+			} else {
+				t = prev.typemap[typeOff(tl)]
+			}
+			// Add to typehash if not seen before.
+			tlist := typehash[t.hash]
+			for _, tcur := range tlist {
+				if tcur == t {
+					continue collect
+				}
+			}
+			typehash[t.hash] = append(tlist, t)
+		}
+
+		// If any of this module's typelinks match a type from a
+		// prior module, prefer that prior type by adding the offset
+		// to this module's typemap.
+		md := modules[len(modules)-1]
+		md.typemap = make(map[typeOff]*_type, len(md.typelinks))
+		for _, tl := range md.typelinks {
+			t := (*_type)(unsafe.Pointer(md.types + uintptr(tl)))
+			for _, candidate := range typehash[t.hash] {
+				if typesEqual(t, candidate) {
+					t = candidate
+					break
+				}
+			}
+			md.typemap[typeOff(tl)] = t
+		}
+
+		prev, modules = md, modules[:len(modules)-1]
+	}
+}
+
+// typesEqual reports whether two types are equal.
+//
+// Everywhere in the runtime and reflect packages, it is assumed that
+// there is exactly one *_type per Go type, so that pointer equality
+// can be used to test if types are equal. There is one place that
+// breaks this assumption: buildmode=shared. In this case a type can
+// appear as two different pieces of memory. This is hidden from the
+// runtime and reflect package by the per-module typemap built in
+// typelinksinit. It uses typesEqual to map types from later modules
+// back into earlier ones.
+//
+// Only typelinksinit needs this function.
+func typesEqual(t, v *_type) bool {
+	if t == v {
+		return true
+	}
+	kind := t.kind & kindMask
+	if kind != v.kind&kindMask {
+		return false
+	}
+	if t._string != v._string {
+		return false
+	}
+	ut := t.uncommon()
+	uv := v.uncommon()
+	if ut != nil || uv != nil {
+		if ut == nil || uv == nil {
+			return false
+		}
+		if !pkgPathEqual(ut.pkgpath, uv.pkgpath) {
+			return false
+		}
+	}
+	if kindBool <= kind && kind <= kindComplex128 {
+		return true
+	}
+	switch kind {
+	case kindString, kindUnsafePointer:
+		return true
+	case kindArray:
+		at := (*arraytype)(unsafe.Pointer(t))
+		av := (*arraytype)(unsafe.Pointer(v))
+		return typesEqual(at.elem, av.elem) && at.len == av.len
+	case kindChan:
+		ct := (*chantype)(unsafe.Pointer(t))
+		cv := (*chantype)(unsafe.Pointer(v))
+		return ct.dir == cv.dir && typesEqual(ct.elem, cv.elem)
+	case kindFunc:
+		ft := (*functype)(unsafe.Pointer(t))
+		fv := (*functype)(unsafe.Pointer(v))
+		if ft.outCount != fv.outCount || ft.inCount != fv.inCount {
+			return false
+		}
+		tin, vin := ft.in(), fv.in()
+		for i := 0; i < len(tin); i++ {
+			if !typesEqual(tin[i], vin[i]) {
+				return false
+			}
+		}
+		tout, vout := ft.out(), fv.out()
+		for i := 0; i < len(tout); i++ {
+			if !typesEqual(tout[i], vout[i]) {
+				return false
+			}
+		}
+		return true
+	case kindInterface:
+		it := (*interfacetype)(unsafe.Pointer(t))
+		iv := (*interfacetype)(unsafe.Pointer(v))
+		if !pkgPathEqual(it.pkgpath, iv.pkgpath) {
+			return false
+		}
+		if len(it.mhdr) != len(iv.mhdr) {
+			return false
+		}
+		for i := range it.mhdr {
+			tm := &it.mhdr[i]
+			vm := &iv.mhdr[i]
+			if tm.name.name() != vm.name.name() {
+				return false
+			}
+			if !pkgPathEqual(tm.name.pkgPath(), vm.name.pkgPath()) {
+				return false
+			}
+			if !typesEqual(tm._type, vm._type) {
+				return false
+			}
+		}
+		return true
+	case kindMap:
+		mt := (*maptype)(unsafe.Pointer(t))
+		mv := (*maptype)(unsafe.Pointer(v))
+		return typesEqual(mt.key, mv.key) && typesEqual(mt.elem, mv.elem)
+	case kindPtr:
+		pt := (*ptrtype)(unsafe.Pointer(t))
+		pv := (*ptrtype)(unsafe.Pointer(v))
+		return typesEqual(pt.elem, pv.elem)
+	case kindSlice:
+		st := (*slicetype)(unsafe.Pointer(t))
+		sv := (*slicetype)(unsafe.Pointer(v))
+		return typesEqual(st.elem, sv.elem)
+	case kindStruct:
+		st := (*structtype)(unsafe.Pointer(t))
+		sv := (*structtype)(unsafe.Pointer(v))
+		if len(st.fields) != len(sv.fields) {
+			return false
+		}
+		for i := range st.fields {
+			tf := &st.fields[i]
+			vf := &sv.fields[i]
+			if tf.name.name() != vf.name.name() {
+				return false
+			}
+			if !pkgPathEqual(tf.name.pkgPath(), vf.name.pkgPath()) {
+				return false
+			}
+			if !typesEqual(tf.typ, vf.typ) {
+				return false
+			}
+			if tf.name.tag() != vf.name.tag() {
+				return false
+			}
+			if tf.offset != vf.offset {
+				return false
+			}
+		}
+		return true
+	default:
+		println("runtime: impossible type kind", kind)
+		throw("runtime: impossible type kind")
+		return false
+	}
+}
+
+func pkgPathEqual(p, q *string) bool {
+	if p == q {
+		return true
+	}
+	if p == nil || q == nil {
+		return false
+	}
+	return *p == *q
+}
-- 
cgit v1.3


From 66afbf1010fa492fb9a266f9019f707bd09f066d Mon Sep 17 00:00:00 2001
From: David Crawshaw 
Date: Wed, 13 Apr 2016 10:06:12 -0400
Subject: cmd/link: use a switch for name prefix switching

Minor cleanup.

Change-Id: I7574f58a7e55c2bb798ebe9c7c98d36b8c258fb8
Reviewed-on: https://go-review.googlesource.com/21982
Run-TryBot: David Crawshaw 
TryBot-Result: Gobot Gobot 
Reviewed-by: Brad Fitzpatrick 
---
 src/cmd/link/internal/ld/symtab.go | 61 +++++++++++++-------------------------
 1 file changed, 21 insertions(+), 40 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go
index 678ed38730..60bec0d6c9 100644
--- a/src/cmd/link/internal/ld/symtab.go
+++ b/src/cmd/link/internal/ld/symtab.go
@@ -384,33 +384,19 @@ func symtab() {
 		symtyperel = s
 	}
 
-	s = Linklookup(Ctxt, "go.string.*", 0)
-	s.Type = obj.SGOSTRING
-	s.Attr |= AttrLocal
-	s.Size = 0
-	s.Attr |= AttrReachable
-	symgostring := s
-
-	s = Linklookup(Ctxt, "go.string.hdr.*", 0)
-	s.Type = obj.SGOSTRINGHDR
-	s.Attr |= AttrLocal
-	s.Size = 0
-	s.Attr |= AttrReachable
-	symgostringhdr := s
-
-	s = Linklookup(Ctxt, "go.func.*", 0)
-	s.Type = obj.SGOFUNC
-	s.Attr |= AttrLocal
-	s.Size = 0
-	s.Attr |= AttrReachable
-	symgofunc := s
-
-	s = Linklookup(Ctxt, "runtime.gcbits.*", 0)
-	s.Type = obj.SGCBITS
-	s.Attr |= AttrLocal
-	s.Size = 0
-	s.Attr |= AttrReachable
-	symgcbits := s
+	groupSym := func(name string, t int16) *LSym {
+		s := Linklookup(Ctxt, name, 0)
+		s.Type = t
+		s.Size = 0
+		s.Attr |= AttrLocal | AttrReachable
+		return s
+	}
+	var (
+		symgostring    = groupSym("go.string.*", obj.SGOSTRING)
+		symgostringhdr = groupSym("go.string.hdr.*", obj.SGOSTRINGHDR)
+		symgofunc      = groupSym("go.func.*", obj.SGOFUNC)
+		symgcbits      = groupSym("runtime.gcbits.*", obj.SGCBITS)
+	)
 
 	symtypelink := Linklookup(Ctxt, "runtime.typelink", 0)
 	symtypelink.Type = obj.STYPELINK
@@ -436,7 +422,8 @@ func symtab() {
 			continue
 		}
 
-		if strings.HasPrefix(s.Name, "type.") {
+		switch {
+		case strings.HasPrefix(s.Name, "type."):
 			if !DynlinkingGo() {
 				s.Attr |= AttrHidden
 			}
@@ -447,23 +434,20 @@ func symtab() {
 				s.Type = obj.STYPE
 				s.Outer = symtype
 			}
-		}
 
-		if strings.HasPrefix(s.Name, "go.typelink.") {
+		case strings.HasPrefix(s.Name, "go.typelink."):
 			ntypelinks++
 			s.Type = obj.STYPELINK
 			s.Attr |= AttrHidden
 			s.Outer = symtypelink
-		}
 
-		if strings.HasPrefix(s.Name, "go.itablink.") {
+		case strings.HasPrefix(s.Name, "go.itablink."):
 			nitablinks++
 			s.Type = obj.SITABLINK
 			s.Attr |= AttrHidden
 			s.Outer = symitablink
-		}
 
-		if strings.HasPrefix(s.Name, "go.string.") {
+		case strings.HasPrefix(s.Name, "go.string."):
 			s.Type = obj.SGOSTRING
 			s.Attr |= AttrHidden
 			s.Outer = symgostring
@@ -471,21 +455,18 @@ func symtab() {
 				s.Type = obj.SGOSTRINGHDR
 				s.Outer = symgostringhdr
 			}
-		}
 
-		if strings.HasPrefix(s.Name, "runtime.gcbits.") {
+		case strings.HasPrefix(s.Name, "runtime.gcbits."):
 			s.Type = obj.SGCBITS
 			s.Attr |= AttrHidden
 			s.Outer = symgcbits
-		}
 
-		if strings.HasPrefix(s.Name, "go.func.") {
+		case strings.HasPrefix(s.Name, "go.func."):
 			s.Type = obj.SGOFUNC
 			s.Attr |= AttrHidden
 			s.Outer = symgofunc
-		}
 
-		if strings.HasPrefix(s.Name, "gcargs.") || strings.HasPrefix(s.Name, "gclocals.") || strings.HasPrefix(s.Name, "gclocals·") {
+		case strings.HasPrefix(s.Name, "gcargs."), strings.HasPrefix(s.Name, "gclocals."), strings.HasPrefix(s.Name, "gclocals·"):
 			s.Type = obj.SGOFUNC
 			s.Attr |= AttrHidden
 			s.Outer = symgofunc
-- 
cgit v1.3


From 6b85a45edc94786c7669823ee47a6ce1156d6a9a Mon Sep 17 00:00:00 2001
From: David Chase 
Date: Mon, 21 Mar 2016 11:32:04 -0400
Subject: cmd/compile: move spills to loop exits when easy.

For call-free inner loops.

Revised statistics:
  85 inner loop spills sunk
 341 inner loop spills remaining
1162 inner loop spills that were candidates for sinking
     ended up completely register allocated
 119 inner loop spills could have been sunk were used in
     "shuffling" at the bottom of the loop.
   1 inner loop spill not sunk because the register assigned
     changed between def and exit,

 Understanding how to make an inner loop definition not be
 a candidate for from-memory shuffling (to force the shuffle
 code to choose some other value) should pick up some of the
 119 other spills disqualified for this reason.

 Modified the stats printing based on feedback from Austin.

Change-Id: If3fb9b5d5a028f42ccc36c4e3d9e0da39db5ca60
Reviewed-on: https://go-review.googlesource.com/21037
Reviewed-by: Keith Randall 
Run-TryBot: David Chase 
TryBot-Result: Gobot Gobot 
---
 src/cmd/compile/internal/ssa/likelyadjust.go | 142 +++++++++++++-
 src/cmd/compile/internal/ssa/regalloc.go     | 284 ++++++++++++++++++++++++++-
 src/cmd/compile/internal/ssa/sparsemap.go    |  16 ++
 3 files changed, 435 insertions(+), 7 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/ssa/likelyadjust.go b/src/cmd/compile/internal/ssa/likelyadjust.go
index 76251bdd14..2f52c4c6e6 100644
--- a/src/cmd/compile/internal/ssa/likelyadjust.go
+++ b/src/cmd/compile/internal/ssa/likelyadjust.go
@@ -11,11 +11,24 @@ import (
 type loop struct {
 	header *Block // The header node of this (reducible) loop
 	outer  *loop  // loop containing this loop
-	// Next two fields not currently used, but cheap to maintain,
-	// and aid in computation of inner-ness and list of blocks.
-	nBlocks      int32 // Number of blocks in this loop but not within inner loops
-	isInner      bool  // True if never discovered to contain a loop
-	containsCall bool  // if any block in this loop or any loop it contains is a BlockCall or BlockDefer
+
+	// By default, children exits, and depth are not initialized.
+	children []*loop  // loops nested directly within this loop. Initialized by assembleChildren().
+	exits    []*Block // exits records blocks reached by exits from this loop. Initialized by findExits().
+
+	// Loops aren't that common, so rather than force regalloc to keep
+	// a map or slice for its data, just put it here.
+	spills  []*Value
+	scratch int32
+
+	// Next three fields used by regalloc and/or
+	// aid in computation of inner-ness and list of blocks.
+	nBlocks int32 // Number of blocks in this loop but not within inner loops
+	depth   int16 // Nesting depth of the loop; 1 is outermost. Initialized by calculateDepths().
+	isInner bool  // True if never discovered to contain a loop
+
+	// register allocation uses this.
+	containsCall bool // if any block in this loop or any loop it contains is a BlockCall or BlockDefer
 }
 
 // outerinner records that outer contains inner
@@ -48,6 +61,9 @@ type loopnest struct {
 	po    []*Block
 	sdom  sparseTree
 	loops []*loop
+
+	// Record which of the lazily initialized fields have actually been initialized.
+	initializedChildren, initializedDepth, initializedExits bool
 }
 
 func min8(a, b int8) int8 {
@@ -295,6 +311,35 @@ func loopnestfor(f *Func) *loopnest {
 			innermost.nBlocks++
 		}
 	}
+
+	ln := &loopnest{f: f, b2l: b2l, po: po, sdom: sdom, loops: loops}
+
+	// Curious about the loopiness? "-d=ssa/likelyadjust/stats"
+	if f.pass.stats > 0 && len(loops) > 0 {
+		ln.assembleChildren()
+		ln.calculateDepths()
+		ln.findExits()
+
+		// Note stats for non-innermost loops are slightly flawed because
+		// they don't account for inner loop exits that span multiple levels.
+
+		for _, l := range loops {
+			x := len(l.exits)
+			cf := 0
+			if !l.containsCall {
+				cf = 1
+			}
+			inner := 0
+			if l.isInner {
+				inner++
+			}
+
+			f.logStat("loopstats:",
+				l.depth, "depth", x, "exits",
+				inner, "is_inner", cf, "is_callfree", l.nBlocks, "n_blocks")
+		}
+	}
+
 	if f.pass.debug > 1 && len(loops) > 0 {
 		fmt.Printf("Loops in %s:\n", f.Name)
 		for _, l := range loops {
@@ -314,5 +359,90 @@ func loopnestfor(f *Func) *loopnest {
 		}
 		fmt.Print("\n")
 	}
-	return &loopnest{f, b2l, po, sdom, loops}
+	return ln
+}
+
+// assembleChildren initializes the children field of each
+// loop in the nest.  Loop A is a child of loop B if A is
+// directly nested within B (based on the reducible-loops
+// detection above)
+func (ln *loopnest) assembleChildren() {
+	if ln.initializedChildren {
+		return
+	}
+	for _, l := range ln.loops {
+		if l.outer != nil {
+			l.outer.children = append(l.outer.children, l)
+		}
+	}
+	ln.initializedChildren = true
+}
+
+// calculateDepths uses the children field of loops
+// to determine the nesting depth (outer=1) of each
+// loop.  This is helpful for finding exit edges.
+func (ln *loopnest) calculateDepths() {
+	if ln.initializedDepth {
+		return
+	}
+	ln.assembleChildren()
+	for _, l := range ln.loops {
+		if l.outer == nil {
+			l.setDepth(1)
+		}
+	}
+	ln.initializedDepth = true
+}
+
+// findExits uses loop depth information to find the
+// exits from a loop.
+func (ln *loopnest) findExits() {
+	if ln.initializedExits {
+		return
+	}
+	ln.calculateDepths()
+	b2l := ln.b2l
+	for _, b := range ln.po {
+		l := b2l[b.ID]
+		if l != nil && len(b.Succs) == 2 {
+			sl := b2l[b.Succs[0].ID]
+			if recordIfExit(l, sl, b.Succs[0]) {
+				continue
+			}
+			sl = b2l[b.Succs[1].ID]
+			if recordIfExit(l, sl, b.Succs[1]) {
+				continue
+			}
+		}
+	}
+	ln.initializedExits = true
+}
+
+// recordIfExit checks sl (the loop containing b) to see if it
+// is outside of loop l, and if so, records b as an exit block
+// from l and returns true.
+func recordIfExit(l, sl *loop, b *Block) bool {
+	if sl != l {
+		if sl == nil || sl.depth <= l.depth {
+			l.exits = append(l.exits, b)
+			return true
+		}
+		// sl is not nil, and is deeper than l
+		// it's possible for this to be a goto into an irreducible loop made from gotos.
+		for sl.depth > l.depth {
+			sl = sl.outer
+		}
+		if sl != l {
+			l.exits = append(l.exits, b)
+			return true
+		}
+	}
+	return false
+}
+
+func (l *loop) setDepth(d int16) {
+	l.depth = d
+	for _, c := range l.children {
+		c.setDepth(d + 1)
+	}
 }
diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go
index dfae8612d6..d1de3646d9 100644
--- a/src/cmd/compile/internal/ssa/regalloc.go
+++ b/src/cmd/compile/internal/ssa/regalloc.go
@@ -91,6 +91,13 @@
 // will have no use (so don't run deadcode after regalloc!).
 // TODO: maybe we should introduce these extra phis?
 
+// Additional not-quite-SSA output occurs when spills are sunk out
+// of loops to the targets of exit edges from the loop.  Before sinking,
+// there is one spill site (one StoreReg) targeting stack slot X, after
+// sinking there may be multiple spill sites targeting stack slot X,
+// with no phi functions at any join points reachable by the multiple
+// spill sites.
+
 package ssa
 
 import (
@@ -100,7 +107,8 @@ import (
 )
 
 const (
-	logSpills = iota
+	moveSpills = iota
+	logSpills
 	regDebug
 	stackDebug
 )
@@ -176,6 +184,7 @@ type valState struct {
 	uses              *use    // list of uses in this block
 	spill             *Value  // spilled copy of the Value
 	spillUsed         bool
+	spillUsedShuffle  bool     // true if used in shuffling, after ordinary uses
 	needReg           bool     // cached value of !v.Type.IsMemory() && !v.Type.IsVoid() && !.v.Type.IsFlags()
 	rematerializeable bool     // cached value of v.rematerializeable()
 	desired           register // register we want value to be in, if any
@@ -243,6 +252,15 @@ type regAllocState struct {
 	loopnest *loopnest
 }
 
+type spillToSink struct {
+	spill *Value // Spill instruction to move (a StoreReg)
+	dests int32  // Bitmask indicating exit blocks from loop in which spill/val is defined. 1<= 0; i-- {
+							v := ss.Values[i]
+							entryCandidates.remove(v.ID) // Cannot be an issue, only keeps the sets smaller.
+							for _, a := range v.Args {
+								if s.isLoopSpillCandidate(loop, a) {
+									entryCandidates.setBit(a.ID, uint(whichExit))
+								}
+							}
+						}
+					}
+
+					for _, e := range loop.spills {
+						whichblocks := entryCandidates.get(e.ID)
+						oldSpill := s.values[e.ID].spill
+						if whichblocks != 0 && whichblocks != -1 { // -1 = not in map.
+							toSink = append(toSink, spillToSink{spill: oldSpill, dests: whichblocks})
+						}
+					}
+
+				} // loop is inner etc
+				loop.scratch = 0 // Don't leave a mess, just in case.
+				loop.spills = nil
+			} // if scratch == nBlocks
+		} // if loop is not nil
+
 		// Clear any final uses.
 		// All that is left should be the pseudo-uses added for values which
 		// are live at the end of b.
@@ -1110,9 +1241,16 @@ func (s *regAllocState) regalloc(f *Func) {
 			// Constants, SP, SB, ...
 			continue
 		}
+		loop := s.loopForBlock(spill.Block)
+		if loop != nil {
+			nSpillsInner--
+		}
+
 		spill.Args[0].Uses--
 		f.freeValue(spill)
+		nSpills--
 	}
+
 	for _, b := range f.Blocks {
 		i := 0
 		for _, v := range b.Values {
@@ -1127,12 +1265,153 @@ func (s *regAllocState) regalloc(f *Func) {
 		// Not important now because this is the last phase that manipulates Values
 	}
 
+	// Must clear these out before any potential recycling, though that's
+	// not currently implemented.
+	for i, ts := range toSink {
+		vsp := ts.spill
+		if vsp.Op == OpInvalid { // This spill was completely eliminated
+			toSink[i].spill = nil
+		}
+	}
+
 	// Anything that didn't get a register gets a stack location here.
 	// (StoreReg, stack-based phis, inputs, ...)
 	stacklive := stackalloc(s.f, s.spillLive)
 
 	// Fix up all merge edges.
 	s.shuffle(stacklive)
+
+	// Insert moved spills (that have not been marked invalid above)
+	// at start of appropriate block and remove the originals from their
+	// location within loops.  Notice that this can break SSA form;
+	// if a spill is sunk to multiple exits, there will be no phi for that
+	// spill at a join point downstream of those two exits, though the
+	// two spills will target the same stack slot.  Notice also that this
+	// takes place after stack allocation, so the stack allocator does
+	// not need to process these malformed flow graphs.
+sinking:
+	for _, ts := range toSink {
+		vsp := ts.spill
+		if vsp == nil { // This spill was completely eliminated
+			nSpillsSunkUnused++
+			continue sinking
+		}
+		e := ts.spilledValue()
+		if s.values[e.ID].spillUsedShuffle {
+			nSpillsNotSunkLateUse++
+			continue sinking
+		}
+
+		// move spills to a better (outside of loop) block.
+		// This would be costly if it occurred very often, but it doesn't.
+		b := vsp.Block
+		loop := s.loopnest.b2l[b.ID]
+		dests := ts.dests
+
+		// Pre-check to be sure that spilled value is still in expected register on all exits where live.
+	check_val_still_in_reg:
+		for i := uint(0); i < 32 && dests != 0; i++ {
+
+			if dests&(1< 1 {
+				panic("Should be impossible given critical edges removed")
+			}
+			p := d.Preds[0] // block in loop exiting to d.
+
+			endregs := s.endRegs[p.ID]
+			for _, regrec := range endregs {
+				if regrec.v == e && regrec.r != noRegister && regrec.c == e { // TODO: regrec.c != e implies different spill possible.
+					continue check_val_still_in_reg
+				}
+			}
+			// If here, the register assignment was lost down at least one exit and it can't be sunk
+			if s.f.pass.debug > moveSpills {
+				s.f.Config.Warnl(e.Line, "lost register assignment for spill %v in %v at exit %v to %v",
+					vsp, b, p, d)
+			}
+			nSpillsChanged++
+			continue sinking
+		}
+
+		nSpillsSunk++
+		nSpillsInner--
+		// don't update nSpills, since spill is only moved, and if it is duplicated, the spills-on-a-path is not increased.
+
+		dests = ts.dests
+
+		// remove vsp from b.Values
+		i := 0
+		for _, w := range b.Values {
+			if vsp == w {
+				continue
+			}
+			b.Values[i] = w
+			i++
+		}
+		b.Values = b.Values[:i]
+
+		for i := uint(0); i < 32 && dests != 0; i++ {
+
+			if dests&(1< moveSpills {
+				s.f.Config.Warnl(e.Line, "moved spill %v in %v for %v to %v in %v",
+					vsp, b, e, vspnew, d)
+			}
+
+			f.setHome(vspnew, f.getHome(vsp.ID)) // copy stack home
+
+			// shuffle vspnew to the beginning of its block
+			copy(d.Values[1:], d.Values[0:len(d.Values)-1])
+			d.Values[0] = vspnew
+		}
+	}
+
+	if f.pass.stats > 0 {
+		f.logStat("spills_info",
+			nSpills, "spills", nSpillsInner, "inner_spills_remaining", nSpillsSunk, "inner_spills_sunk", nSpillsSunkUnused, "inner_spills_unused", nSpillsNotSunkLateUse, "inner_spills_shuffled", nSpillsChanged, "inner_spills_changed")
+	}
+}
+
+// isLoopSpillCandidate indicates whether the spill for v satisfies preliminary
+// spill-sinking conditions just after the last block of loop has been processed.
+// In particular:
+//   v needs a register.
+//   v's spill is not (YET) used.
+//   v's definition is within loop.
+// The spill may be used in the future, either by an outright use
+// in the code, or by shuffling code inserted after stack allocation.
+// Outright uses cause sinking; shuffling (within the loop) inhibits it.
+func (s *regAllocState) isLoopSpillCandidate(loop *loop, v *Value) bool {
+	return s.values[v.ID].needReg && !s.values[v.ID].spillUsed && s.loopnest.b2l[v.Block.ID] == loop
+}
+
+// lateSpillUse notes a late (after stack allocation) use of spill c
+// This will inhibit spill sinking.
+func (s *regAllocState) lateSpillUse(c *Value) {
+	// TODO investigate why this is necessary.
+	// It appears that an outside-the-loop use of
+	// an otherwise sinkable spill makes the spill
+	// a candidate for shuffling, when it would not
+	// otherwise have been the case (spillUsed was not
+	// true when isLoopSpillCandidate was called, yet
+	// it was shuffled).  Such shuffling cuts the amount
+	// of spill sinking by more than half (in make.bash)
+	v := s.orig[c.ID]
+	if v != nil {
+		s.values[v.ID].spillUsedShuffle = true
+	}
 }
 
 // shuffle fixes up all the merge edges (those going into blocks of indegree > 1).
@@ -1307,6 +1586,7 @@ func (e *edgeState) process() {
 		if _, isReg := loc.(*Register); isReg {
 			c = e.p.NewValue1(c.Line, OpCopy, c.Type, c)
 		} else {
+			e.s.lateSpillUse(c)
 			c = e.p.NewValue1(c.Line, OpLoadReg, c.Type, c)
 		}
 		e.set(r, vid, c, false)
@@ -1395,6 +1675,7 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value) bool {
 			}
 		} else {
 			if dstReg {
+				e.s.lateSpillUse(c)
 				x = e.p.NewValue1(c.Line, OpLoadReg, c.Type, c)
 			} else {
 				// mem->mem. Use temp register.
@@ -1412,6 +1693,7 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value) bool {
 				e.erase(loc)
 
 				r := e.findRegFor(c.Type)
+				e.s.lateSpillUse(c)
 				t := e.p.NewValue1(c.Line, OpLoadReg, c.Type, c)
 				e.set(r, vid, t, false)
 				x = e.p.NewValue1(c.Line, OpStoreReg, loc.(LocalSlot).Type, t)
diff --git a/src/cmd/compile/internal/ssa/sparsemap.go b/src/cmd/compile/internal/ssa/sparsemap.go
index 6c0043b230..0211a70f09 100644
--- a/src/cmd/compile/internal/ssa/sparsemap.go
+++ b/src/cmd/compile/internal/ssa/sparsemap.go
@@ -32,6 +32,8 @@ func (s *sparseMap) contains(k ID) bool {
 	return i < len(s.dense) && s.dense[i].key == k
 }
 
+// get returns the value for key k, or -1 if k does
+// not appear in the map.
 func (s *sparseMap) get(k ID) int32 {
 	i := s.sparse[k]
 	if i < len(s.dense) && s.dense[i].key == k {
@@ -50,6 +52,20 @@ func (s *sparseMap) set(k ID, v int32) {
 	s.sparse[k] = len(s.dense) - 1
 }
 
+// setBit sets the v'th bit of k's value, where 0 <= v < 32
+func (s *sparseMap) setBit(k ID, v uint) {
+	if v >= 32 {
+		panic("bit index too large.")
+	}
+	i := s.sparse[k]
+	if i < len(s.dense) && s.dense[i].key == k {
+		s.dense[i].val |= 1 << v
+		return
+	}
+	s.dense = append(s.dense, sparseEntry{k, 1 << v})
+	s.sparse[k] = len(s.dense) - 1
+}
+
 func (s *sparseMap) remove(k ID) {
 	i := s.sparse[k]
 	if i < len(s.dense) && s.dense[i].key == k {
-- 
cgit v1.3


From 7d0d1222477ce50736ee24adb38c1f487d0801d9 Mon Sep 17 00:00:00 2001
From: Robert Griesemer 
Date: Tue, 12 Apr 2016 18:00:04 -0700
Subject: cmd/compile: move more compiler specifics into compiler specific
 export section

Instead of indicating with each function signature if it has an inlineable
body, collect all functions in order and export function bodies with function
index in platform-specific section.

Moves this compiler specific information out of the platform-independent
export data section, and removes an int value for all functions w/o body.
Also simplifies the code a bit.

Change-Id: I8b2d7299dbe81f2706be49ecfb9d9f7da85fd854
Reviewed-on: https://go-review.googlesource.com/21939
Reviewed-by: Matthew Dempsky 
---
 src/cmd/compile/internal/gc/bexport.go |  63 +++++++++++---------
 src/cmd/compile/internal/gc/bimport.go | 104 ++++++++++++++-------------------
 src/go/internal/gcimporter/bimport.go  |   2 -
 3 files changed, 80 insertions(+), 89 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go
index cb438d7573..e780bcf577 100644
--- a/src/cmd/compile/internal/gc/bexport.go
+++ b/src/cmd/compile/internal/gc/bexport.go
@@ -124,10 +124,11 @@ const exportVersion = "v0"
 const exportInlined = true // default: true
 
 type exporter struct {
-	out      *bufio.Writer
-	pkgIndex map[*Pkg]int
-	typIndex map[*Type]int
-	inlined  []*Func
+	out *bufio.Writer
+
+	pkgIndex map[*Pkg]int  // pkg -> pkg index in order of appearance
+	typIndex map[*Type]int // type -> type index in order of appearance
+	funcList []*Func       // in order of appearance
 
 	// debugging support
 	written int // bytes written
@@ -322,27 +323,39 @@ func export(out *bufio.Writer, trace bool) int {
 	// --- inlined function bodies ---
 
 	if p.trace {
-		p.tracef("\n--- inlined function bodies ---\n[ ")
+		p.tracef("\n--- inlined function bodies ---\n")
 		if p.indent != 0 {
 			Fatalf("exporter: incorrect indentation")
 		}
 	}
 
-	// write inlined function bodies
-	p.int(len(p.inlined))
-	if p.trace {
-		p.tracef("]\n")
-	}
-	for _, f := range p.inlined {
-		if p.trace {
-			p.tracef("\n----\nfunc { %s }\n", Hconv(f.Inl, FmtSharp))
-		}
-		p.stmtList(f.Inl)
-		if p.trace {
-			p.tracef("\n")
+	// write inlineable function bodies
+	objcount = 0
+	for i, f := range p.funcList {
+		if f != nil {
+			// function has inlineable body:
+			// write index and body
+			if p.trace {
+				p.tracef("\n----\nfunc { %s }\n", Hconv(f.Inl, FmtSharp))
+			}
+			p.int(i)
+			p.stmtList(f.Inl)
+			if p.trace {
+				p.tracef("\n")
+			}
+			objcount++
 		}
 	}
 
+	// indicate end of list
+	if p.trace {
+		p.tracef("\n")
+	}
+	p.tag(-1) // invalid index terminates list
+
+	// for self-verification only (redundant)
+	p.int(objcount)
+
 	if p.trace {
 		p.tracef("\n--- end ---\n")
 	}
@@ -443,10 +456,9 @@ func (p *exporter) obj(sym *Sym) {
 			p.paramList(sig.Params(), inlineable)
 			p.paramList(sig.Results(), inlineable)
 
-			index := -1
+			var f *Func
 			if inlineable {
-				index = len(p.inlined)
-				p.inlined = append(p.inlined, sym.Def.Func)
+				f = sym.Def.Func
 				// TODO(gri) re-examine reexportdeplist:
 				// Because we can trivially export types
 				// in-place, we don't need to collect types
@@ -454,9 +466,9 @@ func (p *exporter) obj(sym *Sym) {
 				// With an adjusted reexportdeplist used only
 				// by the binary exporter, we can also avoid
 				// the global exportlist.
-				reexportdeplist(sym.Def.Func.Inl)
+				reexportdeplist(f.Inl)
 			}
-			p.int(index)
+			p.funcList = append(p.funcList, f)
 		} else {
 			// variable
 			p.tag(varTag)
@@ -563,13 +575,12 @@ func (p *exporter) typ(t *Type) {
 			p.paramList(sig.Params(), inlineable)
 			p.paramList(sig.Results(), inlineable)
 
-			index := -1
+			var f *Func
 			if inlineable {
-				index = len(p.inlined)
-				p.inlined = append(p.inlined, mfn.Func)
+				f = mfn.Func
 				reexportdeplist(mfn.Func.Inl)
 			}
-			p.int(index)
+			p.funcList = append(p.funcList, f)
 		}
 
 		if p.trace && len(methods) > 0 {
diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go
index 9cebafcaef..2e80b9f81d 100644
--- a/src/cmd/compile/internal/gc/bimport.go
+++ b/src/cmd/compile/internal/gc/bimport.go
@@ -23,9 +23,10 @@ type importer struct {
 	in       *bufio.Reader
 	buf      []byte   // for reading strings
 	bufarray [64]byte // initial underlying array for buf, large enough to avoid allocation when compiling std lib
-	pkgList  []*Pkg
-	typList  []*Type
-	inlined  []*Node // functions with pending inlined function bodies
+
+	pkgList  []*Pkg  // in order of appearance
+	typList  []*Type // in order of appearance
+	funcList []*Node // in order of appearance; nil entry means already declared
 
 	// debugging support
 	debugFormat bool
@@ -107,21 +108,35 @@ func Import(in *bufio.Reader) {
 		Fatalf("importer: got %d objects; want %d", objcount, count)
 	}
 
-	// read inlined functions bodies
+	// read inlineable functions bodies
 	if dclcontext != PEXTERN {
 		Fatalf("importer: unexpected context %d", dclcontext)
 	}
 
-	bcount := p.int() // consistency check only
-	if bcount != len(p.inlined) {
-		Fatalf("importer: expected %d inlined function bodies; got %d", bcount, len(p.inlined))
-	}
-	for _, f := range p.inlined {
+	objcount = 0
+	for i0 := -1; ; {
+		i := p.int() // index of function with inlineable body
+		if i < 0 {
+			break
+		}
+
+		// don't process the same function twice
+		if i <= i0 {
+			Fatalf("importer: index not increasing: %d <= %d", i, i0)
+		}
+		i0 = i
+
 		if Funcdepth != 0 {
 			Fatalf("importer: unexpected Funcdepth %d", Funcdepth)
 		}
-		if f != nil {
-			// function body not yet imported - read body and set it
+
+		// Note: In the original code, funchdr and funcbody are called for
+		// all functions (that were not yet imported). Now, we are calling
+		// them only for functions with inlineable bodies. funchdr does
+		// parameter renaming which doesn't matter if we don't have a body.
+
+		if f := p.funcList[i]; f != nil {
+			// function not yet imported - read body and set it
 			funchdr(f)
 			f.Func.Inl.Set(p.stmtList())
 			funcbody(f)
@@ -131,6 +146,13 @@ func Import(in *bufio.Reader) {
 			p.stmtList()
 			dclcontext = PEXTERN
 		}
+
+		objcount++
+	}
+
+	// self-verification
+	if count := p.int(); count != objcount {
+		Fatalf("importer: got %d functions; want %d", objcount, count)
 	}
 
 	if dclcontext != PEXTERN {
@@ -214,47 +236,23 @@ func (p *importer) obj(tag int) {
 		sym := p.qualifiedName()
 		params := p.paramList()
 		result := p.paramList()
-		inl := p.int()
 
 		sig := functype(nil, params, result)
 		importsym(sym, ONAME)
 		if sym.Def != nil && sym.Def.Op == ONAME {
-			if Eqtype(sig, sym.Def.Type) {
-				// function was imported before (via another import)
-				dclcontext = PDISCARD // since we skip funchdr below
-			} else {
+			// function was imported before (via another import)
+			if !Eqtype(sig, sym.Def.Type) {
 				Fatalf("importer: inconsistent definition for func %v during import\n\t%v\n\t%v", sym, sym.Def.Type, sig)
 			}
-		}
-
-		var n *Node
-		if dclcontext != PDISCARD {
-			n = newfuncname(sym)
-			n.Type = sig
-			declare(n, PFUNC)
-			if inl < 0 {
-				funchdr(n)
-			}
-		}
-
-		if inl >= 0 {
-			// function has inlined body - collect for later
-			if inl != len(p.inlined) {
-				Fatalf("importer: inlined index = %d; want %d", inl, len(p.inlined))
-			}
-			p.inlined = append(p.inlined, n)
-		}
-
-		// parser.go:hidden_import
-		if dclcontext == PDISCARD {
-			dclcontext = PEXTERN // since we skip the funcbody below
+			p.funcList = append(p.funcList, nil)
 			break
 		}
 
-		if inl < 0 {
-			funcbody(n)
-		}
-		importlist = append(importlist, n) // TODO(gri) may only be needed for inlineable functions
+		n := newfuncname(sym)
+		n.Type = sig
+		declare(n, PFUNC)
+		p.funcList = append(p.funcList, n)
+		importlist = append(importlist, n)
 
 		if Debug['E'] > 0 {
 			fmt.Printf("import [%q] func %v \n", importpkg.Path, n)
@@ -316,23 +314,13 @@ func (p *importer) typ() *Type {
 			recv := p.paramList() // TODO(gri) do we need a full param list for the receiver?
 			params := p.paramList()
 			result := p.paramList()
-			inl := p.int()
 
 			n := methodname1(newname(sym), recv[0].Right)
 			n.Type = functype(recv[0], params, result)
 			checkwidth(n.Type)
 			addmethod(sym, n.Type, tsym.Pkg, false, false)
-			if inl < 0 {
-				funchdr(n)
-			}
-
-			if inl >= 0 {
-				// method has inlined body - collect for later
-				if inl != len(p.inlined) {
-					Fatalf("importer: inlined index = %d; want %d", inl, len(p.inlined))
-				}
-				p.inlined = append(p.inlined, n)
-			}
+			p.funcList = append(p.funcList, n)
+			importlist = append(importlist, n)
 
 			// (comment from parser.go)
 			// inl.C's inlnode in on a dotmeth node expects to find the inlineable body as
@@ -341,12 +329,6 @@ func (p *importer) typ() *Type {
 			// this back link here we avoid special casing there.
 			n.Type.SetNname(n)
 
-			// parser.go:hidden_import
-			if inl < 0 {
-				funcbody(n)
-			}
-			importlist = append(importlist, n) // TODO(gri) may only be needed for inlineable functions
-
 			if Debug['E'] > 0 {
 				fmt.Printf("import [%q] meth %v \n", importpkg.Path, n)
 				if Debug['m'] > 2 && len(n.Func.Inl.Slice()) != 0 {
diff --git a/src/go/internal/gcimporter/bimport.go b/src/go/internal/gcimporter/bimport.go
index a9d678b021..81af064b88 100644
--- a/src/go/internal/gcimporter/bimport.go
+++ b/src/go/internal/gcimporter/bimport.go
@@ -186,7 +186,6 @@ func (p *importer) obj(tag int) {
 		params, isddd := p.paramList()
 		result, _ := p.paramList()
 		sig := types.NewSignature(nil, params, result, isddd)
-		p.int() // read and discard index of inlined function body
 		p.declare(types.NewFunc(token.NoPos, pkg, name, sig))
 
 	default:
@@ -269,7 +268,6 @@ func (p *importer) typ(parent *types.Package) types.Type {
 			recv, _ := p.paramList() // TODO(gri) do we need a full param list for the receiver?
 			params, isddd := p.paramList()
 			result, _ := p.paramList()
-			p.int() // read and discard index of inlined function body
 
 			sig := types.NewSignature(recv.At(0), params, result, isddd)
 			t0.AddMethod(types.NewFunc(token.NoPos, parent, name, sig))
-- 
cgit v1.3


From eb79f21c48915454b372de7fee2c6b86d52ea0bc Mon Sep 17 00:00:00 2001
From: Robert Griesemer 
Date: Tue, 12 Apr 2016 21:58:44 -0700
Subject: cmd/compile, go/importer: minor cleanups

Change-Id: Ic7a1fb0dbbf108052c970a4a830269a5673df7df
Reviewed-on: https://go-review.googlesource.com/21963
Reviewed-by: Matthew Dempsky 
---
 src/cmd/compile/internal/gc/bexport.go |  5 ++--
 src/cmd/compile/internal/gc/bimport.go | 10 +++++---
 src/go/internal/gcimporter/bimport.go  | 46 ++++++++++++++--------------------
 3 files changed, 27 insertions(+), 34 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go
index e780bcf577..59a85c2f23 100644
--- a/src/cmd/compile/internal/gc/bexport.go
+++ b/src/cmd/compile/internal/gc/bexport.go
@@ -59,9 +59,8 @@ Encoding format:
 
 The export data starts with a single byte indicating the encoding format
 (compact, or with debugging information), followed by a version string
-(so we can evolve the encoding if need be), the name of the imported
-package, and a string containing platform-specific information for that
-package.
+(so we can evolve the encoding if need be), and then the package object
+for the exported package (with an empty path).
 
 After this header, two lists of objects and the list of inlined function
 bodies follows.
diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go
index 2e80b9f81d..4a93b5a91d 100644
--- a/src/cmd/compile/internal/gc/bimport.go
+++ b/src/cmd/compile/internal/gc/bimport.go
@@ -59,9 +59,6 @@ func Import(in *bufio.Reader) {
 
 	// read package data
 	p.pkg()
-	if p.pkgList[0] != importpkg {
-		Fatalf("importer: imported package not found in pkgList[0]")
-	}
 
 	// defer some type-checking until all types are read in completely
 	// (parser.go:import_package)
@@ -193,7 +190,12 @@ func (p *importer) pkg() *Pkg {
 		Fatalf("importer: bad path in import: %q", path)
 	}
 
-	// an empty path denotes the package we are currently importing
+	// an empty path denotes the package we are currently importing;
+	// it must be the first package we see
+	if (path == "") != (len(p.pkgList) == 0) {
+		panic(fmt.Sprintf("package path %q for pkg index %d", path, len(p.pkgList)))
+	}
+
 	pkg := importpkg
 	if path != "" {
 		pkg = mkpkg(path)
diff --git a/src/go/internal/gcimporter/bimport.go b/src/go/internal/gcimporter/bimport.go
index 81af064b88..7a7bc871f4 100644
--- a/src/go/internal/gcimporter/bimport.go
+++ b/src/go/internal/gcimporter/bimport.go
@@ -16,12 +16,15 @@ import (
 )
 
 type importer struct {
-	imports  map[string]*types.Package
-	data     []byte
+	imports map[string]*types.Package
+	data    []byte
+	path    string
+
 	buf      []byte   // for reading strings
 	bufarray [64]byte // initial underlying array for buf, large enough to avoid allocation when compiling std lib
-	pkgList  []*types.Package
-	typList  []types.Type
+
+	pkgList []*types.Package
+	typList []types.Type
 
 	debugFormat bool
 	read        int // bytes read
@@ -35,6 +38,7 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i
 	p := importer{
 		imports: imports,
 		data:    data,
+		path:    path,
 	}
 	p.buf = p.bufarray[:]
 
@@ -58,25 +62,7 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i
 	p.typList = append(p.typList, predeclared...)
 
 	// read package data
-	// TODO(gri) clean this up
-	i := p.tagOrIndex()
-	if i != packageTag {
-		panic(fmt.Sprintf("package tag expected, got %d", i))
-	}
-	name := p.string()
-	if s := p.string(); s != "" {
-		panic(fmt.Sprintf("empty path expected, got %s", s))
-	}
-	pkg := p.imports[path]
-	if pkg == nil {
-		pkg = types.NewPackage(path, name)
-		p.imports[path] = pkg
-	}
-	p.pkgList = append(p.pkgList, pkg)
-
-	if debug && p.pkgList[0] != pkg {
-		panic("imported packaged not found in pkgList[0]")
-	}
+	pkg := p.pkg()
 
 	// read objects of phase 1 only (see cmd/compiler/internal/gc/bexport.go)
 	objcount := 0
@@ -91,7 +77,7 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i
 
 	// self-verification
 	if count := p.int(); count != objcount {
-		panic(fmt.Sprintf("importer: got %d objects; want %d", objcount, count))
+		panic(fmt.Sprintf("got %d objects; want %d", objcount, count))
 	}
 
 	// ignore compiler-specific import data
@@ -135,16 +121,22 @@ func (p *importer) pkg() *types.Package {
 		panic("empty package name in import")
 	}
 
-	// we should never see an empty import path
-	if path == "" {
-		panic("empty import path")
+	// an empty path denotes the package we are currently importing;
+	// it must be the first package we see
+	if (path == "") != (len(p.pkgList) == 0) {
+		panic(fmt.Sprintf("package path %q for pkg index %d", path, len(p.pkgList)))
 	}
 
 	// if the package was imported before, use that one; otherwise create a new one
+	if path == "" {
+		path = p.path
+	}
 	pkg := p.imports[path]
 	if pkg == nil {
 		pkg = types.NewPackage(path, name)
 		p.imports[path] = pkg
+	} else if pkg.Name() != name {
+		panic(fmt.Sprintf("conflicting names %s and %s for package %q", pkg.Name(), name, path))
 	}
 	p.pkgList = append(p.pkgList, pkg)
 
-- 
cgit v1.3


From da6205b67e844503152b3be7bbb1a25c76cbbce2 Mon Sep 17 00:00:00 2001
From: Dmitry Vyukov 
Date: Fri, 8 Apr 2016 17:56:36 +0200
Subject: cmd/pprof/internal/profile: always subtract 1 from PCs

Go runtime never emits PCs that are not a return address
(except for cpu profiler).

Change-Id: I08d9dc5c7c71e23f34f2f0c16f8baeeb4f64fcd6
Reviewed-on: https://go-review.googlesource.com/21735
Reviewed-by: Ian Lance Taylor 
---
 src/cmd/internal/pprof/profile/legacy_profile.go | 41 ++++++++----------------
 1 file changed, 13 insertions(+), 28 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/internal/pprof/profile/legacy_profile.go b/src/cmd/internal/pprof/profile/legacy_profile.go
index e1f24c4c6d..3d4da6b4d7 100644
--- a/src/cmd/internal/pprof/profile/legacy_profile.go
+++ b/src/cmd/internal/pprof/profile/legacy_profile.go
@@ -110,11 +110,8 @@ func parseGoCount(b []byte) (*Profile, error) {
 			if err != nil {
 				return nil, errMalformed
 			}
-			// Adjust all frames by -1 (except the leaf) to land on top of
-			// the call instruction.
-			if len(locs) > 0 {
-				addr--
-			}
+			// Adjust all frames by -1 to land on the call instruction.
+			addr--
 			loc := locations[addr]
 			if loc == nil {
 				loc = &Location{
@@ -291,11 +288,8 @@ func ParseTracebacks(b []byte) (*Profile, error) {
 		if s, addrs := extractHexAddresses(l); len(s) > 0 {
 			for _, addr := range addrs {
 				// Addresses from stack traces point to the next instruction after
-				// each call. Adjust by -1 to land somewhere on the actual call
-				// (except for the leaf, which is not a call).
-				if len(sloc) > 0 {
-					addr--
-				}
+				// each call. Adjust by -1 to land somewhere on the actual call.
+				addr--
 				loc := locs[addr]
 				if locs[addr] == nil {
 					loc = &Location{
@@ -568,13 +562,10 @@ func parseHeap(b []byte) (p *Profile, err error) {
 			return nil, err
 		}
 		var sloc []*Location
-		for i, addr := range addrs {
+		for _, addr := range addrs {
 			// Addresses from stack traces point to the next instruction after
-			// each call. Adjust by -1 to land somewhere on the actual call
-			// (except for the leaf, which is not a call).
-			if i > 0 {
-				addr--
-			}
+			// each call. Adjust by -1 to land somewhere on the actual call.
+			addr--
 			loc := locs[addr]
 			if locs[addr] == nil {
 				loc = &Location{
@@ -776,13 +767,10 @@ func parseContention(b []byte) (p *Profile, err error) {
 			return nil, err
 		}
 		var sloc []*Location
-		for i, addr := range addrs {
+		for _, addr := range addrs {
 			// Addresses from stack traces point to the next instruction after
-			// each call. Adjust by -1 to land somewhere on the actual call
-			// (except for the leaf, which is not a call).
-			if i > 0 {
-				addr--
-			}
+			// each call. Adjust by -1 to land somewhere on the actual call.
+			addr--
 			loc := locs[addr]
 			if locs[addr] == nil {
 				loc = &Location{
@@ -919,13 +907,10 @@ func parseThread(b []byte) (*Profile, error) {
 		}
 
 		var sloc []*Location
-		for i, addr := range addrs {
+		for _, addr := range addrs {
 			// Addresses from stack traces point to the next instruction after
-			// each call. Adjust by -1 to land somewhere on the actual call
-			// (except for the leaf, which is not a call).
-			if i > 0 {
-				addr--
-			}
+			// each call. Adjust by -1 to land somewhere on the actual call.
+			addr--
 			loc := locs[addr]
 			if locs[addr] == nil {
 				loc = &Location{
-- 
cgit v1.3


From 3ea7cfabbb0549d62d524e4ad30cb464af250fde Mon Sep 17 00:00:00 2001
From: Todd Neal 
Date: Wed, 13 Apr 2016 08:51:46 -0400
Subject: cmd/compile: sort partitions by dom to speed up cse

We do two O(n) scans of all values in an eqclass when computing
substitutions for CSE.

In unfortunate cases, like those found in #15112, we can have a large
eqclass composed of values found in blocks none of whom dominate the
other.  This leads to O(n^2) behavior. The elements are removed one at a
time, with O(n) scans each time.

This CL removes the linear scan by sorting the eqclass so that dominant
values will be sorted first.  As long as we also ensure we don't disturb
the sort order, then we no longer need to scan for the maximally
dominant value.

For the code in issue #15112:

Before:
real    1m26.094s
user    1m30.776s
sys     0m1.125s

Aefter:
real    0m52.099s
user    0m56.829s
sys     0m1.092s

Updates #15112

Change-Id: Ic4f8680ed172e716232436d31963209c146ef850
Reviewed-on: https://go-review.googlesource.com/21981
Reviewed-by: Josh Bleecher Snyder 
Run-TryBot: Todd Neal 
TryBot-Result: Gobot Gobot 
---
 src/cmd/compile/internal/ssa/cse.go        | 32 ++++++++++++++++++++----------
 src/cmd/compile/internal/ssa/sparsetree.go | 12 +++++++++++
 2 files changed, 33 insertions(+), 11 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/ssa/cse.go b/src/cmd/compile/internal/ssa/cse.go
index c12d51e50c..76db9d5467 100644
--- a/src/cmd/compile/internal/ssa/cse.go
+++ b/src/cmd/compile/internal/ssa/cse.go
@@ -137,23 +137,20 @@ func cse(f *Func) {
 	// if v and w are in the same equivalence class and v dominates w.
 	rewrite := make([]*Value, f.NumValues())
 	for _, e := range partition {
+		sort.Sort(sortbyentry{e, f.sdom})
 		for len(e) > 1 {
-			// Find a maximal dominant element in e
+			// e is sorted by entry value so maximal dominant element should be
+			// found first in the slice
 			v := e[0]
-			for _, w := range e[1:] {
-				if f.sdom.isAncestorEq(w.Block, v.Block) {
-					v = w
-				}
-			}
-
+			e = e[1:]
 			// Replace all elements of e which v dominates
 			for i := 0; i < len(e); {
 				w := e[i]
-				if w == v {
-					e, e[i] = e[:len(e)-1], e[len(e)-1]
-				} else if f.sdom.isAncestorEq(v.Block, w.Block) {
+				if f.sdom.isAncestorEq(v.Block, w.Block) {
 					rewrite[w.ID] = v
-					e, e[i] = e[:len(e)-1], e[len(e)-1]
+					// retain the sort order
+					copy(e[i:], e[i+1:])
+					e = e[:len(e)-1]
 				} else {
 					i++
 				}
@@ -308,3 +305,16 @@ func (sv sortvalues) Less(i, j int) bool {
 	// Sort by value ID last to keep the sort result deterministic.
 	return v.ID < w.ID
 }
+
+type sortbyentry struct {
+	a    []*Value // array of values
+	sdom sparseTree
+}
+
+func (sv sortbyentry) Len() int      { return len(sv.a) }
+func (sv sortbyentry) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
+func (sv sortbyentry) Less(i, j int) bool {
+	v := sv.a[i]
+	w := sv.a[j]
+	return sv.sdom.maxdomorder(v.Block) < sv.sdom.maxdomorder(w.Block)
+}
diff --git a/src/cmd/compile/internal/ssa/sparsetree.go b/src/cmd/compile/internal/ssa/sparsetree.go
index cae91e7ddb..45c7897496 100644
--- a/src/cmd/compile/internal/ssa/sparsetree.go
+++ b/src/cmd/compile/internal/ssa/sparsetree.go
@@ -116,6 +116,9 @@ func (t sparseTree) Child(x *Block) *Block {
 
 // isAncestorEq reports whether x is an ancestor of or equal to y.
 func (t sparseTree) isAncestorEq(x, y *Block) bool {
+	if x == y {
+		return true
+	}
 	xx := &t[x.ID]
 	yy := &t[y.ID]
 	return xx.entry <= yy.entry && yy.exit <= xx.exit
@@ -123,7 +126,16 @@ func (t sparseTree) isAncestorEq(x, y *Block) bool {
 
 // isAncestor reports whether x is a strict ancestor of y.
 func (t sparseTree) isAncestor(x, y *Block) bool {
+	if x == y {
+		return false
+	}
 	xx := &t[x.ID]
 	yy := &t[y.ID]
 	return xx.entry < yy.entry && yy.exit < xx.exit
 }
+
+// maxdomorder returns a value to allow a maximal dominator first sort.  maxdomorder(x) < maxdomorder(y) is true
+// if x may dominate y, and false if x cannot dominate y.
+func (t sparseTree) maxdomorder(x *Block) int32 {
+	return t[x.ID].entry
+}
-- 
cgit v1.3


From 79048df2ccc2d4c2ccc4e15d481f7888d48cf440 Mon Sep 17 00:00:00 2001
From: David Crawshaw 
Date: Wed, 6 Apr 2016 07:11:24 -0400
Subject: cmd/link: handle long symbol names

Fixes #15104.

Change-Id: I9ddfbbf39ef0a873b703ee3e04fbb7d1192f5f39
Reviewed-on: https://go-review.googlesource.com/21581
Run-TryBot: David Crawshaw 
TryBot-Result: Gobot Gobot 
Reviewed-by: Ian Lance Taylor 
---
 src/cmd/link/internal/ld/objfile.go | 17 ++++++++++++-----
 src/cmd/link/link_test.go           | 30 ++++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+), 5 deletions(-)
 create mode 100644 src/cmd/link/link_test.go

(limited to 'src/cmd')

diff --git a/src/cmd/link/internal/ld/objfile.go b/src/cmd/link/internal/ld/objfile.go
index 6826737cae..b4d2a2184f 100644
--- a/src/cmd/link/internal/ld/objfile.go
+++ b/src/cmd/link/internal/ld/objfile.go
@@ -531,13 +531,18 @@ func (r *objReader) readSymName() string {
 		r.readInt64()
 		return ""
 	}
-	origName, err := r.rd.Peek(n)
-	if err != nil {
-		log.Fatalf("%s: unexpectedly long symbol name", r.pn)
-	}
 	if cap(r.rdBuf) < n {
 		r.rdBuf = make([]byte, 2*n)
 	}
+	origName, err := r.rd.Peek(n)
+	if err == bufio.ErrBufferFull {
+		// Long symbol names are rare but exist. One source is type
+		// symbols for types with long string forms. See #15104.
+		origName = make([]byte, n)
+		r.readFull(origName)
+	} else if err != nil {
+		log.Fatalf("%s: error reading symbol: %v", err)
+	}
 	adjName := r.rdBuf[:0]
 	for {
 		i := bytes.Index(origName, emptyPkg)
@@ -546,7 +551,9 @@ func (r *objReader) readSymName() string {
 			// Read past the peeked origName, now that we're done with it,
 			// using the rfBuf (also no longer used) as the scratch space.
 			// TODO: use bufio.Reader.Discard if available instead?
-			r.readFull(r.rdBuf[:n])
+			if err == nil {
+				r.readFull(r.rdBuf[:n])
+			}
 			r.rdBuf = adjName[:0] // in case 2*n wasn't enough
 			return s
 		}
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
new file mode 100644
index 0000000000..4ef184518e
--- /dev/null
+++ b/src/cmd/link/link_test.go
@@ -0,0 +1,30 @@
+package main
+
+import "testing"
+
+var AuthorPaidByTheColumnInch struct {
+	fog int `
+	London. Michaelmas term lately over, and the Lord Chancellor sitting in Lincoln’s Inn Hall. Implacable November weather. As much mud in the streets as if the waters had but newly retired from the face of the earth, and it would not be wonderful to meet a Megalosaurus, forty feet long or so, waddling like an elephantine lizard up Holborn Hill. Smoke lowering down from chimney-pots, making a soft black drizzle, with flakes of soot in it as big as full-grown snowflakes—gone into mourning, one might imagine, for the death of the sun. Dogs, undistinguishable in mire. Horses, scarcely better; splashed to their very blinkers. Foot passengers, jostling one another’s umbrellas in a general infection of ill temper, and losing their foot-hold at street-corners, where tens of thousands of other foot passengers have been slipping and sliding since the day broke (if this day ever broke), adding new deposits to the crust upon crust of mud, sticking at those points tenaciously to the pavement, and accumulating at compound interest.
+
+	Fog everywhere. Fog up the river, where it flows among green aits and meadows; fog down the river, where it rolls defiled among the tiers of shipping and the waterside pollutions of a great (and dirty) city. Fog on the Essex marshes, fog on the Kentish heights. Fog creeping into the cabooses of collier-brigs; fog lying out on the yards and hovering in the rigging of great ships; fog drooping on the gunwales of barges and small boats. Fog in the eyes and throats of ancient Greenwich pensioners, wheezing by the firesides of their wards; fog in the stem and bowl of the afternoon pipe of the wrathful skipper, down in his close cabin; fog cruelly pinching the toes and fingers of his shivering little ‘prentice boy on deck. Chance people on the bridges peeping over the parapets into a nether sky of fog, with fog all round them, as if they were up in a balloon and hanging in the misty clouds.
+
+	Gas looming through the fog in divers places in the streets, much as the sun may, from the spongey fields, be seen to loom by husbandman and ploughboy. Most of the shops lighted two hours before their time—as the gas seems to know, for it has a haggard and unwilling look.
+
+	The raw afternoon is rawest, and the dense fog is densest, and the muddy streets are muddiest near that leaden-headed old obstruction, appropriate ornament for the threshold of a leaden-headed old corporation, Temple Bar. And hard by Temple Bar, in Lincoln’s Inn Hall, at the very heart of the fog, sits the Lord High Chancellor in his High Court of Chancery.`
+
+	wind int `
+	It was grand to see how the wind awoke, and bent the trees, and drove the rain before it like a cloud of smoke; and to hear the solemn thunder, and to see the lightning; and while thinking with awe of the tremendous powers by which our little lives are encompassed, to consider how beneficent they are, and how upon the smallest flower and leaf there was already a freshness poured from all this seeming rage, which seemed to make creation new again.`
+
+	jarndyce int `
+	Jarndyce and Jarndyce drones on. This scarecrow of a suit has, over the course of time, become so complicated, that no man alive knows what it means. The parties to it understand it least; but it has been observed that no two Chancery lawyers can talk about it for five minutes, without coming to a total disagreement as to all the premises. Innumerable children have been born into the cause; innumerable young people have married into it; innumerable old people have died out of it. Scores of persons have deliriously found themselves made parties in Jarndyce and Jarndyce, without knowing how or why; whole families have inherited legendary hatreds with the suit. The little plaintiff or defendant, who was promised a new rocking-horse when Jarndyce and Jarndyce should be settled, has grown up, possessed himself of a real horse, and trotted away into the other world. Fair wards of court have faded into mothers and grandmothers; a long procession of Chancellors has come in and gone out; the legion of bills in the suit have been transformed into mere bills of mortality; there are not three Jarndyces left upon the earth perhaps, since old Tom Jarndyce in despair blew his brains out at a coffee-house in Chancery Lane; but Jarndyce and Jarndyce still drags its dreary length before the Court, perennially hopeless.`
+
+	principle int `
+	The one great principle of the English law is, to make business for itself. There is no other principle distinctly, certainly, and consistently maintained through all its narrow turnings. Viewed by this light it becomes a coherent scheme, and not the monstrous maze the laity are apt to think it. Let them but once clearly perceive that its grand principle is to make business for itself at their expense, and surely they will cease to grumble.`
+}
+
+func TestLargeSymName(t *testing.T) {
+	// The compiler generates a symbol name using the string form of the
+	// type. This tests that the linker can read symbol names larger than
+	// the bufio buffer. Issue #15104.
+	_ = AuthorPaidByTheColumnInch
+}
-- 
cgit v1.3


From f120936dfffa3ac935730699587e6957f2d5ea61 Mon Sep 17 00:00:00 2001
From: David Crawshaw 
Date: Thu, 31 Mar 2016 10:02:10 -0400
Subject: cmd/compile, etc: use name for type pkgPath

By replacing the *string used to represent pkgPath with a
reflect.name everywhere, the embedded *string for package paths
inside the reflect.name can be replaced by an offset, nameOff.
This reduces the number of pointers in the type information.

This also moves all reflect.name types into the same section, making
it possible to use nameOff more widely in later CLs.

No significant binary size change for normal binaries, but:

linux/amd64 PIE:
	cmd/go: -440KB (3.7%)
	jujud:  -2.6MB (3.2%)

For #6853.

Change-Id: I3890b132a784a1090b1b72b32febfe0bea77eaee
Reviewed-on: https://go-review.googlesource.com/21395
Run-TryBot: David Crawshaw 
TryBot-Result: Gobot Gobot 
Reviewed-by: Ian Lance Taylor 
---
 src/cmd/compile/internal/gc/go.go      |   2 +-
 src/cmd/compile/internal/gc/reflect.go | 117 +++++++++++++++++++--------------
 src/cmd/internal/obj/data.go           |  13 +++-
 src/reflect/type.go                    |  64 +++++++++---------
 src/runtime/heapdump.go                |   5 +-
 src/runtime/iface.go                   |   8 +--
 src/runtime/type.go                    |  72 ++++++++++++--------
 7 files changed, 168 insertions(+), 113 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go
index 5df49b56d6..8411d2d0ac 100644
--- a/src/cmd/compile/internal/gc/go.go
+++ b/src/cmd/compile/internal/gc/go.go
@@ -20,7 +20,7 @@ const (
 type Pkg struct {
 	Name     string // package name, e.g. "sys"
 	Path     string // string literal used in import statement, e.g. "runtime/internal/sys"
-	Pathsym  *Sym
+	Pathsym  *obj.LSym
 	Prefix   string // escaped path for use in symbol table
 	Imported bool   // export data of this package was parsed
 	Exported bool   // import line written in export data
diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go
index 2bd50b4665..70a75f9324 100644
--- a/src/cmd/compile/internal/gc/reflect.go
+++ b/src/cmd/compile/internal/gc/reflect.go
@@ -412,8 +412,6 @@ func imethods(t *Type) []*Sig {
 	return methods
 }
 
-var dimportpath_gopkg *Pkg
-
 func dimportpath(p *Pkg) {
 	if p.Pathsym != nil {
 		return
@@ -426,27 +424,18 @@ func dimportpath(p *Pkg) {
 		return
 	}
 
-	if dimportpath_gopkg == nil {
-		dimportpath_gopkg = mkpkg("go")
-		dimportpath_gopkg.Name = "go"
-	}
-
-	nam := "importpath." + p.Prefix + "."
-
-	n := Nod(ONAME, nil, nil)
-	n.Sym = Pkglookup(nam, dimportpath_gopkg)
-
-	n.Class = PEXTERN
-	n.Xoffset = 0
-	p.Pathsym = n.Sym
-
+	var str string
 	if p == localpkg {
 		// Note: myimportpath != "", or else dgopkgpath won't call dimportpath.
-		gdatastring(n, myimportpath)
+		str = myimportpath
 	} else {
-		gdatastring(n, p.Path)
+		str = p.Path
 	}
-	ggloblsym(n.Sym, int32(Types[TSTRING].Width), obj.DUPOK|obj.RODATA)
+
+	s := obj.Linklookup(Ctxt, "go.importpath."+p.Prefix+".", 0)
+	ot := dnameData(s, 0, str, "", nil, false)
+	ggloblLSym(s, int32(ot), obj.DUPOK|obj.RODATA)
+	p.Pathsym = s
 }
 
 func dgopkgpath(s *Sym, ot int, pkg *Pkg) int {
@@ -469,7 +458,23 @@ func dgopkgpathLSym(s *obj.LSym, ot int, pkg *Pkg) int {
 	}
 
 	dimportpath(pkg)
-	return dsymptrLSym(s, ot, Linksym(pkg.Pathsym), 0)
+	return dsymptrLSym(s, ot, pkg.Pathsym, 0)
+}
+
+// dgopkgpathOffLSym writes an offset relocation in s at offset ot to the pkg path symbol.
+func dgopkgpathOffLSym(s *obj.LSym, ot int, pkg *Pkg) int {
+	if pkg == localpkg && myimportpath == "" {
+		// If we don't know the full import path of the package being compiled
+		// (i.e. -p was not passed on the compiler command line), emit a reference to
+		// go.importpath.""., which the linker will rewrite using the correct import path.
+		// Every package that imports this one directly defines the symbol.
+		// See also https://groups.google.com/forum/#!topic/golang-dev/myb9s53HxGQ.
+		ns := obj.Linklookup(Ctxt, `go.importpath."".`, 0)
+		return dsymptrOffLSym(s, ot, ns, 0)
+	}
+
+	dimportpath(pkg)
+	return dsymptrOffLSym(s, ot, pkg.Pathsym, 0)
 }
 
 // isExportedField reports whether a struct field is exported.
@@ -495,13 +500,12 @@ func dnameField(s *Sym, ot int, ft *Field) int {
 	if ft.Note != nil {
 		tag = *ft.Note
 	}
-	return dname(s, ot, name, tag, nil, isExportedField(ft))
+	nsym := dname(name, tag, nil, isExportedField(ft))
+	return dsymptrLSym(Linksym(s), ot, nsym, 0)
 }
 
-var dnameCount int
-
-// dname dumps a reflect.name for a struct field or method.
-func dname(s *Sym, ot int, name, tag string, pkg *Pkg, exported bool) int {
+// dnameData writes the contents of a reflect.name into s at offset ot.
+func dnameData(s *obj.LSym, ot int, name, tag string, pkg *Pkg, exported bool) int {
 	if len(name) > 1<<16-1 {
 		Fatalf("name too long: %s", name)
 	}
@@ -534,31 +538,46 @@ func dname(s *Sym, ot int, name, tag string, pkg *Pkg, exported bool) int {
 		copy(tb[2:], tag)
 	}
 
-	// Very few names require a pkgPath *string (only those
-	// defined in a different package than their type). So if
-	// there is no pkgPath, we treat the name contents as string
-	// data that duplicates across packages.
-	var bsym *obj.LSym
+	ot = int(s.WriteBytes(Ctxt, int64(ot), b))
+
+	if pkg != nil {
+		ot = dgopkgpathOffLSym(s, ot, pkg)
+	}
+
+	return ot
+}
+
+var dnameCount int
+
+// dname creates a reflect.name for a struct field or method.
+func dname(name, tag string, pkg *Pkg, exported bool) *obj.LSym {
+	// Write out data as "type.." to signal two things to the
+	// linker, first that when dynamically linking, the symbol
+	// should be moved to a relro section, and second that the
+	// contents should not be decoded as a type.
+	sname := "type..namedata."
 	if pkg == nil {
-		_, bsym = stringsym(string(b))
+		// In the common case, share data with other packages.
+		if name == "" {
+			if exported {
+				sname += "-noname-exported." + tag
+			} else {
+				sname += "-noname-unexported." + tag
+			}
+		} else {
+			sname += name + "." + tag
+		}
 	} else {
-		// Write out data as "type.." to signal two things to the
-		// linker, first that when dynamically linking, the symbol
-		// should be moved to a relro section, and second that the
-		// contents should not be decoded as a type.
-		bsymname := fmt.Sprintf(`type..methodname."".%d`, dnameCount)
+		sname = fmt.Sprintf(`%s"".%d`, sname, dnameCount)
 		dnameCount++
-		bsym = obj.Linklookup(Ctxt, bsymname, 0)
-		bsym.P = b
-		boff := len(b)
-		boff = int(Rnd(int64(boff), int64(Widthptr)))
-		boff = dgopkgpathLSym(bsym, boff, pkg)
-		ggloblLSym(bsym, int32(boff), obj.RODATA|obj.LOCAL)
 	}
-
-	ot = dsymptrLSym(Linksym(s), ot, bsym, 0)
-
-	return ot
+	s := obj.Linklookup(Ctxt, sname, 0)
+	if len(s.P) > 0 {
+		return s
+	}
+	ot := dnameData(s, 0, name, tag, pkg, exported)
+	ggloblLSym(s, int32(ot), obj.DUPOK|obj.RODATA)
+	return s
 }
 
 // dextratype dumps the fields of a runtime.uncommontype.
@@ -627,7 +646,8 @@ func dextratypeData(s *Sym, ot int, t *Type) int {
 		if !exported && a.pkg != typePkg(t) {
 			pkg = a.pkg
 		}
-		ot = dname(s, ot, a.name, "", pkg, exported)
+		nsym := dname(a.name, "", pkg, exported)
+		ot = dsymptrLSym(lsym, ot, nsym, 0)
 		ot = dmethodptrOffLSym(lsym, ot, Linksym(dtypesym(a.mtype)))
 		ot = dmethodptrOffLSym(lsym, ot, Linksym(a.isym))
 		ot = dmethodptrOffLSym(lsym, ot, Linksym(a.tsym))
@@ -1213,7 +1233,8 @@ ok:
 			if !exported && a.pkg != tpkg {
 				pkg = a.pkg
 			}
-			ot = dname(s, ot, a.name, "", pkg, exported)
+			nsym := dname(a.name, "", pkg, exported)
+			ot = dsymptrLSym(Linksym(s), ot, nsym, 0)
 			ot = dsymptr(s, ot, dtypesym(a.type_), 0)
 		}
 
diff --git a/src/cmd/internal/obj/data.go b/src/cmd/internal/obj/data.go
index 546ff37269..d7f0840bc1 100644
--- a/src/cmd/internal/obj/data.go
+++ b/src/cmd/internal/obj/data.go
@@ -75,7 +75,11 @@ func (s *LSym) prepwrite(ctxt *Link, off int64, siz int) {
 	if s.Type == SBSS || s.Type == STLSBSS {
 		ctxt.Diag("cannot supply data for BSS var")
 	}
-	s.Grow(off + int64(siz))
+	l := off + int64(siz)
+	s.Grow(l)
+	if l > s.Size {
+		s.Size = l
+	}
 }
 
 // WriteFloat32 writes f into s at offset off.
@@ -150,6 +154,13 @@ func (s *LSym) WriteString(ctxt *Link, off int64, siz int, str string) {
 	copy(s.P[off:off+int64(siz)], str)
 }
 
+// WriteBytes writes a slice of bytes into s at offset off.
+func (s *LSym) WriteBytes(ctxt *Link, off int64, b []byte) int64 {
+	s.prepwrite(ctxt, off, len(b))
+	copy(s.P[off:], b)
+	return off + int64(len(b))
+}
+
 func Addrel(s *LSym) *Reloc {
 	s.R = append(s.R, Reloc{})
 	return &s.R[len(s.R)-1]
diff --git a/src/reflect/type.go b/src/reflect/type.go
index c7ed402be2..3c7affcd7f 100644
--- a/src/reflect/type.go
+++ b/src/reflect/type.go
@@ -299,9 +299,9 @@ type method struct {
 // Using a pointer to this struct reduces the overall size required
 // to describe an unnamed type with no methods.
 type uncommonType struct {
-	pkgPath *string // import path; nil for built-in types like int, string
-	mcount  uint16  // number of methods
-	moff    uint16  // offset from this uncommontype to [mcount]method
+	pkgPath name   // import path; empty for built-in types like int, string
+	mcount  uint16 // number of methods
+	moff    uint16 // offset from this uncommontype to [mcount]method
 }
 
 // ChanDir represents a channel type's direction.
@@ -354,7 +354,7 @@ type imethod struct {
 // interfaceType represents an interface type.
 type interfaceType struct {
 	rtype   `reflect:"interface"`
-	pkgPath *string   // import path
+	pkgPath name      // import path
 	methods []imethod // sorted by hash
 }
 
@@ -396,7 +396,7 @@ type structField struct {
 // structType represents a struct type.
 type structType struct {
 	rtype   `reflect:"struct"`
-	pkgPath *string
+	pkgPath name
 	fields  []structField // sorted by offset
 }
 
@@ -406,7 +406,7 @@ type structType struct {
 //
 //	1<<0 the name is exported
 //	1<<1 tag data follows the name
-//	1<<2 pkgPath *string follow the name and tag
+//	1<<2 pkgPath nameOff follows the name and tag
 //
 // The next two bytes are the data length:
 //
@@ -417,10 +417,9 @@ type structType struct {
 // If tag data follows then bytes 3+l and 3+l+1 are the tag length,
 // with the data following.
 //
-// If the import path follows, then ptrSize bytes at the end of
-// the data form a *string. The pointer is aligned to its width.
-// The import path is only set for concrete methods that are defined
-// in a different package than their type.
+// If the import path follows, then 4 bytes at the end of
+// the data form a nameOff. The import path is only set for concrete
+// methods that are defined in a different package than their type.
 type name struct {
 	bytes *byte
 }
@@ -446,6 +445,9 @@ func (n *name) tagLen() int {
 }
 
 func (n *name) name() (s string) {
+	if n.bytes == nil {
+		return ""
+	}
 	nl := n.nameLen()
 	if nl == 0 {
 		return ""
@@ -468,16 +470,18 @@ func (n *name) tag() (s string) {
 	return s
 }
 
-func (n *name) pkgPath() *string {
-	if *n.data(0)&(1<<2) == 0 {
-		return nil
+func (n *name) pkgPath() string {
+	if n.bytes == nil || *n.data(0)&(1<<2) == 0 {
+		return ""
 	}
 	off := 3 + n.nameLen()
 	if tl := n.tagLen(); tl > 0 {
 		off += 2 + tl
 	}
-	off = int(round(uintptr(off), ptrSize))
-	return *(**string)(unsafe.Pointer(n.data(off)))
+	var nameOff int32
+	copy((*[4]byte)(unsafe.Pointer(&nameOff))[:], (*[4]byte)(unsafe.Pointer(n.data(off)))[:])
+	pkgPathName := name{(*byte)(resolveTypeOff(unsafe.Pointer(n), nameOff))}
+	return pkgPathName.name()
 }
 
 // round n up to a multiple of a.  a must be a power of 2.
@@ -595,10 +599,10 @@ func (t *uncommonType) methods() []method {
 }
 
 func (t *uncommonType) PkgPath() string {
-	if t == nil || t.pkgPath == nil {
+	if t == nil {
 		return ""
 	}
-	return *t.pkgPath
+	return t.pkgPath.name()
 }
 
 // resolveTypeOff resolves an *rtype offset from a base type.
@@ -752,11 +756,10 @@ func (t *rtype) Method(i int) (m Method) {
 	m.Name = p.name.name()
 	fl := flag(Func)
 	if !p.name.isExported() {
-		pkgPath := p.name.pkgPath()
-		if pkgPath == nil {
-			pkgPath = ut.pkgPath
+		m.PkgPath = p.name.pkgPath()
+		if m.PkgPath == "" {
+			m.PkgPath = ut.pkgPath.name()
 		}
-		m.PkgPath = *pkgPath
 		fl |= flagStickyRO
 	}
 	if p.mtyp != 0 {
@@ -1004,11 +1007,10 @@ func (t *interfaceType) Method(i int) (m Method) {
 	p := &t.methods[i]
 	m.Name = p.name.name()
 	if !p.name.isExported() {
-		pkgPath := p.name.pkgPath()
-		if pkgPath == nil {
-			pkgPath = t.pkgPath
+		m.PkgPath = p.name.pkgPath()
+		if m.PkgPath == "" {
+			m.PkgPath = t.pkgPath.name()
 		}
-		m.PkgPath = *pkgPath
 	}
 	m.Type = toType(p.typ)
 	m.Index = i
@@ -1146,9 +1148,9 @@ func (t *structType) Field(i int) (f StructField) {
 		f.Name = t.Name()
 		f.Anonymous = true
 	}
-	if t.pkgPath != nil && !p.name.isExported() {
+	if !p.name.isExported() {
 		// Fields never have an import path in their name.
-		f.PkgPath = *t.pkgPath
+		f.PkgPath = t.pkgPath.name()
 	}
 	if tag := p.name.tag(); tag != "" {
 		f.Tag = StructTag(tag)
@@ -2325,7 +2327,7 @@ func StructOf(fields []StructField) Type {
 			case Interface:
 				ift := (*interfaceType)(unsafe.Pointer(ft))
 				for im, m := range ift.methods {
-					if m.name.pkgPath() != nil {
+					if m.name.pkgPath() != "" {
 						// TODO(sbinet)
 						panic("reflect: embedded interface with unexported method(s) not implemented")
 					}
@@ -2384,7 +2386,7 @@ func StructOf(fields []StructField) Type {
 				ptr := (*ptrType)(unsafe.Pointer(ft))
 				if unt := ptr.uncommon(); unt != nil {
 					for _, m := range unt.methods() {
-						if m.name.pkgPath() != nil {
+						if m.name.pkgPath() != "" {
 							// TODO(sbinet)
 							panic("reflect: embedded interface with unexported method(s) not implemented")
 						}
@@ -2398,7 +2400,7 @@ func StructOf(fields []StructField) Type {
 				}
 				if unt := ptr.elem.uncommon(); unt != nil {
 					for _, m := range unt.methods() {
-						if m.name.pkgPath() != nil {
+						if m.name.pkgPath() != "" {
 							// TODO(sbinet)
 							panic("reflect: embedded interface with unexported method(s) not implemented")
 						}
@@ -2413,7 +2415,7 @@ func StructOf(fields []StructField) Type {
 			default:
 				if unt := ft.uncommon(); unt != nil {
 					for _, m := range unt.methods() {
-						if m.name.pkgPath() != nil {
+						if m.name.pkgPath() != "" {
 							// TODO(sbinet)
 							panic("reflect: embedded interface with unexported method(s) not implemented")
 						}
diff --git a/src/runtime/heapdump.go b/src/runtime/heapdump.go
index 2410b1954a..adfd660847 100644
--- a/src/runtime/heapdump.go
+++ b/src/runtime/heapdump.go
@@ -183,10 +183,11 @@ func dumptype(t *_type) {
 	dumpint(tagType)
 	dumpint(uint64(uintptr(unsafe.Pointer(t))))
 	dumpint(uint64(t.size))
-	if x := t.uncommon(); x == nil || x.pkgpath == nil {
+	if x := t.uncommon(); x == nil || x.pkgpath.name() == "" {
 		dumpstr(t._string)
 	} else {
-		pkgpath := stringStructOf(x.pkgpath)
+		pkgpathstr := x.pkgpath.name()
+		pkgpath := stringStructOf(&pkgpathstr)
 		namestr := t.name()
 		name := stringStructOf(&namestr)
 		dumpint(uint64(uintptr(pkgpath.len) + 1 + uintptr(name.len)))
diff --git a/src/runtime/iface.go b/src/runtime/iface.go
index 700bdc2f48..84f0ee8f0c 100644
--- a/src/runtime/iface.go
+++ b/src/runtime/iface.go
@@ -101,15 +101,15 @@ func additab(m *itab, locked, canfail bool) {
 		iname := i.name.name()
 		itype := i._type
 		ipkg := i.name.pkgPath()
-		if ipkg == nil {
-			ipkg = inter.pkgpath
+		if ipkg == "" {
+			ipkg = inter.pkgpath.name()
 		}
 		for ; j < nt; j++ {
 			t := &xmhdr[j]
 			if typ.typeOff(t.mtyp) == itype && t.name.name() == iname {
 				pkgPath := t.name.pkgPath()
-				if pkgPath == nil {
-					pkgPath = x.pkgpath
+				if pkgPath == "" {
+					pkgPath = x.pkgpath.name()
 				}
 				if t.name.isExported() || pkgPath == ipkg {
 					if m != nil {
diff --git a/src/runtime/type.go b/src/runtime/type.go
index 86131d3ff3..711753bab5 100644
--- a/src/runtime/type.go
+++ b/src/runtime/type.go
@@ -6,10 +6,7 @@
 
 package runtime
 
-import (
-	"runtime/internal/sys"
-	"unsafe"
-)
+import "unsafe"
 
 // tflag is documented in ../reflect/type.go.
 type tflag uint8
@@ -151,6 +148,33 @@ var reflectOffs struct {
 	minv map[unsafe.Pointer]int32
 }
 
+func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name {
+	if off == 0 {
+		return name{}
+	}
+	base := uintptr(ptrInModule)
+	var md *moduledata
+	for next := &firstmoduledata; next != nil; next = next.next {
+		if base >= next.types && base < next.etypes {
+			md = next
+			break
+		}
+	}
+	if md == nil {
+		println("runtime: nameOff", hex(off), "base", hex(base), "not in ranges:")
+		for next := &firstmoduledata; next != nil; next = next.next {
+			println("\ttypes", hex(next.types), "etypes", hex(next.etypes))
+		}
+		throw("runtime: name offset base pointer out of range")
+	}
+	res := md.types + uintptr(off)
+	if res > md.etypes {
+		println("runtime: nameOff", hex(off), "out of range", hex(md.types), "-", hex(md.etypes))
+		throw("runtime: name offset out of range")
+	}
+	return name{(*byte)(unsafe.Pointer(res))}
+}
+
 func (t *_type) typeOff(off typeOff) *_type {
 	if off == 0 {
 		return nil
@@ -240,6 +264,7 @@ func (t *functype) dotdotdot() bool {
 	return t.outCount&(1<<15) != 0
 }
 
+type nameOff int32
 type typeOff int32
 type textOff int32
 
@@ -251,7 +276,7 @@ type method struct {
 }
 
 type uncommontype struct {
-	pkgpath *string
+	pkgpath name
 	mcount  uint16 // number of methods
 	moff    uint16 // offset from this uncommontype to [mcount]method
 }
@@ -263,7 +288,7 @@ type imethod struct {
 
 type interfacetype struct {
 	typ     _type
-	pkgpath *string
+	pkgpath name
 	mhdr    []imethod
 }
 
@@ -319,7 +344,7 @@ type structfield struct {
 
 type structtype struct {
 	typ     _type
-	pkgPath *string
+	pkgPath name
 	fields  []structfield
 }
 
@@ -350,6 +375,9 @@ func (n *name) tagLen() int {
 }
 
 func (n *name) name() (s string) {
+	if n.bytes == nil {
+		return ""
+	}
 	nl := n.nameLen()
 	if nl == 0 {
 		return ""
@@ -372,16 +400,18 @@ func (n *name) tag() (s string) {
 	return s
 }
 
-func (n *name) pkgPath() *string {
-	if *n.data(0)&(1<<2) == 0 {
-		return nil
+func (n *name) pkgPath() string {
+	if n.bytes == nil || *n.data(0)&(1<<2) == 0 {
+		return ""
 	}
 	off := 3 + n.nameLen()
 	if tl := n.tagLen(); tl > 0 {
 		off += 2 + tl
 	}
-	off = int(round(uintptr(off), sys.PtrSize))
-	return *(**string)(unsafe.Pointer(n.data(off)))
+	var nameOff nameOff
+	copy((*[4]byte)(unsafe.Pointer(&nameOff))[:], (*[4]byte)(unsafe.Pointer(n.data(off)))[:])
+	pkgPathName := resolveNameOff(unsafe.Pointer(n.bytes), nameOff)
+	return pkgPathName.name()
 }
 
 // typelinksinit scans the types from extra modules and builds the
@@ -466,7 +496,7 @@ func typesEqual(t, v *_type) bool {
 		if ut == nil || uv == nil {
 			return false
 		}
-		if !pkgPathEqual(ut.pkgpath, uv.pkgpath) {
+		if ut.pkgpath.name() != uv.pkgpath.name() {
 			return false
 		}
 	}
@@ -506,7 +536,7 @@ func typesEqual(t, v *_type) bool {
 	case kindInterface:
 		it := (*interfacetype)(unsafe.Pointer(t))
 		iv := (*interfacetype)(unsafe.Pointer(v))
-		if !pkgPathEqual(it.pkgpath, iv.pkgpath) {
+		if it.pkgpath.name() != iv.pkgpath.name() {
 			return false
 		}
 		if len(it.mhdr) != len(iv.mhdr) {
@@ -518,7 +548,7 @@ func typesEqual(t, v *_type) bool {
 			if tm.name.name() != vm.name.name() {
 				return false
 			}
-			if !pkgPathEqual(tm.name.pkgPath(), vm.name.pkgPath()) {
+			if tm.name.pkgPath() != vm.name.pkgPath() {
 				return false
 			}
 			if !typesEqual(tm._type, vm._type) {
@@ -550,7 +580,7 @@ func typesEqual(t, v *_type) bool {
 			if tf.name.name() != vf.name.name() {
 				return false
 			}
-			if !pkgPathEqual(tf.name.pkgPath(), vf.name.pkgPath()) {
+			if tf.name.pkgPath() != vf.name.pkgPath() {
 				return false
 			}
 			if !typesEqual(tf.typ, vf.typ) {
@@ -570,13 +600,3 @@ func typesEqual(t, v *_type) bool {
 		return false
 	}
 }
-
-func pkgPathEqual(p, q *string) bool {
-	if p == q {
-		return true
-	}
-	if p == nil || q == nil {
-		return false
-	}
-	return *p == *q
-}
-- 
cgit v1.3


From 6e5027a37a851eb19dba7dad7ea5a8b43e27b842 Mon Sep 17 00:00:00 2001
From: Robert Griesemer 
Date: Wed, 13 Apr 2016 13:17:30 -0700
Subject: cmd/compile: don't export unneeded OAS, OASWB nodes

Also:
- "rewrite" node Op in exporter for some nodes instead of importer
- more comments

Change-Id: I809e6754d14987b28f1da9379951ffa2e690c2a7
Reviewed-on: https://go-review.googlesource.com/22008
Reviewed-by: Matthew Dempsky 
Run-TryBot: Robert Griesemer 
TryBot-Result: Gobot Gobot 
---
 src/cmd/compile/internal/gc/bexport.go | 27 +++++++++++++---------
 src/cmd/compile/internal/gc/bimport.go | 41 +++++++++++++++++-----------------
 2 files changed, 36 insertions(+), 32 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go
index 59a85c2f23..e0810f9139 100644
--- a/src/cmd/compile/internal/gc/bexport.go
+++ b/src/cmd/compile/internal/gc/bexport.go
@@ -783,7 +783,11 @@ func (p *exporter) param(q *Field, n int, numbered bool) {
 		// supply the parameter package here. We need the package
 		// when the function is inlined so we can properly resolve
 		// the name.
-		// TODO(gri) should do this only once per function/method
+		// TODO(gri) This is compiler-specific. Try using importpkg
+		// here and then update the symbols if we find an inlined
+		// body only. Otherwise, the parameter name is ignored and
+		// the package doesn't matter. This would remove an int
+		// (likely 1 byte) for each named parameter.
 		p.pkg(q.Sym.Pkg)
 	}
 	// TODO(gri) This is compiler-specific (escape info).
@@ -1266,12 +1270,11 @@ func (p *exporter) stmt(n *Node) {
 	//	unimplemented - handled by default case
 
 	case OAS, OASWB:
-		p.op(op)
 		// Don't export "v = " initializing statements, hope they're always
 		// preceded by the DCL which will be re-parsed and typecheck to reproduce
 		// the "v = " again.
-		// TODO(gri) if n.Right == nil, don't emit anything
-		if p.bool(n.Right != nil) {
+		if n.Right != nil {
+			p.op(OAS)
 			p.expr(n.Left)
 			p.expr(n.Right)
 		}
@@ -1284,16 +1287,14 @@ func (p *exporter) stmt(n *Node) {
 			p.expr(n.Right)
 		}
 
+	case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV:
+		fallthrough
+
 	case OAS2:
 		p.op(OAS2)
 		p.exprList(n.List)
 		p.exprList(n.Rlist)
 
-	case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV:
-		p.op(op)
-		p.exprList(n.List)
-		p.exprList(n.Rlist)
-
 	case ORETURN:
 		p.op(ORETURN)
 		p.exprList(n.List)
@@ -1332,11 +1333,15 @@ func (p *exporter) stmt(n *Node) {
 		p.stmtList(n.List)
 
 	case OCASE, OXCASE:
-		p.op(op)
+		p.op(OXCASE)
 		p.stmtList(n.List)
 		p.stmtList(n.Nbody)
 
-	case OBREAK, OCONTINUE, OGOTO, OFALL, OXFALL:
+	case OFALL:
+		op = OXFALL
+		fallthrough
+
+	case OBREAK, OCONTINUE, OGOTO, OXFALL:
 		p.op(op)
 		p.exprsOrNil(n.Left, nil)
 
diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go
index 4a93b5a91d..223cc443aa 100644
--- a/src/cmd/compile/internal/gc/bimport.go
+++ b/src/cmd/compile/internal/gc/bimport.go
@@ -626,6 +626,10 @@ func (p *importer) float(x *Mpflt) {
 // re-establish the syntax tree's invariants. At some future point we might be
 // able to avoid this round-about way and create the rewritten nodes directly,
 // possibly avoiding a lot of duplicate work (name resolution, type checking).
+//
+// Refined nodes (e.g., ODOTPTR as a refinement of OXDOT) are exported as their
+// unrefined nodes (since this is what the importer uses). The respective case
+// entries are unreachable in the importer.
 
 func (p *importer) stmtList() []*Node {
 	var list []*Node
@@ -871,14 +875,11 @@ func (p *importer) node() *Node {
 	// case ODCLFIELD:
 	//	unimplemented
 
-	case OAS, OASWB:
-		if p.bool() {
-			lhs := p.expr()
-			rhs := p.expr()
-			return Nod(OAS, lhs, rhs)
-		}
-		// TODO(gri) we should not have emitted anything here
-		return Nod(OEMPTY, nil, nil)
+	// case OAS, OASWB:
+	// 	unreachable - mapped to OAS case below by exporter
+
+	case OAS:
+		return Nod(OAS, p.expr(), p.expr())
 
 	case OASOP:
 		n := Nod(OASOP, nil, nil)
@@ -892,15 +893,10 @@ func (p *importer) node() *Node {
 		}
 		return n
 
-	case OAS2:
-		lhs := p.exprList()
-		rhs := p.exprList()
-		n := Nod(OAS2, nil, nil)
-		n.List.Set(lhs)
-		n.Rlist.Set(rhs)
-		return n
+	// case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV:
+	// 	unreachable - mapped to OAS2 case below by exporter
 
-	case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV:
+	case OAS2:
 		n := Nod(OAS2, nil, nil)
 		n.List.Set(p.exprList())
 		n.Rlist.Set(p.exprList())
@@ -954,7 +950,10 @@ func (p *importer) node() *Node {
 		popdcl()
 		return n
 
-	case OCASE, OXCASE:
+	// case OCASE, OXCASE:
+	// 	unreachable - mapped to OXCASE case below by exporter
+
+	case OXCASE:
 		markdcl()
 		n := Nod(OXCASE, nil, nil)
 		n.List.Set(p.exprList())
@@ -964,10 +963,10 @@ func (p *importer) node() *Node {
 		popdcl()
 		return n
 
-	case OBREAK, OCONTINUE, OGOTO, OFALL, OXFALL:
-		if op == OFALL {
-			op = OXFALL
-		}
+	// case OFALL:
+	// 	unreachable - mapped to OXFALL case below by exporter
+
+	case OBREAK, OCONTINUE, OGOTO, OXFALL:
 		left, _ := p.exprsOrNil()
 		return Nod(op, left, nil)
 
-- 
cgit v1.3


From ae9804595879eb07efd23b9c98eab46693573447 Mon Sep 17 00:00:00 2001
From: Robert Griesemer 
Date: Wed, 13 Apr 2016 16:57:23 -0700
Subject: cmd/compile: use correct export function (fix debugFormat)

Tested with debugFormat enabled and running
(export GO_GCFLAGS=-newexport; sh all.bash).

Change-Id: If7d43e1e594ea43c644232b89e670f7abb6b003e
Reviewed-on: https://go-review.googlesource.com/22033
Reviewed-by: Matthew Dempsky 
---
 src/cmd/compile/internal/gc/bexport.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go
index e0810f9139..eef2e2200d 100644
--- a/src/cmd/compile/internal/gc/bexport.go
+++ b/src/cmd/compile/internal/gc/bexport.go
@@ -350,7 +350,7 @@ func export(out *bufio.Writer, trace bool) int {
 	if p.trace {
 		p.tracef("\n")
 	}
-	p.tag(-1) // invalid index terminates list
+	p.int(-1) // invalid index terminates list
 
 	// for self-verification only (redundant)
 	p.int(objcount)
-- 
cgit v1.3


From 980ab12ade53e70d037ab2ab475148b216d84a14 Mon Sep 17 00:00:00 2001
From: Matthew Dempsky 
Date: Wed, 13 Apr 2016 18:37:18 -0700
Subject: cmd/compile/internal/gc: change flags to bool where possible

Some of the Debug[x] flags are actually boolean too, but not all, so
they need to be handled separately.

While here, change some obj.Flagstr and obj.Flagint64 calls to
directly use flag.StringVar and flag.Int64Var instead.

Change-Id: Iccedf6fed4328240ee2257f57fe6d66688f237c4
Reviewed-on: https://go-review.googlesource.com/22052
Reviewed-by: Michael Hudson-Doyle 
---
 src/cmd/compile/internal/gc/alg.go       |  5 +-
 src/cmd/compile/internal/gc/bexport.go   |  2 +-
 src/cmd/compile/internal/gc/closure.go   |  2 +-
 src/cmd/compile/internal/gc/dcl.go       |  4 +-
 src/cmd/compile/internal/gc/export.go    |  8 ++--
 src/cmd/compile/internal/gc/gen.go       |  2 +-
 src/cmd/compile/internal/gc/go.go        | 18 ++++----
 src/cmd/compile/internal/gc/inl.go       |  4 +-
 src/cmd/compile/internal/gc/lex.go       |  6 +--
 src/cmd/compile/internal/gc/main.go      | 79 ++++++++++++++++----------------
 src/cmd/compile/internal/gc/obj.go       |  8 ++--
 src/cmd/compile/internal/gc/pgen.go      |  2 +-
 src/cmd/compile/internal/gc/racewalk.go  | 10 ++--
 src/cmd/compile/internal/gc/reflect.go   |  4 +-
 src/cmd/compile/internal/gc/ssa.go       |  4 +-
 src/cmd/compile/internal/gc/subr.go      |  2 +-
 src/cmd/compile/internal/gc/typecheck.go |  4 +-
 src/cmd/compile/internal/gc/unsafe.go    |  2 +-
 src/cmd/compile/internal/gc/walk.go      |  9 ++--
 19 files changed, 88 insertions(+), 87 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/compile/internal/gc/alg.go b/src/cmd/compile/internal/gc/alg.go
index e9b5afe838..6e85438610 100644
--- a/src/cmd/compile/internal/gc/alg.go
+++ b/src/cmd/compile/internal/gc/alg.go
@@ -316,11 +316,12 @@ func genhash(sym *Sym, t *Type) {
 	// for a struct containing a reflect.Value, which itself has
 	// an unexported field of type unsafe.Pointer.
 	old_safemode := safemode
+	safemode = false
 
-	safemode = 0
 	Disable_checknil++
 	funccompile(fn)
 	Disable_checknil--
+
 	safemode = old_safemode
 }
 
@@ -509,7 +510,7 @@ func geneq(sym *Sym, t *Type) {
 	// for a struct containing a reflect.Value, which itself has
 	// an unexported field of type unsafe.Pointer.
 	old_safemode := safemode
-	safemode = 0
+	safemode = false
 
 	// Disable checknils while compiling this code.
 	// We are comparing a struct or an array,
diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go
index eef2e2200d..e5fa3c39a6 100644
--- a/src/cmd/compile/internal/gc/bexport.go
+++ b/src/cmd/compile/internal/gc/bexport.go
@@ -261,7 +261,7 @@ func export(out *bufio.Writer, trace bool) int {
 	}
 
 	// write compiler-specific flags
-	p.bool(safemode != 0)
+	p.bool(safemode)
 	if p.trace {
 		p.tracef("\n")
 	}
diff --git a/src/cmd/compile/internal/gc/closure.go b/src/cmd/compile/internal/gc/closure.go
index 80c8d309af..db4eb3f14d 100644
--- a/src/cmd/compile/internal/gc/closure.go
+++ b/src/cmd/compile/internal/gc/closure.go
@@ -419,7 +419,7 @@ func closuredebugruntimecheck(r *Node) {
 			Warnl(r.Lineno, "stack closure, captured vars = %v", r.Func.Cvars)
 		}
 	}
-	if compiling_runtime > 0 && r.Esc == EscHeap {
+	if compiling_runtime && r.Esc == EscHeap {
 		yyerrorl(r.Lineno, "heap-allocated closure, not allowed in runtime.")
 	}
 }
diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go
index c652c65962..e1028f681c 100644
--- a/src/cmd/compile/internal/gc/dcl.go
+++ b/src/cmd/compile/internal/gc/dcl.go
@@ -1330,7 +1330,7 @@ func makefuncsym(s *Sym) {
 	if isblanksym(s) {
 		return
 	}
-	if compiling_runtime != 0 && s.Name == "getg" {
+	if compiling_runtime && s.Name == "getg" {
 		// runtime.getg() is not a real function and so does
 		// not get a funcsym.
 		return
@@ -1440,7 +1440,7 @@ func (c *nowritebarrierrecChecker) visitcall(n *Node) {
 	if fn == nil || fn.Op != ONAME || fn.Class != PFUNC || fn.Name.Defn == nil {
 		return
 	}
-	if (compiling_runtime != 0 || fn.Sym.Pkg == Runtimepkg) && fn.Sym.Name == "allocm" {
+	if (compiling_runtime || fn.Sym.Pkg == Runtimepkg) && fn.Sym.Name == "allocm" {
 		return
 	}
 	defn := fn.Name.Defn
diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go
index ae36657a65..cfe192f3ba 100644
--- a/src/cmd/compile/internal/gc/export.go
+++ b/src/cmd/compile/internal/gc/export.go
@@ -15,8 +15,8 @@ import (
 )
 
 var (
-	newexport    int // if set, use new export format
-	Debug_export int // if set, print debugging information about export data
+	newexport    bool // if set, use new export format
+	Debug_export int  // if set, print debugging information about export data
 	exportsize   int
 )
 
@@ -377,7 +377,7 @@ func dumpexport() {
 	}
 
 	size := 0 // size of export section without enclosing markers
-	if forceNewExport || newexport != 0 {
+	if forceNewExport || newexport {
 		// binary export
 		// The linker also looks for the $$ marker - use char after $$ to distinguish format.
 		exportf("\n$$B\n") // indicate binary format
@@ -417,7 +417,7 @@ func dumpexport() {
 		exportf("\n$$\n") // indicate textual format
 		exportsize = 0
 		exportf("package %s", localpkg.Name)
-		if safemode != 0 {
+		if safemode {
 			exportf(" safe")
 		}
 		exportf("\n")
diff --git a/src/cmd/compile/internal/gc/gen.go b/src/cmd/compile/internal/gc/gen.go
index 7527452c93..cc624cce7a 100644
--- a/src/cmd/compile/internal/gc/gen.go
+++ b/src/cmd/compile/internal/gc/gen.go
@@ -246,7 +246,7 @@ func cgen_dcl(n *Node) {
 	if n.Class&PHEAP == 0 {
 		return
 	}
-	if compiling_runtime != 0 {
+	if compiling_runtime {
 		Yyerror("%v escapes to heap, not allowed in runtime.", n)
 	}
 	if prealloc[n] == nil {
diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go
index 8411d2d0ac..af9aaf0dae 100644
--- a/src/cmd/compile/internal/gc/go.go
+++ b/src/cmd/compile/internal/gc/go.go
@@ -144,9 +144,9 @@ var nsyntaxerrors int
 
 var decldepth int32
 
-var safemode int
+var safemode bool
 
-var nolocalimports int
+var nolocalimports bool
 
 var Debug [256]int
 
@@ -261,21 +261,21 @@ var Funcdepth int32
 
 var typecheckok bool
 
-var compiling_runtime int
+var compiling_runtime bool
 
 var compiling_wrappers int
 
-var use_writebarrier int
+var use_writebarrier bool
 
-var pure_go int
+var pure_go bool
 
 var flag_installsuffix string
 
-var flag_race int
+var flag_race bool
 
-var flag_msan int
+var flag_msan bool
 
-var flag_largemodel int
+var flag_largemodel bool
 
 // Whether we are adding any sort of code instrumentation, such as
 // when the race detector is enabled.
@@ -285,7 +285,7 @@ var debuglive int
 
 var Ctxt *obj.Link
 
-var writearchive int
+var writearchive bool
 
 var bstdout *bufio.Writer
 
diff --git a/src/cmd/compile/internal/gc/inl.go b/src/cmd/compile/internal/gc/inl.go
index ea2394e7f9..f9e425618b 100644
--- a/src/cmd/compile/internal/gc/inl.go
+++ b/src/cmd/compile/internal/gc/inl.go
@@ -71,7 +71,7 @@ func typecheckinl(fn *Node) {
 	}
 
 	save_safemode := safemode
-	safemode = 0
+	safemode = false
 
 	savefn := Curfn
 	Curfn = fn
@@ -492,7 +492,7 @@ func mkinlcall(n *Node, fn *Node, isddd bool) *Node {
 	pkg := fnpkg(fn)
 
 	if pkg != localpkg && pkg != nil {
-		safemode = 0
+		safemode = false
 	}
 	n = mkinlcall1(n, fn, isddd)
 	safemode = save_safemode
diff --git a/src/cmd/compile/internal/gc/lex.go b/src/cmd/compile/internal/gc/lex.go
index 4b95bb7124..09fed98985 100644
--- a/src/cmd/compile/internal/gc/lex.go
+++ b/src/cmd/compile/internal/gc/lex.go
@@ -914,17 +914,17 @@ func (l *lexer) getlinepragma() rune {
 		case "go:noinline":
 			l.pragma |= Noinline
 		case "go:systemstack":
-			if compiling_runtime == 0 {
+			if !compiling_runtime {
 				Yyerror("//go:systemstack only allowed in runtime")
 			}
 			l.pragma |= Systemstack
 		case "go:nowritebarrier":
-			if compiling_runtime == 0 {
+			if !compiling_runtime {
 				Yyerror("//go:nowritebarrier only allowed in runtime")
 			}
 			l.pragma |= Nowritebarrier
 		case "go:nowritebarrierrec":
-			if compiling_runtime == 0 {
+			if !compiling_runtime {
 				Yyerror("//go:nowritebarrierrec only allowed in runtime")
 			}
 			l.pragma |= Nowritebarrierrec | Nowritebarrier // implies Nowritebarrier
diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index 45a510d577..f41097b83b 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -142,15 +142,14 @@ func Main() {
 
 	Nacl = goos == "nacl"
 	if Nacl {
-		flag_largemodel = 1
+		flag_largemodel = true
 	}
 
-	outfile = ""
-	obj.Flagcount("+", "compiling runtime", &compiling_runtime)
+	flag.BoolVar(&compiling_runtime, "+", false, "compiling runtime")
 	obj.Flagcount("%", "debug non-static initializers", &Debug['%'])
 	obj.Flagcount("A", "for bootstrapping, allow 'any' type", &Debug['A'])
 	obj.Flagcount("B", "disable bounds checking", &Debug['B'])
-	obj.Flagstr("D", "set relative `path` for local imports", &localimport)
+	flag.StringVar(&localimport, "D", "", "set relative `path` for local imports")
 	obj.Flagcount("E", "debug symbol export", &Debug['E'])
 	obj.Flagfn1("I", "add `directory` to import search path", addidir)
 	obj.Flagcount("K", "debug missing line numbers", &Debug['K'])
@@ -162,57 +161,59 @@ func Main() {
 	obj.Flagcount("S", "print assembly listing", &Debug['S'])
 	obj.Flagfn0("V", "print compiler version", doversion)
 	obj.Flagcount("W", "debug parse tree after type checking", &Debug['W'])
-	obj.Flagstr("asmhdr", "write assembly header to `file`", &asmhdr)
-	obj.Flagstr("buildid", "record `id` as the build id in the export metadata", &buildid)
-	obj.Flagcount("complete", "compiling complete package (no C or assembly)", &pure_go)
-	obj.Flagstr("d", "print debug information about items in `list`", &debugstr)
+	flag.StringVar(&asmhdr, "asmhdr", "", "write assembly header to `file`")
+	flag.StringVar(&buildid, "buildid", "", "record `id` as the build id in the export metadata")
+	flag.BoolVar(&pure_go, "complete", false, "compiling complete package (no C or assembly)")
+	flag.StringVar(&debugstr, "d", "", "print debug information about items in `list`")
 	obj.Flagcount("e", "no limit on number of errors reported", &Debug['e'])
 	obj.Flagcount("f", "debug stack frames", &Debug['f'])
 	obj.Flagcount("g", "debug code generation", &Debug['g'])
 	obj.Flagcount("h", "halt on error", &Debug['h'])
 	obj.Flagcount("i", "debug line number stack", &Debug['i'])
 	obj.Flagfn1("importmap", "add `definition` of the form source=actual to import map", addImportMap)
-	obj.Flagstr("installsuffix", "set pkg directory `suffix`", &flag_installsuffix)
+	flag.StringVar(&flag_installsuffix, "installsuffix", "", "set pkg directory `suffix`")
 	obj.Flagcount("j", "debug runtime-initialized variables", &Debug['j'])
 	obj.Flagcount("l", "disable inlining", &Debug['l'])
 	obj.Flagcount("live", "debug liveness analysis", &debuglive)
 	obj.Flagcount("m", "print optimization decisions", &Debug['m'])
-	obj.Flagcount("msan", "build code compatible with C/C++ memory sanitizer", &flag_msan)
-	obj.Flagcount("newexport", "use new export format", &newexport) // TODO(gri) remove eventually (issue 13241)
-	obj.Flagcount("nolocalimports", "reject local (relative) imports", &nolocalimports)
-	obj.Flagstr("o", "write output to `file`", &outfile)
-	obj.Flagstr("p", "set expected package import `path`", &myimportpath)
-	obj.Flagcount("pack", "write package file instead of object file", &writearchive)
+	flag.BoolVar(&flag_msan, "msan", false, "build code compatible with C/C++ memory sanitizer")
+	flag.BoolVar(&newexport, "newexport", false, "use new export format") // TODO(gri) remove eventually (issue 13241)
+	flag.BoolVar(&nolocalimports, "nolocalimports", false, "reject local (relative) imports")
+	flag.StringVar(&outfile, "o", "", "write output to `file`")
+	flag.StringVar(&myimportpath, "p", "", "set expected package import `path`")
+	flag.BoolVar(&writearchive, "pack", false, "write package file instead of object file")
 	obj.Flagcount("r", "debug generated wrappers", &Debug['r'])
-	obj.Flagcount("race", "enable race detector", &flag_race)
+	flag.BoolVar(&flag_race, "race", false, "enable race detector")
 	obj.Flagcount("s", "warn about composite literals that can be simplified", &Debug['s'])
-	obj.Flagstr("trimpath", "remove `prefix` from recorded source file paths", &Ctxt.LineHist.TrimPathPrefix)
-	obj.Flagcount("u", "reject unsafe code", &safemode)
+	flag.StringVar(&Ctxt.LineHist.TrimPathPrefix, "trimpath", "", "remove `prefix` from recorded source file paths")
+	flag.BoolVar(&safemode, "u", false, "reject unsafe code")
 	obj.Flagcount("v", "increase debug verbosity", &Debug['v'])
 	obj.Flagcount("w", "debug type checking", &Debug['w'])
-	use_writebarrier = 1
-	obj.Flagcount("wb", "enable write barrier", &use_writebarrier)
+	flag.BoolVar(&use_writebarrier, "wb", true, "enable write barrier")
 	obj.Flagcount("x", "debug lexer", &Debug['x'])
 	obj.Flagcount("y", "debug declarations in canned imports (with -d)", &Debug['y'])
-	var flag_shared int
+	var flag_shared bool
 	var flag_dynlink bool
 	if supportsDynlink(Thearch.LinkArch.Arch) {
-		obj.Flagcount("shared", "generate code that can be linked into a shared library", &flag_shared)
+		flag.BoolVar(&flag_shared, "shared", false, "generate code that can be linked into a shared library")
 		flag.BoolVar(&flag_dynlink, "dynlink", false, "support references to Go symbols defined in other shared libraries")
 	}
 	if Thearch.LinkArch.Family == sys.AMD64 {
-		obj.Flagcount("largemodel", "generate code that assumes a large memory model", &flag_largemodel)
+		flag.BoolVar(&flag_largemodel, "largemodel", false, "generate code that assumes a large memory model")
 	}
-	obj.Flagstr("cpuprofile", "write cpu profile to `file`", &cpuprofile)
-	obj.Flagstr("memprofile", "write memory profile to `file`", &memprofile)
-	obj.Flagint64("memprofilerate", "set runtime.MemProfileRate to `rate`", &memprofilerate)
+	flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to `file`")
+	flag.StringVar(&memprofile, "memprofile", "", "write memory profile to `file`")
+	flag.Int64Var(&memprofilerate, "memprofilerate", 0, "set runtime.MemProfileRate to `rate`")
 	flag.BoolVar(&ssaEnabled, "ssa", true, "use SSA backend to generate code")
 	obj.Flagparse(usage)
 
 	if flag_dynlink {
-		flag_shared = 1
+		flag_shared = true
+	}
+	if flag_shared {
+		// TODO(mdempsky): Change Flag_shared to bool.
+		Ctxt.Flag_shared = 1
 	}
-	Ctxt.Flag_shared = int32(flag_shared)
 	Ctxt.Flag_dynlink = flag_dynlink
 	Ctxt.Flag_optimize = Debug['N'] == 0
 
@@ -225,17 +226,17 @@ func Main() {
 
 	startProfile()
 
-	if flag_race != 0 {
+	if flag_race {
 		racepkg = mkpkg("runtime/race")
 		racepkg.Name = "race"
 	}
-	if flag_msan != 0 {
+	if flag_msan {
 		msanpkg = mkpkg("runtime/msan")
 		msanpkg.Name = "msan"
 	}
-	if flag_race != 0 && flag_msan != 0 {
+	if flag_race && flag_msan {
 		log.Fatal("cannot use both -race and -msan")
-	} else if flag_race != 0 || flag_msan != 0 {
+	} else if flag_race || flag_msan {
 		instrumenting = true
 	}
 
@@ -471,7 +472,7 @@ func Main() {
 		fninit(xtop)
 	}
 
-	if compiling_runtime != 0 {
+	if compiling_runtime {
 		checknowritebarrierrec()
 	}
 
@@ -569,7 +570,7 @@ func islocalname(name string) bool {
 
 func findpkg(name string) (file string, ok bool) {
 	if islocalname(name) {
-		if safemode != 0 || nolocalimports != 0 {
+		if safemode || nolocalimports {
 			return "", false
 		}
 
@@ -612,10 +613,10 @@ func findpkg(name string) (file string, ok bool) {
 		if flag_installsuffix != "" {
 			suffixsep = "_"
 			suffix = flag_installsuffix
-		} else if flag_race != 0 {
+		} else if flag_race {
 			suffixsep = "_"
 			suffix = "race"
-		} else if flag_msan != 0 {
+		} else if flag_msan {
 			suffixsep = "_"
 			suffix = "msan"
 		}
@@ -694,7 +695,7 @@ func importfile(f *Val, indent []byte) {
 	}
 
 	if path_ == "unsafe" {
-		if safemode != 0 {
+		if safemode {
 			Yyerror("cannot import package unsafe")
 			errorexit()
 		}
@@ -818,7 +819,7 @@ func importfile(f *Val, indent []byte) {
 		errorexit()
 	}
 
-	if safemode != 0 && !importpkg.Safe {
+	if safemode && !importpkg.Safe {
 		Yyerror("cannot import unsafe package %q", importpkg.Path)
 	}
 }
@@ -896,7 +897,7 @@ func mkpackage(pkgname string) {
 			p = p[:i]
 		}
 		suffix := ".o"
-		if writearchive > 0 {
+		if writearchive {
 			suffix = ".a"
 		}
 		outfile = p + suffix
diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go
index eed0ed6e24..59ce0547c8 100644
--- a/src/cmd/compile/internal/gc/obj.go
+++ b/src/cmd/compile/internal/gc/obj.go
@@ -33,7 +33,7 @@ func dumpobj() {
 
 	startobj := int64(0)
 	var arhdr [ArhdrSize]byte
-	if writearchive != 0 {
+	if writearchive {
 		bout.WriteString("!\n")
 		arhdr = [ArhdrSize]byte{}
 		bout.Write(arhdr[:])
@@ -43,7 +43,7 @@ func dumpobj() {
 	fmt.Fprintf(bout, "go object %s %s %s %s\n", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion(), obj.Expstring())
 	dumpexport()
 
-	if writearchive != 0 {
+	if writearchive {
 		bout.Flush()
 		size := bout.Offset() - startobj
 		if size&1 != 0 {
@@ -62,7 +62,7 @@ func dumpobj() {
 	}
 
 	if pragcgobuf != "" {
-		if writearchive != 0 {
+		if writearchive {
 			// write empty export section; must be before cgo section
 			fmt.Fprintf(bout, "\n$$\n\n$$\n\n")
 		}
@@ -90,7 +90,7 @@ func dumpobj() {
 	dumpdata()
 	obj.Writeobjdirect(Ctxt, bout)
 
-	if writearchive != 0 {
+	if writearchive {
 		bout.Flush()
 		size := bout.Offset() - startobj
 		if size&1 != 0 {
diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go
index baa960bf75..7b9b91e7b0 100644
--- a/src/cmd/compile/internal/gc/pgen.go
+++ b/src/cmd/compile/internal/gc/pgen.go
@@ -364,7 +364,7 @@ func compile(fn *Node) {
 	dowidth(Curfn.Type)
 
 	if len(fn.Nbody.Slice()) == 0 {
-		if pure_go != 0 || strings.HasPrefix(fn.Func.Nname.Sym.Name, "init.") {
+		if pure_go || strings.HasPrefix(fn.Func.Nname.Sym.Name, "init.") {
 			Yyerror("missing function body for %q", fn.Func.Nname.Sym.Name)
 			return
 		}
diff --git a/src/cmd/compile/internal/gc/racewalk.go b/src/cmd/compile/internal/gc/racewalk.go
index f6e65146d6..a8a5e92485 100644
--- a/src/cmd/compile/internal/gc/racewalk.go
+++ b/src/cmd/compile/internal/gc/racewalk.go
@@ -54,14 +54,14 @@ func instrument(fn *Node) {
 		return
 	}
 
-	if flag_race == 0 || !ispkgin(norace_inst_pkgs) {
+	if !flag_race || !ispkgin(norace_inst_pkgs) {
 		instrumentlist(fn.Nbody, nil)
 
 		// nothing interesting for race detector in fn->enter
 		instrumentlist(fn.Func.Exit, nil)
 	}
 
-	if flag_race != 0 {
+	if flag_race {
 		// nodpc is the PC of the caller as extracted by
 		// getcallerpc. We use -widthptr(FP) for x86.
 		// BUG: this will not work on arm.
@@ -503,7 +503,7 @@ func callinstr(np **Node, init *Nodes, wr int, skip int) bool {
 		n = treecopy(n, 0)
 		makeaddable(n)
 		var f *Node
-		if flag_msan != 0 {
+		if flag_msan {
 			name := "msanread"
 			if wr != 0 {
 				name = "msanwrite"
@@ -515,7 +515,7 @@ func callinstr(np **Node, init *Nodes, wr int, skip int) bool {
 				Fatalf("instrument: %v badwidth", t)
 			}
 			f = mkcall(name, nil, init, uintptraddr(n), Nodintconst(w))
-		} else if flag_race != 0 && (t.IsStruct() || t.IsArray()) {
+		} else if flag_race && (t.IsStruct() || t.IsArray()) {
 			name := "racereadrange"
 			if wr != 0 {
 				name = "racewriterange"
@@ -527,7 +527,7 @@ func callinstr(np **Node, init *Nodes, wr int, skip int) bool {
 				Fatalf("instrument: %v badwidth", t)
 			}
 			f = mkcall(name, nil, init, uintptraddr(n), Nodintconst(w))
-		} else if flag_race != 0 {
+		} else if flag_race {
 			name := "raceread"
 			if wr != 0 {
 				name = "racewrite"
diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go
index 70a75f9324..df68f46d4c 100644
--- a/src/cmd/compile/internal/gc/reflect.go
+++ b/src/cmd/compile/internal/gc/reflect.go
@@ -1424,10 +1424,10 @@ func dumptypestructs() {
 		// add paths for runtime and main, which 6l imports implicitly.
 		dimportpath(Runtimepkg)
 
-		if flag_race != 0 {
+		if flag_race {
 			dimportpath(racepkg)
 		}
-		if flag_msan != 0 {
+		if flag_msan {
 			dimportpath(msanpkg)
 		}
 		dimportpath(mkpkg("main"))
diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go
index fdd14953e6..4a93dc1087 100644
--- a/src/cmd/compile/internal/gc/ssa.go
+++ b/src/cmd/compile/internal/gc/ssa.go
@@ -554,7 +554,7 @@ func (s *state) stmt(n *Node) {
 	case OCALLFUNC, OCALLMETH, OCALLINTER:
 		s.call(n, callNormal)
 		if n.Op == OCALLFUNC && n.Left.Op == ONAME && n.Left.Class == PFUNC &&
-			(compiling_runtime != 0 && n.Left.Sym.Name == "throw" ||
+			(compiling_runtime && n.Left.Sym.Name == "throw" ||
 				n.Left.Sym.Pkg == Runtimepkg && (n.Left.Sym.Name == "gopanic" || n.Left.Sym.Name == "selectgo" || n.Left.Sym.Name == "block")) {
 			m := s.mem()
 			b := s.endBlock()
@@ -579,7 +579,7 @@ func (s *state) stmt(n *Node) {
 		if n.Left.Class&PHEAP == 0 {
 			return
 		}
-		if compiling_runtime != 0 {
+		if compiling_runtime {
 			Fatalf("%v escapes to heap, not allowed in runtime.", n)
 		}
 
diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index 776eb9c64e..f6af11adba 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -750,7 +750,7 @@ func assignop(src *Type, dst *Type, why *string) Op {
 
 	// TODO(rsc,lvd): This behaves poorly in the presence of inlining.
 	// https://golang.org/issue/2795
-	if safemode != 0 && importpkg == nil && src != nil && src.Etype == TUNSAFEPTR {
+	if safemode && importpkg == nil && src != nil && src.Etype == TUNSAFEPTR {
 		Yyerror("cannot use unsafe.Pointer")
 		errorexit()
 	}
diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go
index 7089d7de72..6067677738 100644
--- a/src/cmd/compile/internal/gc/typecheck.go
+++ b/src/cmd/compile/internal/gc/typecheck.go
@@ -1354,7 +1354,7 @@ OpSwitch:
 		if t.Results().NumFields() == 1 {
 			n.Type = l.Type.Results().Field(0).Type
 
-			if n.Op == OCALLFUNC && n.Left.Op == ONAME && (compiling_runtime != 0 || n.Left.Sym.Pkg == Runtimepkg) && n.Left.Sym.Name == "getg" {
+			if n.Op == OCALLFUNC && n.Left.Op == ONAME && (compiling_runtime || n.Left.Sym.Pkg == Runtimepkg) && n.Left.Sym.Name == "getg" {
 				// Emit code for runtime.getg() directly instead of calling function.
 				// Most such rewrites (for example the similar one for math.Sqrt) should be done in walk,
 				// so that the ordering pass can make sure to preserve the semantics of the original code
@@ -2176,7 +2176,7 @@ OpSwitch:
 		}
 	}
 
-	if safemode != 0 && incannedimport == 0 && importpkg == nil && compiling_wrappers == 0 && t != nil && t.Etype == TUNSAFEPTR {
+	if safemode && incannedimport == 0 && importpkg == nil && compiling_wrappers == 0 && t != nil && t.Etype == TUNSAFEPTR {
 		Yyerror("cannot use unsafe.Pointer")
 	}
 
diff --git a/src/cmd/compile/internal/gc/unsafe.go b/src/cmd/compile/internal/gc/unsafe.go
index 338f3c0eae..e1d3b40098 100644
--- a/src/cmd/compile/internal/gc/unsafe.go
+++ b/src/cmd/compile/internal/gc/unsafe.go
@@ -9,7 +9,7 @@ func unsafenmagic(nn *Node) *Node {
 	fn := nn.Left
 	args := nn.List
 
-	if safemode != 0 || fn == nil || fn.Op != ONAME {
+	if safemode || fn == nil || fn.Op != ONAME {
 		return nil
 	}
 	s := fn.Sym
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index 3e5f5161db..78bad8d348 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
@@ -594,8 +594,7 @@ opswitch:
 		// for a struct containing a reflect.Value, which itself has
 		// an unexported field of type unsafe.Pointer.
 		old_safemode := safemode
-
-		safemode = 0
+		safemode = false
 		n = walkcompare(n, init)
 		safemode = old_safemode
 
@@ -1938,7 +1937,7 @@ func walkprint(nn *Node, init *Nodes) *Node {
 			on = substArgTypes(on, n.Type) // any-1
 		} else if Isint[et] {
 			if et == TUINT64 {
-				if (t.Sym.Pkg == Runtimepkg || compiling_runtime != 0) && t.Sym.Name == "hex" {
+				if (t.Sym.Pkg == Runtimepkg || compiling_runtime) && t.Sym.Name == "hex" {
 					on = syslook("printhex")
 				} else {
 					on = syslook("printuint")
@@ -2041,7 +2040,7 @@ func isglobal(n *Node) bool {
 
 // Do we need a write barrier for the assignment l = r?
 func needwritebarrier(l *Node, r *Node) bool {
-	if use_writebarrier == 0 {
+	if !use_writebarrier {
 		return false
 	}
 
@@ -2550,7 +2549,7 @@ func paramstoheap(params *Type, out bool) []*Node {
 		}
 
 		// generate allocation & copying code
-		if compiling_runtime != 0 {
+		if compiling_runtime {
 			Yyerror("%v escapes to heap, not allowed in runtime.", v)
 		}
 		if prealloc[v] == nil {
-- 
cgit v1.3


From babfb4ec3ba3e4e36b1003d6efbaeddf2e975240 Mon Sep 17 00:00:00 2001
From: Matthew Dempsky 
Date: Wed, 13 Apr 2016 18:41:59 -0700
Subject: cmd/internal/obj: change Link.Flag_shared to bool

Change-Id: I9bda2ce6f45fb8292503f86d8f9f161601f222b7
Reviewed-on: https://go-review.googlesource.com/22053
Reviewed-by: Michael Hudson-Doyle 
---
 src/cmd/asm/main.go                      |  4 +---
 src/cmd/compile/internal/gc/cgen.go      |  2 +-
 src/cmd/compile/internal/gc/main.go      |  8 +-------
 src/cmd/compile/internal/ppc64/galign.go |  2 +-
 src/cmd/compile/internal/ppc64/gsubr.go  |  4 ++--
 src/cmd/compile/internal/ppc64/reg.go    |  2 +-
 src/cmd/compile/internal/x86/reg.go      |  2 +-
 src/cmd/internal/obj/arm/asm5.go         |  8 ++++----
 src/cmd/internal/obj/arm64/asm7.go       |  2 +-
 src/cmd/internal/obj/link.go             |  2 +-
 src/cmd/internal/obj/ppc64/asm9.go       | 10 +++++-----
 src/cmd/internal/obj/ppc64/obj9.go       |  4 ++--
 src/cmd/internal/obj/s390x/asmz.go       |  2 +-
 src/cmd/internal/obj/x86/asm6.go         | 22 +++++++++++-----------
 src/cmd/internal/obj/x86/obj6.go         |  4 ++--
 15 files changed, 35 insertions(+), 43 deletions(-)

(limited to 'src/cmd')

diff --git a/src/cmd/asm/main.go b/src/cmd/asm/main.go
index f010ca93f1..40e1d9c4a9 100644
--- a/src/cmd/asm/main.go
+++ b/src/cmd/asm/main.go
@@ -39,9 +39,7 @@ func main() {
 	}
 	ctxt.LineHist.TrimPathPrefix = *flags.TrimPath
 	ctxt.Flag_dynlink = *flags.Dynlink
-	if *flags.Shared || *flags.Dynlink {
-		ctxt.Flag_shared = 1
-	}
+	ctxt.Flag_shared = *flags.Shared || *flags.Dynlink
 	ctxt.Bso = bufio.NewWriter(os.Stdout)
 	defer ctxt.Bso.Flush()
 
diff --git a/src/cmd/compile/internal/gc/cgen.go b/src/cmd/compile/internal/gc/cgen.go
index 9de2a19f68..32ca1ae940 100644
--- a/src/cmd/compile/internal/gc/cgen.go
+++ b/src/cmd/compile/internal/gc/cgen.go
@@ -2363,7 +2363,7 @@ func Ginscall(f *Node, proc int) {
 					// If the MOVD is not needed, insert a hardware NOP
 					// so that the same number of instructions are used
 					// on ppc64 in both shared and non-shared modes.
-					if Ctxt.Flag_shared != 0 {
+					if Ctxt.Flag_shared {
 						p := Thearch.Gins(ppc64.AMOVD, nil, nil)
 						p.From.Type = obj.TYPE_MEM
 						p.From.Offset = 24
diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index f41097b83b..2baf9f6585 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -207,13 +207,7 @@ func Main() {
 	flag.BoolVar(&ssaEnabled, "ssa", true, "use SSA backend to generate code")
 	obj.Flagparse(usage)
 
-	if flag_dynlink {
-		flag_shared = true
-	}
-	if flag_shared {
-		// TODO(mdempsky): Change Flag_shared to bool.
-		Ctxt.Flag_shared = 1
-	}
+	Ctxt.Flag_shared = flag_dynlink || flag_shared
 	Ctxt.Flag_dynlink = flag_dynlink
 	Ctxt.Flag_optimize = Debug['N'] == 0
 
diff --git a/src/cmd/compile/internal/ppc64/galign.go b/src/cmd/compile/internal/ppc64/galign.go
index 04fa4cfc78..a83dff9a8b 100644
--- a/src/cmd/compile/internal/ppc64/galign.go
+++ b/src/cmd/compile/internal/ppc64/galign.go
@@ -11,7 +11,7 @@ import (
 )
 
 func betypeinit() {
-	if gc.Ctxt.Flag_shared != 0 {
+	if gc.Ctxt.Flag_shared {
 		gc.Thearch.ReservedRegs = append(gc.Thearch.ReservedRegs, ppc64.REG_R2)
 		gc.Thearch.ReservedRegs = append(gc.Thearch.ReservedRegs, ppc64.REG_R12)
 	}
diff --git a/src/cmd/compile/internal/ppc64/gsubr.go b/src/cmd/compile/internal/ppc64/gsubr.go
index de6e2fbe05..eb6cd2c5e9 100644
--- a/src/cmd/compile/internal/ppc64/gsubr.go
+++ b/src/cmd/compile/internal/ppc64/gsubr.go
@@ -580,7 +580,7 @@ func rawgins(as obj.As, f *gc.Node, t *gc.Node) *obj.Prog {
 	case obj.ACALL:
 		if p.To.Type == obj.TYPE_REG && p.To.Reg != ppc64.REG_CTR {
 			// Allow front end to emit CALL REG, and rewrite into MOV REG, CTR; CALL CTR.
-			if gc.Ctxt.Flag_shared != 0 {
+			if gc.Ctxt.Flag_shared {
 				// Make sure function pointer is in R12 as well when
 				// compiling Go into PIC.
 				// TODO(mwhudson): it would obviously be better to
@@ -602,7 +602,7 @@ func rawgins(as obj.As, f *gc.Node, t *gc.Node) *obj.Prog {
 			p.To.Type = obj.TYPE_REG
 			p.To.Reg = ppc64.REG_CTR
 
-			if gc.Ctxt.Flag_shared != 0 {
+			if gc.Ctxt.Flag_shared {
 				// When compiling Go into PIC, the function we just
 				// called via pointer might have been implemented in
 				// a separate module and so overwritten the TOC
diff --git a/src/cmd/compile/internal/ppc64/reg.go b/src/cmd/compile/internal/ppc64/reg.go
index 447679e207..558ba4a4f4 100644
--- a/src/cmd/compile/internal/ppc64/reg.go
+++ b/src/cmd/compile/internal/ppc64/reg.go
@@ -113,7 +113,7 @@ func excludedregs() uint64 {
 	// Exclude registers with fixed functions
 	regbits := 1<<0 | RtoB(ppc64.REGSP) | RtoB(ppc64.REGG) | RtoB(ppc64.REGTLS) | RtoB(ppc64.REGTMP)
 
-	if gc.Ctxt.Flag_shared != 0 {
+	if gc.Ctxt.Flag_shared {
 		// When compiling Go into PIC, R2 is reserved to be the TOC pointer
 		// and R12 so that calls via function pointer can stomp on it.
 		regbits |= RtoB(ppc64.REG_R2)
diff --git a/src/cmd/compile/internal/x86/reg.go b/src/cmd/compile/internal/x86/reg.go
index 76d90b8e89..d49a1aed9d 100644
--- a/src/cmd/compile/internal/x86/reg.go
+++ b/src/cmd/compile/internal/x86/reg.go
@@ -62,7 +62,7 @@ func regnames(n *int) []string {
 }
 
 func excludedregs() uint64 {
-	if gc.Ctxt.Flag_shared != 0 {
+	if gc.Ctxt.Flag_shared {
 		return RtoB(x86.REG_SP) | RtoB(x86.REG_CX)
 	} else {
 		return RtoB(x86.REG_SP)
diff --git a/src/cmd/internal/obj/arm/asm5.go b/src/cmd/internal/obj/arm/asm5.go
index f49ee65a04..564f96a94e 100644
--- a/src/cmd/internal/obj/arm/asm5.go
+++ b/src/cmd/internal/obj/arm/asm5.go
@@ -870,7 +870,7 @@ func addpool(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) {
 		t.To.Type = a.Type
 		t.To.Name = a.Name
 
-		if ctxt.Flag_shared != 0 && t.To.Sym != nil {
+		if ctxt.Flag_shared && t.To.Sym != nil {
 			t.Rel = p
 		}
 
@@ -1015,7 +1015,7 @@ func aclass(ctxt *obj.Link, a *obj.Addr) int {
 
 			ctxt.Instoffset = 0 // s.b. unused but just in case
 			if a.Sym.Type == obj.STLSBSS {
-				if ctxt.Flag_shared != 0 {
+				if ctxt.Flag_shared {
 					return C_TLS_IE
 				} else {
 					return C_TLS_LE
@@ -1322,7 +1322,7 @@ func buildop(ctxt *obj.Link) {
 	}
 	for n = 0; optab[n].as != obj.AXXX; n++ {
 		if optab[n].flag&LPCREL != 0 {
-			if ctxt.Flag_shared != 0 {
+			if ctxt.Flag_shared {
 				optab[n].size += int8(optab[n].pcrelsiz)
 			} else {
 				optab[n].flag &^= LPCREL
@@ -1633,7 +1633,7 @@ func asmout(ctxt *obj.Link, p *obj.Prog, o *Optab, out []uint32) {
 			rel.Sym = p.To.Sym
 			rel.Add = p.To.Offset
 
-			if ctxt.Flag_shared != 0 {
+			if ctxt.Flag_shared {
 				if p.To.Name == obj.NAME_GOTREF {
 					rel.Type = obj.R_GOTPCREL
 				} else {
diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go
index d0ae6115cb..55397132e0 100644
--- a/src/cmd/internal/obj/arm64/asm7.go
+++ b/src/cmd/internal/obj/arm64/asm7.go
@@ -972,7 +972,7 @@ func aclass(ctxt *obj.Link, a *obj.Addr) int {
 			ctxt.Instoffset = a.Offset
 			if a.Sym != nil { // use relocation
 				if a.Sym.Type == obj.STLSBSS {
-					if ctxt.Flag_shared != 0 {
+					if ctxt.Flag_shared {
 						return C_TLS_IE
 					} else {
 						return C_TLS_LE
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 55c9f4f9e2..5f257f60ab 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -617,7 +617,7 @@ type Link struct {
 	Debugvlog     int32
 	Debugdivmod   int32
 	Debugpcln     int32
-	Flag_shared   int32
+	Flag_shared   bool
 	Flag_dynlink  bool
 	Flag_optimize bool
 	Bso           *bufio.Writer
diff --git a/src/cmd/internal/obj/ppc64/asm9.go b/src/cmd/internal/obj/ppc64/asm9.go
index 0497d3b678..e793f26803 100644
--- a/src/cmd/internal/obj/ppc64/asm9.go
+++ b/src/cmd/internal/obj/ppc64/asm9.go
@@ -585,7 +585,7 @@ func aclass(ctxt *obj.Link, a *obj.Addr) int {
 			ctxt.Instoffset = a.Offset
 			if a.Sym != nil { // use relocation
 				if a.Sym.Type == obj.STLSBSS {
-					if ctxt.Flag_shared != 0 {
+					if ctxt.Flag_shared {
 						return C_TLS_IE
 					} else {
 						return C_TLS_LE
@@ -1413,7 +1413,7 @@ func opform(ctxt *obj.Link, insn uint32) int {
 func symbolAccess(ctxt *obj.Link, s *obj.LSym, d int64, reg int16, op uint32) (o1, o2 uint32) {
 	var base uint32
 	form := opform(ctxt, op)
-	if ctxt.Flag_shared != 0 {
+	if ctxt.Flag_shared {
 		base = REG_R2
 	} else {
 		base = REG_R0
@@ -1425,7 +1425,7 @@ func symbolAccess(ctxt *obj.Link, s *obj.LSym, d int64, reg int16, op uint32) (o
 	rel.Siz = 8
 	rel.Sym = s
 	rel.Add = d
-	if ctxt.Flag_shared != 0 {
+	if ctxt.Flag_shared {
 		switch form {
 		case D_FORM:
 			rel.Type = obj.R_ADDRPOWER_TOCREL
@@ -1646,7 +1646,7 @@ func asmout(ctxt *obj.Link, p *obj.Prog, o *Optab, out []uint32) {
 			if v != 0 {
 				ctxt.Diag("illegal indexed instruction\n%v", p)
 			}
-			if ctxt.Flag_shared != 0 && r == REG_R13 {
+			if ctxt.Flag_shared && r == REG_R13 {
 				rel := obj.Addrel(ctxt.Cursym)
 				rel.Off = int32(ctxt.Pc)
 				rel.Siz = 4
@@ -1677,7 +1677,7 @@ func asmout(ctxt *obj.Link, p *obj.Prog, o *Optab, out []uint32) {
 			if v != 0 {
 				ctxt.Diag("illegal indexed instruction\n%v", p)
 			}
-			if ctxt.Flag_shared != 0 && r == REG_R13 {
+			if ctxt.Flag_shared && r == REG_R13 {
 				rel := obj.Addrel(ctxt.Cursym)
 				rel.Off = int32(ctxt.Pc)
 				rel.Siz = 4
diff --git a/src/cmd/internal/obj/ppc64/obj9.go b/src/cmd/internal/obj/ppc64/obj9.go
index 7a24d1d1bf..4f9b3943cf 100644
--- a/src/cmd/internal/obj/ppc64/obj9.go
+++ b/src/cmd/internal/obj/ppc64/obj9.go
@@ -470,7 +470,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym) {
 
 			q = p
 
-			if ctxt.Flag_shared != 0 && cursym.Name != "runtime.duffzero" && cursym.Name != "runtime.duffcopy" && cursym.Name != "runtime.stackBarrier" {
+			if ctxt.Flag_shared && cursym.Name != "runtime.duffzero" && cursym.Name != "runtime.duffcopy" && cursym.Name != "runtime.stackBarrier" {
 				// When compiling Go into PIC, all functions must start
 				// with instructions to load the TOC pointer into r2:
 				//
@@ -558,7 +558,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym) {
 				q.Spadj = int32(-aoffset)
 			}
 
-			if ctxt.Flag_shared != 0 {
+			if ctxt.Flag_shared {
 				q = obj.Appendp(ctxt, q)
 				q.As = AMOVD
 				q.Lineno = p.Lineno
diff --git a/src/cmd/internal/obj/s390x/asmz.go b/src/cmd/internal/obj/s390x/asmz.go
index bae4dc3ce7..9b26580d11 100644
--- a/src/cmd/internal/obj/s390x/asmz.go
+++ b/src/cmd/internal/obj/s390x/asmz.go
@@ -473,7 +473,7 @@ func aclass(ctxt *obj.Link, a *obj.Addr) int {
 			}
 			ctxt.Instoffset = a.Offset
 			if a.Sym.Type == obj.STLSBSS {
-				if ctxt.Flag_shared != 0 {
+				if ctxt.Flag_shared {
 					return C_TLS_IE // initial exec model
 				}
 				return C_TLS_LE // local exec model
diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go
index c563a7a48d..e806a834fd 100644
--- a/src/cmd/internal/obj/x86/asm6.go
+++ b/src/cmd/internal/obj/x86/asm6.go
@@ -2165,7 +2165,7 @@ func prefixof(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int {
 					return 0x64 // FS
 				}
 
-				if ctxt.Flag_shared != 0 {
+				if ctxt.Flag_shared {
 					log.Fatalf("unknown TLS base register for linux with -shared")
 				} else {
 					return 0x64 // FS
@@ -2185,7 +2185,7 @@ func prefixof(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int {
 	}
 
 	if p.Mode == 32 {
-		if a.Index == REG_TLS && ctxt.Flag_shared != 0 {
+		if a.Index == REG_TLS && ctxt.Flag_shared {
 			// When building for inclusion into a shared library, an instruction of the form
 			//     MOVL 0(CX)(TLS*1), AX
 			// becomes
@@ -2214,7 +2214,7 @@ func prefixof(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int {
 		return 0x26
 
 	case REG_TLS:
-		if ctxt.Flag_shared != 0 {
+		if ctxt.Flag_shared {
 			// When building for inclusion into a shared library, an instruction of the form
 			//     MOV 0(CX)(TLS*1), AX
 			// becomes
@@ -2288,7 +2288,7 @@ func oclass(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int {
 
 		case obj.NAME_EXTERN,
 			obj.NAME_STATIC:
-			if a.Sym != nil && isextern(a.Sym) || (p.Mode == 32 && ctxt.Flag_shared == 0) {
+			if a.Sym != nil && isextern(a.Sym) || (p.Mode == 32 && !ctxt.Flag_shared) {
 				return Yi32
 			}
 			return Yiauto // use pc-relative addressing
@@ -2707,7 +2707,7 @@ func vaddr(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r *obj.Reloc) int64 {
 		if a.Name == obj.NAME_GOTREF {
 			r.Siz = 4
 			r.Type = obj.R_GOTPCREL
-		} else if isextern(s) || (p.Mode != 64 && ctxt.Flag_shared == 0) {
+		} else if isextern(s) || (p.Mode != 64 && !ctxt.Flag_shared) {
 			r.Siz = 4
 			r.Type = obj.R_ADDR
 		} else {
@@ -2728,7 +2728,7 @@ func vaddr(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r *obj.Reloc) int64 {
 			log.Fatalf("reloc")
 		}
 
-		if ctxt.Flag_shared == 0 || isAndroid {
+		if !ctxt.Flag_shared || isAndroid {
 			r.Type = obj.R_TLS_LE
 			r.Siz = 4
 			r.Off = -1 // caller must fill in
@@ -2793,7 +2793,7 @@ func asmandsz(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r int, rex int, m64 int)
 			if !isextern(a.Sym) && p.Mode == 64 {
 				goto bad
 			}
-			if p.Mode == 32 && ctxt.Flag_shared != 0 {
+			if p.Mode == 32 && ctxt.Flag_shared {
 				base = REG_CX
 			} else {
 				base = REG_NONE
@@ -2838,7 +2838,7 @@ func asmandsz(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r int, rex int, m64 int)
 		if a.Sym == nil {
 			ctxt.Diag("bad addr: %v", p)
 		}
-		if p.Mode == 32 && ctxt.Flag_shared != 0 {
+		if p.Mode == 32 && ctxt.Flag_shared {
 			base = REG_CX
 		} else {
 			base = REG_NONE
@@ -2892,7 +2892,7 @@ func asmandsz(ctxt *obj.Link, p *obj.Prog, a *obj.Addr, r int, rex int, m64 int)
 	}
 
 	if REG_AX <= base && base <= REG_R15 {
-		if a.Index == REG_TLS && ctxt.Flag_shared == 0 {
+		if a.Index == REG_TLS && !ctxt.Flag_shared {
 			rel = obj.Reloc{}
 			rel.Type = obj.R_TLS_LE
 			rel.Siz = 4
@@ -3945,7 +3945,7 @@ func doasm(ctxt *obj.Link, p *obj.Prog) {
 
 						case obj.Hlinux,
 							obj.Hnacl:
-							if ctxt.Flag_shared != 0 {
+							if ctxt.Flag_shared {
 								// Note that this is not generating the same insns as the other cases.
 								//     MOV TLS, R_to
 								// becomes
@@ -4019,7 +4019,7 @@ func doasm(ctxt *obj.Link, p *obj.Prog) {
 						log.Fatalf("unknown TLS base location for %s", obj.Headstr(ctxt.Headtype))
 
 					case obj.Hlinux:
-						if ctxt.Flag_shared == 0 {
+						if !ctxt.Flag_shared {
 							log.Fatalf("unknown TLS base location for linux without -shared")
 						}
 						// Note that this is not generating the same insn as the other cases.
diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go
index 302a597f4c..b638c048e8 100644
--- a/src/cmd/internal/obj/x86/obj6.go
+++ b/src/cmd/internal/obj/x86/obj6.go
@@ -66,7 +66,7 @@ func CanUse1InsnTLS(ctxt *obj.Link) bool {
 		obj.Hwindows:
 		return false
 	case obj.Hlinux:
-		return ctxt.Flag_shared == 0
+		return !ctxt.Flag_shared
 	}
 
 	return true
@@ -314,7 +314,7 @@ func progedit(ctxt *obj.Link, p *obj.Prog) {
 		rewriteToUseGot(ctxt, p)
 	}
 
-	if ctxt.Flag_shared != 0 && p.Mode == 32 {
+	if ctxt.Flag_shared && p.Mode == 32 {
 		rewriteToPcrel(ctxt, p)
 	}
 }
-- 
cgit v1.3


From 2c9d773f7411de211389b9e1da441fae68f826d8 Mon Sep 17 00:00:00 2001
From: Dmitry Vyukov 
Date: Thu, 14 Apr 2016 16:31:42 +0200
Subject: misc/trace: update trace viewer html

The old trace-viewer is broken since Chrome 49:
https://bugs.chromium.org/p/chromium/issues/detail?id=569417
It was fixed in:
https://github.com/catapult-project/catapult/commit/506457cbd726324f327b80ae11f46c1dfeb8710d

This change updates trace-viewer to the latest version
(now it is called catapult).

This version has a bug in the lean config that we use, though:
https://github.com/catapult-project/catapult/issues/2247
So use full config for now (it works, but leads to larger html).
When the bug is fixed we need to switch back to lean config (issue #15302).

Change-Id: Ifb8d782ced66e3292d81c5604039fe18eaf267c5
Reviewed-on: https://go-review.googlesource.com/22013
Reviewed-by: Brad Fitzpatrick 
---
 misc/trace/README.md              |   12 +-
 misc/trace/trace_viewer_lean.html | 8773 ++++++++++++++++++++++++++-----------
 src/cmd/trace/trace.go            |   91 +-
 3 files changed, 6332 insertions(+), 2544 deletions(-)

(limited to 'src/cmd')

diff --git a/misc/trace/README.md b/misc/trace/README.md
index 8561c79bfd..2e8bb057d4 100644
--- a/misc/trace/README.md
+++ b/misc/trace/README.md
@@ -1,12 +1,16 @@
 This directory contains helper file for trace viewer (`go tool trace`).
 
 `trace_viewer_lean.html` was generated by following
-[instructions](https://github.com/google/trace-viewer/wiki/Embedding)
-on revision `280626ef607decf36291e290d5f0322b173e8a7f` using:
+[instructions](https://github.com/catapult-project/catapult/blob/master/tracing/docs/embedding-trace-viewer.md)
+on revision `623a005a3ffa9de13c4b92bc72290e7bcd1ca591`
+of [catapult](https://github.com/catapult-project/catapult) using:
 ```
-trace-viewer$ ./vulcanize_trace_viewer --config=lean
-trace-viewer$ cp bin/trace_viewer_lean.html $GOROOT/misc/trace/
+catapult$ ./tracing/bin/vulcanize_trace_viewer --config=full
+catapult$ cp tracing/bin/trace_viewer_full.html $GOROOT/misc/trace/trace_viewer_lean.html
 ```
+We are supposed to use --config=lean (produces smaller html),
+but it is broken at the moment:
+https://github.com/catapult-project/catapult/issues/2247
 
 The license for trace-viewer is as follows:
 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
diff --git a/misc/trace/trace_viewer_lean.html b/misc/trace/trace_viewer_lean.html
index 5d40bc5805..7939aae8a6 100644
--- a/misc/trace/trace_viewer_lean.html
+++ b/misc/trace/trace_viewer_lean.html
@@ -1,113 +1,9 @@
-
+
 
   
-  
-    
-
-
-
-
-
-
-
-
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+  
+
+  
+
+  
 
-
-
+  
+
+  
 
-
-
-
-
-
-
-
 
-
-
 
-
-
+
+  
+
+  
+
+  
 
-    table tr > td {
-      padding: 2px 4px 2px 4px;
-      vertical-align: text-top;
-      width: 150px;
+  
+
+  
+  
+
+  
+
+  
+  
+
+  
+
+  
+
+  
+
+  
+
+  
-
-
-
+    
+  
 
-
-
+  
+
+  
+  
+
+  
+
+  
+
+  
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+    
+ -
- - - - - - - + + + + - - - - - - + +
+ + +
+ + + +
+ - - - - - - - - - - - - - - - - - - - + #icon { + display: flex; + flex: 0 0 auto; + flex: 0 0 auto; + } + dialog { + position: absolute; + padding: 0; + border: 0; + margin: 0; + } + dialog::backdrop { + background: rgba(0,0,0,.05); + } - - - - - - - - +
+ -
- - +
+ +
+
- - - - - - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + +