[dev.typeparams] merge master into dev.typeparams

Change-Id: Ib2a0f85e00a7366b784e3615366ca3bde4ec8c49
This commit is contained in:
Rob Findley 2020-10-12 15:41:05 -04:00
commit 986cad14e2
170 changed files with 3046 additions and 1321 deletions

View file

@ -38,6 +38,17 @@ Do not send CLs removing the interior tags from such phrases.
<code>netbsd/arm64</code> port). <code>netbsd/arm64</code> port).
</p> </p>
<h3 id="386">386</h3>
<p><!-- golang.org/issue/40255, golang.org/issue/41848, CL 258957, and CL 260017 -->
As <a href="go1.15#386">announced</a> in the Go 1.15 release notes,
Go 1.16 drops support for x87 mode compilation (<code>GO386=387</code>).
Support for non-SSE2 processors is now available using soft float
mode (<code>GO386=softfloat</code>).
Users running on non-SSE2 processors should replace <code>GO386=387</code>
with <code>GO386=softfloat</code>.
</p>
<h2 id="tools">Tools</h2> <h2 id="tools">Tools</h2>
<p> <p>
@ -162,6 +173,8 @@ Do not send CLs removing the interior tags from such phrases.
TODO: update with final numbers later in the release. TODO: update with final numbers later in the release.
</p> </p>
<!-- CL 255259: https://golang.org/cl/255259: cmd/link: enable ASLR on windows binaries built with -buildmode=c-shared -->
<h2 id="library">Core library</h2> <h2 id="library">Core library</h2>
<p> <p>
@ -200,6 +213,14 @@ Do not send CLs removing the interior tags from such phrases.
with <code>"use of closed network connection"</code>. with <code>"use of closed network connection"</code>.
</p> </p>
<h3 id="reflect"><a href="/pkg/reflect/">reflect</a></h3>
<p><!-- CL 259237, golang.org/issue/22075 -->
For interface types and values, <a href="/pkg/reflect/#Value.Method">Method</a>,
<a href="/pkg/reflect/#Value.MethodByName">MethodByName</a>, and
<a href="/pkg/reflect/#Value.NumMethod">NumMethod</a> now
operate on the interface's exported method set, rather than its full method set.
</p>
<h3 id="text/template/parse"><a href="/pkg/text/template/parse/">text/template/parse</a></h3> <h3 id="text/template/parse"><a href="/pkg/text/template/parse/">text/template/parse</a></h3>
@ -273,5 +294,18 @@ Do not send CLs removing the interior tags from such phrases.
of the form <code>"Range": "bytes=--N"</code> where <code>"-N"</code> is a negative suffix length, for of the form <code>"Range": "bytes=--N"</code> where <code>"-N"</code> is a negative suffix length, for
example <code>"Range": "bytes=--2"</code>. It now replies with a <code>416 "Range Not Satisfiable"</code> response. example <code>"Range": "bytes=--2"</code>. It now replies with a <code>416 "Range Not Satisfiable"</code> response.
</p> </p>
<p><!-- CL 256498, golang.org/issue/36990 -->
Cookies set with <code>SameSiteDefaultMode</code> now behave according to the current
spec (no attribute is set) instead of generating a SameSite key without a value.
</p>
</dd> </dd>
</dl><!-- net/http --> </dl><!-- net/http -->
<dl id="runtime/debug"><dt><a href="/pkg/runtime/debug/">runtime/debug</a></dt>
<dd>
<p><!-- CL 249677 -->
TODO: <a href="https://golang.org/cl/249677">https://golang.org/cl/249677</a>: provide Addr method for errors from SetPanicOnFault
</p>
</dd>
</dl><!-- runtime/debug -->

View file

@ -666,16 +666,13 @@ For example, you should not set <code>$GOHOSTARCH</code> to
<code>arm</code> on an x86 system. <code>arm</code> on an x86 system.
</p> </p>
<li><code>$GO386</code> (for <code>386</code> only, default is auto-detected <li><code>$GO386</code> (for <code>386</code> only, defaults to <code>sse2</code>)
if built on either <code>386</code> or <code>amd64</code>, <code>387</code> otherwise)
<p> <p>
This controls the code generated by gc to use either the 387 floating-point unit This variable controls how gc implements floating point computations.
(set to <code>387</code>) or SSE2 instructions (set to <code>sse2</code>) for
floating point computations.
</p> </p>
<ul> <ul>
<li><code>GO386=387</code>: use x87 for floating point operations; should support all x86 chips (Pentium MMX or later).</li> <li><code>GO386=softfloat</code>: use software floating point operations; should support all x86 chips (Pentium MMX or later).</li>
<li><code>GO386=sse2</code>: use SSE2 for floating point operations; has better performance than 387, but only available on Pentium 4/Opteron/Athlon 64 or later.</li> <li><code>GO386=sse2</code>: use SSE2 for floating point operations; has better performance but only available on Pentium 4/Opteron/Athlon 64 or later.</li>
</ul> </ul>
</li> </li>

View file

@ -62,7 +62,7 @@ import (
func testSigaltstack(t *testing.T) { func testSigaltstack(t *testing.T) {
switch { switch {
case runtime.GOOS == "solaris", runtime.GOOS == "illumos", (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64": case runtime.GOOS == "solaris", runtime.GOOS == "illumos", runtime.GOOS == "ios" && runtime.GOARCH == "arm64":
t.Skipf("switching signal stack not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) t.Skipf("switching signal stack not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
} }

View file

@ -603,7 +603,7 @@ func TestExtar(t *testing.T) {
if runtime.Compiler == "gccgo" { if runtime.Compiler == "gccgo" {
t.Skip("skipping -extar test when using gccgo") t.Skip("skipping -extar test when using gccgo")
} }
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" { if runtime.GOOS == "ios" {
t.Skip("shell scripts are not executable on iOS hosts") t.Skip("shell scripts are not executable on iOS hosts")
} }

View file

@ -81,6 +81,8 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8
SHA512H2 V4.D2, V3, V2 // 628464ce SHA512H2 V4.D2, V3, V2 // 628464ce
SHA512SU0 V9.D2, V8.D2 // 2881c0ce SHA512SU0 V9.D2, V8.D2 // 2881c0ce
SHA512SU1 V7.D2, V6.D2, V5.D2 // c58867ce SHA512SU1 V7.D2, V6.D2, V5.D2 // c58867ce
VRAX1 V26.D2, V29.D2, V30.D2 // be8f7ace
VXAR $63, V27.D2, V21.D2, V26.D2 // bafe9bce
VADDV V0.S4, V0 // 00b8b14e VADDV V0.S4, V0 // 00b8b14e
VMOVI $82, V0.B16 // 40e6024f VMOVI $82, V0.B16 // 40e6024f
VUADDLV V6.B16, V6 // c638306e VUADDLV V6.B16, V6 // c638306e
@ -139,6 +141,8 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8
VTBL V14.B16, [V3.B16, V4.B16, V5.B16], V17.B16 // 71400e4e VTBL V14.B16, [V3.B16, V4.B16, V5.B16], V17.B16 // 71400e4e
VTBL V13.B16, [V29.B16, V30.B16, V31.B16, V0.B16], V28.B16 // bc630d4e VTBL V13.B16, [V29.B16, V30.B16, V31.B16, V0.B16], V28.B16 // bc630d4e
VTBL V3.B8, [V27.B16], V8.B8 // 6803030e VTBL V3.B8, [V27.B16], V8.B8 // 6803030e
VEOR3 V2.B16, V7.B16, V12.B16, V25.B16 // 990907ce
VBCAX V1.B16, V2.B16, V26.B16, V31.B16 // 5f0722ce
VZIP1 V16.H8, V3.H8, V19.H8 // 7338504e VZIP1 V16.H8, V3.H8, V19.H8 // 7338504e
VZIP2 V22.D2, V25.D2, V21.D2 // 357bd64e VZIP2 V22.D2, V25.D2, V21.D2 // 357bd64e
VZIP1 V6.D2, V9.D2, V11.D2 // 2b39c64e VZIP1 V6.D2, V9.D2, V11.D2 // 2b39c64e

View file

@ -170,35 +170,51 @@ func usage() {
var ptrSizeMap = map[string]int64{ var ptrSizeMap = map[string]int64{
"386": 4, "386": 4,
"alpha": 8,
"amd64": 8, "amd64": 8,
"arm": 4, "arm": 4,
"arm64": 8, "arm64": 8,
"m68k": 4,
"mips": 4, "mips": 4,
"mipsle": 4, "mipsle": 4,
"mips64": 8, "mips64": 8,
"mips64le": 8, "mips64le": 8,
"nios2": 4,
"ppc": 4,
"ppc64": 8, "ppc64": 8,
"ppc64le": 8, "ppc64le": 8,
"riscv": 4,
"riscv64": 8, "riscv64": 8,
"s390": 4, "s390": 4,
"s390x": 8, "s390x": 8,
"sh": 4,
"shbe": 4,
"sparc": 4,
"sparc64": 8, "sparc64": 8,
} }
var intSizeMap = map[string]int64{ var intSizeMap = map[string]int64{
"386": 4, "386": 4,
"alpha": 8,
"amd64": 8, "amd64": 8,
"arm": 4, "arm": 4,
"arm64": 8, "arm64": 8,
"m68k": 4,
"mips": 4, "mips": 4,
"mipsle": 4, "mipsle": 4,
"mips64": 8, "mips64": 8,
"mips64le": 8, "mips64le": 8,
"nios2": 4,
"ppc": 4,
"ppc64": 8, "ppc64": 8,
"ppc64le": 8, "ppc64le": 8,
"riscv": 4,
"riscv64": 8, "riscv64": 8,
"s390": 4, "s390": 4,
"s390x": 8, "s390x": 8,
"sh": 4,
"shbe": 4,
"sparc": 4,
"sparc64": 8, "sparc64": 8,
} }

View file

@ -126,30 +126,6 @@ const (
aliasTag aliasTag
) )
// untype returns the "pseudo" untyped type for a Ctype (import/export use only).
// (we can't use a pre-initialized array because we must be sure all types are
// set up)
func untype(ctype Ctype) *types.Type {
switch ctype {
case CTINT:
return types.Idealint
case CTRUNE:
return types.Idealrune
case CTFLT:
return types.Idealfloat
case CTCPLX:
return types.Idealcomplex
case CTSTR:
return types.Idealstring
case CTBOOL:
return types.Idealbool
case CTNIL:
return types.Types[TNIL]
}
Fatalf("exporter: unknown Ctype")
return nil
}
var predecl []*types.Type // initialized lazily var predecl []*types.Type // initialized lazily
func predeclared() []*types.Type { func predeclared() []*types.Type {
@ -184,13 +160,13 @@ func predeclared() []*types.Type {
types.Errortype, types.Errortype,
// untyped types // untyped types
untype(CTBOOL), types.UntypedBool,
untype(CTINT), types.UntypedInt,
untype(CTRUNE), types.UntypedRune,
untype(CTFLT), types.UntypedFloat,
untype(CTCPLX), types.UntypedComplex,
untype(CTSTR), types.UntypedString,
untype(CTNIL), types.Types[TNIL],
// package unsafe // package unsafe
types.Types[TUNSAFEPTR], types.Types[TUNSAFEPTR],

View file

@ -1019,17 +1019,17 @@ func nodlit(v Val) *Node {
func idealType(ct Ctype) *types.Type { func idealType(ct Ctype) *types.Type {
switch ct { switch ct {
case CTSTR: case CTSTR:
return types.Idealstring return types.UntypedString
case CTBOOL: case CTBOOL:
return types.Idealbool return types.UntypedBool
case CTINT: case CTINT:
return types.Idealint return types.UntypedInt
case CTRUNE: case CTRUNE:
return types.Idealrune return types.UntypedRune
case CTFLT: case CTFLT:
return types.Idealfloat return types.UntypedFloat
case CTCPLX: case CTCPLX:
return types.Idealcomplex return types.UntypedComplex
case CTNIL: case CTNIL:
return types.Types[TNIL] return types.Types[TNIL]
} }
@ -1080,17 +1080,17 @@ func defaultlit2(l *Node, r *Node, force bool) (*Node, *Node) {
func ctype(t *types.Type) Ctype { func ctype(t *types.Type) Ctype {
switch t { switch t {
case types.Idealbool: case types.UntypedBool:
return CTBOOL return CTBOOL
case types.Idealstring: case types.UntypedString:
return CTSTR return CTSTR
case types.Idealint: case types.UntypedInt:
return CTINT return CTINT
case types.Idealrune: case types.UntypedRune:
return CTRUNE return CTRUNE
case types.Idealfloat: case types.UntypedFloat:
return CTFLT return CTFLT
case types.Idealcomplex: case types.UntypedComplex:
return CTCPLX return CTCPLX
} }
Fatalf("bad type %v", t) Fatalf("bad type %v", t)
@ -1111,17 +1111,17 @@ func defaultType(t *types.Type) *types.Type {
} }
switch t { switch t {
case types.Idealbool: case types.UntypedBool:
return types.Types[TBOOL] return types.Types[TBOOL]
case types.Idealstring: case types.UntypedString:
return types.Types[TSTRING] return types.Types[TSTRING]
case types.Idealint: case types.UntypedInt:
return types.Types[TINT] return types.Types[TINT]
case types.Idealrune: case types.UntypedRune:
return types.Runetype return types.Runetype
case types.Idealfloat: case types.UntypedFloat:
return types.Types[TFLOAT64] return types.Types[TFLOAT64]
case types.Idealcomplex: case types.UntypedComplex:
return types.Types[TCOMPLEX128] return types.Types[TCOMPLEX128]
} }

View file

@ -96,7 +96,7 @@ func importsym(ipkg *types.Pkg, s *types.Sym, op Op) *Node {
return n return n
} }
// pkgtype returns the named type declared by symbol s. // importtype returns the named type declared by symbol s.
// If no such type has been declared yet, a forward declaration is returned. // If no such type has been declared yet, a forward declaration is returned.
// ipkg is the package being imported // ipkg is the package being imported
func importtype(ipkg *types.Pkg, pos src.XPos, s *types.Sym) *types.Type { func importtype(ipkg *types.Pkg, pos src.XPos, s *types.Sym) *types.Type {

View file

@ -773,17 +773,17 @@ func tconv2(b *bytes.Buffer, t *types.Type, flag FmtFlag, mode fmtMode, visited
if int(t.Etype) < len(basicnames) && basicnames[t.Etype] != "" { if int(t.Etype) < len(basicnames) && basicnames[t.Etype] != "" {
var name string var name string
switch t { switch t {
case types.Idealbool: case types.UntypedBool:
name = "untyped bool" name = "untyped bool"
case types.Idealstring: case types.UntypedString:
name = "untyped string" name = "untyped string"
case types.Idealint: case types.UntypedInt:
name = "untyped int" name = "untyped int"
case types.Idealrune: case types.UntypedRune:
name = "untyped rune" name = "untyped rune"
case types.Idealfloat: case types.UntypedFloat:
name = "untyped float" name = "untyped float"
case types.Idealcomplex: case types.UntypedComplex:
name = "untyped complex" name = "untyped complex"
default: default:
name = basicnames[t.Etype] name = basicnames[t.Etype]
@ -1333,7 +1333,7 @@ func (n *Node) exprfmt(s fmt.State, prec int, mode fmtMode) {
n.Orig.exprfmt(s, prec, mode) n.Orig.exprfmt(s, prec, mode)
return return
} }
if n.Type != nil && n.Type.Etype != TIDEAL && n.Type.Etype != TNIL && n.Type != types.Idealbool && n.Type != types.Idealstring { if n.Type != nil && n.Type.Etype != TIDEAL && n.Type.Etype != TNIL && n.Type != types.UntypedBool && n.Type != types.UntypedString {
// Need parens when type begins with what might // Need parens when type begins with what might
// be misinterpreted as a unary operator: * or <-. // be misinterpreted as a unary operator: * or <-.
if n.Type.IsPtr() || (n.Type.IsChan() && n.Type.ChanDir() == types.Crecv) { if n.Type.IsPtr() || (n.Type.IsChan() && n.Type.ChanDir() == types.Crecv) {

View file

@ -751,11 +751,11 @@ func (w *exportWriter) param(f *types.Field) {
func constTypeOf(typ *types.Type) Ctype { func constTypeOf(typ *types.Type) Ctype {
switch typ { switch typ {
case types.Idealint, types.Idealrune: case types.UntypedInt, types.UntypedRune:
return CTINT return CTINT
case types.Idealfloat: case types.UntypedFloat:
return CTFLT return CTFLT
case types.Idealcomplex: case types.UntypedComplex:
return CTCPLX return CTCPLX
} }
@ -780,8 +780,8 @@ func constTypeOf(typ *types.Type) Ctype {
} }
func (w *exportWriter) value(typ *types.Type, v Val) { func (w *exportWriter) value(typ *types.Type, v Val) {
if typ.IsUntyped() { if vt := idealType(v.Ctype()); typ.IsUntyped() && typ != vt {
typ = untype(v.Ctype()) Fatalf("exporter: untyped type mismatch, have: %v, want: %v", typ, vt)
} }
w.typ(typ) w.typ(typ)

View file

@ -375,7 +375,7 @@ func (p *importReader) value() (typ *types.Type, v Val) {
v.U = p.string() v.U = p.string()
case CTINT: case CTINT:
x := new(Mpint) x := new(Mpint)
x.Rune = typ == types.Idealrune x.Rune = typ == types.UntypedRune
p.mpint(&x.Val, typ) p.mpint(&x.Val, typ)
v.U = x v.U = x
case CTFLT: case CTFLT:

View file

@ -1275,8 +1275,9 @@ func dtypesym(t *types.Type) *obj.LSym {
} }
ot = dgopkgpath(lsym, ot, tpkg) ot = dgopkgpath(lsym, ot, tpkg)
xcount := sort.Search(n, func(i int) bool { return !types.IsExported(m[i].name.Name) })
ot = dsymptr(lsym, ot, lsym, ot+3*Widthptr+uncommonSize(t)) ot = dsymptr(lsym, ot, lsym, ot+3*Widthptr+uncommonSize(t))
ot = duintptr(lsym, ot, uint64(n)) ot = duintptr(lsym, ot, uint64(xcount))
ot = duintptr(lsym, ot, uint64(n)) ot = duintptr(lsym, ot, uint64(n))
dataAdd := imethodSize() * n dataAdd := imethodSize() * n
ot = dextratype(lsym, ot, t, dataAdd) ot = dextratype(lsym, ot, t, dataAdd)

View file

@ -50,12 +50,10 @@ func initssaconfig() {
// Caching is disabled in the backend, so generating these here avoids allocations. // Caching is disabled in the backend, so generating these here avoids allocations.
_ = types.NewPtr(types.Types[TINTER]) // *interface{} _ = types.NewPtr(types.Types[TINTER]) // *interface{}
_ = types.NewPtr(types.NewPtr(types.Types[TSTRING])) // **string _ = types.NewPtr(types.NewPtr(types.Types[TSTRING])) // **string
_ = types.NewPtr(types.NewPtr(types.Idealstring)) // **string
_ = types.NewPtr(types.NewSlice(types.Types[TINTER])) // *[]interface{} _ = types.NewPtr(types.NewSlice(types.Types[TINTER])) // *[]interface{}
_ = types.NewPtr(types.NewPtr(types.Bytetype)) // **byte _ = types.NewPtr(types.NewPtr(types.Bytetype)) // **byte
_ = types.NewPtr(types.NewSlice(types.Bytetype)) // *[]byte _ = types.NewPtr(types.NewSlice(types.Bytetype)) // *[]byte
_ = types.NewPtr(types.NewSlice(types.Types[TSTRING])) // *[]string _ = types.NewPtr(types.NewSlice(types.Types[TSTRING])) // *[]string
_ = types.NewPtr(types.NewSlice(types.Idealstring)) // *[]string
_ = types.NewPtr(types.NewPtr(types.NewPtr(types.Types[TUINT8]))) // ***uint8 _ = types.NewPtr(types.NewPtr(types.NewPtr(types.Types[TUINT8]))) // ***uint8
_ = types.NewPtr(types.Types[TINT16]) // *int16 _ = types.NewPtr(types.Types[TINT16]) // *int16
_ = types.NewPtr(types.Types[TINT64]) // *int64 _ = types.NewPtr(types.Types[TINT64]) // *int64
@ -4251,6 +4249,7 @@ func (s *state) openDeferExit() {
s.lastDeferExit = deferExit s.lastDeferExit = deferExit
s.lastDeferCount = len(s.openDefers) s.lastDeferCount = len(s.openDefers)
zeroval := s.constInt8(types.Types[TUINT8], 0) zeroval := s.constInt8(types.Types[TUINT8], 0)
testLateExpansion := ssa.LateCallExpansionEnabledWithin(s.f)
// Test for and run defers in reverse order // Test for and run defers in reverse order
for i := len(s.openDefers) - 1; i >= 0; i-- { for i := len(s.openDefers) - 1; i >= 0; i-- {
r := s.openDefers[i] r := s.openDefers[i]
@ -4288,18 +4287,32 @@ func (s *state) openDeferExit() {
stksize := fn.Type.ArgWidth() stksize := fn.Type.ArgWidth()
var ACArgs []ssa.Param var ACArgs []ssa.Param
var ACResults []ssa.Param var ACResults []ssa.Param
var callArgs []*ssa.Value
if r.rcvr != nil { if r.rcvr != nil {
// rcvr in case of OCALLINTER // rcvr in case of OCALLINTER
v := s.load(r.rcvr.Type.Elem(), r.rcvr) v := s.load(r.rcvr.Type.Elem(), r.rcvr)
addr := s.constOffPtrSP(s.f.Config.Types.UintptrPtr, argStart) addr := s.constOffPtrSP(s.f.Config.Types.UintptrPtr, argStart)
ACArgs = append(ACArgs, ssa.Param{Type: types.Types[TUINTPTR], Offset: int32(argStart)}) ACArgs = append(ACArgs, ssa.Param{Type: types.Types[TUINTPTR], Offset: int32(argStart)})
if testLateExpansion {
callArgs = append(callArgs, v)
} else {
s.store(types.Types[TUINTPTR], addr, v) s.store(types.Types[TUINTPTR], addr, v)
} }
}
for j, argAddrVal := range r.argVals { for j, argAddrVal := range r.argVals {
f := getParam(r.n, j) f := getParam(r.n, j)
pt := types.NewPtr(f.Type) pt := types.NewPtr(f.Type)
ACArgs = append(ACArgs, ssa.Param{Type: f.Type, Offset: int32(argStart + f.Offset)})
if testLateExpansion {
var a *ssa.Value
if !canSSAType(f.Type) {
a = s.newValue2(ssa.OpDereference, f.Type, argAddrVal, s.mem())
} else {
a = s.load(f.Type, argAddrVal)
}
callArgs = append(callArgs, a)
} else {
addr := s.constOffPtrSP(pt, argStart+f.Offset) addr := s.constOffPtrSP(pt, argStart+f.Offset)
ACArgs = append(ACArgs, ssa.Param{Type: types.Types[TUINTPTR], Offset: int32(argStart + f.Offset)})
if !canSSAType(f.Type) { if !canSSAType(f.Type) {
s.move(f.Type, addr, argAddrVal) s.move(f.Type, addr, argAddrVal)
} else { } else {
@ -4307,18 +4320,37 @@ func (s *state) openDeferExit() {
s.storeType(f.Type, addr, argVal, 0, false) s.storeType(f.Type, addr, argVal, 0, false)
} }
} }
}
var call *ssa.Value var call *ssa.Value
if r.closure != nil { if r.closure != nil {
v := s.load(r.closure.Type.Elem(), r.closure) v := s.load(r.closure.Type.Elem(), r.closure)
s.maybeNilCheckClosure(v, callDefer) s.maybeNilCheckClosure(v, callDefer)
codeptr := s.rawLoad(types.Types[TUINTPTR], v) codeptr := s.rawLoad(types.Types[TUINTPTR], v)
call = s.newValue3A(ssa.OpClosureCall, types.TypeMem, ssa.ClosureAuxCall(ACArgs, ACResults), codeptr, v, s.mem()) aux := ssa.ClosureAuxCall(ACArgs, ACResults)
if testLateExpansion {
callArgs = append(callArgs, s.mem())
call = s.newValue2A(ssa.OpClosureLECall, aux.LateExpansionResultType(), aux, codeptr, v)
call.AddArgs(callArgs...)
} else {
call = s.newValue3A(ssa.OpClosureCall, types.TypeMem, aux, codeptr, v, s.mem())
}
} else {
aux := ssa.StaticAuxCall(fn.Sym.Linksym(), ACArgs, ACResults)
if testLateExpansion {
callArgs = append(callArgs, s.mem())
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
call.AddArgs(callArgs...)
} else { } else {
// Do a static call if the original call was a static function or method // Do a static call if the original call was a static function or method
call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, ssa.StaticAuxCall(fn.Sym.Linksym(), ACArgs, ACResults), s.mem()) call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, aux, s.mem())
}
} }
call.AuxInt = stksize call.AuxInt = stksize
if testLateExpansion {
s.vars[&memVar] = s.newValue1I(ssa.OpSelectN, types.TypeMem, int64(len(ACResults)), call)
} else {
s.vars[&memVar] = call s.vars[&memVar] = call
}
// Make sure that the stack slots with pointers are kept live // Make sure that the stack slots with pointers are kept live
// through the call (which is a pre-emption point). Also, we will // through the call (which is a pre-emption point). Also, we will
// use the first call of the last defer exit to compute liveness // use the first call of the last defer exit to compute liveness
@ -4375,9 +4407,7 @@ func (s *state) call(n *Node, k callKind, returnResultAddr bool) *ssa.Value {
switch n.Op { switch n.Op {
case OCALLFUNC: case OCALLFUNC:
if k != callDeferStack && ssa.LateCallExpansionEnabledWithin(s.f) { testLateExpansion = k != callDeferStack && ssa.LateCallExpansionEnabledWithin(s.f)
testLateExpansion = true
}
if k == callNormal && fn.Op == ONAME && fn.Class() == PFUNC { if k == callNormal && fn.Op == ONAME && fn.Class() == PFUNC {
sym = fn.Sym sym = fn.Sym
break break
@ -4392,9 +4422,7 @@ func (s *state) call(n *Node, k callKind, returnResultAddr bool) *ssa.Value {
if fn.Op != ODOTMETH { if fn.Op != ODOTMETH {
s.Fatalf("OCALLMETH: n.Left not an ODOTMETH: %v", fn) s.Fatalf("OCALLMETH: n.Left not an ODOTMETH: %v", fn)
} }
if k != callDeferStack && ssa.LateCallExpansionEnabledWithin(s.f) { testLateExpansion = k != callDeferStack && ssa.LateCallExpansionEnabledWithin(s.f)
testLateExpansion = true
}
if k == callNormal { if k == callNormal {
sym = fn.Sym sym = fn.Sym
break break
@ -4406,9 +4434,7 @@ func (s *state) call(n *Node, k callKind, returnResultAddr bool) *ssa.Value {
if fn.Op != ODOTINTER { if fn.Op != ODOTINTER {
s.Fatalf("OCALLINTER: n.Left not an ODOTINTER: %v", fn.Op) s.Fatalf("OCALLINTER: n.Left not an ODOTINTER: %v", fn.Op)
} }
if k != callDeferStack && ssa.LateCallExpansionEnabledWithin(s.f) { testLateExpansion = k != callDeferStack && ssa.LateCallExpansionEnabledWithin(s.f)
testLateExpansion = true
}
var iclosure *ssa.Value var iclosure *ssa.Value
iclosure, rcvr = s.getClosureAndRcvr(fn) iclosure, rcvr = s.getClosureAndRcvr(fn)
if k == callNormal { if k == callNormal {
@ -4427,6 +4453,7 @@ func (s *state) call(n *Node, k callKind, returnResultAddr bool) *ssa.Value {
var call *ssa.Value var call *ssa.Value
if k == callDeferStack { if k == callDeferStack {
testLateExpansion = ssa.LateCallExpansionEnabledWithin(s.f)
// Make a defer struct d on the stack. // Make a defer struct d on the stack.
t := deferstruct(stksize) t := deferstruct(stksize)
d := tempAt(n.Pos, s.curfn, t) d := tempAt(n.Pos, s.curfn, t)
@ -4477,10 +4504,17 @@ func (s *state) call(n *Node, k callKind, returnResultAddr bool) *ssa.Value {
} }
// Call runtime.deferprocStack with pointer to _defer record. // Call runtime.deferprocStack with pointer to _defer record.
ACArgs = append(ACArgs, ssa.Param{Type: types.Types[TUINTPTR], Offset: int32(Ctxt.FixedFrameSize())})
aux := ssa.StaticAuxCall(deferprocStack, ACArgs, ACResults)
if testLateExpansion {
callArgs = append(callArgs, addr, s.mem())
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
call.AddArgs(callArgs...)
} else {
arg0 := s.constOffPtrSP(types.Types[TUINTPTR], Ctxt.FixedFrameSize()) arg0 := s.constOffPtrSP(types.Types[TUINTPTR], Ctxt.FixedFrameSize())
s.store(types.Types[TUINTPTR], arg0, addr) s.store(types.Types[TUINTPTR], arg0, addr)
ACArgs = append(ACArgs, ssa.Param{Type: types.Types[TUINTPTR], Offset: int32(Ctxt.FixedFrameSize())}) call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, aux, s.mem())
call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, ssa.StaticAuxCall(deferprocStack, ACArgs, ACResults), s.mem()) }
if stksize < int64(Widthptr) { if stksize < int64(Widthptr) {
// We need room for both the call to deferprocStack and the call to // We need room for both the call to deferprocStack and the call to
// the deferred function. // the deferred function.
@ -5039,15 +5073,22 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args .
s.prevCall = nil s.prevCall = nil
// Write args to the stack // Write args to the stack
off := Ctxt.FixedFrameSize() off := Ctxt.FixedFrameSize()
testLateExpansion := ssa.LateCallExpansionEnabledWithin(s.f)
var ACArgs []ssa.Param var ACArgs []ssa.Param
var ACResults []ssa.Param var ACResults []ssa.Param
var callArgs []*ssa.Value
for _, arg := range args { for _, arg := range args {
t := arg.Type t := arg.Type
off = Rnd(off, t.Alignment()) off = Rnd(off, t.Alignment())
ptr := s.constOffPtrSP(t.PtrTo(), off)
size := t.Size() size := t.Size()
ACArgs = append(ACArgs, ssa.Param{Type: t, Offset: int32(off)}) ACArgs = append(ACArgs, ssa.Param{Type: t, Offset: int32(off)})
if testLateExpansion {
callArgs = append(callArgs, arg)
} else {
ptr := s.constOffPtrSP(t.PtrTo(), off)
s.store(t, ptr, arg) s.store(t, ptr, arg)
}
off += size off += size
} }
off = Rnd(off, int64(Widthreg)) off = Rnd(off, int64(Widthreg))
@ -5061,8 +5102,17 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args .
} }
// Issue call // Issue call
call := s.newValue1A(ssa.OpStaticCall, types.TypeMem, ssa.StaticAuxCall(fn, ACArgs, ACResults), s.mem()) var call *ssa.Value
aux := ssa.StaticAuxCall(fn, ACArgs, ACResults)
if testLateExpansion {
callArgs = append(callArgs, s.mem())
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
call.AddArgs(callArgs...)
s.vars[&memVar] = s.newValue1I(ssa.OpSelectN, types.TypeMem, int64(len(ACResults)), call)
} else {
call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, aux, s.mem())
s.vars[&memVar] = call s.vars[&memVar] = call
}
if !returns { if !returns {
// Finish block // Finish block
@ -5078,12 +5128,25 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args .
// Load results // Load results
res := make([]*ssa.Value, len(results)) res := make([]*ssa.Value, len(results))
if testLateExpansion {
for i, t := range results {
off = Rnd(off, t.Alignment())
if canSSAType(t) {
res[i] = s.newValue1I(ssa.OpSelectN, t, int64(i), call)
} else {
addr := s.newValue1I(ssa.OpSelectNAddr, types.NewPtr(t), int64(i), call)
res[i] = s.rawLoad(t, addr)
}
off += t.Size()
}
} else {
for i, t := range results { for i, t := range results {
off = Rnd(off, t.Alignment()) off = Rnd(off, t.Alignment())
ptr := s.constOffPtrSP(types.NewPtr(t), off) ptr := s.constOffPtrSP(types.NewPtr(t), off)
res[i] = s.load(t, ptr) res[i] = s.load(t, ptr)
off += t.Size() off += t.Size()
} }
}
off = Rnd(off, int64(Widthptr)) off = Rnd(off, int64(Widthptr))
// Remember how much callee stack space we needed. // Remember how much callee stack space we needed.

View file

@ -825,7 +825,7 @@ func assignconvfn(n *Node, t *types.Type, context func() string) *Node {
// Convert ideal bool from comparison to plain bool // Convert ideal bool from comparison to plain bool
// if the next step is non-bool (like interface{}). // if the next step is non-bool (like interface{}).
if n.Type == types.Idealbool && !t.IsBoolean() { if n.Type == types.UntypedBool && !t.IsBoolean() {
if n.Op == ONAME || n.Op == OLITERAL { if n.Op == ONAME || n.Op == OLITERAL {
r := nod(OCONVNOP, n, nil) r := nod(OCONVNOP, n, nil)
r.Type = types.Types[TBOOL] r.Type = types.Types[TBOOL]

View file

@ -440,7 +440,7 @@ func (c *exprClause) test(exprname *Node) *Node {
// Optimize "switch true { ...}" and "switch false { ... }". // Optimize "switch true { ...}" and "switch false { ... }".
if Isconst(exprname, CTBOOL) && !c.lo.Type.IsInterface() { if Isconst(exprname, CTBOOL) && !c.lo.Type.IsInterface() {
if exprname.Val().U.(bool) { if exprname.Bool() {
return c.lo return c.lo
} else { } else {
return nodl(c.pos, ONOT, c.lo, nil) return nodl(c.pos, ONOT, c.lo, nil)

View file

@ -361,7 +361,7 @@ func typecheck1(n *Node, top int) (res *Node) {
ok |= ctxExpr ok |= ctxExpr
if n.Type == nil && n.Val().Ctype() == CTSTR { if n.Type == nil && n.Val().Ctype() == CTSTR {
n.Type = types.Idealstring n.Type = types.UntypedString
} }
case ONONAME: case ONONAME:
@ -623,8 +623,8 @@ func typecheck1(n *Node, top int) (res *Node) {
// no defaultlit for left // no defaultlit for left
// the outer context gives the type // the outer context gives the type
n.Type = l.Type n.Type = l.Type
if (l.Type == types.Idealfloat || l.Type == types.Idealcomplex) && r.Op == OLITERAL { if (l.Type == types.UntypedFloat || l.Type == types.UntypedComplex) && r.Op == OLITERAL {
n.Type = types.Idealint n.Type = types.UntypedInt
} }
break break
@ -777,7 +777,7 @@ func typecheck1(n *Node, top int) (res *Node) {
if iscmp[n.Op] { if iscmp[n.Op] {
evconst(n) evconst(n)
t = types.Idealbool t = types.UntypedBool
if n.Op != OLITERAL { if n.Op != OLITERAL {
l, r = defaultlit2(l, r, true) l, r = defaultlit2(l, r, true)
n.Left = l n.Left = l
@ -1458,7 +1458,7 @@ func typecheck1(n *Node, top int) (res *Node) {
// Determine result type. // Determine result type.
switch t.Etype { switch t.Etype {
case TIDEAL: case TIDEAL:
n.Type = types.Idealfloat n.Type = types.UntypedFloat
case TCOMPLEX64: case TCOMPLEX64:
n.Type = types.Types[TFLOAT32] n.Type = types.Types[TFLOAT32]
case TCOMPLEX128: case TCOMPLEX128:
@ -1504,7 +1504,7 @@ func typecheck1(n *Node, top int) (res *Node) {
return n return n
case TIDEAL: case TIDEAL:
t = types.Idealcomplex t = types.UntypedComplex
case TFLOAT32: case TFLOAT32:
t = types.Types[TCOMPLEX64] t = types.Types[TCOMPLEX64]
@ -2724,9 +2724,9 @@ func errorDetails(nl Nodes, tstruct *types.Type, isddd bool) string {
// e.g in error messages about wrong arguments to return. // e.g in error messages about wrong arguments to return.
func sigrepr(t *types.Type, isddd bool) string { func sigrepr(t *types.Type, isddd bool) string {
switch t { switch t {
case types.Idealstring: case types.UntypedString:
return "string" return "string"
case types.Idealbool: case types.UntypedBool:
return "bool" return "bool"
} }

View file

@ -123,21 +123,21 @@ func lexinit() {
asNode(s2.Def).SetSubOp(s.op) asNode(s2.Def).SetSubOp(s.op)
} }
types.Idealstring = types.New(TSTRING) types.UntypedString = types.New(TSTRING)
types.Idealbool = types.New(TBOOL) types.UntypedBool = types.New(TBOOL)
types.Types[TANY] = types.New(TANY) types.Types[TANY] = types.New(TANY)
s := builtinpkg.Lookup("true") s := builtinpkg.Lookup("true")
s.Def = asTypesNode(nodbool(true)) s.Def = asTypesNode(nodbool(true))
asNode(s.Def).Sym = lookup("true") asNode(s.Def).Sym = lookup("true")
asNode(s.Def).Name = new(Name) asNode(s.Def).Name = new(Name)
asNode(s.Def).Type = types.Idealbool asNode(s.Def).Type = types.UntypedBool
s = builtinpkg.Lookup("false") s = builtinpkg.Lookup("false")
s.Def = asTypesNode(nodbool(false)) s.Def = asTypesNode(nodbool(false))
asNode(s.Def).Sym = lookup("false") asNode(s.Def).Sym = lookup("false")
asNode(s.Def).Name = new(Name) asNode(s.Def).Name = new(Name)
asNode(s.Def).Type = types.Idealbool asNode(s.Def).Type = types.UntypedBool
s = lookup("_") s = lookup("_")
s.Block = -100 s.Block = -100
@ -351,7 +351,7 @@ func typeinit() {
sizeofString = Rnd(sliceLenOffset+int64(Widthptr), int64(Widthptr)) sizeofString = Rnd(sliceLenOffset+int64(Widthptr), int64(Widthptr))
dowidth(types.Types[TSTRING]) dowidth(types.Types[TSTRING])
dowidth(types.Idealstring) dowidth(types.UntypedString)
} }
func makeErrorInterface() *types.Type { func makeErrorInterface() *types.Type {

View file

@ -39,7 +39,9 @@ func expandCalls(f *Func) {
hiOffset = 4 hiOffset = 4
} }
pairTypes := func(et types.EType) (tHi, tLo *types.Type) { // intPairTypes returns the pair of 32-bit int types needed to encode a 64-bit integer type on a target
// that has no 64-bit integer registers.
intPairTypes := func(et types.EType) (tHi, tLo *types.Type) {
tHi = tUint32 tHi = tUint32
if et == types.TINT64 { if et == types.TINT64 {
tHi = tInt32 tHi = tInt32
@ -147,8 +149,8 @@ func expandCalls(f *Func) {
} }
} }
// storeArg converts stores of SSA-able aggregates into a series of stores of smaller types into // storeArg converts stores of SSA-able aggregate arguments (passed to a call) into a series of stores of
// individual parameter slots. // smaller types into individual parameter slots.
// TODO when registers really arrive, must also decompose anything split across two registers or registers and memory. // TODO when registers really arrive, must also decompose anything split across two registers or registers and memory.
var storeArg func(pos src.XPos, b *Block, a *Value, t *types.Type, offset int64, mem *Value) *Value var storeArg func(pos src.XPos, b *Block, a *Value, t *types.Type, offset int64, mem *Value) *Value
storeArg = func(pos src.XPos, b *Block, a *Value, t *types.Type, offset int64, mem *Value) *Value { storeArg = func(pos src.XPos, b *Block, a *Value, t *types.Type, offset int64, mem *Value) *Value {
@ -165,7 +167,7 @@ func expandCalls(f *Func) {
return storeArg(pos, b, a.Args[0], t.Elem(), offset, mem) return storeArg(pos, b, a.Args[0], t.Elem(), offset, mem)
case OpInt64Make: case OpInt64Make:
tHi, tLo := pairTypes(t.Etype) tHi, tLo := intPairTypes(t.Etype)
mem = storeArg(pos, b, a.Args[0], tHi, offset+hiOffset, mem) mem = storeArg(pos, b, a.Args[0], tHi, offset+hiOffset, mem)
return storeArg(pos, b, a.Args[1], tLo, offset+lowOffset, mem) return storeArg(pos, b, a.Args[1], tLo, offset+lowOffset, mem)
} }
@ -207,7 +209,7 @@ func expandCalls(f *Func) {
if t.Width == regSize { if t.Width == regSize {
break break
} }
tHi, tLo := pairTypes(t.Etype) tHi, tLo := intPairTypes(t.Etype)
sel := src.Block.NewValue1(pos, OpInt64Hi, tHi, src) sel := src.Block.NewValue1(pos, OpInt64Hi, tHi, src)
mem = splitStore(dst, sel, mem, v, tHi, offset+hiOffset, firstStorePos) mem = splitStore(dst, sel, mem, v, tHi, offset+hiOffset, firstStorePos)
firstStorePos = firstStorePos.WithNotStmt() firstStorePos = firstStorePos.WithNotStmt()
@ -261,6 +263,9 @@ func expandCalls(f *Func) {
return x return x
} }
// rewriteArgs removes all the Args from a call and converts the call args into appropriate
// stores (or later, register movement). Extra args for interface and closure calls are ignored,
// but removed.
rewriteArgs := func(v *Value, firstArg int) *Value { rewriteArgs := func(v *Value, firstArg int) *Value {
// Thread the stores on the memory arg // Thread the stores on the memory arg
aux := v.Aux.(*AuxCall) aux := v.Aux.(*AuxCall)
@ -283,7 +288,7 @@ func expandCalls(f *Func) {
// TODO this will be more complicated with registers in the picture. // TODO this will be more complicated with registers in the picture.
src := a.Args[0] src := a.Args[0]
dst := f.ConstOffPtrSP(src.Type, aux.OffsetOfArg(auxI), sp) dst := f.ConstOffPtrSP(src.Type, aux.OffsetOfArg(auxI), sp)
if a.Uses == 1 { if a.Uses == 1 && a.Block == v.Block {
a.reset(OpMove) a.reset(OpMove)
a.Pos = pos a.Pos = pos
a.Type = types.TypeMem a.Type = types.TypeMem
@ -292,7 +297,7 @@ func expandCalls(f *Func) {
a.SetArgs3(dst, src, mem) a.SetArgs3(dst, src, mem)
mem = a mem = a
} else { } else {
mem = a.Block.NewValue3A(pos, OpMove, types.TypeMem, aux.TypeOfArg(auxI), dst, src, mem) mem = v.Block.NewValue3A(pos, OpMove, types.TypeMem, aux.TypeOfArg(auxI), dst, src, mem)
mem.AuxInt = aux.SizeOfArg(auxI) mem.AuxInt = aux.SizeOfArg(auxI)
} }
} else { } else {

View file

@ -672,7 +672,7 @@ func (f *Func) Idom() []*Block {
return f.cachedIdom return f.cachedIdom
} }
// sdom returns a sparse tree representing the dominator relationships // Sdom returns a sparse tree representing the dominator relationships
// among the blocks of f. // among the blocks of f.
func (f *Func) Sdom() SparseTree { func (f *Func) Sdom() SparseTree {
if f.cachedSdom == nil { if f.cachedSdom == nil {

View file

@ -1274,8 +1274,8 @@
(CMPQconst (ANDQconst _ [m]) [n]) && 0 <= m && m < n => (FlagLT_ULT) (CMPQconst (ANDQconst _ [m]) [n]) && 0 <= m && m < n => (FlagLT_ULT)
(CMPQconst (ANDLconst _ [m]) [n]) && 0 <= m && m < n => (FlagLT_ULT) (CMPQconst (ANDLconst _ [m]) [n]) && 0 <= m && m < n => (FlagLT_ULT)
(CMPLconst (ANDLconst _ [m]) [n]) && 0 <= m && m < n => (FlagLT_ULT) (CMPLconst (ANDLconst _ [m]) [n]) && 0 <= m && m < n => (FlagLT_ULT)
(CMPWconst (ANDLconst _ [m]) [n]) && 0 <= m && int16(m) < n => (FlagLT_ULT) (CMPWconst (ANDLconst _ [m]) [n]) && 0 <= int16(m) && int16(m) < n => (FlagLT_ULT)
(CMPBconst (ANDLconst _ [m]) [n]) && 0 <= m && int8(m) < n => (FlagLT_ULT) (CMPBconst (ANDLconst _ [m]) [n]) && 0 <= int8(m) && int8(m) < n => (FlagLT_ULT)
// TESTQ c c sets flags like CMPQ c 0. // TESTQ c c sets flags like CMPQ c 0.
(TESTQconst [c] (MOVQconst [d])) && int64(c) == d && c == 0 => (FlagEQ) (TESTQconst [c] (MOVQconst [d])) && int64(c) == d && c == 0 => (FlagEQ)

View file

@ -541,10 +541,10 @@ var genericOps = []opData{
{name: "SelectN", argLength: 1, aux: "Int64"}, // arg0=tuple, auxint=field index. Returns the auxint'th member. {name: "SelectN", argLength: 1, aux: "Int64"}, // arg0=tuple, auxint=field index. Returns the auxint'th member.
{name: "SelectNAddr", argLength: 1, aux: "Int64"}, // arg0=tuple, auxint=field index. Returns the address of auxint'th member. Used for un-SSA-able result types. {name: "SelectNAddr", argLength: 1, aux: "Int64"}, // arg0=tuple, auxint=field index. Returns the address of auxint'th member. Used for un-SSA-able result types.
// Atomic operations used for semantically inlining runtime/internal/atomic. // Atomic operations used for semantically inlining sync/atomic and
// Atomic loads return a new memory so that the loads are properly ordered // runtime/internal/atomic. Atomic loads return a new memory so that
// with respect to other loads and stores. // the loads are properly ordered with respect to other loads and
// TODO: use for sync/atomic at some point. // stores.
{name: "AtomicLoad8", argLength: 2, typ: "(UInt8,Mem)"}, // Load from arg0. arg1=memory. Returns loaded value and new memory. {name: "AtomicLoad8", argLength: 2, typ: "(UInt8,Mem)"}, // Load from arg0. arg1=memory. Returns loaded value and new memory.
{name: "AtomicLoad32", argLength: 2, typ: "(UInt32,Mem)"}, // Load from arg0. arg1=memory. Returns loaded value and new memory. {name: "AtomicLoad32", argLength: 2, typ: "(UInt32,Mem)"}, // Load from arg0. arg1=memory. Returns loaded value and new memory.
{name: "AtomicLoad64", argLength: 2, typ: "(UInt64,Mem)"}, // Load from arg0. arg1=memory. Returns loaded value and new memory. {name: "AtomicLoad64", argLength: 2, typ: "(UInt64,Mem)"}, // Load from arg0. arg1=memory. Returns loaded value and new memory.

View file

@ -6886,7 +6886,7 @@ func rewriteValueAMD64_OpAMD64CMPBconst(v *Value) bool {
return true return true
} }
// match: (CMPBconst (ANDLconst _ [m]) [n]) // match: (CMPBconst (ANDLconst _ [m]) [n])
// cond: 0 <= m && int8(m) < n // cond: 0 <= int8(m) && int8(m) < n
// result: (FlagLT_ULT) // result: (FlagLT_ULT)
for { for {
n := auxIntToInt8(v.AuxInt) n := auxIntToInt8(v.AuxInt)
@ -6894,7 +6894,7 @@ func rewriteValueAMD64_OpAMD64CMPBconst(v *Value) bool {
break break
} }
m := auxIntToInt32(v_0.AuxInt) m := auxIntToInt32(v_0.AuxInt)
if !(0 <= m && int8(m) < n) { if !(0 <= int8(m) && int8(m) < n) {
break break
} }
v.reset(OpAMD64FlagLT_ULT) v.reset(OpAMD64FlagLT_ULT)
@ -8243,7 +8243,7 @@ func rewriteValueAMD64_OpAMD64CMPWconst(v *Value) bool {
return true return true
} }
// match: (CMPWconst (ANDLconst _ [m]) [n]) // match: (CMPWconst (ANDLconst _ [m]) [n])
// cond: 0 <= m && int16(m) < n // cond: 0 <= int16(m) && int16(m) < n
// result: (FlagLT_ULT) // result: (FlagLT_ULT)
for { for {
n := auxIntToInt16(v.AuxInt) n := auxIntToInt16(v.AuxInt)
@ -8251,7 +8251,7 @@ func rewriteValueAMD64_OpAMD64CMPWconst(v *Value) bool {
break break
} }
m := auxIntToInt32(v_0.AuxInt) m := auxIntToInt32(v_0.AuxInt)
if !(0 <= m && int16(m) < n) { if !(0 <= int16(m) && int16(m) < n) {
break break
} }
v.reset(OpAMD64FlagLT_ULT) v.reset(OpAMD64FlagLT_ULT)

View file

@ -287,6 +287,7 @@ func tokstring(tok token) string {
// Convenience methods using the current token position. // Convenience methods using the current token position.
func (p *parser) pos() Pos { return p.posAt(p.line, p.col) } func (p *parser) pos() Pos { return p.posAt(p.line, p.col) }
func (p *parser) error(msg string) { p.errorAt(p.pos(), msg) }
func (p *parser) syntaxError(msg string) { p.syntaxErrorAt(p.pos(), msg) } func (p *parser) syntaxError(msg string) { p.syntaxErrorAt(p.pos(), msg) }
// The stopset contains keywords that start a statement. // The stopset contains keywords that start a statement.
@ -997,17 +998,20 @@ loop:
// x[i:j... // x[i:j...
t.Index[1] = p.expr() t.Index[1] = p.expr()
} }
if p.got(_Colon) { if p.tok == _Colon {
t.Full = true t.Full = true
// x[i:j:...] // x[i:j:...]
if t.Index[1] == nil { if t.Index[1] == nil {
p.error("middle index required in 3-index slice") p.error("middle index required in 3-index slice")
t.Index[1] = p.badExpr()
} }
p.next()
if p.tok != _Rbrack { if p.tok != _Rbrack {
// x[i:j:k... // x[i:j:k...
t.Index[2] = p.expr() t.Index[2] = p.expr()
} else { } else {
p.error("final index required in 3-index slice") p.error("final index required in 3-index slice")
t.Index[2] = p.badExpr()
} }
} }
p.want(_Rbrack) p.want(_Rbrack)
@ -1836,6 +1840,7 @@ func (p *parser) header(keyword token) (init SimpleStmt, cond Expr, post SimpleS
if p.tok == _Lbrace { if p.tok == _Lbrace {
if keyword == _If { if keyword == _If {
p.syntaxError("missing condition in if statement") p.syntaxError("missing condition in if statement")
cond = p.badExpr()
} }
return return
} }
@ -1907,6 +1912,9 @@ done:
} else { } else {
p.syntaxErrorAt(semi.pos, "missing condition in if statement") p.syntaxErrorAt(semi.pos, "missing condition in if statement")
} }
b := new(BadExpr)
b.pos = semi.pos
cond = b
} }
case *ExprStmt: case *ExprStmt:
cond = s.X cond = s.X

View file

@ -105,14 +105,14 @@ var (
Errortype *Type Errortype *Type
// Types to represent untyped string and boolean constants. // Types to represent untyped string and boolean constants.
Idealstring *Type UntypedString *Type
Idealbool *Type UntypedBool *Type
// Types to represent untyped numeric constants. // Types to represent untyped numeric constants.
Idealint = New(TIDEAL) UntypedInt = New(TIDEAL)
Idealrune = New(TIDEAL) UntypedRune = New(TIDEAL)
Idealfloat = New(TIDEAL) UntypedFloat = New(TIDEAL)
Idealcomplex = New(TIDEAL) UntypedComplex = New(TIDEAL)
) )
// A Type represents a Go type. // A Type represents a Go type.
@ -1436,7 +1436,7 @@ func (t *Type) IsUntyped() bool {
if t == nil { if t == nil {
return false return false
} }
if t == Idealstring || t == Idealbool { if t == UntypedString || t == UntypedBool {
return true return true
} }
switch t.Etype { switch t.Etype {

View file

@ -7,6 +7,9 @@ package x86
import ( import (
"cmd/compile/internal/gc" "cmd/compile/internal/gc"
"cmd/internal/obj/x86" "cmd/internal/obj/x86"
"cmd/internal/objabi"
"fmt"
"os"
) )
func Init(arch *gc.Arch) { func Init(arch *gc.Arch) {
@ -15,6 +18,18 @@ func Init(arch *gc.Arch) {
arch.SSAGenValue = ssaGenValue arch.SSAGenValue = ssaGenValue
arch.SSAGenBlock = ssaGenBlock arch.SSAGenBlock = ssaGenBlock
arch.MAXWIDTH = (1 << 32) - 1 arch.MAXWIDTH = (1 << 32) - 1
switch v := objabi.GO386; v {
case "sse2":
case "softfloat":
arch.SoftFloat = true
case "387":
fmt.Fprintf(os.Stderr, "unsupported setting GO386=387. Consider using GO386=softfloat instead.\n")
gc.Exit(1)
default:
fmt.Fprintf(os.Stderr, "unsupported setting GO386=%s\n", v)
gc.Exit(1)
}
arch.ZeroRange = zerorange arch.ZeroRange = zerorange
arch.Ginsnop = ginsnop arch.Ginsnop = ginsnop

11
src/cmd/dist/build.go vendored
View file

@ -30,6 +30,7 @@ var (
gohostos string gohostos string
goos string goos string
goarm string goarm string
go386 string
gomips string gomips string
gomips64 string gomips64 string
goppc64 string goppc64 string
@ -141,6 +142,12 @@ func xinit() {
} }
goarm = b goarm = b
b = os.Getenv("GO386")
if b == "" {
b = "sse2"
}
go386 = b
b = os.Getenv("GOMIPS") b = os.Getenv("GOMIPS")
if b == "" { if b == "" {
b = "hardfloat" b = "hardfloat"
@ -212,6 +219,7 @@ func xinit() {
defaultldso = os.Getenv("GO_LDSO") defaultldso = os.Getenv("GO_LDSO")
// For tools being invoked but also for os.ExpandEnv. // For tools being invoked but also for os.ExpandEnv.
os.Setenv("GO386", go386)
os.Setenv("GOARCH", goarch) os.Setenv("GOARCH", goarch)
os.Setenv("GOARM", goarm) os.Setenv("GOARM", goarm)
os.Setenv("GOHOSTARCH", gohostarch) os.Setenv("GOHOSTARCH", gohostarch)
@ -1153,6 +1161,9 @@ func cmdenv() {
if goarch == "arm" { if goarch == "arm" {
xprintf(format, "GOARM", goarm) xprintf(format, "GOARM", goarm)
} }
if goarch == "386" {
xprintf(format, "GO386", go386)
}
if goarch == "mips" || goarch == "mipsle" { if goarch == "mips" || goarch == "mipsle" {
xprintf(format, "GOMIPS", gomips) xprintf(format, "GOMIPS", gomips)
} }

View file

@ -41,6 +41,7 @@ func mkzversion(dir, file string) {
// package objabi // package objabi
// //
// const defaultGOROOT = <goroot> // const defaultGOROOT = <goroot>
// const defaultGO386 = <go386>
// const defaultGOARM = <goarm> // const defaultGOARM = <goarm>
// const defaultGOMIPS = <gomips> // const defaultGOMIPS = <gomips>
// const defaultGOMIPS64 = <gomips64> // const defaultGOMIPS64 = <gomips64>
@ -69,6 +70,7 @@ func mkzbootstrap(file string) {
fmt.Fprintln(&buf) fmt.Fprintln(&buf)
fmt.Fprintf(&buf, "import \"runtime\"\n") fmt.Fprintf(&buf, "import \"runtime\"\n")
fmt.Fprintln(&buf) fmt.Fprintln(&buf)
fmt.Fprintf(&buf, "const defaultGO386 = `%s`\n", go386)
fmt.Fprintf(&buf, "const defaultGOARM = `%s`\n", goarm) fmt.Fprintf(&buf, "const defaultGOARM = `%s`\n", goarm)
fmt.Fprintf(&buf, "const defaultGOMIPS = `%s`\n", gomips) fmt.Fprintf(&buf, "const defaultGOMIPS = `%s`\n", gomips)
fmt.Fprintf(&buf, "const defaultGOMIPS64 = `%s`\n", gomips64) fmt.Fprintf(&buf, "const defaultGOMIPS64 = `%s`\n", gomips64)

View file

@ -903,7 +903,7 @@ func (t *tester) addCmd(dt *distTest, dir string, cmdline ...interface{}) *exec.
} }
func (t *tester) iOS() bool { func (t *tester) iOS() bool {
return (goos == "darwin" || goos == "ios") && goarch == "arm64" return goos == "ios"
} }
func (t *tester) out(v string) { func (t *tester) out(v string) {
@ -992,7 +992,7 @@ func (t *tester) supportedBuildmode(mode string) bool {
case "c-shared": case "c-shared":
switch pair { switch pair {
case "linux-386", "linux-amd64", "linux-arm", "linux-arm64", "linux-ppc64le", "linux-s390x", case "linux-386", "linux-amd64", "linux-arm", "linux-arm64", "linux-ppc64le", "linux-s390x",
"darwin-amd64", "darwin-amd64", "darwin-arm64",
"freebsd-amd64", "freebsd-amd64",
"android-arm", "android-arm64", "android-386", "android-arm", "android-arm64", "android-386",
"windows-amd64", "windows-386": "windows-amd64", "windows-386":
@ -1011,7 +1011,7 @@ func (t *tester) supportedBuildmode(mode string) bool {
switch pair { switch pair {
case "linux-386", "linux-amd64", "linux-arm", "linux-s390x", "linux-ppc64le": case "linux-386", "linux-amd64", "linux-arm", "linux-s390x", "linux-ppc64le":
return true return true
case "darwin-amd64": case "darwin-amd64", "darwin-arm64":
return true return true
case "freebsd-amd64": case "freebsd-amd64":
return true return true
@ -1023,7 +1023,7 @@ func (t *tester) supportedBuildmode(mode string) bool {
"linux-386", "linux-amd64", "linux-arm", "linux-arm64", "linux-ppc64le", "linux-s390x", "linux-386", "linux-amd64", "linux-arm", "linux-arm64", "linux-ppc64le", "linux-s390x",
"android-amd64", "android-arm", "android-arm64", "android-386": "android-amd64", "android-arm", "android-arm64", "android-386":
return true return true
case "darwin-amd64": case "darwin-amd64", "darwin-arm64":
return true return true
case "windows-amd64", "windows-386", "windows-arm": case "windows-amd64", "windows-386", "windows-arm":
return true return true

View file

@ -36,7 +36,7 @@ func TestMain(m *testing.M) {
} }
func maybeSkip(t *testing.T) { func maybeSkip(t *testing.T) {
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" { if runtime.GOOS == "ios" {
t.Skip("iOS does not have a full file tree") t.Skip("iOS does not have a full file tree")
} }
} }

View file

@ -21,11 +21,11 @@ var gotypesFix = fix{
} }
func gotypes(f *ast.File) bool { func gotypes(f *ast.File) bool {
truth := fixGoTypes(f) fixed := fixGoTypes(f)
if fixGoExact(f) { if fixGoExact(f) {
truth = true fixed = true
} }
return truth return fixed
} }
func fixGoTypes(f *ast.File) bool { func fixGoTypes(f *ast.File) bool {

View file

@ -137,6 +137,21 @@ func processFile(filename string, useStdin bool) error {
return err return err
} }
// Make sure file is in canonical format.
// This "fmt" pseudo-fix cannot be disabled.
newSrc, err := gofmtFile(file)
if err != nil {
return err
}
if !bytes.Equal(newSrc, src) {
newFile, err := parser.ParseFile(fset, filename, newSrc, parserMode)
if err != nil {
return err
}
file = newFile
fmt.Fprintf(&fixlog, " fmt")
}
// Apply all fixes to file. // Apply all fixes to file.
newFile := file newFile := file
fixed := false fixed := false
@ -180,7 +195,7 @@ func processFile(filename string, useStdin bool) error {
// output of the printer run on a standard AST generated by the parser, // output of the printer run on a standard AST generated by the parser,
// but the source we generated inside the loop above is the // but the source we generated inside the loop above is the
// output of the printer run on a mangled AST generated by a fixer. // output of the printer run on a mangled AST generated by a fixer.
newSrc, err := gofmtFile(newFile) newSrc, err = gofmtFile(newFile)
if err != nil { if err != nil {
return err return err
} }

View file

@ -207,7 +207,7 @@ func typecheck(cfg *TypeConfig, f *ast.File) (typeof map[interface{}]string, ass
return nil return nil
}() }()
if err != nil { if err != nil {
fmt.Printf("warning: no cgo types: %s\n", err) fmt.Fprintf(os.Stderr, "go fix: warning: no cgo types: %s\n", err)
} }
} }

View file

@ -3,7 +3,7 @@ module cmd
go 1.16 go 1.16
require ( require (
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99
github.com/ianlancetaylor/demangle v0.0.0-20200414190113-039b1ae3a340 // indirect github.com/ianlancetaylor/demangle v0.0.0-20200414190113-039b1ae3a340 // indirect
golang.org/x/arch v0.0.0-20200826200359-b19915210f00 golang.org/x/arch v0.0.0-20200826200359-b19915210f00
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a

View file

@ -1,8 +1,8 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3 h1:SRgJV+IoxM5MKyFdlSUeNy6/ycRUF2yBAKdAQswoHUk= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200414190113-039b1ae3a340 h1:S1+yTUaFPXuDZnPDbO+TrDFIjPzQraYH8/CwSlu9Fac= github.com/ianlancetaylor/demangle v0.0.0-20200414190113-039b1ae3a340 h1:S1+yTUaFPXuDZnPDbO+TrDFIjPzQraYH8/CwSlu9Fac=
github.com/ianlancetaylor/demangle v0.0.0-20200414190113-039b1ae3a340/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200414190113-039b1ae3a340/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=

View file

@ -1852,6 +1852,9 @@
// GOARM // GOARM
// For GOARCH=arm, the ARM architecture for which to compile. // For GOARCH=arm, the ARM architecture for which to compile.
// Valid values are 5, 6, 7. // Valid values are 5, 6, 7.
// GO386
// For GOARCH=386, how to implement floating point instructions.
// Valid values are sse2 (default), softfloat.
// GOMIPS // GOMIPS
// For GOARCH=mips{,le}, whether to use floating point instructions. // For GOARCH=mips{,le}, whether to use floating point instructions.
// Valid values are hardfloat (default), softfloat. // Valid values are hardfloat (default), softfloat.

View file

@ -58,11 +58,10 @@ func init() {
switch runtime.GOOS { switch runtime.GOOS {
case "android", "js": case "android", "js":
canRun = false canRun = false
case "darwin", "ios": case "darwin":
switch runtime.GOARCH { // nothing to do
case "arm64": case "ios":
canRun = false canRun = false
}
case "linux": case "linux":
switch runtime.GOARCH { switch runtime.GOARCH {
case "arm": case "arm":

View file

@ -256,6 +256,7 @@ var (
// Used in envcmd.MkEnv and build ID computations. // Used in envcmd.MkEnv and build ID computations.
GOARM = envOr("GOARM", fmt.Sprint(objabi.GOARM)) GOARM = envOr("GOARM", fmt.Sprint(objabi.GOARM))
GO386 = envOr("GO386", objabi.GO386)
GOMIPS = envOr("GOMIPS", objabi.GOMIPS) GOMIPS = envOr("GOMIPS", objabi.GOMIPS)
GOMIPS64 = envOr("GOMIPS64", objabi.GOMIPS64) GOMIPS64 = envOr("GOMIPS64", objabi.GOMIPS64)
GOPPC64 = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", objabi.GOPPC64)) GOPPC64 = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", objabi.GOPPC64))
@ -279,6 +280,8 @@ func GetArchEnv() (key, val string) {
switch Goarch { switch Goarch {
case "arm": case "arm":
return "GOARM", GOARM return "GOARM", GOARM
case "386":
return "GO386", GO386
case "mips", "mipsle": case "mips", "mipsle":
return "GOMIPS", GOMIPS return "GOMIPS", GOMIPS
case "mips64", "mips64le": case "mips64", "mips64le":

View file

@ -203,10 +203,19 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
} }
// Do we need to call ExtraEnvVarsCostly, which is a bit expensive? // Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
// Only if we're listing all environment variables ("go env") needCostly := false
// or the variables being requested are in the extra list. if *envU || *envW {
needCostly := true // We're overwriting or removing default settings,
if len(args) > 0 { // so it doesn't really matter what the existing settings are.
//
// Moreover, we haven't validated the new settings yet, so it is
// important that we NOT perform any actions based on them,
// such as initializing the builder to compute other variables.
} else if len(args) == 0 {
// We're listing all environment variables ("go env"),
// including the expensive ones.
needCostly = true
} else {
needCostly = false needCostly = false
for _, arg := range args { for _, arg := range args {
switch argKey(arg) { switch argKey(arg) {
@ -269,6 +278,13 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
} }
} }
gotmp, okGOTMP := add["GOTMPDIR"]
if okGOTMP {
if !filepath.IsAbs(gotmp) && gotmp != "" {
base.Fatalf("go env -w: GOTMPDIR must be an absolute path")
}
}
updateEnvFile(add, nil) updateEnvFile(add, nil)
return return
} }

View file

@ -581,6 +581,9 @@ Architecture-specific environment variables:
GOARM GOARM
For GOARCH=arm, the ARM architecture for which to compile. For GOARCH=arm, the ARM architecture for which to compile.
Valid values are 5, 6, 7. Valid values are 5, 6, 7.
GO386
For GOARCH=386, how to implement floating point instructions.
Valid values are sse2 (default), softfloat.
GOMIPS GOMIPS
For GOARCH=mips{,le}, whether to use floating point instructions. For GOARCH=mips{,le}, whether to use floating point instructions.
Valid values are hardfloat (default), softfloat. Valid values are hardfloat (default), softfloat.

View file

@ -79,6 +79,7 @@ type PackagePublic struct {
CgoFiles []string `json:",omitempty"` // .go source files that import "C" CgoFiles []string `json:",omitempty"` // .go source files that import "C"
CompiledGoFiles []string `json:",omitempty"` // .go output from running cgo on CgoFiles CompiledGoFiles []string `json:",omitempty"` // .go output from running cgo on CgoFiles
IgnoredGoFiles []string `json:",omitempty"` // .go source files ignored due to build constraints IgnoredGoFiles []string `json:",omitempty"` // .go source files ignored due to build constraints
IgnoredOtherFiles []string `json:",omitempty"` // non-.go source files ignored due to build constraints
CFiles []string `json:",omitempty"` // .c source files CFiles []string `json:",omitempty"` // .c source files
CXXFiles []string `json:",omitempty"` // .cc, .cpp and .cxx source files CXXFiles []string `json:",omitempty"` // .cc, .cpp and .cxx source files
MFiles []string `json:",omitempty"` // .m source files MFiles []string `json:",omitempty"` // .m source files
@ -127,6 +128,7 @@ func (p *Package) AllFiles() []string {
p.CgoFiles, p.CgoFiles,
// no p.CompiledGoFiles, because they are from GoFiles or generated by us // no p.CompiledGoFiles, because they are from GoFiles or generated by us
p.IgnoredGoFiles, p.IgnoredGoFiles,
p.IgnoredOtherFiles,
p.CFiles, p.CFiles,
p.CXXFiles, p.CXXFiles,
p.MFiles, p.MFiles,
@ -185,7 +187,7 @@ type NoGoError struct {
} }
func (e *NoGoError) Error() string { func (e *NoGoError) Error() string {
if len(e.Package.constraintIgnoredGoFiles()) > 0 { if len(e.Package.IgnoredGoFiles) > 0 {
// Go files exist, but they were ignored due to build constraints. // Go files exist, but they were ignored due to build constraints.
return "build constraints exclude all Go files in " + e.Package.Dir return "build constraints exclude all Go files in " + e.Package.Dir
} }
@ -330,6 +332,7 @@ func (p *Package) copyBuild(pp *build.Package) {
p.GoFiles = pp.GoFiles p.GoFiles = pp.GoFiles
p.CgoFiles = pp.CgoFiles p.CgoFiles = pp.CgoFiles
p.IgnoredGoFiles = pp.IgnoredGoFiles p.IgnoredGoFiles = pp.IgnoredGoFiles
p.IgnoredOtherFiles = pp.IgnoredOtherFiles
p.CFiles = pp.CFiles p.CFiles = pp.CFiles
p.CXXFiles = pp.CXXFiles p.CXXFiles = pp.CXXFiles
p.MFiles = pp.MFiles p.MFiles = pp.MFiles
@ -2009,22 +2012,7 @@ func (p *Package) InternalXGoFiles() []string {
// using absolute paths. "Possibly relevant" means that files are not excluded // using absolute paths. "Possibly relevant" means that files are not excluded
// due to build tags, but files with names beginning with . or _ are still excluded. // due to build tags, but files with names beginning with . or _ are still excluded.
func (p *Package) InternalAllGoFiles() []string { func (p *Package) InternalAllGoFiles() []string {
return p.mkAbs(str.StringList(p.constraintIgnoredGoFiles(), p.GoFiles, p.CgoFiles, p.TestGoFiles, p.XTestGoFiles)) return p.mkAbs(str.StringList(p.IgnoredGoFiles, p.GoFiles, p.CgoFiles, p.TestGoFiles, p.XTestGoFiles))
}
// constraintIgnoredGoFiles returns the list of Go files ignored for reasons
// other than having a name beginning with '.' or '_'.
func (p *Package) constraintIgnoredGoFiles() []string {
if len(p.IgnoredGoFiles) == 0 {
return nil
}
files := make([]string, 0, len(p.IgnoredGoFiles))
for _, f := range p.IgnoredGoFiles {
if f != "" && f[0] != '.' && f[0] != '_' {
files = append(files, f)
}
}
return files
} }
// usesSwig reports whether the package needs to run SWIG. // usesSwig reports whether the package needs to run SWIG.

View file

@ -79,9 +79,8 @@ type Repo interface {
ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error)
// RecentTag returns the most recent tag on rev or one of its predecessors // RecentTag returns the most recent tag on rev or one of its predecessors
// with the given prefix and major version. // with the given prefix. allowed may be used to filter out unwanted versions.
// An empty major string matches any major version. RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error)
RecentTag(rev, prefix, major string) (tag string, err error)
// DescendsFrom reports whether rev or any of its ancestors has the given tag. // DescendsFrom reports whether rev or any of its ancestors has the given tag.
// //

View file

@ -644,7 +644,7 @@ func (r *gitRepo) readFileRevs(tags []string, file string, fileMap map[string]*F
return missing, nil return missing, nil
} }
func (r *gitRepo) RecentTag(rev, prefix, major string) (tag string, err error) { func (r *gitRepo) RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error) {
info, err := r.Stat(rev) info, err := r.Stat(rev)
if err != nil { if err != nil {
return "", err return "", err
@ -680,7 +680,10 @@ func (r *gitRepo) RecentTag(rev, prefix, major string) (tag string, err error) {
// NOTE: Do not replace the call to semver.Compare with semver.Max. // NOTE: Do not replace the call to semver.Compare with semver.Max.
// We want to return the actual tag, not a canonicalized version of it, // We want to return the actual tag, not a canonicalized version of it,
// and semver.Max currently canonicalizes (see golang.org/issue/32700). // and semver.Max currently canonicalizes (see golang.org/issue/32700).
if c := semver.Canonical(semtag); c != "" && strings.HasPrefix(semtag, c) && (major == "" || semver.Major(c) == major) && semver.Compare(semtag, highest) > 0 { if c := semver.Canonical(semtag); c == "" || !strings.HasPrefix(semtag, c) || !allowed(semtag) {
continue
}
if semver.Compare(semtag, highest) > 0 {
highest = semtag highest = semtag
} }
} }

View file

@ -395,7 +395,7 @@ func (r *vcsRepo) ReadFileRevs(revs []string, file string, maxSize int64) (map[s
return nil, vcsErrorf("ReadFileRevs not implemented") return nil, vcsErrorf("ReadFileRevs not implemented")
} }
func (r *vcsRepo) RecentTag(rev, prefix, major string) (tag string, err error) { func (r *vcsRepo) RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error) {
// We don't technically need to lock here since we're returning an error // We don't technically need to lock here since we're returning an error
// uncondititonally, but doing so anyway will help to avoid baking in // uncondititonally, but doing so anyway will help to avoid baking in
// lock-inversion bugs. // lock-inversion bugs.

View file

@ -419,9 +419,14 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
tagPrefix = r.codeDir + "/" tagPrefix = r.codeDir + "/"
} }
isRetracted, err := r.retractedVersions()
if err != nil {
isRetracted = func(string) bool { return false }
}
// tagToVersion returns the version obtained by trimming tagPrefix from tag. // tagToVersion returns the version obtained by trimming tagPrefix from tag.
// If the tag is invalid or a pseudo-version, tagToVersion returns an empty // If the tag is invalid, retracted, or a pseudo-version, tagToVersion returns
// version. // an empty version.
tagToVersion := func(tag string) (v string, tagIsCanonical bool) { tagToVersion := func(tag string) (v string, tagIsCanonical bool) {
if !strings.HasPrefix(tag, tagPrefix) { if !strings.HasPrefix(tag, tagPrefix) {
return "", false return "", false
@ -436,6 +441,9 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
if v == "" || !strings.HasPrefix(trimmed, v) { if v == "" || !strings.HasPrefix(trimmed, v) {
return "", false // Invalid or incomplete version (just vX or vX.Y). return "", false // Invalid or incomplete version (just vX or vX.Y).
} }
if isRetracted(v) {
return "", false
}
if v == trimmed { if v == trimmed {
tagIsCanonical = true tagIsCanonical = true
} }
@ -500,15 +508,24 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
return checkGoMod() return checkGoMod()
} }
// Find the highest tagged version in the revision's history, subject to
// major version and +incompatible constraints. Use that version as the
// pseudo-version base so that the pseudo-version sorts higher. Ignore
// retracted versions.
allowedMajor := func(major string) func(v string) bool {
return func(v string) bool {
return (major == "" || semver.Major(v) == major) && !isRetracted(v)
}
}
if pseudoBase == "" { if pseudoBase == "" {
var tag string var tag string
if r.pseudoMajor != "" || canUseIncompatible() { if r.pseudoMajor != "" || canUseIncompatible() {
tag, _ = r.code.RecentTag(info.Name, tagPrefix, r.pseudoMajor) tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor(r.pseudoMajor))
} else { } else {
// Allow either v1 or v0, but not incompatible higher versions. // Allow either v1 or v0, but not incompatible higher versions.
tag, _ = r.code.RecentTag(info.Name, tagPrefix, "v1") tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor("v1"))
if tag == "" { if tag == "" {
tag, _ = r.code.RecentTag(info.Name, tagPrefix, "v0") tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor("v0"))
} }
} }
pseudoBase, _ = tagToVersion(tag) // empty if the tag is invalid pseudoBase, _ = tagToVersion(tag) // empty if the tag is invalid
@ -869,6 +886,57 @@ func (r *codeRepo) modPrefix(rev string) string {
return r.modPath + "@" + rev return r.modPath + "@" + rev
} }
func (r *codeRepo) retractedVersions() (func(string) bool, error) {
versions, err := r.Versions("")
if err != nil {
return nil, err
}
for i, v := range versions {
if strings.HasSuffix(v, "+incompatible") {
versions = versions[:i]
break
}
}
if len(versions) == 0 {
return func(string) bool { return false }, nil
}
var highest string
for i := len(versions) - 1; i >= 0; i-- {
v := versions[i]
if semver.Prerelease(v) == "" {
highest = v
break
}
}
if highest == "" {
highest = versions[len(versions)-1]
}
data, err := r.GoMod(highest)
if err != nil {
return nil, err
}
f, err := modfile.ParseLax("go.mod", data, nil)
if err != nil {
return nil, err
}
retractions := make([]modfile.VersionInterval, len(f.Retract))
for _, r := range f.Retract {
retractions = append(retractions, r.VersionInterval)
}
return func(v string) bool {
for _, r := range retractions {
if semver.Compare(r.Low, v) <= 0 && semver.Compare(v, r.High) <= 0 {
return true
}
}
return false
}, nil
}
func (r *codeRepo) Zip(dst io.Writer, version string) error { func (r *codeRepo) Zip(dst io.Writer, version string) error {
if version != module.CanonicalVersion(version) { if version != module.CanonicalVersion(version) {
return fmt.Errorf("version %s is not canonical", version) return fmt.Errorf("version %s is not canonical", version)

View file

@ -221,10 +221,8 @@ func pkgImportPath(pkgpath string) *load.Package {
// See https://golang.org/issue/18878. // See https://golang.org/issue/18878.
func TestRespectSetgidDir(t *testing.T) { func TestRespectSetgidDir(t *testing.T) {
switch runtime.GOOS { switch runtime.GOOS {
case "darwin", "ios": case "ios":
if runtime.GOARCH == "arm64" {
t.Skip("can't set SetGID bit with chmod on iOS") t.Skip("can't set SetGID bit with chmod on iOS")
}
case "windows", "plan9": case "windows", "plan9":
t.Skip("chown/chmod setgid are not supported on Windows or Plan 9") t.Skip("chown/chmod setgid are not supported on Windows or Plan 9")
} }

View file

@ -928,6 +928,7 @@ type vetConfig struct {
ImportPath string // canonical import path ("package path") ImportPath string // canonical import path ("package path")
GoFiles []string // absolute paths to package source files GoFiles []string // absolute paths to package source files
NonGoFiles []string // absolute paths to package non-Go files NonGoFiles []string // absolute paths to package non-Go files
IgnoredFiles []string // absolute paths to ignored source files
ImportMap map[string]string // map import path in source code to package path ImportMap map[string]string // map import path in source code to package path
PackageFile map[string]string // map package path to .a file with export data PackageFile map[string]string // map package path to .a file with export data
@ -951,6 +952,8 @@ func buildVetConfig(a *Action, srcfiles []string) {
} }
} }
ignored := str.StringList(a.Package.IgnoredGoFiles, a.Package.IgnoredOtherFiles)
// Pass list of absolute paths to vet, // Pass list of absolute paths to vet,
// so that vet's error messages will use absolute paths, // so that vet's error messages will use absolute paths,
// so that we can reformat them relative to the directory // so that we can reformat them relative to the directory
@ -961,6 +964,7 @@ func buildVetConfig(a *Action, srcfiles []string) {
Dir: a.Package.Dir, Dir: a.Package.Dir,
GoFiles: mkAbsFiles(a.Package.Dir, gofiles), GoFiles: mkAbsFiles(a.Package.Dir, gofiles),
NonGoFiles: mkAbsFiles(a.Package.Dir, nongofiles), NonGoFiles: mkAbsFiles(a.Package.Dir, nongofiles),
IgnoredFiles: mkAbsFiles(a.Package.Dir, ignored),
ImportPath: a.Package.ImportPath, ImportPath: a.Package.ImportPath,
ImportMap: make(map[string]string), ImportMap: make(map[string]string),
PackageFile: make(map[string]string), PackageFile: make(map[string]string),
@ -1052,17 +1056,28 @@ func (b *Builder) vet(ctx context.Context, a *Action) error {
// This is OK as long as the packages that are farther down the // This is OK as long as the packages that are farther down the
// dependency tree turn on *more* analysis, as here. // dependency tree turn on *more* analysis, as here.
// (The unsafeptr check does not write any facts for use by // (The unsafeptr check does not write any facts for use by
// later vet runs.) // later vet runs, nor does unreachable.)
if a.Package.Goroot && !VetExplicit && VetTool == "" { if a.Package.Goroot && !VetExplicit && VetTool == "" {
// Turn off -unsafeptr checks.
// There's too much unsafe.Pointer code
// that vet doesn't like in low-level packages
// like runtime, sync, and reflect.
// Note that $GOROOT/src/buildall.bash // Note that $GOROOT/src/buildall.bash
// does the same for the misc-compile trybots // does the same for the misc-compile trybots
// and should be updated if these flags are // and should be updated if these flags are
// changed here. // changed here.
//
// There's too much unsafe.Pointer code
// that vet doesn't like in low-level packages
// like runtime, sync, and reflect.
vetFlags = []string{"-unsafeptr=false"} vetFlags = []string{"-unsafeptr=false"}
// Also turn off -unreachable checks during go test.
// During testing it is very common to make changes
// like hard-coded forced returns or panics that make
// code unreachable. It's unreasonable to insist on files
// not having any unreachable code during "go test".
// (buildall.bash still runs with -unreachable enabled
// for the overall whole-tree scan.)
if cfg.CmdName == "test" {
vetFlags = append(vetFlags, "-unreachable=false")
}
} }
// Note: We could decide that vet should compute export data for // Note: We could decide that vet should compute export data for

View file

@ -24,6 +24,12 @@ stdout GOARCH=
stdout GOOS= stdout GOOS=
stdout GOROOT= stdout GOROOT=
# checking errors
! go env -w
stderr 'go env -w: no KEY=VALUE arguments given'
! go env -u
stderr 'go env -u: no arguments given'
# go env -w changes default setting # go env -w changes default setting
env root= env root=
[windows] env root=c: [windows] env root=c:
@ -97,6 +103,26 @@ stderr 'GOPATH entry cannot start with shell metacharacter'
! go env -w GOPATH=./go ! go env -w GOPATH=./go
stderr 'GOPATH entry is relative; must be absolute path' stderr 'GOPATH entry is relative; must be absolute path'
# go env -w rejects invalid GOTMPDIR values
! go env -w GOTMPDIR=x
stderr 'go env -w: GOTMPDIR must be an absolute path'
# go env -w should accept absolute GOTMPDIR value
# and should not create it
[windows] go env -w GOTMPDIR=$WORK\x\y\z
[!windows] go env -w GOTMPDIR=$WORK/x/y/z
! exists $WORK/x/y/z
# we should be able to clear an env
go env -u GOTMPDIR
go env GOTMPDIR
stdout ^$
[windows] go env -w GOTMPDIR=$WORK\x\y\z
[!windows] go env -w GOTMPDIR=$WORK/x/y/z
go env -w GOTMPDIR=
go env GOTMPDIR
stdout ^$
# go env -w/-u checks validity of GOOS/ARCH combinations # go env -w/-u checks validity of GOOS/ARCH combinations
env GOOS= env GOOS=
env GOARCH= env GOARCH=

View file

@ -0,0 +1,62 @@
# When converting a commit to a pseudo-version, don't use a retracted version
# as the base.
# Verifies golang.org/issue/41700.
[!net] skip
[!exec:git] skip
env GOPROXY=direct
env GOSUMDB=off
go mod init m
# Control: check that v1.0.0 is the only version and is retracted.
go list -m -versions vcs-test.golang.org/git/retract-pseudo.git
stdout '^vcs-test.golang.org/git/retract-pseudo.git$'
go list -m -versions -retracted vcs-test.golang.org/git/retract-pseudo.git
stdout '^vcs-test.golang.org/git/retract-pseudo.git v1.0.0$'
# 713affd19d7b is a commit after v1.0.0. Don't use v1.0.0 as the base.
go list -m vcs-test.golang.org/git/retract-pseudo.git@713affd19d7b
stdout '^vcs-test.golang.org/git/retract-pseudo.git v0.0.0-20201009173747-713affd19d7b$'
# 64c061ed4371 is the commit v1.0.0 refers to. Don't convert to v1.0.0.
go list -m vcs-test.golang.org/git/retract-pseudo.git@64c061ed4371
stdout '^vcs-test.golang.org/git/retract-pseudo.git v0.0.0-20201009173747-64c061ed4371'
# A retracted version is a valid base. Retraction should not validate existing
# pseudo-versions, nor should it turn invalid pseudo-versions valid.
go get -d vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-713affd19d7b
go list -m vcs-test.golang.org/git/retract-pseudo.git
stdout '^vcs-test.golang.org/git/retract-pseudo.git v1.0.1-0.20201009173747-713affd19d7b$'
! go get -d vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371
stderr '^go get vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371: vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371: invalid pseudo-version: tag \(v1.0.0\) found on revision 64c061ed4371 is already canonical, so should not be replaced with a pseudo-version derived from that tag$'
-- retract-pseudo.sh --
#!/bin/bash
# This is not part of the test.
# Run this to generate and update the repository on vcs-test.golang.org.
set -euo pipefail
rm -rf retract-pseudo
mkdir retract-pseudo
cd retract-pseudo
git init
# Create the module.
# Retract v1.0.0 and tag v1.0.0 at the same commit.
# The module has no unretracted release versions.
go mod init vcs-test.golang.org/git/retract-pseudo.git
go mod edit -retract v1.0.0
echo 'package p' >p.go
git add -A
git commit -m 'create module retract-pseudo'
git tag v1.0.0
# Commit a trivial change so the default branch does not point to v1.0.0.
git mv p.go q.go
git commit -m 'trivial change'
zip -r ../retract-pseudo.zip .
gsutil cp ../retract-pseudo.zip gs://vcs-test/git/retract-pseudo.zip

View file

@ -2,21 +2,25 @@ env GO111MODULE=on
# Issue 35837: "go vet -<analyzer> <std package>" should use the requested # Issue 35837: "go vet -<analyzer> <std package>" should use the requested
# analyzers, not the default analyzers for 'go test'. # analyzers, not the default analyzers for 'go test'.
go vet -n -unreachable=false encoding/binary go vet -n -buildtags=false runtime
stderr '-unreachable=false' stderr '-buildtags=false'
! stderr '-unsafeptr=false' ! stderr '-unsafeptr=false'
# Issue 37030: "go vet <std package>" without other flags should disable the # Issue 37030: "go vet <std package>" without other flags should disable the
# unsafeptr check by default. # unsafeptr check by default.
go vet -n encoding/binary go vet -n runtime
stderr '-unsafeptr=false' stderr '-unsafeptr=false'
! stderr '-unreachable=false' ! stderr '-unreachable=false'
# However, it should be enabled if requested explicitly. # However, it should be enabled if requested explicitly.
go vet -n -unsafeptr encoding/binary go vet -n -unsafeptr runtime
stderr '-unsafeptr' stderr '-unsafeptr'
! stderr '-unsafeptr=false' ! stderr '-unsafeptr=false'
# -unreachable is disabled during test but on during plain vet.
go test -n runtime
stderr '-unreachable=false'
# A flag terminator should be allowed before the package list. # A flag terminator should be allowed before the package list.
go vet -n -- . go vet -n -- .
@ -60,10 +64,10 @@ stderr '[/\\]vet'$GOEXE'["]? .* -errorsas .* ["]?\$WORK[/\\][^ ]*[/\\]vet\.cfg'
# "go test" on a standard package should by default disable an explicit list. # "go test" on a standard package should by default disable an explicit list.
go test -x -run=none encoding/binary go test -x -run=none encoding/binary
stderr '[/\\]vet'$GOEXE'["]? -unsafeptr=false ["]?\$WORK[/\\][^ ]*[/\\]vet\.cfg' stderr '[/\\]vet'$GOEXE'["]? -unsafeptr=false -unreachable=false ["]?\$WORK[/\\][^ ]*[/\\]vet\.cfg'
go test -x -vet= -run=none encoding/binary go test -x -vet= -run=none encoding/binary
stderr '[/\\]vet'$GOEXE'["]? -unsafeptr=false ["]?\$WORK[/\\][^ ]*[/\\]vet\.cfg' stderr '[/\\]vet'$GOEXE'["]? -unsafeptr=false -unreachable=false ["]?\$WORK[/\\][^ ]*[/\\]vet\.cfg'
# Both should allow users to override via the -vet flag. # Both should allow users to override via the -vet flag.
go test -x -vet=unreachable -run=none . go test -x -vet=unreachable -run=none .

View file

@ -958,9 +958,11 @@ const (
AVADDP AVADDP
AVAND AVAND
AVBIF AVBIF
AVBCAX
AVCMEQ AVCMEQ
AVCNT AVCNT
AVEOR AVEOR
AVEOR3
AVMOV AVMOV
AVLD1 AVLD1
AVLD2 AVLD2
@ -989,6 +991,7 @@ const (
AVPMULL2 AVPMULL2
AVEXT AVEXT
AVRBIT AVRBIT
AVRAX1
AVUSHR AVUSHR
AVUSHLL AVUSHLL
AVUSHLL2 AVUSHLL2
@ -1001,6 +1004,7 @@ const (
AVBSL AVBSL
AVBIT AVBIT
AVTBL AVTBL
AVXAR
AVZIP1 AVZIP1
AVZIP2 AVZIP2
AVCMTST AVCMTST

View file

@ -464,9 +464,11 @@ var Anames = []string{
"VADDP", "VADDP",
"VAND", "VAND",
"VBIF", "VBIF",
"VBCAX",
"VCMEQ", "VCMEQ",
"VCNT", "VCNT",
"VEOR", "VEOR",
"VEOR3",
"VMOV", "VMOV",
"VLD1", "VLD1",
"VLD2", "VLD2",
@ -495,6 +497,7 @@ var Anames = []string{
"VPMULL2", "VPMULL2",
"VEXT", "VEXT",
"VRBIT", "VRBIT",
"VRAX1",
"VUSHR", "VUSHR",
"VUSHLL", "VUSHLL",
"VUSHLL2", "VUSHLL2",
@ -507,6 +510,7 @@ var Anames = []string{
"VBSL", "VBSL",
"VBIT", "VBIT",
"VTBL", "VTBL",
"VXAR",
"VZIP1", "VZIP1",
"VZIP2", "VZIP2",
"VCMTST", "VCMTST",

View file

@ -843,6 +843,8 @@ var optab = []Optab{
{ASHA256H, C_ARNG, C_VREG, C_NONE, C_VREG, 1, 4, 0, 0, 0}, {ASHA256H, C_ARNG, C_VREG, C_NONE, C_VREG, 1, 4, 0, 0, 0},
{AVREV32, C_ARNG, C_NONE, C_NONE, C_ARNG, 83, 4, 0, 0, 0}, {AVREV32, C_ARNG, C_NONE, C_NONE, C_ARNG, 83, 4, 0, 0, 0},
{AVPMULL, C_ARNG, C_ARNG, C_NONE, C_ARNG, 93, 4, 0, 0, 0}, {AVPMULL, C_ARNG, C_ARNG, C_NONE, C_ARNG, 93, 4, 0, 0, 0},
{AVEOR3, C_ARNG, C_ARNG, C_ARNG, C_ARNG, 103, 4, 0, 0, 0},
{AVXAR, C_VCON, C_ARNG, C_ARNG, C_ARNG, 104, 4, 0, 0, 0},
{obj.AUNDEF, C_NONE, C_NONE, C_NONE, C_NONE, 90, 4, 0, 0, 0}, {obj.AUNDEF, C_NONE, C_NONE, C_NONE, C_NONE, 90, 4, 0, 0, 0},
{obj.APCDATA, C_VCON, C_NONE, C_NONE, C_VCON, 0, 0, 0, 0, 0}, {obj.APCDATA, C_VCON, C_NONE, C_NONE, C_VCON, 0, 0, 0, 0, 0},
@ -2769,6 +2771,7 @@ func buildop(ctxt *obj.Link) {
case AVADD: case AVADD:
oprangeset(AVSUB, t) oprangeset(AVSUB, t)
oprangeset(AVRAX1, t)
case AAESD: case AAESD:
oprangeset(AAESE, t) oprangeset(AAESE, t)
@ -2827,6 +2830,9 @@ func buildop(ctxt *obj.Link) {
oprangeset(AVLD4, t) oprangeset(AVLD4, t)
oprangeset(AVLD4R, t) oprangeset(AVLD4R, t)
case AVEOR3:
oprangeset(AVBCAX, t)
case ASHA1H, case ASHA1H,
AVCNT, AVCNT,
AVMOV, AVMOV,
@ -2839,7 +2845,8 @@ func buildop(ctxt *obj.Link) {
AVDUP, AVDUP,
AVMOVI, AVMOVI,
APRFM, APRFM,
AVEXT: AVEXT,
AVXAR:
break break
case obj.ANOP, case obj.ANOP,
@ -3120,12 +3127,13 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
case 6: /* b ,O(R); bl ,O(R) */ case 6: /* b ,O(R); bl ,O(R) */
o1 = c.opbrr(p, p.As) o1 = c.opbrr(p, p.As)
o1 |= uint32(p.To.Reg&31) << 5 o1 |= uint32(p.To.Reg&31) << 5
if p.As == obj.ACALL {
rel := obj.Addrel(c.cursym) rel := obj.Addrel(c.cursym)
rel.Off = int32(c.pc) rel.Off = int32(c.pc)
rel.Siz = 0 rel.Siz = 0
rel.Type = objabi.R_CALLIND rel.Type = objabi.R_CALLIND
}
case 7: /* beq s */ case 7: /* beq s */
o1 = c.opbra(p, p.As) o1 = c.opbra(p, p.As)
@ -4204,7 +4212,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
rel.Add = 0 rel.Add = 0
rel.Type = objabi.R_ARM64_GOTPCREL rel.Type = objabi.R_ARM64_GOTPCREL
case 72: /* vaddp/vand/vcmeq/vorr/vadd/veor/vfmla/vfmls/vbit/vbsl/vcmtst/vsub/vbif/vuzip1/vuzip2 Vm.<T>, Vn.<T>, Vd.<T> */ case 72: /* vaddp/vand/vcmeq/vorr/vadd/veor/vfmla/vfmls/vbit/vbsl/vcmtst/vsub/vbif/vuzip1/vuzip2/vrax1 Vm.<T>, Vn.<T>, Vd.<T> */
af := int((p.From.Reg >> 5) & 15) af := int((p.From.Reg >> 5) & 15)
af3 := int((p.Reg >> 5) & 15) af3 := int((p.Reg >> 5) & 15)
at := int((p.To.Reg >> 5) & 15) at := int((p.To.Reg >> 5) & 15)
@ -4268,6 +4276,12 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
} else { } else {
size = 0 size = 0
} }
case AVRAX1:
if af != ARNG_2D {
c.ctxt.Diag("invalid arrangement: %v", p)
}
size = 0
Q = 0
} }
o1 |= (uint32(Q&1) << 30) | (uint32(size&3) << 22) | (uint32(rf&31) << 16) | (uint32(r&31) << 5) | uint32(rt&31) o1 |= (uint32(Q&1) << 30) | (uint32(size&3) << 22) | (uint32(rf&31) << 16) | (uint32(r&31) << 5) | uint32(rt&31)
@ -5185,6 +5199,51 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
c.ctxt.Diag("shift amount out of range: %v\n", p) c.ctxt.Diag("shift amount out of range: %v\n", p)
} }
o1 |= uint32(immh)<<19 | uint32(shift)<<16 | uint32(rf&31)<<5 | uint32(p.To.Reg&31) o1 |= uint32(immh)<<19 | uint32(shift)<<16 | uint32(rf&31)<<5 | uint32(p.To.Reg&31)
case 103: /* VEOR3/VBCAX Va.B16, Vm.B16, Vn.B16, Vd.B16 */
ta := (p.From.Reg >> 5) & 15
tm := (p.Reg >> 5) & 15
td := (p.To.Reg >> 5) & 15
tn := ((p.GetFrom3().Reg) >> 5) & 15
if ta != tm || ta != tn || ta != td || ta != ARNG_16B {
c.ctxt.Diag("invalid arrangement: %v", p)
break
}
o1 = c.oprrr(p, p.As)
ra := int(p.From.Reg)
rm := int(p.Reg)
rn := int(p.GetFrom3().Reg)
rd := int(p.To.Reg)
o1 |= uint32(rm&31)<<16 | uint32(ra&31)<<10 | uint32(rn&31)<<5 | uint32(rd)&31
case 104: /* vxar $imm4, Vm.<T>, Vn.<T>, Vd.<T> */
af := ((p.GetFrom3().Reg) >> 5) & 15
at := (p.To.Reg >> 5) & 15
a := (p.Reg >> 5) & 15
index := int(p.From.Offset)
if af != a || af != at {
c.ctxt.Diag("invalid arrangement: %v", p)
break
}
if af != ARNG_2D {
c.ctxt.Diag("invalid arrangement, should be D2: %v", p)
break
}
if index < 0 || index > 63 {
c.ctxt.Diag("illegal offset: %v", p)
}
o1 = c.opirr(p, p.As)
rf := (p.GetFrom3().Reg) & 31
rt := (p.To.Reg) & 31
r := (p.Reg) & 31
o1 |= (uint32(r&31) << 16) | (uint32(index&63) << 10) | (uint32(rf&31) << 5) | uint32(rt&31)
} }
out[0] = o1 out[0] = o1
out[1] = o2 out[1] = o2
@ -5760,6 +5819,9 @@ func (c *ctxt7) oprrr(p *obj.Prog, a obj.As) uint32 {
case AVAND: case AVAND:
return 7<<25 | 1<<21 | 7<<10 return 7<<25 | 1<<21 | 7<<10
case AVBCAX:
return 0xCE<<24 | 1<<21
case AVCMEQ: case AVCMEQ:
return 1<<29 | 0x71<<21 | 0x23<<10 return 1<<29 | 0x71<<21 | 0x23<<10
@ -5775,12 +5837,18 @@ func (c *ctxt7) oprrr(p *obj.Prog, a obj.As) uint32 {
case AVEOR: case AVEOR:
return 1<<29 | 0x71<<21 | 7<<10 return 1<<29 | 0x71<<21 | 7<<10
case AVEOR3:
return 0xCE << 24
case AVORR: case AVORR:
return 7<<25 | 5<<21 | 7<<10 return 7<<25 | 5<<21 | 7<<10
case AVREV16: case AVREV16:
return 3<<26 | 2<<24 | 1<<21 | 3<<11 return 3<<26 | 2<<24 | 1<<21 | 3<<11
case AVRAX1:
return 0xCE<<24 | 3<<21 | 1<<15 | 3<<10
case AVREV32: case AVREV32:
return 11<<26 | 2<<24 | 1<<21 | 1<<11 return 11<<26 | 2<<24 | 1<<21 | 1<<11
@ -6038,6 +6106,8 @@ func (c *ctxt7) opirr(p *obj.Prog, a obj.As) uint32 {
case AVUSHLL2, AVUXTL2: case AVUSHLL2, AVUXTL2:
return 3<<29 | 15<<24 | 0x29<<10 return 3<<29 | 15<<24 | 0x29<<10
case AVXAR:
return 0xCE<<24 | 1<<23
} }
c.ctxt.Diag("%v: bad irr %v", p, a) c.ctxt.Diag("%v: bad irr %v", p, a)

View file

@ -589,7 +589,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
q1.To.Reg = REGSP q1.To.Reg = REGSP
q1.Spadj = c.autosize q1.Spadj = c.autosize
if c.ctxt.Headtype == objabi.Hdarwin { if objabi.GOOS == "ios" {
// iOS does not support SA_ONSTACK. We will run the signal handler // iOS does not support SA_ONSTACK. We will run the signal handler
// on the G stack. If we write below SP, it may be clobbered by // on the G stack. If we write below SP, it may be clobbered by
// the signal handler. So we save LR after decrementing SP. // the signal handler. So we save LR after decrementing SP.

View file

@ -24,6 +24,7 @@ var (
GOROOT = envOr("GOROOT", defaultGOROOT) GOROOT = envOr("GOROOT", defaultGOROOT)
GOARCH = envOr("GOARCH", defaultGOARCH) GOARCH = envOr("GOARCH", defaultGOARCH)
GOOS = envOr("GOOS", defaultGOOS) GOOS = envOr("GOOS", defaultGOOS)
GO386 = envOr("GO386", defaultGO386)
GOAMD64 = goamd64() GOAMD64 = goamd64()
GOARM = goarm() GOARM = goarm()
GOMIPS = gomips() GOMIPS = gomips()
@ -135,14 +136,6 @@ func init() {
if GOARCH != "amd64" { if GOARCH != "amd64" {
Regabi_enabled = 0 Regabi_enabled = 0
} }
if v := os.Getenv("GO386"); v != "" && v != "sse2" {
msg := fmt.Sprintf("unsupported setting GO386=%s", v)
if v == "387" {
msg += ". 387 support was dropped in Go 1.16. Consider using gccgo instead."
}
log.Fatal(msg)
}
} }
// Note: must agree with runtime.framepointer_enabled. // Note: must agree with runtime.framepointer_enabled.

View file

@ -32,6 +32,7 @@ func MSanSupported(goos, goarch string) bool {
} }
// MustLinkExternal reports whether goos/goarch requires external linking. // MustLinkExternal reports whether goos/goarch requires external linking.
// (This is the opposite of internal/testenv.CanInternalLink. Keep them in sync.)
func MustLinkExternal(goos, goarch string) bool { func MustLinkExternal(goos, goarch string) bool {
switch goos { switch goos {
case "android": case "android":
@ -69,7 +70,7 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool {
case "linux/amd64", "linux/arm", "linux/arm64", "linux/386", "linux/ppc64le", "linux/s390x", case "linux/amd64", "linux/arm", "linux/arm64", "linux/386", "linux/ppc64le", "linux/s390x",
"android/amd64", "android/arm", "android/arm64", "android/386", "android/amd64", "android/arm", "android/arm64", "android/386",
"freebsd/amd64", "freebsd/amd64",
"darwin/amd64", "darwin/amd64", "darwin/arm64",
"windows/amd64", "windows/386": "windows/amd64", "windows/386":
return true return true
} }
@ -86,7 +87,7 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool {
case "linux/386", "linux/amd64", "linux/arm", "linux/arm64", "linux/ppc64le", "linux/s390x", case "linux/386", "linux/amd64", "linux/arm", "linux/arm64", "linux/ppc64le", "linux/s390x",
"android/amd64", "android/arm", "android/arm64", "android/386", "android/amd64", "android/arm", "android/arm64", "android/386",
"freebsd/amd64", "freebsd/amd64",
"darwin/amd64", "darwin/amd64", "darwin/arm64",
"aix/ppc64", "aix/ppc64",
"windows/386", "windows/amd64", "windows/arm": "windows/386", "windows/amd64", "windows/arm":
return true return true
@ -104,7 +105,7 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool {
switch platform { switch platform {
case "linux/amd64", "linux/arm", "linux/arm64", "linux/386", "linux/s390x", "linux/ppc64le", case "linux/amd64", "linux/arm", "linux/arm64", "linux/386", "linux/s390x", "linux/ppc64le",
"android/amd64", "android/arm", "android/arm64", "android/386", "android/amd64", "android/arm", "android/arm64", "android/386",
"darwin/amd64", "darwin/amd64", "darwin/arm64",
"freebsd/amd64": "freebsd/amd64":
return true return true
} }

View file

@ -0,0 +1,18 @@
// Copyright 2020 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 sys
import (
"internal/testenv"
"runtime"
"testing"
)
func TestMustLinkExternalMatchesTestenv(t *testing.T) {
// MustLinkExternal and testenv.CanInternalLink are the exact opposite.
if b := MustLinkExternal(runtime.GOOS, runtime.GOARCH); b != !testenv.CanInternalLink() {
t.Fatalf("MustLinkExternal() == %v, testenv.CanInternalLink() == %v, don't match", b, testenv.CanInternalLink())
}
}

View file

@ -371,7 +371,7 @@ func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy
rt := r.Type rt := r.Type
siz := r.Size siz := r.Size
if ldr.SymType(rs) == sym.SHOSTOBJ || rt == objabi.R_CALLARM64 || rt == objabi.R_ADDRARM64 { if ldr.SymType(rs) == sym.SHOSTOBJ || rt == objabi.R_CALLARM64 || rt == objabi.R_ADDRARM64 || rt == objabi.R_ARM64_GOTPCREL {
if ldr.SymDynid(rs) < 0 { if ldr.SymDynid(rs) < 0 {
ldr.Errorf(s, "reloc %d (%s) to non-macho symbol %s type=%d (%s)", rt, sym.RelocName(arch, rt), ldr.SymName(rs), ldr.SymType(rs), ldr.SymType(rs)) ldr.Errorf(s, "reloc %d (%s) to non-macho symbol %s type=%d (%s)", rt, sym.RelocName(arch, rt), ldr.SymName(rs), ldr.SymType(rs), ldr.SymType(rs))
return false return false
@ -415,6 +415,22 @@ func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sy
} }
v |= 1 << 24 // pc-relative bit v |= 1 << 24 // pc-relative bit
v |= ld.MACHO_ARM64_RELOC_PAGE21 << 28 v |= ld.MACHO_ARM64_RELOC_PAGE21 << 28
case objabi.R_ARM64_GOTPCREL:
siz = 4
// Two relocation entries: MACHO_ARM64_RELOC_GOT_LOAD_PAGEOFF12 MACHO_ARM64_RELOC_GOT_LOAD_PAGE21
// if r.Xadd is non-zero, add two MACHO_ARM64_RELOC_ADDEND.
if r.Xadd != 0 {
out.Write32(uint32(sectoff + 4))
out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(r.Xadd&0xffffff))
}
out.Write32(uint32(sectoff + 4))
out.Write32(v | (ld.MACHO_ARM64_RELOC_GOT_LOAD_PAGEOFF12 << 28) | (2 << 25))
if r.Xadd != 0 {
out.Write32(uint32(sectoff))
out.Write32((ld.MACHO_ARM64_RELOC_ADDEND << 28) | (2 << 25) | uint32(r.Xadd&0xffffff))
}
v |= 1 << 24 // pc-relative bit
v |= ld.MACHO_ARM64_RELOC_GOT_LOAD_PAGE21 << 28
} }
switch siz { switch siz {
@ -457,7 +473,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade
} }
nExtReloc = 2 // need two ELF/Mach-O relocations. see elfreloc1/machoreloc1 nExtReloc = 2 // need two ELF/Mach-O relocations. see elfreloc1/machoreloc1
if target.IsDarwin() && rt == objabi.R_ADDRARM64 && xadd != 0 { if target.IsDarwin() && xadd != 0 {
nExtReloc = 4 // need another two relocations for non-zero addend nExtReloc = 4 // need another two relocations for non-zero addend
} }

View file

@ -39,7 +39,13 @@ func (mode *BuildMode) Set(s string) error {
case "pie": case "pie":
switch objabi.GOOS { switch objabi.GOOS {
case "aix", "android", "linux", "windows": case "aix", "android", "linux", "windows":
case "darwin", "freebsd": case "darwin":
switch objabi.GOARCH {
case "amd64", "arm64":
default:
return badmode()
}
case "freebsd":
switch objabi.GOARCH { switch objabi.GOARCH {
case "amd64": case "amd64":
default: default:
@ -95,7 +101,13 @@ func (mode *BuildMode) Set(s string) error {
default: default:
return badmode() return badmode()
} }
case "darwin", "freebsd": case "darwin":
switch objabi.GOARCH {
case "amd64", "arm64":
default:
return badmode()
}
case "freebsd":
switch objabi.GOARCH { switch objabi.GOARCH {
case "amd64": case "amd64":
default: default:

View file

@ -238,6 +238,10 @@ func TestSizes(t *testing.T) {
if runtime.GOOS == "plan9" { if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; no DWARF symbol table in executables") t.Skip("skipping on plan9; no DWARF symbol table in executables")
} }
// External linking may bring in C symbols with unknown size. Skip.
testenv.MustInternalLink(t)
t.Parallel() t.Parallel()
// DWARF sizes should never be -1. // DWARF sizes should never be -1.
@ -919,6 +923,7 @@ func TestAbstractOriginSanityIssue26237(t *testing.T) {
func TestRuntimeTypeAttrInternal(t *testing.T) { func TestRuntimeTypeAttrInternal(t *testing.T) {
testenv.MustHaveGoBuild(t) testenv.MustHaveGoBuild(t)
testenv.MustInternalLink(t)
if runtime.GOOS == "plan9" { if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; no DWARF symbol table in executables") t.Skip("skipping on plan9; no DWARF symbol table in executables")
@ -1018,6 +1023,9 @@ func main() {
t.Fatalf("*main.X DIE had no runtime type attr. DIE: %v", dies[0]) t.Fatalf("*main.X DIE had no runtime type attr. DIE: %v", dies[0])
} }
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
return // everything is PIE on ARM64, addresses are relocated
}
if rtAttr.(uint64)+types.Addr != addr { if rtAttr.(uint64)+types.Addr != addr {
t.Errorf("DWARF type offset was %#x+%#x, but test program said %#x", rtAttr.(uint64), types.Addr, addr) t.Errorf("DWARF type offset was %#x+%#x, but test program said %#x", rtAttr.(uint64), types.Addr, addr)
} }
@ -1203,6 +1211,15 @@ func main() {
} }
} }
// When external linking, we put all symbols in the symbol table (so the
// external linker can find them). Skip the symbol table check.
// TODO: maybe there is some way to tell the external linker not to put
// those symbols in the executable's symbol table? Prefix the symbol name
// with "." or "L" to pretend it is a label?
if !testenv.CanInternalLink() {
return
}
syms, err := f.Symbols() syms, err := f.Symbols()
if err != nil { if err != nil {
t.Fatalf("error reading symbols: %v", err) t.Fatalf("error reading symbols: %v", err)

View file

@ -18,8 +18,13 @@ import (
) )
func TestUndefinedRelocErrors(t *testing.T) { func TestUndefinedRelocErrors(t *testing.T) {
t.Parallel()
testenv.MustHaveGoBuild(t) testenv.MustHaveGoBuild(t)
// When external linking, symbols may be defined externally, so we allow
// undefined symbols and let external linker resolve. Skip the test.
testenv.MustInternalLink(t)
t.Parallel()
dir, err := ioutil.TempDir("", "go-build") dir, err := ioutil.TempDir("", "go-build")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -1254,7 +1254,9 @@ func (ctxt *Link) hostlink() {
// -headerpad is incompatible with -fembed-bitcode. // -headerpad is incompatible with -fembed-bitcode.
argv = append(argv, "-Wl,-headerpad,1144") argv = append(argv, "-Wl,-headerpad,1144")
} }
if ctxt.DynlinkingGo() && !ctxt.Arch.InFamily(sys.ARM, sys.ARM64) { if ctxt.DynlinkingGo() && objabi.GOOS != "ios" {
// -flat_namespace is deprecated on iOS.
// It is useful for supporting plugins. We don't support plugins on iOS.
argv = append(argv, "-Wl,-flat_namespace") argv = append(argv, "-Wl,-flat_namespace")
} }
if !combineDwarf { if !combineDwarf {
@ -1327,9 +1329,6 @@ func (ctxt *Link) hostlink() {
case BuildModeCShared: case BuildModeCShared:
if ctxt.HeadType == objabi.Hdarwin { if ctxt.HeadType == objabi.Hdarwin {
argv = append(argv, "-dynamiclib") argv = append(argv, "-dynamiclib")
if ctxt.Arch.Family != sys.AMD64 {
argv = append(argv, "-Wl,-read_only_relocs,suppress")
}
} else { } else {
// ELF. // ELF.
argv = append(argv, "-Wl,-Bsymbolic") argv = append(argv, "-Wl,-Bsymbolic")

View file

@ -103,6 +103,8 @@ const (
MACHO_ARM64_RELOC_BRANCH26 = 2 MACHO_ARM64_RELOC_BRANCH26 = 2
MACHO_ARM64_RELOC_PAGE21 = 3 MACHO_ARM64_RELOC_PAGE21 = 3
MACHO_ARM64_RELOC_PAGEOFF12 = 4 MACHO_ARM64_RELOC_PAGEOFF12 = 4
MACHO_ARM64_RELOC_GOT_LOAD_PAGE21 = 5
MACHO_ARM64_RELOC_GOT_LOAD_PAGEOFF12 = 6
MACHO_ARM64_RELOC_ADDEND = 10 MACHO_ARM64_RELOC_ADDEND = 10
MACHO_GENERIC_RELOC_VANILLA = 0 MACHO_GENERIC_RELOC_VANILLA = 0
MACHO_FAKE_GOTPCREL = 100 MACHO_FAKE_GOTPCREL = 100
@ -473,6 +475,18 @@ func (ctxt *Link) domacho() {
sb.SetReachable(true) sb.SetReachable(true)
sb.AddUint8(0) sb.AddUint8(0)
} }
// Do not export C symbols dynamically in plugins, as runtime C symbols like crosscall2
// are in pclntab and end up pointing at the host binary, breaking unwinding.
// See Issue #18190.
if ctxt.BuildMode == BuildModePlugin {
for _, name := range []string{"_cgo_topofstack", "__cgo_topofstack", "_cgo_panic", "crosscall2"} {
s := ctxt.loader.Lookup(name, 0)
if s != 0 {
ctxt.loader.SetAttrCgoExportDynamic(s, false)
}
}
}
} }
func machoadddynlib(lib string, linkmode LinkMode) { func machoadddynlib(lib string, linkmode LinkMode) {
@ -897,19 +911,12 @@ func machosymtab(ctxt *Link) {
symtab.AddUint32(ctxt.Arch, uint32(symstr.Size())) symtab.AddUint32(ctxt.Arch, uint32(symstr.Size()))
export := machoShouldExport(ctxt, ldr, s) export := machoShouldExport(ctxt, ldr, s)
isGoSymbol := strings.Contains(ldr.SymExtname(s), ".")
// In normal buildmodes, only add _ to C symbols, as // Prefix symbol names with "_" to match the system toolchain.
// Go symbols have dot in the name. // (We used to only prefix C symbols, which is all required for the build.
// // But some tools don't recognize Go symbols as symbols, so we prefix them
// Do not export C symbols in plugins, as runtime C // as well.)
// symbols like crosscall2 are in pclntab and end up
// pointing at the host binary, breaking unwinding.
// See Issue #18190.
cexport := !isGoSymbol && (ctxt.BuildMode != BuildModePlugin || onlycsymbol(ldr.SymName(s)))
if cexport || export || isGoSymbol {
symstr.AddUint8('_') symstr.AddUint8('_')
}
// replace "·" as ".", because DTrace cannot handle it. // replace "·" as ".", because DTrace cannot handle it.
symstr.Addstring(strings.Replace(ldr.SymExtname(s), "·", ".", -1)) symstr.Addstring(strings.Replace(ldr.SymExtname(s), "·", ".", -1))
@ -920,10 +927,13 @@ func machosymtab(ctxt *Link) {
symtab.AddUint16(ctxt.Arch, 0) // desc symtab.AddUint16(ctxt.Arch, 0) // desc
symtab.AddUintXX(ctxt.Arch, 0, ctxt.Arch.PtrSize) // no value symtab.AddUintXX(ctxt.Arch, 0, ctxt.Arch.PtrSize) // no value
} else { } else {
if ldr.AttrCgoExport(s) || export { if export || ldr.AttrCgoExportDynamic(s) {
symtab.AddUint8(0x0f) symtab.AddUint8(0x0f) // N_SECT | N_EXT
} else if ldr.AttrCgoExportStatic(s) {
// Only export statically, not dynamically. (N_PEXT is like hidden visibility)
symtab.AddUint8(0x1f) // N_SECT | N_EXT | N_PEXT
} else { } else {
symtab.AddUint8(0x0e) symtab.AddUint8(0x0e) // N_SECT
} }
o := s o := s
if outer := ldr.OuterSym(o); outer != 0 { if outer := ldr.OuterSym(o); outer != 0 {

View file

@ -13,7 +13,6 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
// pclntab holds the state needed for pclntab generation. // pclntab holds the state needed for pclntab generation.
@ -113,23 +112,7 @@ func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.Compilat
return state, compUnits, funcs return state, compUnits, funcs
} }
// onlycsymbol looks at a symbol's name to report whether this is a
// symbol that is referenced by C code
func onlycsymbol(sname string) bool {
switch sname {
case "_cgo_topofstack", "__cgo_topofstack", "_cgo_panic", "crosscall2":
return true
}
if strings.HasPrefix(sname, "_cgoexp_") {
return true
}
return false
}
func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool { func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool {
if ctxt.BuildMode == BuildModePlugin && ctxt.HeadType == objabi.Hdarwin && onlycsymbol(ctxt.loader.SymName(s)) {
return false
}
// We want to generate func table entries only for the "lowest // We want to generate func table entries only for the "lowest
// level" symbols, not containers of subsymbols. // level" symbols, not containers of subsymbols.
return !container.Has(s) return !container.Has(s)

View file

@ -181,6 +181,7 @@ main.x: relocation target main.zero not defined
func TestIssue33979(t *testing.T) { func TestIssue33979(t *testing.T) {
testenv.MustHaveGoBuild(t) testenv.MustHaveGoBuild(t)
testenv.MustHaveCGO(t) testenv.MustHaveCGO(t)
testenv.MustInternalLink(t)
// Skip test on platforms that do not support cgo internal linking. // Skip test on platforms that do not support cgo internal linking.
switch runtime.GOARCH { switch runtime.GOARCH {

View file

@ -15,6 +15,11 @@ func canInternalLink() bool {
switch runtime.GOOS { switch runtime.GOOS {
case "aix": case "aix":
return false return false
case "darwin":
switch runtime.GOARCH {
case "arm64":
return false
}
case "dragonfly": case "dragonfly":
return false return false
case "freebsd": case "freebsd":

View file

@ -173,6 +173,9 @@ func testGoExec(t *testing.T, iscgo, isexternallinker bool) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return true return true
} }
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
return true // On darwin/arm64 everything is PIE
}
return false return false
} }

View file

@ -171,7 +171,10 @@ func (*objTool) Demangle(names []string) (map[string]string, error) {
return make(map[string]string), nil return make(map[string]string), nil
} }
func (t *objTool) Disasm(file string, start, end uint64) ([]driver.Inst, error) { func (t *objTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]driver.Inst, error) {
if intelSyntax {
return nil, fmt.Errorf("printing assembly in Intel syntax is not supported")
}
d, err := t.cachedDisasm(file) d, err := t.cachedDisasm(file)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -142,7 +142,7 @@ type ObjTool interface {
// Disasm disassembles the named object file, starting at // Disasm disassembles the named object file, starting at
// the start address and stopping at (before) the end address. // the start address and stopping at (before) the end address.
Disasm(file string, start, end uint64) ([]Inst, error) Disasm(file string, start, end uint64, intelSyntax bool) ([]Inst, error)
} }
// An Inst is a single instruction in an assembly listing. // An Inst is a single instruction in an assembly listing.
@ -269,8 +269,8 @@ func (f *internalObjFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym,
return pluginSyms, nil return pluginSyms, nil
} }
func (o *internalObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { func (o *internalObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {
insts, err := o.ObjTool.Disasm(file, start, end) insts, err := o.ObjTool.Disasm(file, start, end, intelSyntax)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -19,6 +19,7 @@ import (
"debug/elf" "debug/elf"
"debug/macho" "debug/macho"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -26,6 +27,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
@ -39,6 +41,8 @@ type Binutils struct {
rep *binrep rep *binrep
} }
var objdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\d*)\.(\d*)\.(\d*)|.*(trunk).*)`)
// binrep is an immutable representation for Binutils. It is atomically // binrep is an immutable representation for Binutils. It is atomically
// replaced on every mutation to provide thread-safe access. // replaced on every mutation to provide thread-safe access.
type binrep struct { type binrep struct {
@ -51,6 +55,7 @@ type binrep struct {
nmFound bool nmFound bool
objdump string objdump string
objdumpFound bool objdumpFound bool
isLLVMObjdump bool
// if fast, perform symbolization using nm (symbol names only), // if fast, perform symbolization using nm (symbol names only),
// instead of file-line detail from the slower addr2line. // instead of file-line detail from the slower addr2line.
@ -132,15 +137,103 @@ func initTools(b *binrep, config string) {
} }
defaultPath := paths[""] defaultPath := paths[""]
b.llvmSymbolizer, b.llvmSymbolizerFound = findExe("llvm-symbolizer", append(paths["llvm-symbolizer"], defaultPath...)) b.llvmSymbolizer, b.llvmSymbolizerFound = chooseExe([]string{"llvm-symbolizer"}, []string{}, append(paths["llvm-symbolizer"], defaultPath...))
b.addr2line, b.addr2lineFound = findExe("addr2line", append(paths["addr2line"], defaultPath...)) b.addr2line, b.addr2lineFound = chooseExe([]string{"addr2line"}, []string{"gaddr2line"}, append(paths["addr2line"], defaultPath...))
if !b.addr2lineFound { // The "-n" option is supported by LLVM since 2011. The output of llvm-nm
// On MacOS, brew installs addr2line under gaddr2line name, so search for // and GNU nm with "-n" option is interchangeable for our purposes, so we do
// that if the tool is not found by its default name. // not need to differrentiate them.
b.addr2line, b.addr2lineFound = findExe("gaddr2line", append(paths["addr2line"], defaultPath...)) b.nm, b.nmFound = chooseExe([]string{"llvm-nm", "nm"}, []string{"gnm"}, append(paths["nm"], defaultPath...))
b.objdump, b.objdumpFound, b.isLLVMObjdump = findObjdump(append(paths["objdump"], defaultPath...))
}
// findObjdump finds and returns path to preferred objdump binary.
// Order of preference is: llvm-objdump, objdump.
// On MacOS only, also looks for gobjdump with least preference.
// Accepts a list of paths and returns:
// a string with path to the preferred objdump binary if found,
// or an empty string if not found;
// a boolean if any acceptable objdump was found;
// a boolean indicating if it is an LLVM objdump.
func findObjdump(paths []string) (string, bool, bool) {
objdumpNames := []string{"llvm-objdump", "objdump"}
if runtime.GOOS == "darwin" {
objdumpNames = append(objdumpNames, "gobjdump")
} }
b.nm, b.nmFound = findExe("nm", append(paths["nm"], defaultPath...))
b.objdump, b.objdumpFound = findExe("objdump", append(paths["objdump"], defaultPath...)) for _, objdumpName := range objdumpNames {
if objdump, objdumpFound := findExe(objdumpName, paths); objdumpFound {
cmdOut, err := exec.Command(objdump, "--version").Output()
if err != nil {
continue
}
if isLLVMObjdump(string(cmdOut)) {
return objdump, true, true
}
if isBuObjdump(string(cmdOut)) {
return objdump, true, false
}
}
}
return "", false, false
}
// chooseExe finds and returns path to preferred binary. names is a list of
// names to search on both Linux and OSX. osxNames is a list of names specific
// to OSX. names always has a higher priority than osxNames. The order of
// the name within each list decides its priority (e.g. the first name has a
// higher priority than the second name in the list).
//
// It returns a string with path to the binary and a boolean indicating if any
// acceptable binary was found.
func chooseExe(names, osxNames []string, paths []string) (string, bool) {
if runtime.GOOS == "darwin" {
names = append(names, osxNames...)
}
for _, name := range names {
if binary, found := findExe(name, paths); found {
return binary, true
}
}
return "", false
}
// isLLVMObjdump accepts a string with path to an objdump binary,
// and returns a boolean indicating if the given binary is an LLVM
// objdump binary of an acceptable version.
func isLLVMObjdump(output string) bool {
fields := objdumpLLVMVerRE.FindStringSubmatch(output)
if len(fields) != 5 {
return false
}
if fields[4] == "trunk" {
return true
}
verMajor, err := strconv.Atoi(fields[1])
if err != nil {
return false
}
verPatch, err := strconv.Atoi(fields[3])
if err != nil {
return false
}
if runtime.GOOS == "linux" && verMajor >= 8 {
// Ensure LLVM objdump is at least version 8.0 on Linux.
// Some flags, like --demangle, and double dashes for options are
// not supported by previous versions.
return true
}
if runtime.GOOS == "darwin" {
// Ensure LLVM objdump is at least version 10.0.1 on MacOS.
return verMajor > 10 || (verMajor == 10 && verPatch >= 1)
}
return false
}
// isBuObjdump accepts a string with path to an objdump binary,
// and returns a boolean indicating if the given binary is a GNU
// binutils objdump binary. No version check is performed.
func isBuObjdump(output string) bool {
return strings.Contains(output, "GNU objdump")
} }
// findExe looks for an executable command on a set of paths. // findExe looks for an executable command on a set of paths.
@ -157,12 +250,25 @@ func findExe(cmd string, paths []string) (string, bool) {
// Disasm returns the assembly instructions for the specified address range // Disasm returns the assembly instructions for the specified address range
// of a binary. // of a binary.
func (bu *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { func (bu *Binutils) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {
b := bu.get() b := bu.get()
cmd := exec.Command(b.objdump, "-d", "-C", "--no-show-raw-insn", "-l", if !b.objdumpFound {
fmt.Sprintf("--start-address=%#x", start), return nil, errors.New("cannot disasm: no objdump tool available")
fmt.Sprintf("--stop-address=%#x", end), }
file) args := []string{"--disassemble-all", "--demangle", "--no-show-raw-insn",
"--line-numbers", fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", end)}
if intelSyntax {
if b.isLLVMObjdump {
args = append(args, "--x86-asm-syntax=intel")
} else {
args = append(args, "-M", "intel")
}
}
args = append(args, file)
cmd := exec.Command(b.objdump, args...)
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("%v: %v", cmd.Args, err) return nil, fmt.Errorf("%v: %v", cmd.Args, err)

View file

@ -27,8 +27,9 @@ import (
var ( var (
nmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+)\s+(.)\s+(.*)`) nmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+)\s+(.)\s+(.*)`)
objdumpAsmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+):\s+(.*)`) objdumpAsmOutputRE = regexp.MustCompile(`^\s*([[:xdigit:]]+):\s+(.*)`)
objdumpOutputFileLine = regexp.MustCompile(`^(.*):([0-9]+)`) objdumpOutputFileLine = regexp.MustCompile(`^;?\s?(.*):([0-9]+)`)
objdumpOutputFunction = regexp.MustCompile(`^(\S.*)\(\):`) objdumpOutputFunction = regexp.MustCompile(`^;?\s?(\S.*)\(\):`)
objdumpOutputFunctionLLVM = regexp.MustCompile(`^([[:xdigit:]]+)?\s?(.*):`)
) )
func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) { func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
@ -143,6 +144,11 @@ func disassemble(asm []byte) ([]plugin.Inst, error) {
if fields := objdumpOutputFunction.FindStringSubmatch(input); len(fields) == 2 { if fields := objdumpOutputFunction.FindStringSubmatch(input); len(fields) == 2 {
function = fields[1] function = fields[1]
continue continue
} else {
if fields := objdumpOutputFunctionLLVM.FindStringSubmatch(input); len(fields) == 3 {
function = fields[2]
continue
}
} }
// Reset on unrecognized lines. // Reset on unrecognized lines.
function, file, line = "", "", 0 function, file, line = "", "", 0

View file

@ -69,8 +69,9 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
flagHTTP := flag.String("http", "", "Present interactive web UI at the specified http host:port") flagHTTP := flag.String("http", "", "Present interactive web UI at the specified http host:port")
flagNoBrowser := flag.Bool("no_browser", false, "Skip opening a browswer for the interactive web UI") flagNoBrowser := flag.Bool("no_browser", false, "Skip opening a browswer for the interactive web UI")
// Flags used during command processing // Flags that set configuration properties.
installedFlags := installFlags(flag) cfg := currentConfig()
configFlagSetter := installConfigFlags(flag, &cfg)
flagCommands := make(map[string]*bool) flagCommands := make(map[string]*bool)
flagParamCommands := make(map[string]*string) flagParamCommands := make(map[string]*string)
@ -107,8 +108,8 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
} }
} }
// Report conflicting options // Apply any specified flags to cfg.
if err := updateFlags(installedFlags); err != nil { if err := configFlagSetter(); err != nil {
return nil, nil, err return nil, nil, err
} }
@ -124,7 +125,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
return nil, nil, errors.New("-no_browser only makes sense with -http") return nil, nil, errors.New("-no_browser only makes sense with -http")
} }
si := pprofVariables["sample_index"].value si := cfg.SampleIndex
si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI) si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI)
si = sampleIndex(flagMeanDelay, si, "delay", "-mean_delay", o.UI) si = sampleIndex(flagMeanDelay, si, "delay", "-mean_delay", o.UI)
si = sampleIndex(flagContentions, si, "contentions", "-contentions", o.UI) si = sampleIndex(flagContentions, si, "contentions", "-contentions", o.UI)
@ -132,10 +133,10 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
si = sampleIndex(flagInUseObjects, si, "inuse_objects", "-inuse_objects", o.UI) si = sampleIndex(flagInUseObjects, si, "inuse_objects", "-inuse_objects", o.UI)
si = sampleIndex(flagAllocSpace, si, "alloc_space", "-alloc_space", o.UI) si = sampleIndex(flagAllocSpace, si, "alloc_space", "-alloc_space", o.UI)
si = sampleIndex(flagAllocObjects, si, "alloc_objects", "-alloc_objects", o.UI) si = sampleIndex(flagAllocObjects, si, "alloc_objects", "-alloc_objects", o.UI)
pprofVariables.set("sample_index", si) cfg.SampleIndex = si
if *flagMeanDelay { if *flagMeanDelay {
pprofVariables.set("mean", "true") cfg.Mean = true
} }
source := &source{ source := &source{
@ -154,7 +155,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
return nil, nil, err return nil, nil, err
} }
normalize := pprofVariables["normalize"].boolValue() normalize := cfg.Normalize
if normalize && len(source.Base) == 0 { if normalize && len(source.Base) == 0 {
return nil, nil, errors.New("must have base profile to normalize by") return nil, nil, errors.New("must have base profile to normalize by")
} }
@ -163,6 +164,8 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
if bu, ok := o.Obj.(*binutils.Binutils); ok { if bu, ok := o.Obj.(*binutils.Binutils); ok {
bu.SetTools(*flagTools) bu.SetTools(*flagTools)
} }
setCurrentConfig(cfg)
return source, cmd, nil return source, cmd, nil
} }
@ -194,66 +197,72 @@ func dropEmpty(list []*string) []string {
return l return l
} }
// installFlags creates command line flags for pprof variables. // installConfigFlags creates command line flags for configuration
func installFlags(flag plugin.FlagSet) flagsInstalled { // fields and returns a function which can be called after flags have
f := flagsInstalled{ // been parsed to copy any flags specified on the command line to
ints: make(map[string]*int), // *cfg.
bools: make(map[string]*bool), func installConfigFlags(flag plugin.FlagSet, cfg *config) func() error {
floats: make(map[string]*float64), // List of functions for setting the different parts of a config.
strings: make(map[string]*string), var setters []func()
} var err error // Holds any errors encountered while running setters.
for n, v := range pprofVariables {
switch v.kind {
case boolKind:
if v.group != "" {
// Set all radio variables to false to identify conflicts.
f.bools[n] = flag.Bool(n, false, v.help)
} else {
f.bools[n] = flag.Bool(n, v.boolValue(), v.help)
}
case intKind:
f.ints[n] = flag.Int(n, v.intValue(), v.help)
case floatKind:
f.floats[n] = flag.Float64(n, v.floatValue(), v.help)
case stringKind:
f.strings[n] = flag.String(n, v.value, v.help)
}
}
return f
}
// updateFlags updates the pprof variables according to the flags for _, field := range configFields {
// parsed in the command line. n := field.name
func updateFlags(f flagsInstalled) error { help := configHelp[n]
vars := pprofVariables var setter func()
groups := map[string]string{} switch ptr := cfg.fieldPtr(field).(type) {
for n, v := range f.bools { case *bool:
vars.set(n, fmt.Sprint(*v)) f := flag.Bool(n, *ptr, help)
setter = func() { *ptr = *f }
case *int:
f := flag.Int(n, *ptr, help)
setter = func() { *ptr = *f }
case *float64:
f := flag.Float64(n, *ptr, help)
setter = func() { *ptr = *f }
case *string:
if len(field.choices) == 0 {
f := flag.String(n, *ptr, help)
setter = func() { *ptr = *f }
} else {
// Make a separate flag per possible choice.
// Set all flags to initially false so we can
// identify conflicts.
bools := make(map[string]*bool)
for _, choice := range field.choices {
bools[choice] = flag.Bool(choice, false, configHelp[choice])
}
setter = func() {
var set []string
for k, v := range bools {
if *v { if *v {
g := vars[n].group set = append(set, k)
if g != "" && groups[g] != "" {
return fmt.Errorf("conflicting options %q and %q set", n, groups[g])
}
groups[g] = n
} }
} }
for n, v := range f.ints { switch len(set) {
vars.set(n, fmt.Sprint(*v)) case 0:
// Leave as default value.
case 1:
*ptr = set[0]
default:
err = fmt.Errorf("conflicting options set: %v", set)
} }
for n, v := range f.floats {
vars.set(n, fmt.Sprint(*v))
} }
for n, v := range f.strings { }
vars.set(n, *v) }
setters = append(setters, setter)
}
return func() error {
// Apply the setter for every flag.
for _, setter := range setters {
setter()
if err != nil {
return err
}
} }
return nil return nil
} }
type flagsInstalled struct {
ints map[string]*int
bools map[string]*bool
floats map[string]*float64
strings map[string]*string
} }
// isBuildID determines if the profile may contain a build ID, by // isBuildID determines if the profile may contain a build ID, by

View file

@ -22,7 +22,6 @@ import (
"os/exec" "os/exec"
"runtime" "runtime"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
@ -70,9 +69,7 @@ func AddCommand(cmd string, format int, post PostProcessor, desc, usage string)
// SetVariableDefault sets the default value for a pprof // SetVariableDefault sets the default value for a pprof
// variable. This enables extensions to set their own defaults. // variable. This enables extensions to set their own defaults.
func SetVariableDefault(variable, value string) { func SetVariableDefault(variable, value string) {
if v := pprofVariables[variable]; v != nil { configure(variable, value)
v.value = value
}
} }
// PostProcessor is a function that applies post-processing to the report output // PostProcessor is a function that applies post-processing to the report output
@ -124,130 +121,132 @@ var pprofCommands = commands{
"weblist": {report.WebList, nil, invokeVisualizer("html", browsers()), true, "Display annotated source in a web browser", listHelp("weblist", false)}, "weblist": {report.WebList, nil, invokeVisualizer("html", browsers()), true, "Display annotated source in a web browser", listHelp("weblist", false)},
} }
// pprofVariables are the configuration parameters that affect the // configHelp contains help text per configuration parameter.
// reported generated by pprof. var configHelp = map[string]string{
var pprofVariables = variables{
// Filename for file-based output formats, stdout by default. // Filename for file-based output formats, stdout by default.
"output": &variable{stringKind, "", "", helpText("Output filename for file-based outputs")}, "output": helpText("Output filename for file-based outputs"),
// Comparisons. // Comparisons.
"drop_negative": &variable{boolKind, "f", "", helpText( "drop_negative": helpText(
"Ignore negative differences", "Ignore negative differences",
"Do not show any locations with values <0.")}, "Do not show any locations with values <0."),
// Graph handling options. // Graph handling options.
"call_tree": &variable{boolKind, "f", "", helpText( "call_tree": helpText(
"Create a context-sensitive call tree", "Create a context-sensitive call tree",
"Treat locations reached through different paths as separate.")}, "Treat locations reached through different paths as separate."),
// Display options. // Display options.
"relative_percentages": &variable{boolKind, "f", "", helpText( "relative_percentages": helpText(
"Show percentages relative to focused subgraph", "Show percentages relative to focused subgraph",
"If unset, percentages are relative to full graph before focusing", "If unset, percentages are relative to full graph before focusing",
"to facilitate comparison with original graph.")}, "to facilitate comparison with original graph."),
"unit": &variable{stringKind, "minimum", "", helpText( "unit": helpText(
"Measurement units to display", "Measurement units to display",
"Scale the sample values to this unit.", "Scale the sample values to this unit.",
"For time-based profiles, use seconds, milliseconds, nanoseconds, etc.", "For time-based profiles, use seconds, milliseconds, nanoseconds, etc.",
"For memory profiles, use megabytes, kilobytes, bytes, etc.", "For memory profiles, use megabytes, kilobytes, bytes, etc.",
"Using auto will scale each value independently to the most natural unit.")}, "Using auto will scale each value independently to the most natural unit."),
"compact_labels": &variable{boolKind, "f", "", "Show minimal headers"}, "compact_labels": "Show minimal headers",
"source_path": &variable{stringKind, "", "", "Search path for source files"}, "source_path": "Search path for source files",
"trim_path": &variable{stringKind, "", "", "Path to trim from source paths before search"}, "trim_path": "Path to trim from source paths before search",
"intel_syntax": helpText(
"Show assembly in Intel syntax",
"Only applicable to commands `disasm` and `weblist`"),
// Filtering options // Filtering options
"nodecount": &variable{intKind, "-1", "", helpText( "nodecount": helpText(
"Max number of nodes to show", "Max number of nodes to show",
"Uses heuristics to limit the number of locations to be displayed.", "Uses heuristics to limit the number of locations to be displayed.",
"On graphs, dotted edges represent paths through nodes that have been removed.")}, "On graphs, dotted edges represent paths through nodes that have been removed."),
"nodefraction": &variable{floatKind, "0.005", "", "Hide nodes below <f>*total"}, "nodefraction": "Hide nodes below <f>*total",
"edgefraction": &variable{floatKind, "0.001", "", "Hide edges below <f>*total"}, "edgefraction": "Hide edges below <f>*total",
"trim": &variable{boolKind, "t", "", helpText( "trim": helpText(
"Honor nodefraction/edgefraction/nodecount defaults", "Honor nodefraction/edgefraction/nodecount defaults",
"Set to false to get the full profile, without any trimming.")}, "Set to false to get the full profile, without any trimming."),
"focus": &variable{stringKind, "", "", helpText( "focus": helpText(
"Restricts to samples going through a node matching regexp", "Restricts to samples going through a node matching regexp",
"Discard samples that do not include a node matching this regexp.", "Discard samples that do not include a node matching this regexp.",
"Matching includes the function name, filename or object name.")}, "Matching includes the function name, filename or object name."),
"ignore": &variable{stringKind, "", "", helpText( "ignore": helpText(
"Skips paths going through any nodes matching regexp", "Skips paths going through any nodes matching regexp",
"If set, discard samples that include a node matching this regexp.", "If set, discard samples that include a node matching this regexp.",
"Matching includes the function name, filename or object name.")}, "Matching includes the function name, filename or object name."),
"prune_from": &variable{stringKind, "", "", helpText( "prune_from": helpText(
"Drops any functions below the matched frame.", "Drops any functions below the matched frame.",
"If set, any frames matching the specified regexp and any frames", "If set, any frames matching the specified regexp and any frames",
"below it will be dropped from each sample.")}, "below it will be dropped from each sample."),
"hide": &variable{stringKind, "", "", helpText( "hide": helpText(
"Skips nodes matching regexp", "Skips nodes matching regexp",
"Discard nodes that match this location.", "Discard nodes that match this location.",
"Other nodes from samples that include this location will be shown.", "Other nodes from samples that include this location will be shown.",
"Matching includes the function name, filename or object name.")}, "Matching includes the function name, filename or object name."),
"show": &variable{stringKind, "", "", helpText( "show": helpText(
"Only show nodes matching regexp", "Only show nodes matching regexp",
"If set, only show nodes that match this location.", "If set, only show nodes that match this location.",
"Matching includes the function name, filename or object name.")}, "Matching includes the function name, filename or object name."),
"show_from": &variable{stringKind, "", "", helpText( "show_from": helpText(
"Drops functions above the highest matched frame.", "Drops functions above the highest matched frame.",
"If set, all frames above the highest match are dropped from every sample.", "If set, all frames above the highest match are dropped from every sample.",
"Matching includes the function name, filename or object name.")}, "Matching includes the function name, filename or object name."),
"tagfocus": &variable{stringKind, "", "", helpText( "tagfocus": helpText(
"Restricts to samples with tags in range or matched by regexp", "Restricts to samples with tags in range or matched by regexp",
"Use name=value syntax to limit the matching to a specific tag.", "Use name=value syntax to limit the matching to a specific tag.",
"Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:", "Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:",
"String tag filter examples: foo, foo.*bar, mytag=foo.*bar")}, "String tag filter examples: foo, foo.*bar, mytag=foo.*bar"),
"tagignore": &variable{stringKind, "", "", helpText( "tagignore": helpText(
"Discard samples with tags in range or matched by regexp", "Discard samples with tags in range or matched by regexp",
"Use name=value syntax to limit the matching to a specific tag.", "Use name=value syntax to limit the matching to a specific tag.",
"Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:", "Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:",
"String tag filter examples: foo, foo.*bar, mytag=foo.*bar")}, "String tag filter examples: foo, foo.*bar, mytag=foo.*bar"),
"tagshow": &variable{stringKind, "", "", helpText( "tagshow": helpText(
"Only consider tags matching this regexp", "Only consider tags matching this regexp",
"Discard tags that do not match this regexp")}, "Discard tags that do not match this regexp"),
"taghide": &variable{stringKind, "", "", helpText( "taghide": helpText(
"Skip tags matching this regexp", "Skip tags matching this regexp",
"Discard tags that match this regexp")}, "Discard tags that match this regexp"),
// Heap profile options // Heap profile options
"divide_by": &variable{floatKind, "1", "", helpText( "divide_by": helpText(
"Ratio to divide all samples before visualization", "Ratio to divide all samples before visualization",
"Divide all samples values by a constant, eg the number of processors or jobs.")}, "Divide all samples values by a constant, eg the number of processors or jobs."),
"mean": &variable{boolKind, "f", "", helpText( "mean": helpText(
"Average sample value over first value (count)", "Average sample value over first value (count)",
"For memory profiles, report average memory per allocation.", "For memory profiles, report average memory per allocation.",
"For time-based profiles, report average time per event.")}, "For time-based profiles, report average time per event."),
"sample_index": &variable{stringKind, "", "", helpText( "sample_index": helpText(
"Sample value to report (0-based index or name)", "Sample value to report (0-based index or name)",
"Profiles contain multiple values per sample.", "Profiles contain multiple values per sample.",
"Use sample_index=i to select the ith value (starting at 0).")}, "Use sample_index=i to select the ith value (starting at 0)."),
"normalize": &variable{boolKind, "f", "", helpText( "normalize": helpText(
"Scales profile based on the base profile.")}, "Scales profile based on the base profile."),
// Data sorting criteria // Data sorting criteria
"flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")}, "flat": helpText("Sort entries based on own weight"),
"cum": &variable{boolKind, "f", "cumulative", helpText("Sort entries based on cumulative weight")}, "cum": helpText("Sort entries based on cumulative weight"),
// Output granularity // Output granularity
"functions": &variable{boolKind, "t", "granularity", helpText( "functions": helpText(
"Aggregate at the function level.", "Aggregate at the function level.",
"Ignores the filename where the function was defined.")}, "Ignores the filename where the function was defined."),
"filefunctions": &variable{boolKind, "t", "granularity", helpText( "filefunctions": helpText(
"Aggregate at the function level.", "Aggregate at the function level.",
"Takes into account the filename where the function was defined.")}, "Takes into account the filename where the function was defined."),
"files": &variable{boolKind, "f", "granularity", "Aggregate at the file level."}, "files": "Aggregate at the file level.",
"lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."}, "lines": "Aggregate at the source code line level.",
"addresses": &variable{boolKind, "f", "granularity", helpText( "addresses": helpText(
"Aggregate at the address level.", "Aggregate at the address level.",
"Includes functions' addresses in the output.")}, "Includes functions' addresses in the output."),
"noinlines": &variable{boolKind, "f", "", helpText( "noinlines": helpText(
"Ignore inlines.", "Ignore inlines.",
"Attributes inlined functions to their first out-of-line caller.")}, "Attributes inlined functions to their first out-of-line caller."),
} }
func helpText(s ...string) string { func helpText(s ...string) string {
return strings.Join(s, "\n") + "\n" return strings.Join(s, "\n") + "\n"
} }
// usage returns a string describing the pprof commands and variables. // usage returns a string describing the pprof commands and configuration
// if commandLine is set, the output reflect cli usage. // options. if commandLine is set, the output reflect cli usage.
func usage(commandLine bool) string { func usage(commandLine bool) string {
var prefix string var prefix string
if commandLine { if commandLine {
@ -269,40 +268,33 @@ func usage(commandLine bool) string {
} else { } else {
help = " Commands:\n" help = " Commands:\n"
commands = append(commands, fmtHelp("o/options", "List options and their current values")) commands = append(commands, fmtHelp("o/options", "List options and their current values"))
commands = append(commands, fmtHelp("quit/exit/^D", "Exit pprof")) commands = append(commands, fmtHelp("q/quit/exit/^D", "Exit pprof"))
} }
help = help + strings.Join(commands, "\n") + "\n\n" + help = help + strings.Join(commands, "\n") + "\n\n" +
" Options:\n" " Options:\n"
// Print help for variables after sorting them. // Print help for configuration options after sorting them.
// Collect radio variables by their group name to print them together. // Collect choices for multi-choice options print them together.
radioOptions := make(map[string][]string)
var variables []string var variables []string
for name, vr := range pprofVariables { var radioStrings []string
if vr.group != "" { for _, f := range configFields {
radioOptions[vr.group] = append(radioOptions[vr.group], name) if len(f.choices) == 0 {
variables = append(variables, fmtHelp(prefix+f.name, configHelp[f.name]))
continue continue
} }
variables = append(variables, fmtHelp(prefix+name, vr.help)) // Format help for for this group.
s := []string{fmtHelp(f.name, "")}
for _, choice := range f.choices {
s = append(s, " "+fmtHelp(prefix+choice, configHelp[choice]))
} }
sort.Strings(variables)
help = help + strings.Join(variables, "\n") + "\n\n" +
" Option groups (only set one per group):\n"
var radioStrings []string
for radio, ops := range radioOptions {
sort.Strings(ops)
s := []string{fmtHelp(radio, "")}
for _, op := range ops {
s = append(s, " "+fmtHelp(prefix+op, pprofVariables[op].help))
}
radioStrings = append(radioStrings, strings.Join(s, "\n")) radioStrings = append(radioStrings, strings.Join(s, "\n"))
} }
sort.Strings(variables)
sort.Strings(radioStrings) sort.Strings(radioStrings)
return help + strings.Join(radioStrings, "\n") return help + strings.Join(variables, "\n") + "\n\n" +
" Option groups (only set one per group):\n" +
strings.Join(radioStrings, "\n")
} }
func reportHelp(c string, cum, redirect bool) string { func reportHelp(c string, cum, redirect bool) string {
@ -445,105 +437,8 @@ func invokeVisualizer(suffix string, visualizers []string) PostProcessor {
} }
} }
// variables describe the configuration parameters recognized by pprof. // stringToBool is a custom parser for bools. We avoid using strconv.ParseBool
type variables map[string]*variable // to remain compatible with old pprof behavior (e.g., treating "" as true).
// variable is a single configuration parameter.
type variable struct {
kind int // How to interpret the value, must be one of the enums below.
value string // Effective value. Only values appropriate for the Kind should be set.
group string // boolKind variables with the same Group != "" cannot be set simultaneously.
help string // Text describing the variable, in multiple lines separated by newline.
}
const (
// variable.kind must be one of these variables.
boolKind = iota
intKind
floatKind
stringKind
)
// set updates the value of a variable, checking that the value is
// suitable for the variable Kind.
func (vars variables) set(name, value string) error {
v := vars[name]
if v == nil {
return fmt.Errorf("no variable %s", name)
}
var err error
switch v.kind {
case boolKind:
var b bool
if b, err = stringToBool(value); err == nil {
if v.group != "" && !b {
err = fmt.Errorf("%q can only be set to true", name)
}
}
case intKind:
_, err = strconv.Atoi(value)
case floatKind:
_, err = strconv.ParseFloat(value, 64)
case stringKind:
// Remove quotes, particularly useful for empty values.
if len(value) > 1 && strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
value = value[1 : len(value)-1]
}
}
if err != nil {
return err
}
vars[name].value = value
if group := vars[name].group; group != "" {
for vname, vvar := range vars {
if vvar.group == group && vname != name {
vvar.value = "f"
}
}
}
return err
}
// boolValue returns the value of a boolean variable.
func (v *variable) boolValue() bool {
b, err := stringToBool(v.value)
if err != nil {
panic("unexpected value " + v.value + " for bool ")
}
return b
}
// intValue returns the value of an intKind variable.
func (v *variable) intValue() int {
i, err := strconv.Atoi(v.value)
if err != nil {
panic("unexpected value " + v.value + " for int ")
}
return i
}
// floatValue returns the value of a Float variable.
func (v *variable) floatValue() float64 {
f, err := strconv.ParseFloat(v.value, 64)
if err != nil {
panic("unexpected value " + v.value + " for float ")
}
return f
}
// stringValue returns a canonical representation for a variable.
func (v *variable) stringValue() string {
switch v.kind {
case boolKind:
return fmt.Sprint(v.boolValue())
case intKind:
return fmt.Sprint(v.intValue())
case floatKind:
return fmt.Sprint(v.floatValue())
}
return v.value
}
func stringToBool(s string) (bool, error) { func stringToBool(s string) (bool, error) {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case "true", "t", "yes", "y", "1", "": case "true", "t", "yes", "y", "1", "":
@ -554,13 +449,3 @@ func stringToBool(s string) (bool, error) {
return false, fmt.Errorf(`illegal value "%s" for bool variable`, s) return false, fmt.Errorf(`illegal value "%s" for bool variable`, s)
} }
} }
// makeCopy returns a duplicate of a set of shell variables.
func (vars variables) makeCopy() variables {
varscopy := make(variables, len(vars))
for n, v := range vars {
vcopy := *v
varscopy[n] = &vcopy
}
return varscopy
}

View file

@ -0,0 +1,367 @@
package driver
import (
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"sync"
)
// config holds settings for a single named config.
// The JSON tag name for a field is used both for JSON encoding and as
// a named variable.
type config struct {
// Filename for file-based output formats, stdout by default.
Output string `json:"-"`
// Display options.
CallTree bool `json:"call_tree,omitempty"`
RelativePercentages bool `json:"relative_percentages,omitempty"`
Unit string `json:"unit,omitempty"`
CompactLabels bool `json:"compact_labels,omitempty"`
SourcePath string `json:"-"`
TrimPath string `json:"-"`
IntelSyntax bool `json:"intel_syntax,omitempty"`
Mean bool `json:"mean,omitempty"`
SampleIndex string `json:"-"`
DivideBy float64 `json:"-"`
Normalize bool `json:"normalize,omitempty"`
Sort string `json:"sort,omitempty"`
// Filtering options
DropNegative bool `json:"drop_negative,omitempty"`
NodeCount int `json:"nodecount,omitempty"`
NodeFraction float64 `json:"nodefraction,omitempty"`
EdgeFraction float64 `json:"edgefraction,omitempty"`
Trim bool `json:"trim,omitempty"`
Focus string `json:"focus,omitempty"`
Ignore string `json:"ignore,omitempty"`
PruneFrom string `json:"prune_from,omitempty"`
Hide string `json:"hide,omitempty"`
Show string `json:"show,omitempty"`
ShowFrom string `json:"show_from,omitempty"`
TagFocus string `json:"tagfocus,omitempty"`
TagIgnore string `json:"tagignore,omitempty"`
TagShow string `json:"tagshow,omitempty"`
TagHide string `json:"taghide,omitempty"`
NoInlines bool `json:"noinlines,omitempty"`
// Output granularity
Granularity string `json:"granularity,omitempty"`
}
// defaultConfig returns the default configuration values; it is unaffected by
// flags and interactive assignments.
func defaultConfig() config {
return config{
Unit: "minimum",
NodeCount: -1,
NodeFraction: 0.005,
EdgeFraction: 0.001,
Trim: true,
DivideBy: 1.0,
Sort: "flat",
Granularity: "functions",
}
}
// currentConfig holds the current configuration values; it is affected by
// flags and interactive assignments.
var currentCfg = defaultConfig()
var currentMu sync.Mutex
func currentConfig() config {
currentMu.Lock()
defer currentMu.Unlock()
return currentCfg
}
func setCurrentConfig(cfg config) {
currentMu.Lock()
defer currentMu.Unlock()
currentCfg = cfg
}
// configField contains metadata for a single configuration field.
type configField struct {
name string // JSON field name/key in variables
urlparam string // URL parameter name
saved bool // Is field saved in settings?
field reflect.StructField // Field in config
choices []string // Name Of variables in group
defaultValue string // Default value for this field.
}
var (
configFields []configField // Precomputed metadata per config field
// configFieldMap holds an entry for every config field as well as an
// entry for every valid choice for a multi-choice field.
configFieldMap map[string]configField
)
func init() {
// Config names for fields that are not saved in settings and therefore
// do not have a JSON name.
notSaved := map[string]string{
// Not saved in settings, but present in URLs.
"SampleIndex": "sample_index",
// Following fields are also not placed in URLs.
"Output": "output",
"SourcePath": "source_path",
"TrimPath": "trim_path",
"DivideBy": "divide_by",
}
// choices holds the list of allowed values for config fields that can
// take on one of a bounded set of values.
choices := map[string][]string{
"sort": {"cum", "flat"},
"granularity": {"functions", "filefunctions", "files", "lines", "addresses"},
}
// urlparam holds the mapping from a config field name to the URL
// parameter used to hold that config field. If no entry is present for
// a name, the corresponding field is not saved in URLs.
urlparam := map[string]string{
"drop_negative": "dropneg",
"call_tree": "calltree",
"relative_percentages": "rel",
"unit": "unit",
"compact_labels": "compact",
"intel_syntax": "intel",
"nodecount": "n",
"nodefraction": "nf",
"edgefraction": "ef",
"trim": "trim",
"focus": "f",
"ignore": "i",
"prune_from": "prunefrom",
"hide": "h",
"show": "s",
"show_from": "sf",
"tagfocus": "tf",
"tagignore": "ti",
"tagshow": "ts",
"taghide": "th",
"mean": "mean",
"sample_index": "si",
"normalize": "norm",
"sort": "sort",
"granularity": "g",
"noinlines": "noinlines",
}
def := defaultConfig()
configFieldMap = map[string]configField{}
t := reflect.TypeOf(config{})
for i, n := 0, t.NumField(); i < n; i++ {
field := t.Field(i)
js := strings.Split(field.Tag.Get("json"), ",")
if len(js) == 0 {
continue
}
// Get the configuration name for this field.
name := js[0]
if name == "-" {
name = notSaved[field.Name]
if name == "" {
// Not a configurable field.
continue
}
}
f := configField{
name: name,
urlparam: urlparam[name],
saved: (name == js[0]),
field: field,
choices: choices[name],
}
f.defaultValue = def.get(f)
configFields = append(configFields, f)
configFieldMap[f.name] = f
for _, choice := range f.choices {
configFieldMap[choice] = f
}
}
}
// fieldPtr returns a pointer to the field identified by f in *cfg.
func (cfg *config) fieldPtr(f configField) interface{} {
// reflect.ValueOf: converts to reflect.Value
// Elem: dereferences cfg to make *cfg
// FieldByIndex: fetches the field
// Addr: takes address of field
// Interface: converts back from reflect.Value to a regular value
return reflect.ValueOf(cfg).Elem().FieldByIndex(f.field.Index).Addr().Interface()
}
// get returns the value of field f in cfg.
func (cfg *config) get(f configField) string {
switch ptr := cfg.fieldPtr(f).(type) {
case *string:
return *ptr
case *int:
return fmt.Sprint(*ptr)
case *float64:
return fmt.Sprint(*ptr)
case *bool:
return fmt.Sprint(*ptr)
}
panic(fmt.Sprintf("unsupported config field type %v", f.field.Type))
}
// set sets the value of field f in cfg to value.
func (cfg *config) set(f configField, value string) error {
switch ptr := cfg.fieldPtr(f).(type) {
case *string:
if len(f.choices) > 0 {
// Verify that value is one of the allowed choices.
for _, choice := range f.choices {
if choice == value {
*ptr = value
return nil
}
}
return fmt.Errorf("invalid %q value %q", f.name, value)
}
*ptr = value
case *int:
v, err := strconv.Atoi(value)
if err != nil {
return err
}
*ptr = v
case *float64:
v, err := strconv.ParseFloat(value, 64)
if err != nil {
return err
}
*ptr = v
case *bool:
v, err := stringToBool(value)
if err != nil {
return err
}
*ptr = v
default:
panic(fmt.Sprintf("unsupported config field type %v", f.field.Type))
}
return nil
}
// isConfigurable returns true if name is either the name of a config field, or
// a valid value for a multi-choice config field.
func isConfigurable(name string) bool {
_, ok := configFieldMap[name]
return ok
}
// isBoolConfig returns true if name is either name of a boolean config field,
// or a valid value for a multi-choice config field.
func isBoolConfig(name string) bool {
f, ok := configFieldMap[name]
if !ok {
return false
}
if name != f.name {
return true // name must be one possible value for the field
}
var cfg config
_, ok = cfg.fieldPtr(f).(*bool)
return ok
}
// completeConfig returns the list of configurable names starting with prefix.
func completeConfig(prefix string) []string {
var result []string
for v := range configFieldMap {
if strings.HasPrefix(v, prefix) {
result = append(result, v)
}
}
return result
}
// configure stores the name=value mapping into the current config, correctly
// handling the case when name identifies a particular choice in a field.
func configure(name, value string) error {
currentMu.Lock()
defer currentMu.Unlock()
f, ok := configFieldMap[name]
if !ok {
return fmt.Errorf("unknown config field %q", name)
}
if f.name == name {
return currentCfg.set(f, value)
}
// name must be one of the choices. If value is true, set field-value
// to name.
if v, err := strconv.ParseBool(value); v && err == nil {
return currentCfg.set(f, name)
}
return fmt.Errorf("unknown config field %q", name)
}
// resetTransient sets all transient fields in *cfg to their currently
// configured values.
func (cfg *config) resetTransient() {
current := currentConfig()
cfg.Output = current.Output
cfg.SourcePath = current.SourcePath
cfg.TrimPath = current.TrimPath
cfg.DivideBy = current.DivideBy
cfg.SampleIndex = current.SampleIndex
}
// applyURL updates *cfg based on params.
func (cfg *config) applyURL(params url.Values) error {
for _, f := range configFields {
var value string
if f.urlparam != "" {
value = params.Get(f.urlparam)
}
if value == "" {
continue
}
if err := cfg.set(f, value); err != nil {
return fmt.Errorf("error setting config field %s: %v", f.name, err)
}
}
return nil
}
// makeURL returns a URL based on initialURL that contains the config contents
// as parameters. The second result is true iff a parameter value was changed.
func (cfg *config) makeURL(initialURL url.URL) (url.URL, bool) {
q := initialURL.Query()
changed := false
for _, f := range configFields {
if f.urlparam == "" || !f.saved {
continue
}
v := cfg.get(f)
if v == f.defaultValue {
v = "" // URL for of default value is the empty string.
} else if f.field.Type.Kind() == reflect.Bool {
// Shorten bool values to "f" or "t"
v = v[:1]
}
if q.Get(f.urlparam) == v {
continue
}
changed = true
if v == "" {
q.Del(f.urlparam)
} else {
q.Set(f.urlparam, v)
}
}
if changed {
initialURL.RawQuery = q.Encode()
}
return initialURL, changed
}

View file

@ -50,7 +50,7 @@ func PProf(eo *plugin.Options) error {
} }
if cmd != nil { if cmd != nil {
return generateReport(p, cmd, pprofVariables, o) return generateReport(p, cmd, currentConfig(), o)
} }
if src.HTTPHostport != "" { if src.HTTPHostport != "" {
@ -59,7 +59,7 @@ func PProf(eo *plugin.Options) error {
return interactive(p, o) return interactive(p, o)
} }
func generateRawReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) (*command, *report.Report, error) { func generateRawReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) (*command, *report.Report, error) {
p = p.Copy() // Prevent modification to the incoming profile. p = p.Copy() // Prevent modification to the incoming profile.
// Identify units of numeric tags in profile. // Identify units of numeric tags in profile.
@ -71,16 +71,16 @@ func generateRawReport(p *profile.Profile, cmd []string, vars variables, o *plug
panic("unexpected nil command") panic("unexpected nil command")
} }
vars = applyCommandOverrides(cmd[0], c.format, vars) cfg = applyCommandOverrides(cmd[0], c.format, cfg)
// Delay focus after configuring report to get percentages on all samples. // Delay focus after configuring report to get percentages on all samples.
relative := vars["relative_percentages"].boolValue() relative := cfg.RelativePercentages
if relative { if relative {
if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil { if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
return nil, nil, err return nil, nil, err
} }
} }
ropt, err := reportOptions(p, numLabelUnits, vars) ropt, err := reportOptions(p, numLabelUnits, cfg)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -95,19 +95,19 @@ func generateRawReport(p *profile.Profile, cmd []string, vars variables, o *plug
rpt := report.New(p, ropt) rpt := report.New(p, ropt)
if !relative { if !relative {
if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil { if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
return nil, nil, err return nil, nil, err
} }
} }
if err := aggregate(p, vars); err != nil { if err := aggregate(p, cfg); err != nil {
return nil, nil, err return nil, nil, err
} }
return c, rpt, nil return c, rpt, nil
} }
func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error { func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {
c, rpt, err := generateRawReport(p, cmd, vars, o) c, rpt, err := generateRawReport(p, cmd, cfg, o)
if err != nil { if err != nil {
return err return err
} }
@ -129,7 +129,7 @@ func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.
} }
// If no output is specified, use default visualizer. // If no output is specified, use default visualizer.
output := vars["output"].value output := cfg.Output
if output == "" { if output == "" {
if c.visualizer != nil { if c.visualizer != nil {
return c.visualizer(src, os.Stdout, o.UI) return c.visualizer(src, os.Stdout, o.UI)
@ -151,7 +151,7 @@ func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.
return out.Close() return out.Close()
} }
func applyCommandOverrides(cmd string, outputFormat int, v variables) variables { func applyCommandOverrides(cmd string, outputFormat int, cfg config) config {
// Some report types override the trim flag to false below. This is to make // Some report types override the trim flag to false below. This is to make
// sure the default heuristics of excluding insignificant nodes and edges // sure the default heuristics of excluding insignificant nodes and edges
// from the call graph do not apply. One example where it is important is // from the call graph do not apply. One example where it is important is
@ -160,55 +160,55 @@ func applyCommandOverrides(cmd string, outputFormat int, v variables) variables
// data is selected. So, with trimming enabled, the report could end up // data is selected. So, with trimming enabled, the report could end up
// showing no data if the specified function is "uninteresting" as far as the // showing no data if the specified function is "uninteresting" as far as the
// trimming is concerned. // trimming is concerned.
trim := v["trim"].boolValue() trim := cfg.Trim
switch cmd { switch cmd {
case "disasm", "weblist": case "disasm", "weblist":
trim = false trim = false
v.set("addresses", "t") cfg.Granularity = "addresses"
// Force the 'noinlines' mode so that source locations for a given address // Force the 'noinlines' mode so that source locations for a given address
// collapse and there is only one for the given address. Without this // collapse and there is only one for the given address. Without this
// cumulative metrics would be double-counted when annotating the assembly. // cumulative metrics would be double-counted when annotating the assembly.
// This is because the merge is done by address and in case of an inlined // This is because the merge is done by address and in case of an inlined
// stack each of the inlined entries is a separate callgraph node. // stack each of the inlined entries is a separate callgraph node.
v.set("noinlines", "t") cfg.NoInlines = true
case "peek": case "peek":
trim = false trim = false
case "list": case "list":
trim = false trim = false
v.set("lines", "t") cfg.Granularity = "lines"
// Do not force 'noinlines' to be false so that specifying // Do not force 'noinlines' to be false so that specifying
// "-list foo -noinlines" is supported and works as expected. // "-list foo -noinlines" is supported and works as expected.
case "text", "top", "topproto": case "text", "top", "topproto":
if v["nodecount"].intValue() == -1 { if cfg.NodeCount == -1 {
v.set("nodecount", "0") cfg.NodeCount = 0
} }
default: default:
if v["nodecount"].intValue() == -1 { if cfg.NodeCount == -1 {
v.set("nodecount", "80") cfg.NodeCount = 80
} }
} }
switch outputFormat { switch outputFormat {
case report.Proto, report.Raw, report.Callgrind: case report.Proto, report.Raw, report.Callgrind:
trim = false trim = false
v.set("addresses", "t") cfg.Granularity = "addresses"
v.set("noinlines", "f") cfg.NoInlines = false
} }
if !trim { if !trim {
v.set("nodecount", "0") cfg.NodeCount = 0
v.set("nodefraction", "0") cfg.NodeFraction = 0
v.set("edgefraction", "0") cfg.EdgeFraction = 0
} }
return v return cfg
} }
func aggregate(prof *profile.Profile, v variables) error { func aggregate(prof *profile.Profile, cfg config) error {
var function, filename, linenumber, address bool var function, filename, linenumber, address bool
inlines := !v["noinlines"].boolValue() inlines := !cfg.NoInlines
switch { switch cfg.Granularity {
case v["addresses"].boolValue(): case "addresses":
if inlines { if inlines {
return nil return nil
} }
@ -216,15 +216,15 @@ func aggregate(prof *profile.Profile, v variables) error {
filename = true filename = true
linenumber = true linenumber = true
address = true address = true
case v["lines"].boolValue(): case "lines":
function = true function = true
filename = true filename = true
linenumber = true linenumber = true
case v["files"].boolValue(): case "files":
filename = true filename = true
case v["functions"].boolValue(): case "functions":
function = true function = true
case v["filefunctions"].boolValue(): case "filefunctions":
function = true function = true
filename = true filename = true
default: default:
@ -233,8 +233,8 @@ func aggregate(prof *profile.Profile, v variables) error {
return prof.Aggregate(inlines, function, filename, linenumber, address) return prof.Aggregate(inlines, function, filename, linenumber, address)
} }
func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars variables) (*report.Options, error) { func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
si, mean := vars["sample_index"].value, vars["mean"].boolValue() si, mean := cfg.SampleIndex, cfg.Mean
value, meanDiv, sample, err := sampleFormat(p, si, mean) value, meanDiv, sample, err := sampleFormat(p, si, mean)
if err != nil { if err != nil {
return nil, err return nil, err
@ -245,29 +245,37 @@ func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars var
stype = "mean_" + stype stype = "mean_" + stype
} }
if vars["divide_by"].floatValue() == 0 { if cfg.DivideBy == 0 {
return nil, fmt.Errorf("zero divisor specified") return nil, fmt.Errorf("zero divisor specified")
} }
var filters []string var filters []string
for _, k := range []string{"focus", "ignore", "hide", "show", "show_from", "tagfocus", "tagignore", "tagshow", "taghide"} { addFilter := func(k string, v string) {
v := vars[k].value
if v != "" { if v != "" {
filters = append(filters, k+"="+v) filters = append(filters, k+"="+v)
} }
} }
addFilter("focus", cfg.Focus)
addFilter("ignore", cfg.Ignore)
addFilter("hide", cfg.Hide)
addFilter("show", cfg.Show)
addFilter("show_from", cfg.ShowFrom)
addFilter("tagfocus", cfg.TagFocus)
addFilter("tagignore", cfg.TagIgnore)
addFilter("tagshow", cfg.TagShow)
addFilter("taghide", cfg.TagHide)
ropt := &report.Options{ ropt := &report.Options{
CumSort: vars["cum"].boolValue(), CumSort: cfg.Sort == "cum",
CallTree: vars["call_tree"].boolValue(), CallTree: cfg.CallTree,
DropNegative: vars["drop_negative"].boolValue(), DropNegative: cfg.DropNegative,
CompactLabels: vars["compact_labels"].boolValue(), CompactLabels: cfg.CompactLabels,
Ratio: 1 / vars["divide_by"].floatValue(), Ratio: 1 / cfg.DivideBy,
NodeCount: vars["nodecount"].intValue(), NodeCount: cfg.NodeCount,
NodeFraction: vars["nodefraction"].floatValue(), NodeFraction: cfg.NodeFraction,
EdgeFraction: vars["edgefraction"].floatValue(), EdgeFraction: cfg.EdgeFraction,
ActiveFilters: filters, ActiveFilters: filters,
NumLabelUnits: numLabelUnits, NumLabelUnits: numLabelUnits,
@ -277,10 +285,12 @@ func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars var
SampleType: stype, SampleType: stype,
SampleUnit: sample.Unit, SampleUnit: sample.Unit,
OutputUnit: vars["unit"].value, OutputUnit: cfg.Unit,
SourcePath: vars["source_path"].stringValue(), SourcePath: cfg.SourcePath,
TrimPath: vars["trim_path"].stringValue(), TrimPath: cfg.TrimPath,
IntelSyntax: cfg.IntelSyntax,
} }
if len(p.Mapping) > 0 && p.Mapping[0].File != "" { if len(p.Mapping) > 0 && p.Mapping[0].File != "" {

View file

@ -28,15 +28,15 @@ import (
var tagFilterRangeRx = regexp.MustCompile("([+-]?[[:digit:]]+)([[:alpha:]]+)?") var tagFilterRangeRx = regexp.MustCompile("([+-]?[[:digit:]]+)([[:alpha:]]+)?")
// applyFocus filters samples based on the focus/ignore options // applyFocus filters samples based on the focus/ignore options
func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variables, ui plugin.UI) error { func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, cfg config, ui plugin.UI) error {
focus, err := compileRegexOption("focus", v["focus"].value, nil) focus, err := compileRegexOption("focus", cfg.Focus, nil)
ignore, err := compileRegexOption("ignore", v["ignore"].value, err) ignore, err := compileRegexOption("ignore", cfg.Ignore, err)
hide, err := compileRegexOption("hide", v["hide"].value, err) hide, err := compileRegexOption("hide", cfg.Hide, err)
show, err := compileRegexOption("show", v["show"].value, err) show, err := compileRegexOption("show", cfg.Show, err)
showfrom, err := compileRegexOption("show_from", v["show_from"].value, err) showfrom, err := compileRegexOption("show_from", cfg.ShowFrom, err)
tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, numLabelUnits, ui, err) tagfocus, err := compileTagFilter("tagfocus", cfg.TagFocus, numLabelUnits, ui, err)
tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, numLabelUnits, ui, err) tagignore, err := compileTagFilter("tagignore", cfg.TagIgnore, numLabelUnits, ui, err)
prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err) prunefrom, err := compileRegexOption("prune_from", cfg.PruneFrom, err)
if err != nil { if err != nil {
return err return err
} }
@ -54,8 +54,8 @@ func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variab
warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui) warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui)
warnNoMatches(tagignore == nil || tim, "TagIgnore", ui) warnNoMatches(tagignore == nil || tim, "TagIgnore", ui)
tagshow, err := compileRegexOption("tagshow", v["tagshow"].value, err) tagshow, err := compileRegexOption("tagshow", cfg.TagShow, err)
taghide, err := compileRegexOption("taghide", v["taghide"].value, err) taghide, err := compileRegexOption("taghide", cfg.TagHide, err)
tns, tnh := prof.FilterTagsByName(tagshow, taghide) tns, tnh := prof.FilterTagsByName(tagshow, taghide)
warnNoMatches(tagshow == nil || tns, "TagShow", ui) warnNoMatches(tagshow == nil || tns, "TagShow", ui)
warnNoMatches(tagignore == nil || tnh, "TagHide", ui) warnNoMatches(tagignore == nil || tnh, "TagHide", ui)

View file

@ -38,7 +38,10 @@ type treeNode struct {
func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) { func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
// Force the call tree so that the graph is a tree. // Force the call tree so that the graph is a tree.
// Also do not trim the tree so that the flame graph contains all functions. // Also do not trim the tree so that the flame graph contains all functions.
rpt, errList := ui.makeReport(w, req, []string{"svg"}, "call_tree", "true", "trim", "false") rpt, errList := ui.makeReport(w, req, []string{"svg"}, func(cfg *config) {
cfg.CallTree = true
cfg.Trim = false
})
if rpt == nil { if rpt == nil {
return // error already reported return // error already reported
} }
@ -96,7 +99,7 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
return return
} }
ui.render(w, "flamegraph", rpt, errList, config.Labels, webArgs{ ui.render(w, req, "flamegraph", rpt, errList, config.Labels, webArgs{
FlameGraph: template.JS(b), FlameGraph: template.JS(b),
Nodes: nodeArr, Nodes: nodeArr,
}) })

View file

@ -34,17 +34,14 @@ var tailDigitsRE = regexp.MustCompile("[0-9]+$")
func interactive(p *profile.Profile, o *plugin.Options) error { func interactive(p *profile.Profile, o *plugin.Options) error {
// Enter command processing loop. // Enter command processing loop.
o.UI.SetAutoComplete(newCompleter(functionNames(p))) o.UI.SetAutoComplete(newCompleter(functionNames(p)))
pprofVariables.set("compact_labels", "true") configure("compact_labels", "true")
pprofVariables["sample_index"].help += fmt.Sprintf("Or use sample_index=name, with name in %v.\n", sampleTypes(p)) configHelp["sample_index"] += fmt.Sprintf("Or use sample_index=name, with name in %v.\n", sampleTypes(p))
// Do not wait for the visualizer to complete, to allow multiple // Do not wait for the visualizer to complete, to allow multiple
// graphs to be visualized simultaneously. // graphs to be visualized simultaneously.
interactiveMode = true interactiveMode = true
shortcuts := profileShortcuts(p) shortcuts := profileShortcuts(p)
// Get all groups in pprofVariables to allow for clearer error messages.
groups := groupOptions(pprofVariables)
greetings(p, o.UI) greetings(p, o.UI)
for { for {
input, err := o.UI.ReadLine("(pprof) ") input, err := o.UI.ReadLine("(pprof) ")
@ -69,7 +66,12 @@ func interactive(p *profile.Profile, o *plugin.Options) error {
} }
value = strings.TrimSpace(value) value = strings.TrimSpace(value)
} }
if v := pprofVariables[name]; v != nil { if isConfigurable(name) {
// All non-bool options require inputs
if len(s) == 1 && !isBoolConfig(name) {
o.UI.PrintErr(fmt.Errorf("please specify a value, e.g. %s=<val>", name))
continue
}
if name == "sample_index" { if name == "sample_index" {
// Error check sample_index=xxx to ensure xxx is a valid sample type. // Error check sample_index=xxx to ensure xxx is a valid sample type.
index, err := p.SampleIndexByName(value) index, err := p.SampleIndexByName(value)
@ -77,23 +79,17 @@ func interactive(p *profile.Profile, o *plugin.Options) error {
o.UI.PrintErr(err) o.UI.PrintErr(err)
continue continue
} }
if index < 0 || index >= len(p.SampleType) {
o.UI.PrintErr(fmt.Errorf("invalid sample_index %q", value))
continue
}
value = p.SampleType[index].Type value = p.SampleType[index].Type
} }
if err := pprofVariables.set(name, value); err != nil { if err := configure(name, value); err != nil {
o.UI.PrintErr(err) o.UI.PrintErr(err)
} }
continue continue
} }
// Allow group=variable syntax by converting into variable="".
if v := pprofVariables[value]; v != nil && v.group == name {
if err := pprofVariables.set(value, ""); err != nil {
o.UI.PrintErr(err)
}
continue
} else if okValues := groups[name]; okValues != nil {
o.UI.PrintErr(fmt.Errorf("unrecognized value for %s: %q. Use one of %s", name, value, strings.Join(okValues, ", ")))
continue
}
} }
tokens := strings.Fields(input) tokens := strings.Fields(input)
@ -105,16 +101,16 @@ func interactive(p *profile.Profile, o *plugin.Options) error {
case "o", "options": case "o", "options":
printCurrentOptions(p, o.UI) printCurrentOptions(p, o.UI)
continue continue
case "exit", "quit": case "exit", "quit", "q":
return nil return nil
case "help": case "help":
commandHelp(strings.Join(tokens[1:], " "), o.UI) commandHelp(strings.Join(tokens[1:], " "), o.UI)
continue continue
} }
args, vars, err := parseCommandLine(tokens) args, cfg, err := parseCommandLine(tokens)
if err == nil { if err == nil {
err = generateReportWrapper(p, args, vars, o) err = generateReportWrapper(p, args, cfg, o)
} }
if err != nil { if err != nil {
@ -124,30 +120,13 @@ func interactive(p *profile.Profile, o *plugin.Options) error {
} }
} }
// groupOptions returns a map containing all non-empty groups
// mapped to an array of the option names in that group in
// sorted order.
func groupOptions(vars variables) map[string][]string {
groups := make(map[string][]string)
for name, option := range vars {
group := option.group
if group != "" {
groups[group] = append(groups[group], name)
}
}
for _, names := range groups {
sort.Strings(names)
}
return groups
}
var generateReportWrapper = generateReport // For testing purposes. var generateReportWrapper = generateReport // For testing purposes.
// greetings prints a brief welcome and some overall profile // greetings prints a brief welcome and some overall profile
// information before accepting interactive commands. // information before accepting interactive commands.
func greetings(p *profile.Profile, ui plugin.UI) { func greetings(p *profile.Profile, ui plugin.UI) {
numLabelUnits := identifyNumLabelUnits(p, ui) numLabelUnits := identifyNumLabelUnits(p, ui)
ropt, err := reportOptions(p, numLabelUnits, pprofVariables) ropt, err := reportOptions(p, numLabelUnits, currentConfig())
if err == nil { if err == nil {
rpt := report.New(p, ropt) rpt := report.New(p, ropt)
ui.Print(strings.Join(report.ProfileLabels(rpt), "\n")) ui.Print(strings.Join(report.ProfileLabels(rpt), "\n"))
@ -200,27 +179,16 @@ func sampleTypes(p *profile.Profile) []string {
func printCurrentOptions(p *profile.Profile, ui plugin.UI) { func printCurrentOptions(p *profile.Profile, ui plugin.UI) {
var args []string var args []string
type groupInfo struct { current := currentConfig()
set string for _, f := range configFields {
values []string n := f.name
} v := current.get(f)
groups := make(map[string]*groupInfo)
for n, o := range pprofVariables {
v := o.stringValue()
comment := "" comment := ""
if g := o.group; g != "" {
gi, ok := groups[g]
if !ok {
gi = &groupInfo{}
groups[g] = gi
}
if o.boolValue() {
gi.set = n
}
gi.values = append(gi.values, n)
continue
}
switch { switch {
case len(f.choices) > 0:
values := append([]string{}, f.choices...)
sort.Strings(values)
comment = "[" + strings.Join(values, " | ") + "]"
case n == "sample_index": case n == "sample_index":
st := sampleTypes(p) st := sampleTypes(p)
if v == "" { if v == "" {
@ -242,18 +210,13 @@ func printCurrentOptions(p *profile.Profile, ui plugin.UI) {
} }
args = append(args, fmt.Sprintf(" %-25s = %-20s %s", n, v, comment)) args = append(args, fmt.Sprintf(" %-25s = %-20s %s", n, v, comment))
} }
for g, vars := range groups {
sort.Strings(vars.values)
comment := commentStart + " [" + strings.Join(vars.values, " | ") + "]"
args = append(args, fmt.Sprintf(" %-25s = %-20s %s", g, vars.set, comment))
}
sort.Strings(args) sort.Strings(args)
ui.Print(strings.Join(args, "\n")) ui.Print(strings.Join(args, "\n"))
} }
// parseCommandLine parses a command and returns the pprof command to // parseCommandLine parses a command and returns the pprof command to
// execute and a set of variables for the report. // execute and the configuration to use for the report.
func parseCommandLine(input []string) ([]string, variables, error) { func parseCommandLine(input []string) ([]string, config, error) {
cmd, args := input[:1], input[1:] cmd, args := input[:1], input[1:]
name := cmd[0] name := cmd[0]
@ -267,25 +230,32 @@ func parseCommandLine(input []string) ([]string, variables, error) {
} }
} }
if c == nil { if c == nil {
return nil, nil, fmt.Errorf("unrecognized command: %q", name) if _, ok := configHelp[name]; ok {
value := "<val>"
if len(args) > 0 {
value = args[0]
}
return nil, config{}, fmt.Errorf("did you mean: %s=%s", name, value)
}
return nil, config{}, fmt.Errorf("unrecognized command: %q", name)
} }
if c.hasParam { if c.hasParam {
if len(args) == 0 { if len(args) == 0 {
return nil, nil, fmt.Errorf("command %s requires an argument", name) return nil, config{}, fmt.Errorf("command %s requires an argument", name)
} }
cmd = append(cmd, args[0]) cmd = append(cmd, args[0])
args = args[1:] args = args[1:]
} }
// Copy the variables as options set in the command line are not persistent. // Copy config since options set in the command line should not persist.
vcopy := pprofVariables.makeCopy() vcopy := currentConfig()
var focus, ignore string var focus, ignore string
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
t := args[i] t := args[i]
if _, err := strconv.ParseInt(t, 10, 32); err == nil { if n, err := strconv.ParseInt(t, 10, 32); err == nil {
vcopy.set("nodecount", t) vcopy.NodeCount = int(n)
continue continue
} }
switch t[0] { switch t[0] {
@ -294,14 +264,14 @@ func parseCommandLine(input []string) ([]string, variables, error) {
if outputFile == "" { if outputFile == "" {
i++ i++
if i >= len(args) { if i >= len(args) {
return nil, nil, fmt.Errorf("unexpected end of line after >") return nil, config{}, fmt.Errorf("unexpected end of line after >")
} }
outputFile = args[i] outputFile = args[i]
} }
vcopy.set("output", outputFile) vcopy.Output = outputFile
case '-': case '-':
if t == "--cum" || t == "-cum" { if t == "--cum" || t == "-cum" {
vcopy.set("cum", "t") vcopy.Sort = "cum"
continue continue
} }
ignore = catRegex(ignore, t[1:]) ignore = catRegex(ignore, t[1:])
@ -311,30 +281,27 @@ func parseCommandLine(input []string) ([]string, variables, error) {
} }
if name == "tags" { if name == "tags" {
updateFocusIgnore(vcopy, "tag", focus, ignore) if focus != "" {
} else { vcopy.TagFocus = focus
updateFocusIgnore(vcopy, "", focus, ignore)
} }
if ignore != "" {
if vcopy["nodecount"].intValue() == -1 && (name == "text" || name == "top") { vcopy.TagIgnore = ignore
vcopy.set("nodecount", "10") }
} else {
if focus != "" {
vcopy.Focus = focus
}
if ignore != "" {
vcopy.Ignore = ignore
}
}
if vcopy.NodeCount == -1 && (name == "text" || name == "top") {
vcopy.NodeCount = 10
} }
return cmd, vcopy, nil return cmd, vcopy, nil
} }
func updateFocusIgnore(v variables, prefix, f, i string) {
if f != "" {
focus := prefix + "focus"
v.set(focus, catRegex(v[focus].value, f))
}
if i != "" {
ignore := prefix + "ignore"
v.set(ignore, catRegex(v[ignore].value, i))
}
}
func catRegex(a, b string) string { func catRegex(a, b string) string {
if a != "" && b != "" { if a != "" && b != "" {
return a + "|" + b return a + "|" + b
@ -362,8 +329,8 @@ func commandHelp(args string, ui plugin.UI) {
return return
} }
if v := pprofVariables[args]; v != nil { if help, ok := configHelp[args]; ok {
ui.Print(v.help + "\n") ui.Print(help + "\n")
return return
} }
@ -373,18 +340,17 @@ func commandHelp(args string, ui plugin.UI) {
// newCompleter creates an autocompletion function for a set of commands. // newCompleter creates an autocompletion function for a set of commands.
func newCompleter(fns []string) func(string) string { func newCompleter(fns []string) func(string) string {
return func(line string) string { return func(line string) string {
v := pprofVariables
switch tokens := strings.Fields(line); len(tokens) { switch tokens := strings.Fields(line); len(tokens) {
case 0: case 0:
// Nothing to complete // Nothing to complete
case 1: case 1:
// Single token -- complete command name // Single token -- complete command name
if match := matchVariableOrCommand(v, tokens[0]); match != "" { if match := matchVariableOrCommand(tokens[0]); match != "" {
return match return match
} }
case 2: case 2:
if tokens[0] == "help" { if tokens[0] == "help" {
if match := matchVariableOrCommand(v, tokens[1]); match != "" { if match := matchVariableOrCommand(tokens[1]); match != "" {
return tokens[0] + " " + match return tokens[0] + " " + match
} }
return line return line
@ -408,26 +374,19 @@ func newCompleter(fns []string) func(string) string {
} }
// matchVariableOrCommand attempts to match a string token to the prefix of a Command. // matchVariableOrCommand attempts to match a string token to the prefix of a Command.
func matchVariableOrCommand(v variables, token string) string { func matchVariableOrCommand(token string) string {
token = strings.ToLower(token) token = strings.ToLower(token)
found := "" var matches []string
for cmd := range pprofCommands { for cmd := range pprofCommands {
if strings.HasPrefix(cmd, token) { if strings.HasPrefix(cmd, token) {
if found != "" { matches = append(matches, cmd)
}
}
matches = append(matches, completeConfig(token)...)
if len(matches) == 1 {
return matches[0]
}
return "" return ""
}
found = cmd
}
}
for variable := range v {
if strings.HasPrefix(variable, token) {
if found != "" {
return ""
}
found = variable
}
}
return found
} }
// functionCompleter replaces provided substring with a function // functionCompleter replaces provided substring with a function

View file

@ -0,0 +1,157 @@
package driver
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
)
// settings holds pprof settings.
type settings struct {
// Configs holds a list of named UI configurations.
Configs []namedConfig `json:"configs"`
}
// namedConfig associates a name with a config.
type namedConfig struct {
Name string `json:"name"`
config
}
// settingsFileName returns the name of the file where settings should be saved.
func settingsFileName() (string, error) {
// Return "pprof/settings.json" under os.UserConfigDir().
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "pprof", "settings.json"), nil
}
// readSettings reads settings from fname.
func readSettings(fname string) (*settings, error) {
data, err := ioutil.ReadFile(fname)
if err != nil {
if os.IsNotExist(err) {
return &settings{}, nil
}
return nil, fmt.Errorf("could not read settings: %w", err)
}
settings := &settings{}
if err := json.Unmarshal(data, settings); err != nil {
return nil, fmt.Errorf("could not parse settings: %w", err)
}
for i := range settings.Configs {
settings.Configs[i].resetTransient()
}
return settings, nil
}
// writeSettings saves settings to fname.
func writeSettings(fname string, settings *settings) error {
data, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return fmt.Errorf("could not encode settings: %w", err)
}
// create the settings directory if it does not exist
// XDG specifies permissions 0700 when creating settings dirs:
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
return fmt.Errorf("failed to create settings directory: %w", err)
}
if err := ioutil.WriteFile(fname, data, 0644); err != nil {
return fmt.Errorf("failed to write settings: %w", err)
}
return nil
}
// configMenuEntry holds information for a single config menu entry.
type configMenuEntry struct {
Name string
URL string
Current bool // Is this the currently selected config?
UserConfig bool // Is this a user-provided config?
}
// configMenu returns a list of items to add to a menu in the web UI.
func configMenu(fname string, url url.URL) []configMenuEntry {
// Start with system configs.
configs := []namedConfig{{Name: "Default", config: defaultConfig()}}
if settings, err := readSettings(fname); err == nil {
// Add user configs.
configs = append(configs, settings.Configs...)
}
// Convert to menu entries.
result := make([]configMenuEntry, len(configs))
lastMatch := -1
for i, cfg := range configs {
dst, changed := cfg.config.makeURL(url)
if !changed {
lastMatch = i
}
result[i] = configMenuEntry{
Name: cfg.Name,
URL: dst.String(),
UserConfig: (i != 0),
}
}
// Mark the last matching config as currennt
if lastMatch >= 0 {
result[lastMatch].Current = true
}
return result
}
// editSettings edits settings by applying fn to them.
func editSettings(fname string, fn func(s *settings) error) error {
settings, err := readSettings(fname)
if err != nil {
return err
}
if err := fn(settings); err != nil {
return err
}
return writeSettings(fname, settings)
}
// setConfig saves the config specified in request to fname.
func setConfig(fname string, request url.URL) error {
q := request.Query()
name := q.Get("config")
if name == "" {
return fmt.Errorf("invalid config name")
}
cfg := currentConfig()
if err := cfg.applyURL(q); err != nil {
return err
}
return editSettings(fname, func(s *settings) error {
for i, c := range s.Configs {
if c.Name == name {
s.Configs[i].config = cfg
return nil
}
}
s.Configs = append(s.Configs, namedConfig{Name: name, config: cfg})
return nil
})
}
// removeConfig removes config from fname.
func removeConfig(fname, config string) error {
return editSettings(fname, func(s *settings) error {
for i, c := range s.Configs {
if c.Name == config {
s.Configs = append(s.Configs[:i], s.Configs[i+1:]...)
return nil
}
}
return fmt.Errorf("config %s not found", config)
})
}

View file

@ -166,6 +166,73 @@ a {
color: gray; color: gray;
pointer-events: none; pointer-events: none;
} }
.menu-check-mark {
position: absolute;
left: 2px;
}
.menu-delete-btn {
position: absolute;
right: 2px;
}
{{/* Used to disable events when a modal dialog is displayed */}}
#dialog-overlay {
display: none;
position: fixed;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
background-color: rgba(1,1,1,0.1);
}
.dialog {
{{/* Displayed centered horizontally near the top */}}
display: none;
position: fixed;
margin: 0px;
top: 60px;
left: 50%;
transform: translateX(-50%);
z-index: 3;
font-size: 125%;
background-color: #ffffff;
box-shadow: 0 1px 5px rgba(0,0,0,.3);
}
.dialog-header {
font-size: 120%;
border-bottom: 1px solid #CCCCCC;
width: 100%;
text-align: center;
background: #EEEEEE;
user-select: none;
}
.dialog-footer {
border-top: 1px solid #CCCCCC;
width: 100%;
text-align: right;
padding: 10px;
}
.dialog-error {
margin: 10px;
color: red;
}
.dialog input {
margin: 10px;
font-size: inherit;
}
.dialog button {
margin-left: 10px;
font-size: inherit;
}
#save-dialog, #delete-dialog {
width: 50%;
max-width: 20em;
}
#delete-prompt {
padding: 10px;
}
#content { #content {
overflow-y: scroll; overflow-y: scroll;
@ -200,6 +267,8 @@ table thead {
font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
} }
table tr th { table tr th {
position: sticky;
top: 0;
background-color: #ddd; background-color: #ddd;
text-align: right; text-align: right;
padding: .3em .5em; padding: .3em .5em;
@ -282,6 +351,24 @@ table tr td {
</div> </div>
</div> </div>
<div id="config" class="menu-item">
<div class="menu-name">
Config
<i class="downArrow"></i>
</div>
<div class="submenu">
<a title="{{.Help.save_config}}" id="save-config">Save as ...</a>
<hr>
{{range .Configs}}
<a href="{{.URL}}">
{{if .Current}}<span class="menu-check-mark"></span>{{end}}
{{.Name}}
{{if .UserConfig}}<span class="menu-delete-btn" data-config={{.Name}}>🗙</span>{{end}}
</a>
{{end}}
</div>
</div>
<div> <div>
<input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40> <input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
</div> </div>
@ -294,6 +381,31 @@ table tr td {
</div> </div>
</div> </div>
<div id="dialog-overlay"></div>
<div class="dialog" id="save-dialog">
<div class="dialog-header">Save options as</div>
<datalist id="config-list">
{{range .Configs}}{{if .UserConfig}}<option value="{{.Name}}" />{{end}}{{end}}
</datalist>
<input id="save-name" type="text" list="config-list" placeholder="New config" />
<div class="dialog-footer">
<span class="dialog-error" id="save-error"></span>
<button id="save-cancel">Cancel</button>
<button id="save-confirm">Save</button>
</div>
</div>
<div class="dialog" id="delete-dialog">
<div class="dialog-header" id="delete-dialog-title">Delete config</div>
<div id="delete-prompt"></div>
<div class="dialog-footer">
<span class="dialog-error" id="delete-error"></span>
<button id="delete-cancel">Cancel</button>
<button id="delete-confirm">Delete</button>
</div>
</div>
<div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div> <div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div>
{{end}} {{end}}
@ -583,6 +695,131 @@ function initMenus() {
}, { passive: true, capture: true }); }, { passive: true, capture: true });
} }
function sendURL(method, url, done) {
fetch(url.toString(), {method: method})
.then((response) => { done(response.ok); })
.catch((error) => { done(false); });
}
// Initialize handlers for saving/loading configurations.
function initConfigManager() {
'use strict';
// Initialize various elements.
function elem(id) {
const result = document.getElementById(id);
if (!result) console.warn('element ' + id + ' not found');
return result;
}
const overlay = elem('dialog-overlay');
const saveDialog = elem('save-dialog');
const saveInput = elem('save-name');
const saveError = elem('save-error');
const delDialog = elem('delete-dialog');
const delPrompt = elem('delete-prompt');
const delError = elem('delete-error');
let currentDialog = null;
let currentDeleteTarget = null;
function showDialog(dialog) {
if (currentDialog != null) {
overlay.style.display = 'none';
currentDialog.style.display = 'none';
}
currentDialog = dialog;
if (dialog != null) {
overlay.style.display = 'block';
dialog.style.display = 'block';
}
}
function cancelDialog(e) {
showDialog(null);
}
// Show dialog for saving the current config.
function showSaveDialog(e) {
saveError.innerText = '';
showDialog(saveDialog);
saveInput.focus();
}
// Commit save config.
function commitSave(e) {
const name = saveInput.value;
const url = new URL(document.URL);
// Set path relative to existing path.
url.pathname = new URL('./saveconfig', document.URL).pathname;
url.searchParams.set('config', name);
saveError.innerText = '';
sendURL('POST', url, (ok) => {
if (!ok) {
saveError.innerText = 'Save failed';
} else {
showDialog(null);
location.reload(); // Reload to show updated config menu
}
});
}
function handleSaveInputKey(e) {
if (e.key === 'Enter') commitSave(e);
}
function deleteConfig(e, elem) {
e.preventDefault();
const config = elem.dataset.config;
delPrompt.innerText = 'Delete ' + config + '?';
currentDeleteTarget = elem;
showDialog(delDialog);
}
function commitDelete(e, elem) {
if (!currentDeleteTarget) return;
const config = currentDeleteTarget.dataset.config;
const url = new URL('./deleteconfig', document.URL);
url.searchParams.set('config', config);
delError.innerText = '';
sendURL('DELETE', url, (ok) => {
if (!ok) {
delError.innerText = 'Delete failed';
return;
}
showDialog(null);
// Remove menu entry for this config.
if (currentDeleteTarget && currentDeleteTarget.parentElement) {
currentDeleteTarget.parentElement.remove();
}
});
}
// Bind event on elem to fn.
function bind(event, elem, fn) {
if (elem == null) return;
elem.addEventListener(event, fn);
if (event == 'click') {
// Also enable via touch.
elem.addEventListener('touchstart', fn);
}
}
bind('click', elem('save-config'), showSaveDialog);
bind('click', elem('save-cancel'), cancelDialog);
bind('click', elem('save-confirm'), commitSave);
bind('keydown', saveInput, handleSaveInputKey);
bind('click', elem('delete-cancel'), cancelDialog);
bind('click', elem('delete-confirm'), commitDelete);
// Activate deletion button for all config entries in menu.
for (const del of Array.from(document.getElementsByClassName('menu-delete-btn'))) {
bind('click', del, (e) => {
deleteConfig(e, del);
});
}
}
function viewer(baseUrl, nodes) { function viewer(baseUrl, nodes) {
'use strict'; 'use strict';
@ -875,6 +1112,7 @@ function viewer(baseUrl, nodes) {
} }
addAction('details', handleDetails); addAction('details', handleDetails);
initConfigManager();
search.addEventListener('input', handleSearch); search.addEventListener('input', handleSearch);
search.addEventListener('keydown', handleKey); search.addEventListener('keydown', handleKey);

View file

@ -39,9 +39,14 @@ type webInterface struct {
options *plugin.Options options *plugin.Options
help map[string]string help map[string]string
templates *template.Template templates *template.Template
settingsFile string
} }
func makeWebInterface(p *profile.Profile, opt *plugin.Options) *webInterface { func makeWebInterface(p *profile.Profile, opt *plugin.Options) (*webInterface, error) {
settingsFile, err := settingsFileName()
if err != nil {
return nil, err
}
templates := template.New("templategroup") templates := template.New("templategroup")
addTemplates(templates) addTemplates(templates)
report.AddSourceTemplates(templates) report.AddSourceTemplates(templates)
@ -50,7 +55,8 @@ func makeWebInterface(p *profile.Profile, opt *plugin.Options) *webInterface {
options: opt, options: opt,
help: make(map[string]string), help: make(map[string]string),
templates: templates, templates: templates,
} settingsFile: settingsFile,
}, nil
} }
// maxEntries is the maximum number of entries to print for text interfaces. // maxEntries is the maximum number of entries to print for text interfaces.
@ -80,6 +86,7 @@ type webArgs struct {
TextBody string TextBody string
Top []report.TextItem Top []report.TextItem
FlameGraph template.JS FlameGraph template.JS
Configs []configMenuEntry
} }
func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, disableBrowser bool) error { func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, disableBrowser bool) error {
@ -88,16 +95,20 @@ func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, d
return err return err
} }
interactiveMode = true interactiveMode = true
ui := makeWebInterface(p, o) ui, err := makeWebInterface(p, o)
if err != nil {
return err
}
for n, c := range pprofCommands { for n, c := range pprofCommands {
ui.help[n] = c.description ui.help[n] = c.description
} }
for n, v := range pprofVariables { for n, help := range configHelp {
ui.help[n] = v.help ui.help[n] = help
} }
ui.help["details"] = "Show information about the profile and this view" ui.help["details"] = "Show information about the profile and this view"
ui.help["graph"] = "Display profile as a directed graph" ui.help["graph"] = "Display profile as a directed graph"
ui.help["reset"] = "Show the entire profile" ui.help["reset"] = "Show the entire profile"
ui.help["save_config"] = "Save current settings"
server := o.HTTPServer server := o.HTTPServer
if server == nil { if server == nil {
@ -114,6 +125,8 @@ func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, d
"/source": http.HandlerFunc(ui.source), "/source": http.HandlerFunc(ui.source),
"/peek": http.HandlerFunc(ui.peek), "/peek": http.HandlerFunc(ui.peek),
"/flamegraph": http.HandlerFunc(ui.flamegraph), "/flamegraph": http.HandlerFunc(ui.flamegraph),
"/saveconfig": http.HandlerFunc(ui.saveConfig),
"/deleteconfig": http.HandlerFunc(ui.deleteConfig),
}, },
} }
@ -206,21 +219,9 @@ func isLocalhost(host string) bool {
func openBrowser(url string, o *plugin.Options) { func openBrowser(url string, o *plugin.Options) {
// Construct URL. // Construct URL.
u, _ := gourl.Parse(url) baseURL, _ := gourl.Parse(url)
q := u.Query() current := currentConfig()
for _, p := range []struct{ param, key string }{ u, _ := current.makeURL(*baseURL)
{"f", "focus"},
{"s", "show"},
{"sf", "show_from"},
{"i", "ignore"},
{"h", "hide"},
{"si", "sample_index"},
} {
if v := pprofVariables[p.key].value; v != "" {
q.Set(p.param, v)
}
}
u.RawQuery = q.Encode()
// Give server a little time to get ready. // Give server a little time to get ready.
time.Sleep(time.Millisecond * 500) time.Sleep(time.Millisecond * 500)
@ -240,28 +241,23 @@ func openBrowser(url string, o *plugin.Options) {
o.UI.PrintErr(u.String()) o.UI.PrintErr(u.String())
} }
func varsFromURL(u *gourl.URL) variables {
vars := pprofVariables.makeCopy()
vars["focus"].value = u.Query().Get("f")
vars["show"].value = u.Query().Get("s")
vars["show_from"].value = u.Query().Get("sf")
vars["ignore"].value = u.Query().Get("i")
vars["hide"].value = u.Query().Get("h")
vars["sample_index"].value = u.Query().Get("si")
return vars
}
// makeReport generates a report for the specified command. // makeReport generates a report for the specified command.
// If configEditor is not null, it is used to edit the config used for the report.
func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request, func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request,
cmd []string, vars ...string) (*report.Report, []string) { cmd []string, configEditor func(*config)) (*report.Report, []string) {
v := varsFromURL(req.URL) cfg := currentConfig()
for i := 0; i+1 < len(vars); i += 2 { if err := cfg.applyURL(req.URL.Query()); err != nil {
v[vars[i]].value = vars[i+1] http.Error(w, err.Error(), http.StatusBadRequest)
ui.options.UI.PrintErr(err)
return nil, nil
}
if configEditor != nil {
configEditor(&cfg)
} }
catcher := &errorCatcher{UI: ui.options.UI} catcher := &errorCatcher{UI: ui.options.UI}
options := *ui.options options := *ui.options
options.UI = catcher options.UI = catcher
_, rpt, err := generateRawReport(ui.prof, cmd, v, &options) _, rpt, err := generateRawReport(ui.prof, cmd, cfg, &options)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
ui.options.UI.PrintErr(err) ui.options.UI.PrintErr(err)
@ -271,7 +267,7 @@ func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request,
} }
// render generates html using the named template based on the contents of data. // render generates html using the named template based on the contents of data.
func (ui *webInterface) render(w http.ResponseWriter, tmpl string, func (ui *webInterface) render(w http.ResponseWriter, req *http.Request, tmpl string,
rpt *report.Report, errList, legend []string, data webArgs) { rpt *report.Report, errList, legend []string, data webArgs) {
file := getFromLegend(legend, "File: ", "unknown") file := getFromLegend(legend, "File: ", "unknown")
profile := getFromLegend(legend, "Type: ", "unknown") profile := getFromLegend(legend, "Type: ", "unknown")
@ -281,6 +277,8 @@ func (ui *webInterface) render(w http.ResponseWriter, tmpl string,
data.SampleTypes = sampleTypes(ui.prof) data.SampleTypes = sampleTypes(ui.prof)
data.Legend = legend data.Legend = legend
data.Help = ui.help data.Help = ui.help
data.Configs = configMenu(ui.settingsFile, *req.URL)
html := &bytes.Buffer{} html := &bytes.Buffer{}
if err := ui.templates.ExecuteTemplate(html, tmpl, data); err != nil { if err := ui.templates.ExecuteTemplate(html, tmpl, data); err != nil {
http.Error(w, "internal template error", http.StatusInternalServerError) http.Error(w, "internal template error", http.StatusInternalServerError)
@ -293,7 +291,7 @@ func (ui *webInterface) render(w http.ResponseWriter, tmpl string,
// dot generates a web page containing an svg diagram. // dot generates a web page containing an svg diagram.
func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) { func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
rpt, errList := ui.makeReport(w, req, []string{"svg"}) rpt, errList := ui.makeReport(w, req, []string{"svg"}, nil)
if rpt == nil { if rpt == nil {
return // error already reported return // error already reported
} }
@ -320,7 +318,7 @@ func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
nodes = append(nodes, n.Info.Name) nodes = append(nodes, n.Info.Name)
} }
ui.render(w, "graph", rpt, errList, legend, webArgs{ ui.render(w, req, "graph", rpt, errList, legend, webArgs{
HTMLBody: template.HTML(string(svg)), HTMLBody: template.HTML(string(svg)),
Nodes: nodes, Nodes: nodes,
}) })
@ -345,7 +343,9 @@ func dotToSvg(dot []byte) ([]byte, error) {
} }
func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) { func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {
rpt, errList := ui.makeReport(w, req, []string{"top"}, "nodecount", "500") rpt, errList := ui.makeReport(w, req, []string{"top"}, func(cfg *config) {
cfg.NodeCount = 500
})
if rpt == nil { if rpt == nil {
return // error already reported return // error already reported
} }
@ -355,7 +355,7 @@ func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {
nodes = append(nodes, item.Name) nodes = append(nodes, item.Name)
} }
ui.render(w, "top", rpt, errList, legend, webArgs{ ui.render(w, req, "top", rpt, errList, legend, webArgs{
Top: top, Top: top,
Nodes: nodes, Nodes: nodes,
}) })
@ -364,7 +364,7 @@ func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {
// disasm generates a web page containing disassembly. // disasm generates a web page containing disassembly.
func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) { func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
args := []string{"disasm", req.URL.Query().Get("f")} args := []string{"disasm", req.URL.Query().Get("f")}
rpt, errList := ui.makeReport(w, req, args) rpt, errList := ui.makeReport(w, req, args, nil)
if rpt == nil { if rpt == nil {
return // error already reported return // error already reported
} }
@ -377,7 +377,7 @@ func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
} }
legend := report.ProfileLabels(rpt) legend := report.ProfileLabels(rpt)
ui.render(w, "plaintext", rpt, errList, legend, webArgs{ ui.render(w, req, "plaintext", rpt, errList, legend, webArgs{
TextBody: out.String(), TextBody: out.String(),
}) })
@ -387,7 +387,7 @@ func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
// data. // data.
func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) { func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) {
args := []string{"weblist", req.URL.Query().Get("f")} args := []string{"weblist", req.URL.Query().Get("f")}
rpt, errList := ui.makeReport(w, req, args) rpt, errList := ui.makeReport(w, req, args, nil)
if rpt == nil { if rpt == nil {
return // error already reported return // error already reported
} }
@ -401,7 +401,7 @@ func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) {
} }
legend := report.ProfileLabels(rpt) legend := report.ProfileLabels(rpt)
ui.render(w, "sourcelisting", rpt, errList, legend, webArgs{ ui.render(w, req, "sourcelisting", rpt, errList, legend, webArgs{
HTMLBody: template.HTML(body.String()), HTMLBody: template.HTML(body.String()),
}) })
} }
@ -409,7 +409,9 @@ func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) {
// peek generates a web page listing callers/callers. // peek generates a web page listing callers/callers.
func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) { func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {
args := []string{"peek", req.URL.Query().Get("f")} args := []string{"peek", req.URL.Query().Get("f")}
rpt, errList := ui.makeReport(w, req, args, "lines", "t") rpt, errList := ui.makeReport(w, req, args, func(cfg *config) {
cfg.Granularity = "lines"
})
if rpt == nil { if rpt == nil {
return // error already reported return // error already reported
} }
@ -422,11 +424,30 @@ func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {
} }
legend := report.ProfileLabels(rpt) legend := report.ProfileLabels(rpt)
ui.render(w, "plaintext", rpt, errList, legend, webArgs{ ui.render(w, req, "plaintext", rpt, errList, legend, webArgs{
TextBody: out.String(), TextBody: out.String(),
}) })
} }
// saveConfig saves URL configuration.
func (ui *webInterface) saveConfig(w http.ResponseWriter, req *http.Request) {
if err := setConfig(ui.settingsFile, *req.URL); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
ui.options.UI.PrintErr(err)
return
}
}
// deleteConfig deletes a configuration.
func (ui *webInterface) deleteConfig(w http.ResponseWriter, req *http.Request) {
name := req.URL.Query().Get("config")
if err := removeConfig(ui.settingsFile, name); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
ui.options.UI.PrintErr(err)
return
}
}
// getFromLegend returns the suffix of an entry in legend that starts // getFromLegend returns the suffix of an entry in legend that starts
// with param. It returns def if no such entry is found. // with param. It returns def if no such entry is found.
func getFromLegend(legend []string, param, def string) string { func getFromLegend(legend []string, param, def string) string {

View file

@ -114,7 +114,7 @@ type ObjTool interface {
// Disasm disassembles the named object file, starting at // Disasm disassembles the named object file, starting at
// the start address and stopping at (before) the end address. // the start address and stopping at (before) the end address.
Disasm(file string, start, end uint64) ([]Inst, error) Disasm(file string, start, end uint64, intelSyntax bool) ([]Inst, error)
} }
// An Inst is a single instruction in an assembly listing. // An Inst is a single instruction in an assembly listing.

View file

@ -79,6 +79,8 @@ type Options struct {
Symbol *regexp.Regexp // Symbols to include on disassembly report. Symbol *regexp.Regexp // Symbols to include on disassembly report.
SourcePath string // Search path for source files. SourcePath string // Search path for source files.
TrimPath string // Paths to trim from source file paths. TrimPath string // Paths to trim from source file paths.
IntelSyntax bool // Whether or not to print assembly in Intel syntax.
} }
// Generate generates a report as directed by the Report. // Generate generates a report as directed by the Report.
@ -438,7 +440,7 @@ func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) e
flatSum, cumSum := sns.Sum() flatSum, cumSum := sns.Sum()
// Get the function assembly. // Get the function assembly.
insts, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End) insts, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End, o.IntelSyntax)
if err != nil { if err != nil {
return err return err
} }
@ -1201,6 +1203,13 @@ func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedE
nodeCount, origCount)) nodeCount, origCount))
} }
} }
// Help new users understand the graph.
// A new line is intentionally added here to better show this message.
if fullHeaders {
label = append(label, "\\lSee https://git.io/JfYMW for how to read the graph")
}
return label return label
} }

View file

@ -205,7 +205,7 @@ func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) er
ff := fileFunction{n.Info.File, n.Info.Name} ff := fileFunction{n.Info.File, n.Info.Name}
fns := fileNodes[ff] fns := fileNodes[ff]
asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj) asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj, o.IntelSyntax)
start, end := sourceCoordinates(asm) start, end := sourceCoordinates(asm)
fnodes, path, err := getSourceFromFile(ff.fileName, reader, fns, start, end) fnodes, path, err := getSourceFromFile(ff.fileName, reader, fns, start, end)
@ -239,7 +239,7 @@ func sourceCoordinates(asm map[int][]assemblyInstruction) (start, end int) {
// assemblyPerSourceLine disassembles the binary containing a symbol // assemblyPerSourceLine disassembles the binary containing a symbol
// and classifies the assembly instructions according to its // and classifies the assembly instructions according to its
// corresponding source line, annotating them with a set of samples. // corresponding source line, annotating them with a set of samples.
func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj plugin.ObjTool) map[int][]assemblyInstruction { func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj plugin.ObjTool, intelSyntax bool) map[int][]assemblyInstruction {
assembly := make(map[int][]assemblyInstruction) assembly := make(map[int][]assemblyInstruction)
// Identify symbol to use for this collection of samples. // Identify symbol to use for this collection of samples.
o := findMatchingSymbol(objSyms, rs) o := findMatchingSymbol(objSyms, rs)
@ -248,7 +248,7 @@ func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj
} }
// Extract assembly for matched symbol // Extract assembly for matched symbol
insts, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End) insts, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End, intelSyntax)
if err != nil { if err != nil {
return assembly return assembly
} }

View file

@ -398,13 +398,15 @@ func (p *Profile) CheckValid() error {
} }
} }
for _, ln := range l.Line { for _, ln := range l.Line {
if f := ln.Function; f != nil { f := ln.Function
if f == nil {
return fmt.Errorf("location id: %d has a line with nil function", l.ID)
}
if f.ID == 0 || functions[f.ID] != f { if f.ID == 0 || functions[f.ID] != f {
return fmt.Errorf("inconsistent function %p: %d", f, f.ID) return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
} }
} }
} }
}
return nil return nil
} }

View file

@ -1,4 +1,4 @@
# github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3 # github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99
## explicit ## explicit
github.com/google/pprof/driver github.com/google/pprof/driver
github.com/google/pprof/internal/binutils github.com/google/pprof/internal/binutils

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build darwin,amd64 // +build darwin,!ios
// Package macOS provides cgo-less wrappers for Core Foundation and // Package macOS provides cgo-less wrappers for Core Foundation and
// Security.framework, similarly to how package syscall provides access to // Security.framework, similarly to how package syscall provides access to

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build darwin,amd64 // +build darwin,!ios
#include "textflag.h" #include "textflag.h"

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build darwin,amd64 // +build darwin,!ios
package macOS package macOS

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build darwin,amd64 // +build darwin,!ios
#include "textflag.h" #include "textflag.h"

View file

@ -4,7 +4,7 @@
package x509 package x509
//go:generate go run root_darwin_ios_gen.go -version 55161.80.1 //go:generate go run root_ios_gen.go -version 55161.80.1
import "sync" import "sync"

View file

@ -1,7 +1,7 @@
// Code generated by root_darwin_ios_gen.go -version 55161.80.1; DO NOT EDIT. // Code generated by root_ios_gen.go -version 55161.80.1; DO NOT EDIT.
// Update the version in root.go and regenerate with "go generate". // Update the version in root.go and regenerate with "go generate".
// +build darwin,arm64 darwin,amd64,ios // +build ios
// +build !x509omitbundledroots // +build !x509omitbundledroots
package x509 package x509

View file

@ -4,7 +4,7 @@
// +build ignore // +build ignore
// Generates root_darwin_iosx.go. // Generates root_ios.go.
// //
// As of iOS 13, there is no API for querying the system trusted X.509 root // As of iOS 13, there is no API for querying the system trusted X.509 root
// certificates. // certificates.
@ -37,10 +37,7 @@ import (
) )
func main() { func main() {
// Temporarily name the file _iosx.go, to avoid restricting it to GOOS=ios, var output = flag.String("output", "root_ios.go", "file name to write")
// as this is also used for darwin/arm64 (macOS).
// TODO: maybe use darwin/amd64 implementation on macOS arm64?
var output = flag.String("output", "root_darwin_iosx.go", "file name to write")
var version = flag.String("version", "", "security_certificates version") var version = flag.String("version", "", "security_certificates version")
flag.Parse() flag.Parse()
if *version == "" { if *version == "" {
@ -159,10 +156,10 @@ func main() {
} }
} }
const header = `// Code generated by root_darwin_ios_gen.go -version %s; DO NOT EDIT. const header = `// Code generated by root_ios_gen.go -version %s; DO NOT EDIT.
// Update the version in root.go and regenerate with "go generate". // Update the version in root.go and regenerate with "go generate".
// +build darwin,arm64 darwin,amd64,ios // +build ios
// +build !x509omitbundledroots // +build !x509omitbundledroots
package x509 package x509

View file

@ -411,8 +411,9 @@ type Package struct {
// Source files // Source files
GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
CgoFiles []string // .go source files that import "C" CgoFiles []string // .go source files that import "C"
IgnoredGoFiles []string // .go source files ignored for this build IgnoredGoFiles []string // .go source files ignored for this build (including ignored _test.go files)
InvalidGoFiles []string // .go source files with detected problems (parse error, wrong package name, and so on) InvalidGoFiles []string // .go source files with detected problems (parse error, wrong package name, and so on)
IgnoredOtherFiles []string // non-.go source files ignored for this build
CFiles []string // .c source files CFiles []string // .c source files
CXXFiles []string // .cc, .cpp and .cxx source files CXXFiles []string // .cc, .cpp and .cxx source files
MFiles []string // .m (Objective-C) source files MFiles []string // .m (Objective-C) source files
@ -816,46 +817,28 @@ Found:
continue continue
} }
if !match { if !match {
if ext == ".go" { if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") {
// not due to build constraints - don't report
} else if ext == ".go" {
p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
} else if fileListForExt(p, ext) != nil {
p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, name)
} }
continue continue
} }
// Going to save the file. For non-Go files, can stop here. // Going to save the file. For non-Go files, can stop here.
switch ext { switch ext {
case ".c": case ".go":
p.CFiles = append(p.CFiles, name) // keep going
continue
case ".cc", ".cpp", ".cxx":
p.CXXFiles = append(p.CXXFiles, name)
continue
case ".m":
p.MFiles = append(p.MFiles, name)
continue
case ".h", ".hh", ".hpp", ".hxx":
p.HFiles = append(p.HFiles, name)
continue
case ".f", ".F", ".for", ".f90":
p.FFiles = append(p.FFiles, name)
continue
case ".s":
p.SFiles = append(p.SFiles, name)
continue
case ".S", ".sx": case ".S", ".sx":
// special case for cgo, handled at end
Sfiles = append(Sfiles, name) Sfiles = append(Sfiles, name)
continue continue
case ".swig": default:
p.SwigFiles = append(p.SwigFiles, name) if list := fileListForExt(p, ext); list != nil {
continue *list = append(*list, name)
case ".swigcxx": }
p.SwigCXXFiles = append(p.SwigCXXFiles, name)
continue
case ".syso":
// binary objects to add to package archive
// Likely of the form foo_windows.syso, but
// the name was vetted above with goodOSArchFile.
p.SysoFiles = append(p.SysoFiles, name)
continue continue
} }
@ -996,6 +979,9 @@ Found:
if len(p.CgoFiles) > 0 { if len(p.CgoFiles) > 0 {
p.SFiles = append(p.SFiles, Sfiles...) p.SFiles = append(p.SFiles, Sfiles...)
sort.Strings(p.SFiles) sort.Strings(p.SFiles)
} else {
p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, Sfiles...)
sort.Strings(p.IgnoredOtherFiles)
} }
if badGoError != nil { if badGoError != nil {
@ -1007,6 +993,30 @@ Found:
return p, pkgerr return p, pkgerr
} }
func fileListForExt(p *Package, ext string) *[]string {
switch ext {
case ".c":
return &p.CFiles
case ".cc", ".cpp", ".cxx":
return &p.CXXFiles
case ".m":
return &p.MFiles
case ".h", ".hh", ".hpp", ".hxx":
return &p.HFiles
case ".f", ".F", ".for", ".f90":
return &p.FFiles
case ".s", ".S", ".sx":
return &p.SFiles
case ".swig":
return &p.SwigFiles
case ".swigcxx":
return &p.SwigCXXFiles
case ".syso":
return &p.SysoFiles
}
return nil
}
var errNoModules = errors.New("not using modules") var errNoModules = errors.New("not using modules")
// importGo checks whether it can use the go command to find the directory for path. // importGo checks whether it can use the go command to find the directory for path.
@ -1302,6 +1312,8 @@ func (ctxt *Context) MatchFile(dir, name string) (match bool, err error) {
return return
} }
var dummyPkg Package
// matchFile determines whether the file with the given name in the given directory // matchFile determines whether the file with the given name in the given directory
// should be included in the package being constructed. // should be included in the package being constructed.
// It returns the data read from the file. // It returns the data read from the file.
@ -1326,16 +1338,15 @@ func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binary
return return
} }
switch ext { if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil {
case ".go", ".c", ".cc", ".cxx", ".cpp", ".m", ".s", ".h", ".hh", ".hpp", ".hxx", ".f", ".F", ".f90", ".S", ".sx", ".swig", ".swigcxx": // skip
// tentatively okay - read to make sure return
case ".syso": }
if ext == ".syso" {
// binary, no reading // binary, no reading
match = true match = true
return return
default:
// skip
return
} }
filename = ctxt.joinPath(dir, name) filename = ctxt.joinPath(dir, name)
@ -1360,8 +1371,8 @@ func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binary
} }
// Look for +build comments to accept or reject the file. // Look for +build comments to accept or reject the file.
var sawBinaryOnly bool ok, sawBinaryOnly := ctxt.shouldBuild(data, allTags)
if !ctxt.shouldBuild(data, allTags, &sawBinaryOnly) && !ctxt.UseAllFiles { if !ok && !ctxt.UseAllFiles {
return return
} }
@ -1411,10 +1422,11 @@ var binaryOnlyComment = []byte("//go:binary-only-package")
// //
// marks the file as applicable only on Windows and Linux. // marks the file as applicable only on Windows and Linux.
// //
// If shouldBuild finds a //go:binary-only-package comment in the file, // For each build tag it consults, shouldBuild sets allTags[tag] = true.
// it sets *binaryOnly to true. Otherwise it does not change *binaryOnly.
// //
func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool, binaryOnly *bool) bool { // shouldBuild reports whether the file should be built
// and whether a //go:binary-only-package comment was found.
func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) (shouldBuild bool, binaryOnly bool) {
sawBinaryOnly := false sawBinaryOnly := false
// Pass 1. Identify leading run of // comments and blank lines, // Pass 1. Identify leading run of // comments and blank lines,
@ -1441,7 +1453,7 @@ func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool, binary
// Pass 2. Process each line in the run. // Pass 2. Process each line in the run.
p = content p = content
allok := true shouldBuild = true
for len(p) > 0 { for len(p) > 0 {
line := p line := p
if i := bytes.IndexByte(line, '\n'); i >= 0 { if i := bytes.IndexByte(line, '\n'); i >= 0 {
@ -1468,17 +1480,13 @@ func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool, binary
} }
} }
if !ok { if !ok {
allok = false shouldBuild = false
} }
} }
} }
} }
if binaryOnly != nil && sawBinaryOnly { return shouldBuild, sawBinaryOnly
*binaryOnly = true
}
return allok
} }
// saveCgo saves the information from the #cgo lines in the import "C" comment. // saveCgo saves the information from the #cgo lines in the import "C" comment.

View file

@ -6,6 +6,7 @@ package build
import ( import (
"flag" "flag"
"fmt"
"internal/testenv" "internal/testenv"
"io" "io"
"io/ioutil" "io/ioutil"
@ -120,7 +121,7 @@ func TestMultiplePackageImport(t *testing.T) {
} }
func TestLocalDirectory(t *testing.T) { func TestLocalDirectory(t *testing.T) {
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" { if runtime.GOOS == "ios" {
t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH) t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH)
} }
@ -138,48 +139,78 @@ func TestLocalDirectory(t *testing.T) {
} }
} }
func TestShouldBuild(t *testing.T) { var shouldBuildTests = []struct {
const file1 = "// +build tag1\n\n" + content string
"package main\n" tags map[string]bool
want1 := map[string]bool{"tag1": true} binaryOnly bool
shouldBuild bool
const file2 = "// +build cgo\n\n" + }{
{
content: "// +build yes\n\n" +
"package main\n",
tags: map[string]bool{"yes": true},
shouldBuild: true,
},
{
content: "// +build no yes\n\n" +
"package main\n",
tags: map[string]bool{"yes": true, "no": true},
shouldBuild: true,
},
{
content: "// +build no,yes no\n\n" +
"package main\n",
tags: map[string]bool{"yes": true, "no": true},
shouldBuild: false,
},
{
content: "// +build cgo\n\n" +
"// Copyright The Go Authors.\n\n" +
"// This package implements parsing of tags like\n" + "// This package implements parsing of tags like\n" +
"// +build tag1\n" + "// +build tag1\n" +
"package build" "package build",
want2 := map[string]bool{"cgo": true} tags: map[string]bool{"cgo": true},
shouldBuild: false,
const file3 = "// Copyright The Go Authors.\n\n" + },
{
content: "// Copyright The Go Authors.\n\n" +
"package build\n\n" + "package build\n\n" +
"// shouldBuild checks tags given by lines of the form\n" + "// shouldBuild checks tags given by lines of the form\n" +
"// +build tag\n" + "// +build tag\n" +
"func shouldBuild(content []byte)\n" "func shouldBuild(content []byte)\n",
want3 := map[string]bool{} tags: map[string]bool{},
shouldBuild: true,
},
{
// too close to package line
content: "// +build yes\n" +
"package main\n",
tags: map[string]bool{},
shouldBuild: true,
},
{
// too close to package line
content: "// +build no\n" +
"package main\n",
tags: map[string]bool{},
shouldBuild: true,
},
}
ctx := &Context{BuildTags: []string{"tag1"}} func TestShouldBuild(t *testing.T) {
m := map[string]bool{} for i, tt := range shouldBuildTests {
if !ctx.shouldBuild([]byte(file1), m, nil) { t.Run(fmt.Sprint(i), func(t *testing.T) {
t.Errorf("shouldBuild(file1) = false, want true") ctx := &Context{BuildTags: []string{"yes"}}
tags := map[string]bool{}
shouldBuild, binaryOnly := ctx.shouldBuild([]byte(tt.content), tags)
if shouldBuild != tt.shouldBuild || binaryOnly != tt.binaryOnly || !reflect.DeepEqual(tags, tt.tags) {
t.Errorf("mismatch:\n"+
"have shouldBuild=%v, binaryOnly=%v, tags=%v\n"+
"want shouldBuild=%v, binaryOnly=%v, tags=%v",
shouldBuild, binaryOnly, tags,
tt.shouldBuild, tt.binaryOnly, tt.tags)
} }
if !reflect.DeepEqual(m, want1) { })
t.Errorf("shouldBuild(file1) tags = %v, want %v", m, want1)
}
m = map[string]bool{}
if ctx.shouldBuild([]byte(file2), m, nil) {
t.Errorf("shouldBuild(file2) = true, want false")
}
if !reflect.DeepEqual(m, want2) {
t.Errorf("shouldBuild(file2) tags = %v, want %v", m, want2)
}
m = map[string]bool{}
ctx = &Context{BuildTags: nil}
if !ctx.shouldBuild([]byte(file3), m, nil) {
t.Errorf("shouldBuild(file3) = false, want true")
}
if !reflect.DeepEqual(m, want3) {
t.Errorf("shouldBuild(file3) tags = %v, want %v", m, want3)
} }
} }
@ -250,7 +281,7 @@ func TestMatchFile(t *testing.T) {
} }
func TestImportCmd(t *testing.T) { func TestImportCmd(t *testing.T) {
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" { if runtime.GOOS == "ios" {
t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH) t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH)
} }

View file

@ -318,7 +318,6 @@ var depsRules = `
# so large dependencies must be kept out. # so large dependencies must be kept out.
# This is a long-looking list but most of these # This is a long-looking list but most of these
# are small with few dependencies. # are small with few dependencies.
# math/rand should probably be removed at some point.
CGO, CGO,
golang.org/x/net/dns/dnsmessage, golang.org/x/net/dns/dnsmessage,
golang.org/x/net/lif, golang.org/x/net/lif,
@ -327,11 +326,11 @@ var depsRules = `
internal/poll, internal/poll,
internal/singleflight, internal/singleflight,
internal/race, internal/race,
math/rand,
os os
< net; < net;
fmt, unicode !< net; fmt, unicode !< net;
math/rand !< net; # net uses runtime instead
# NET is net plus net-helper packages. # NET is net plus net-helper packages.
FMT, net FMT, net
@ -449,7 +448,7 @@ var depsRules = `
OS, compress/gzip, regexp OS, compress/gzip, regexp
< internal/profile; < internal/profile;
html/template, internal/profile, net/http, runtime/pprof, runtime/trace html, internal/profile, net/http, runtime/pprof, runtime/trace
< net/http/pprof; < net/http/pprof;
# RPC # RPC
@ -479,7 +478,7 @@ var depsRules = `
CGO, OS, fmt CGO, OS, fmt
< os/signal/internal/pty; < os/signal/internal/pty;
NET, testing NET, testing, math/rand
< golang.org/x/net/nettest; < golang.org/x/net/nettest;
FMT, container/heap, math/rand FMT, container/heap, math/rand

View file

@ -6,12 +6,11 @@
// These hash functions are intended to be used to implement hash tables or // These hash functions are intended to be used to implement hash tables or
// other data structures that need to map arbitrary strings or byte // other data structures that need to map arbitrary strings or byte
// sequences to a uniform distribution on unsigned 64-bit integers. // sequences to a uniform distribution on unsigned 64-bit integers.
// Each different instance of a hash table or data structure should use its own Seed.
// //
// The hash functions are collision-resistant but not cryptographically secure. // The hash functions are not cryptographically secure.
// (See crypto/sha256 and crypto/sha512 for cryptographic use.) // (See crypto/sha256 and crypto/sha512 for cryptographic use.)
// //
// The hash value of a given byte sequence is consistent within a
// single process, but will be different in different processes.
package maphash package maphash
import "unsafe" import "unsafe"

View file

@ -1302,7 +1302,7 @@ func TestUnterminatedStringError(t *testing.T) {
t.Fatal("expected error") t.Fatal("expected error")
} }
str := err.Error() str := err.Error()
if !strings.Contains(str, "X:3: unexpected unterminated raw quoted string") { if !strings.Contains(str, "X:3: unterminated raw quoted string") {
t.Fatalf("unexpected error: %s", str) t.Fatalf("unexpected error: %s", str)
} }
} }

Some files were not shown because too many files have changed in this diff Show more