From 01fd2d05c8b7bfc083977ca73123a5541b289737 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Wed, 23 Dec 2020 00:58:27 -0500 Subject: [dev.regabi] cmd/compile: split out package dwarfgen [generated] [git-generate] cd src/cmd/compile/internal/gc rf ' # Inline and remove ngotype. ex { import "cmd/compile/internal/ir" import "cmd/compile/internal/reflectdata" var n ir.Node ngotype(n) -> reflectdata.TypeSym(n.Type()) } rm ngotype mv recordFlags RecordFlags mv recordPackageName RecordPackageName mv RecordFlags RecordPackageName dwarf.go mv debuginfo Info mv genAbstractFunc AbstractFunc mv scope.go scope_test.go dwarf.go dwinl.go cmd/compile/internal/dwarfgen ' Change-Id: I31fa982900dbba2066ca4c7a706af922e5481c70 Reviewed-on: https://go-review.googlesource.com/c/go/+/279477 Trust: Russ Cox Run-TryBot: Russ Cox Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/dwarfgen/dwarf.go | 483 +++++++++++++++++++++ src/cmd/compile/internal/dwarfgen/dwinl.go | 453 ++++++++++++++++++++ src/cmd/compile/internal/dwarfgen/scope.go | 112 +++++ src/cmd/compile/internal/dwarfgen/scope_test.go | 539 ++++++++++++++++++++++++ src/cmd/compile/internal/gc/dwarf.go | 412 ------------------ src/cmd/compile/internal/gc/dwinl.go | 452 -------------------- src/cmd/compile/internal/gc/main.go | 76 +--- src/cmd/compile/internal/gc/obj.go | 2 +- src/cmd/compile/internal/gc/scope.go | 111 ----- src/cmd/compile/internal/gc/scope_test.go | 538 ----------------------- src/cmd/compile/internal/gc/subr.go | 8 - 11 files changed, 1593 insertions(+), 1593 deletions(-) create mode 100644 src/cmd/compile/internal/dwarfgen/dwarf.go create mode 100644 src/cmd/compile/internal/dwarfgen/dwinl.go create mode 100644 src/cmd/compile/internal/dwarfgen/scope.go create mode 100644 src/cmd/compile/internal/dwarfgen/scope_test.go delete mode 100644 src/cmd/compile/internal/gc/dwarf.go delete mode 100644 src/cmd/compile/internal/gc/dwinl.go delete mode 100644 src/cmd/compile/internal/gc/scope.go delete mode 100644 src/cmd/compile/internal/gc/scope_test.go (limited to 'src') diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go new file mode 100644 index 0000000000..19cb70058c --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -0,0 +1,483 @@ +// Copyright 2011 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 dwarfgen + +import ( + "bytes" + "flag" + "fmt" + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/types" + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) { + fn := curfn.(*ir.Func) + + if fn.Nname != nil { + expect := fn.Sym().Linksym() + if fnsym.ABI() == obj.ABI0 { + expect = fn.Sym().LinksymABI0() + } + if fnsym != expect { + base.Fatalf("unexpected fnsym: %v != %v", fnsym, expect) + } + } + + // Back when there were two different *Funcs for a function, this code + // was not consistent about whether a particular *Node being processed + // was an ODCLFUNC or ONAME node. Partly this is because inlined function + // bodies have no ODCLFUNC node, which was it's own inconsistency. + // In any event, the handling of the two different nodes for DWARF purposes + // was subtly different, likely in unintended ways. CL 272253 merged the + // two nodes' Func fields, so that code sees the same *Func whether it is + // holding the ODCLFUNC or the ONAME. This resulted in changes in the + // DWARF output. To preserve the existing DWARF output and leave an + // intentional change for a future CL, this code does the following when + // fn.Op == ONAME: + // + // 1. Disallow use of createComplexVars in createDwarfVars. + // It was not possible to reach that code for an ONAME before, + // because the DebugInfo was set only on the ODCLFUNC Func. + // Calling into it in the ONAME case causes an index out of bounds panic. + // + // 2. Do not populate apdecls. fn.Func.Dcl was in the ODCLFUNC Func, + // not the ONAME Func. Populating apdecls for the ONAME case results + // in selected being populated after createSimpleVars is called in + // createDwarfVars, and then that causes the loop to skip all the entries + // in dcl, meaning that the RecordAutoType calls don't happen. + // + // These two adjustments keep toolstash -cmp working for now. + // Deciding the right answer is, as they say, future work. + // + // We can tell the difference between the old ODCLFUNC and ONAME + // cases by looking at the infosym.Name. If it's empty, DebugInfo is + // being called from (*obj.Link).populateDWARF, which used to use + // the ODCLFUNC. If it's non-empty (the name will end in $abstract), + // DebugInfo is being called from (*obj.Link).DwarfAbstractFunc, + // which used to use the ONAME form. + isODCLFUNC := infosym.Name == "" + + var apdecls []*ir.Name + // Populate decls for fn. + if isODCLFUNC { + for _, n := range fn.Dcl { + if n.Op() != ir.ONAME { // might be OTYPE or OLITERAL + continue + } + switch n.Class_ { + case ir.PAUTO: + if !n.Used() { + // Text == nil -> generating abstract function + if fnsym.Func().Text != nil { + base.Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)") + } + continue + } + case ir.PPARAM, ir.PPARAMOUT: + default: + continue + } + apdecls = append(apdecls, n) + fnsym.Func().RecordAutoType(reflectdata.TypeSym(n.Type()).Linksym()) + } + } + + decls, dwarfVars := createDwarfVars(fnsym, isODCLFUNC, fn, apdecls) + + // For each type referenced by the functions auto vars but not + // already referenced by a dwarf var, attach an R_USETYPE relocation to + // the function symbol to insure that the type included in DWARF + // processing during linking. + typesyms := []*obj.LSym{} + for t, _ := range fnsym.Func().Autot { + typesyms = append(typesyms, t) + } + sort.Sort(obj.BySymName(typesyms)) + for _, sym := range typesyms { + r := obj.Addrel(infosym) + r.Sym = sym + r.Type = objabi.R_USETYPE + } + fnsym.Func().Autot = nil + + var varScopes []ir.ScopeID + for _, decl := range decls { + pos := declPos(decl) + varScopes = append(varScopes, findScope(fn.Marks, pos)) + } + + scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes) + var inlcalls dwarf.InlCalls + if base.Flag.GenDwarfInl > 0 { + inlcalls = assembleInlines(fnsym, dwarfVars) + } + return scopes, inlcalls +} + +func declPos(decl *ir.Name) src.XPos { + if decl.Name().Defn != nil && (decl.Name().Captured() || decl.Name().Byval()) { + // It's not clear which position is correct for captured variables here: + // * decl.Pos is the wrong position for captured variables, in the inner + // function, but it is the right position in the outer function. + // * decl.Name.Defn is nil for captured variables that were arguments + // on the outer function, however the decl.Pos for those seems to be + // correct. + // * decl.Name.Defn is the "wrong" thing for variables declared in the + // header of a type switch, it's their position in the header, rather + // than the position of the case statement. In principle this is the + // right thing, but here we prefer the latter because it makes each + // instance of the header variable local to the lexical block of its + // case statement. + // This code is probably wrong for type switch variables that are also + // captured. + return decl.Name().Defn.Pos() + } + return decl.Pos() +} + +// createDwarfVars process fn, returning a list of DWARF variables and the +// Nodes they represent. +func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var) { + // Collect a raw list of DWARF vars. + var vars []*dwarf.Var + var decls []*ir.Name + var selected map[*ir.Name]bool + if base.Ctxt.Flag_locationlists && base.Ctxt.Flag_optimize && fn.DebugInfo != nil && complexOK { + decls, vars, selected = createComplexVars(fnsym, fn) + } else { + decls, vars, selected = createSimpleVars(fnsym, apDecls) + } + + dcl := apDecls + if fnsym.WasInlined() { + dcl = preInliningDcls(fnsym) + } + + // If optimization is enabled, the list above will typically be + // missing some of the original pre-optimization variables in the + // function (they may have been promoted to registers, folded into + // constants, dead-coded away, etc). Input arguments not eligible + // for SSA optimization are also missing. Here we add back in entries + // for selected missing vars. Note that the recipe below creates a + // conservative location. The idea here is that we want to + // communicate to the user that "yes, there is a variable named X + // in this function, but no, I don't have enough information to + // reliably report its contents." + // For non-SSA-able arguments, however, the correct information + // is known -- they have a single home on the stack. + for _, n := range dcl { + if _, found := selected[n]; found { + continue + } + c := n.Sym().Name[0] + if c == '.' || n.Type().IsUntyped() { + continue + } + if n.Class_ == ir.PPARAM && !ssagen.TypeOK(n.Type()) { + // SSA-able args get location lists, and may move in and + // out of registers, so those are handled elsewhere. + // Autos and named output params seem to get handled + // with VARDEF, which creates location lists. + // Args not of SSA-able type are treated here; they + // are homed on the stack in a single place for the + // entire call. + vars = append(vars, createSimpleVar(fnsym, n)) + decls = append(decls, n) + continue + } + typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) + decls = append(decls, n) + abbrev := dwarf.DW_ABRV_AUTO_LOCLIST + isReturnValue := (n.Class_ == ir.PPARAMOUT) + if n.Class_ == ir.PPARAM || n.Class_ == ir.PPARAMOUT { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } else if n.Class_ == ir.PAUTOHEAP { + // If dcl in question has been promoted to heap, do a bit + // of extra work to recover original class (auto or param); + // see issue 30908. This insures that we get the proper + // signature in the abstract function DIE, but leaves a + // misleading location for the param (we want pointer-to-heap + // and not stack). + // TODO(thanm): generate a better location expression + stackcopy := n.Name().Stackcopy + if stackcopy != nil && (stackcopy.Class_ == ir.PPARAM || stackcopy.Class_ == ir.PPARAMOUT) { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + isReturnValue = (stackcopy.Class_ == ir.PPARAMOUT) + } + } + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.Name().InlFormal() || n.Name().InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.Name().InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } + } + } + declpos := base.Ctxt.InnermostPos(n.Pos()) + vars = append(vars, &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: isReturnValue, + Abbrev: abbrev, + StackOffset: int32(n.FrameOffset()), + Type: base.Ctxt.Lookup(typename), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.Col(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + }) + // Record go type of to insure that it gets emitted by the linker. + fnsym.Func().RecordAutoType(reflectdata.TypeSym(n.Type()).Linksym()) + } + + return decls, vars +} + +// Given a function that was inlined at some point during the +// compilation, return a sorted list of nodes corresponding to the +// autos/locals in that function prior to inlining. If this is a +// function that is not local to the package being compiled, then the +// names of the variables may have been "versioned" to avoid conflicts +// with local vars; disregard this versioning when sorting. +func preInliningDcls(fnsym *obj.LSym) []*ir.Name { + fn := base.Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*ir.Func) + var rdcl []*ir.Name + for _, n := range fn.Inl.Dcl { + c := n.Sym().Name[0] + // Avoid reporting "_" parameters, since if there are more than + // one, it can result in a collision later on, as in #23179. + if unversion(n.Sym().Name) == "_" || c == '.' || n.Type().IsUntyped() { + continue + } + rdcl = append(rdcl, n) + } + return rdcl +} + +// createSimpleVars creates a DWARF entry for every variable declared in the +// function, claiming that they are permanently on the stack. +func createSimpleVars(fnsym *obj.LSym, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, map[*ir.Name]bool) { + var vars []*dwarf.Var + var decls []*ir.Name + selected := make(map[*ir.Name]bool) + for _, n := range apDecls { + if ir.IsAutoTmp(n) { + continue + } + + decls = append(decls, n) + vars = append(vars, createSimpleVar(fnsym, n)) + selected[n] = true + } + return decls, vars, selected +} + +func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var { + var abbrev int + var offs int64 + + switch n.Class_ { + case ir.PAUTO: + offs = n.FrameOffset() + abbrev = dwarf.DW_ABRV_AUTO + if base.Ctxt.FixedFrameSize() == 0 { + offs -= int64(types.PtrSize) + } + if objabi.Framepointer_enabled || objabi.GOARCH == "arm64" { + // There is a word space for FP on ARM64 even if the frame pointer is disabled + offs -= int64(types.PtrSize) + } + + case ir.PPARAM, ir.PPARAMOUT: + abbrev = dwarf.DW_ABRV_PARAM + offs = n.FrameOffset() + base.Ctxt.FixedFrameSize() + default: + base.Fatalf("createSimpleVar unexpected class %v for node %v", n.Class_, n) + } + + typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) + delete(fnsym.Func().Autot, reflectdata.TypeSym(n.Type()).Linksym()) + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.Name().InlFormal() || n.Name().InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.Name().InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM + } + } + } + declpos := base.Ctxt.InnermostPos(declPos(n)) + return &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: n.Class_ == ir.PPARAMOUT, + IsInlFormal: n.Name().InlFormal(), + Abbrev: abbrev, + StackOffset: int32(offs), + Type: base.Ctxt.Lookup(typename), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.Col(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + } +} + +// createComplexVars creates recomposed DWARF vars with location lists, +// suitable for describing optimized code. +func createComplexVars(fnsym *obj.LSym, fn *ir.Func) ([]*ir.Name, []*dwarf.Var, map[*ir.Name]bool) { + debugInfo := fn.DebugInfo.(*ssa.FuncDebug) + + // Produce a DWARF variable entry for each user variable. + var decls []*ir.Name + var vars []*dwarf.Var + ssaVars := make(map[*ir.Name]bool) + + for varID, dvar := range debugInfo.Vars { + n := dvar + ssaVars[n] = true + for _, slot := range debugInfo.VarSlots[varID] { + ssaVars[debugInfo.Slots[slot].N] = true + } + + if dvar := createComplexVar(fnsym, fn, ssa.VarID(varID)); dvar != nil { + decls = append(decls, n) + vars = append(vars, dvar) + } + } + + return decls, vars, ssaVars +} + +// createComplexVar builds a single DWARF variable entry and location list. +func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var { + debug := fn.DebugInfo.(*ssa.FuncDebug) + n := debug.Vars[varID] + + var abbrev int + switch n.Class_ { + case ir.PAUTO: + abbrev = dwarf.DW_ABRV_AUTO_LOCLIST + case ir.PPARAM, ir.PPARAMOUT: + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + default: + return nil + } + + gotype := reflectdata.TypeSym(n.Type()).Linksym() + delete(fnsym.Func().Autot, gotype) + typename := dwarf.InfoPrefix + gotype.Name[len("type."):] + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.Name().InlFormal() || n.Name().InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.Name().InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } + } + } + declpos := base.Ctxt.InnermostPos(n.Pos()) + dvar := &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: n.Class_ == ir.PPARAMOUT, + IsInlFormal: n.Name().InlFormal(), + Abbrev: abbrev, + Type: base.Ctxt.Lookup(typename), + // The stack offset is used as a sorting key, so for decomposed + // variables just give it the first one. It's not used otherwise. + // This won't work well if the first slot hasn't been assigned a stack + // location, but it's not obvious how to do better. + StackOffset: ssagen.StackOffset(debug.Slots[debug.VarSlots[varID][0]]), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.Col(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + } + list := debug.LocationLists[varID] + if len(list) != 0 { + dvar.PutLocationList = func(listSym, startPC dwarf.Sym) { + debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym)) + } + } + return dvar +} + +// RecordFlags records the specified command-line flags to be placed +// in the DWARF info. +func RecordFlags(flags ...string) { + if base.Ctxt.Pkgpath == "" { + // We can't record the flags if we don't know what the + // package name is. + return + } + + type BoolFlag interface { + IsBoolFlag() bool + } + type CountFlag interface { + IsCountFlag() bool + } + var cmd bytes.Buffer + for _, name := range flags { + f := flag.Lookup(name) + if f == nil { + continue + } + getter := f.Value.(flag.Getter) + if getter.String() == f.DefValue { + // Flag has default value, so omit it. + continue + } + if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() { + val, ok := getter.Get().(bool) + if ok && val { + fmt.Fprintf(&cmd, " -%s", f.Name) + continue + } + } + if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() { + val, ok := getter.Get().(int) + if ok && val == 1 { + fmt.Fprintf(&cmd, " -%s", f.Name) + continue + } + } + fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get()) + } + + if cmd.Len() == 0 { + return + } + s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + base.Ctxt.Pkgpath) + s.Type = objabi.SDWARFCUINFO + // Sometimes (for example when building tests) we can link + // together two package main archives. So allow dups. + s.Set(obj.AttrDuplicateOK, true) + base.Ctxt.Data = append(base.Ctxt.Data, s) + s.P = cmd.Bytes()[1:] +} + +// RecordPackageName records the name of the package being +// compiled, so that the linker can save it in the compile unit's DIE. +func RecordPackageName() { + s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + base.Ctxt.Pkgpath) + s.Type = objabi.SDWARFCUINFO + // Sometimes (for example when building tests) we can link + // together two package main archives. So allow dups. + s.Set(obj.AttrDuplicateOK, true) + base.Ctxt.Data = append(base.Ctxt.Data, s) + s.P = []byte(types.LocalPkg.Name) +} diff --git a/src/cmd/compile/internal/dwarfgen/dwinl.go b/src/cmd/compile/internal/dwarfgen/dwinl.go new file mode 100644 index 0000000000..d5687cb1d7 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/dwinl.go @@ -0,0 +1,453 @@ +// Copyright 2017 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 dwarfgen + +import ( + "fmt" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/src" +) + +// To identify variables by original source position. +type varPos struct { + DeclName string + DeclFile string + DeclLine uint + DeclCol uint +} + +// This is the main entry point for collection of raw material to +// drive generation of DWARF "inlined subroutine" DIEs. See proposal +// 22080 for more details and background info. +func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls { + var inlcalls dwarf.InlCalls + + if base.Debug.DwarfInl != 0 { + base.Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name) + } + + // This maps inline index (from Ctxt.InlTree) to index in inlcalls.Calls + imap := make(map[int]int) + + // Walk progs to build up the InlCalls data structure + var prevpos src.XPos + for p := fnsym.Func().Text; p != nil; p = p.Link { + if p.Pos == prevpos { + continue + } + ii := posInlIndex(p.Pos) + if ii >= 0 { + insertInlCall(&inlcalls, ii, imap) + } + prevpos = p.Pos + } + + // This is used to partition DWARF vars by inline index. Vars not + // produced by the inliner will wind up in the vmap[0] entry. + vmap := make(map[int32][]*dwarf.Var) + + // Now walk the dwarf vars and partition them based on whether they + // were produced by the inliner (dwv.InlIndex > 0) or were original + // vars/params from the function (dwv.InlIndex == 0). + for _, dwv := range dwVars { + + vmap[dwv.InlIndex] = append(vmap[dwv.InlIndex], dwv) + + // Zero index => var was not produced by an inline + if dwv.InlIndex == 0 { + continue + } + + // Look up index in our map, then tack the var in question + // onto the vars list for the correct inlined call. + ii := int(dwv.InlIndex) - 1 + idx, ok := imap[ii] + if !ok { + // We can occasionally encounter a var produced by the + // inliner for which there is no remaining prog; add a new + // entry to the call list in this scenario. + idx = insertInlCall(&inlcalls, ii, imap) + } + inlcalls.Calls[idx].InlVars = + append(inlcalls.Calls[idx].InlVars, dwv) + } + + // Post process the map above to assign child indices to vars. + // + // A given variable is treated differently depending on whether it + // is part of the top-level function (ii == 0) or if it was + // produced as a result of an inline (ii != 0). + // + // If a variable was not produced by an inline and its containing + // function was not inlined, then we just assign an ordering of + // based on variable name. + // + // If a variable was not produced by an inline and its containing + // function was inlined, then we need to assign a child index + // based on the order of vars in the abstract function (in + // addition, those vars that don't appear in the abstract + // function, such as "~r1", are flagged as such). + // + // If a variable was produced by an inline, then we locate it in + // the pre-inlining decls for the target function and assign child + // index accordingly. + for ii, sl := range vmap { + var m map[varPos]int + if ii == 0 { + if !fnsym.WasInlined() { + for j, v := range sl { + v.ChildIndex = int32(j) + } + continue + } + m = makePreinlineDclMap(fnsym) + } else { + ifnlsym := base.Ctxt.InlTree.InlinedFunction(int(ii - 1)) + m = makePreinlineDclMap(ifnlsym) + } + + // Here we assign child indices to variables based on + // pre-inlined decls, and set the "IsInAbstract" flag + // appropriately. In addition: parameter and local variable + // names are given "middle dot" version numbers as part of the + // writing them out to export data (see issue 4326). If DWARF + // inlined routine generation is turned on, we want to undo + // this versioning, since DWARF variables in question will be + // parented by the inlined routine and not the top-level + // caller. + synthCount := len(m) + for _, v := range sl { + canonName := unversion(v.Name) + vp := varPos{ + DeclName: canonName, + DeclFile: v.DeclFile, + DeclLine: v.DeclLine, + DeclCol: v.DeclCol, + } + synthesized := strings.HasPrefix(v.Name, "~r") || canonName == "_" || strings.HasPrefix(v.Name, "~b") + if idx, found := m[vp]; found { + v.ChildIndex = int32(idx) + v.IsInAbstract = !synthesized + v.Name = canonName + } else { + // Variable can't be found in the pre-inline dcl list. + // In the top-level case (ii=0) this can happen + // because a composite variable was split into pieces, + // and we're looking at a piece. We can also see + // return temps (~r%d) that were created during + // lowering, or unnamed params ("_"). + v.ChildIndex = int32(synthCount) + synthCount++ + } + } + } + + // Make a second pass through the progs to compute PC ranges for + // the various inlined calls. + start := int64(-1) + curii := -1 + var prevp *obj.Prog + for p := fnsym.Func().Text; p != nil; prevp, p = p, p.Link { + if prevp != nil && p.Pos == prevp.Pos { + continue + } + ii := posInlIndex(p.Pos) + if ii == curii { + continue + } + // Close out the current range + if start != -1 { + addRange(inlcalls.Calls, start, p.Pc, curii, imap) + } + // Begin new range + start = p.Pc + curii = ii + } + if start != -1 { + addRange(inlcalls.Calls, start, fnsym.Size, curii, imap) + } + + // Issue 33188: if II foo is a child of II bar, then ensure that + // bar's ranges include the ranges of foo (the loop above will produce + // disjoint ranges). + for k, c := range inlcalls.Calls { + if c.Root { + unifyCallRanges(inlcalls, k) + } + } + + // Debugging + if base.Debug.DwarfInl != 0 { + dumpInlCalls(inlcalls) + dumpInlVars(dwVars) + } + + // Perform a consistency check on inlined routine PC ranges + // produced by unifyCallRanges above. In particular, complain in + // cases where you have A -> B -> C (e.g. C is inlined into B, and + // B is inlined into A) and the ranges for B are not enclosed + // within the ranges for A, or C within B. + for k, c := range inlcalls.Calls { + if c.Root { + checkInlCall(fnsym.Name, inlcalls, fnsym.Size, k, -1) + } + } + + return inlcalls +} + +// Secondary hook for DWARF inlined subroutine generation. This is called +// late in the compilation when it is determined that we need an +// abstract function DIE for an inlined routine imported from a +// previously compiled package. +func AbstractFunc(fn *obj.LSym) { + ifn := base.Ctxt.DwFixups.GetPrecursorFunc(fn) + if ifn == nil { + base.Ctxt.Diag("failed to locate precursor fn for %v", fn) + return + } + _ = ifn.(*ir.Func) + if base.Debug.DwarfInl != 0 { + base.Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name) + } + base.Ctxt.DwarfAbstractFunc(ifn, fn, base.Ctxt.Pkgpath) +} + +// Undo any versioning performed when a name was written +// out as part of export data. +func unversion(name string) string { + if i := strings.Index(name, "·"); i > 0 { + name = name[:i] + } + return name +} + +// Given a function that was inlined as part of the compilation, dig +// up the pre-inlining DCL list for the function and create a map that +// supports lookup of pre-inline dcl index, based on variable +// position/name. NB: the recipe for computing variable pos/file/line +// needs to be kept in sync with the similar code in gc.createSimpleVars +// and related functions. +func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int { + dcl := preInliningDcls(fnsym) + m := make(map[varPos]int) + for i, n := range dcl { + pos := base.Ctxt.InnermostPos(n.Pos()) + vp := varPos{ + DeclName: unversion(n.Sym().Name), + DeclFile: pos.RelFilename(), + DeclLine: pos.RelLine(), + DeclCol: pos.Col(), + } + if _, found := m[vp]; found { + base.Fatalf("child dcl collision on symbol %s within %v\n", n.Sym().Name, fnsym.Name) + } + m[vp] = i + } + return m +} + +func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int { + callIdx, found := imap[inlIdx] + if found { + return callIdx + } + + // Haven't seen this inline yet. Visit parent of inline if there + // is one. We do this first so that parents appear before their + // children in the resulting table. + parCallIdx := -1 + parInlIdx := base.Ctxt.InlTree.Parent(inlIdx) + if parInlIdx >= 0 { + parCallIdx = insertInlCall(dwcalls, parInlIdx, imap) + } + + // Create new entry for this inline + inlinedFn := base.Ctxt.InlTree.InlinedFunction(inlIdx) + callXPos := base.Ctxt.InlTree.CallPos(inlIdx) + absFnSym := base.Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn) + pb := base.Ctxt.PosTable.Pos(callXPos).Base() + callFileSym := base.Ctxt.Lookup(pb.SymFilename()) + ic := dwarf.InlCall{ + InlIndex: inlIdx, + CallFile: callFileSym, + CallLine: uint32(callXPos.Line()), + AbsFunSym: absFnSym, + Root: parCallIdx == -1, + } + dwcalls.Calls = append(dwcalls.Calls, ic) + callIdx = len(dwcalls.Calls) - 1 + imap[inlIdx] = callIdx + + if parCallIdx != -1 { + // Add this inline to parent's child list + dwcalls.Calls[parCallIdx].Children = append(dwcalls.Calls[parCallIdx].Children, callIdx) + } + + return callIdx +} + +// Given a src.XPos, return its associated inlining index if it +// corresponds to something created as a result of an inline, or -1 if +// there is no inline info. Note that the index returned will refer to +// the deepest call in the inlined stack, e.g. if you have "A calls B +// calls C calls D" and all three callees are inlined (B, C, and D), +// the index for a node from the inlined body of D will refer to the +// call to D from C. Whew. +func posInlIndex(xpos src.XPos) int { + pos := base.Ctxt.PosTable.Pos(xpos) + if b := pos.Base(); b != nil { + ii := b.InliningIndex() + if ii >= 0 { + return ii + } + } + return -1 +} + +func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) { + if start == -1 { + panic("bad range start") + } + if end == -1 { + panic("bad range end") + } + if ii == -1 { + return + } + if start == end { + return + } + // Append range to correct inlined call + callIdx, found := imap[ii] + if !found { + base.Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start) + } + call := &calls[callIdx] + call.Ranges = append(call.Ranges, dwarf.Range{Start: start, End: end}) +} + +func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) { + for i := 0; i < ilevel; i++ { + base.Ctxt.Logf(" ") + } + ic := inlcalls.Calls[idx] + callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex) + base.Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name) + for _, f := range ic.InlVars { + base.Ctxt.Logf(" %v", f.Name) + } + base.Ctxt.Logf(" ) C: (") + for _, k := range ic.Children { + base.Ctxt.Logf(" %v", k) + } + base.Ctxt.Logf(" ) R:") + for _, r := range ic.Ranges { + base.Ctxt.Logf(" [%d,%d)", r.Start, r.End) + } + base.Ctxt.Logf("\n") + for _, k := range ic.Children { + dumpInlCall(inlcalls, k, ilevel+1) + } + +} + +func dumpInlCalls(inlcalls dwarf.InlCalls) { + for k, c := range inlcalls.Calls { + if c.Root { + dumpInlCall(inlcalls, k, 0) + } + } +} + +func dumpInlVars(dwvars []*dwarf.Var) { + for i, dwv := range dwvars { + typ := "local" + if dwv.Abbrev == dwarf.DW_ABRV_PARAM_LOCLIST || dwv.Abbrev == dwarf.DW_ABRV_PARAM { + typ = "param" + } + ia := 0 + if dwv.IsInAbstract { + ia = 1 + } + base.Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ) + } +} + +func rangesContains(par []dwarf.Range, rng dwarf.Range) (bool, string) { + for _, r := range par { + if rng.Start >= r.Start && rng.End <= r.End { + return true, "" + } + } + msg := fmt.Sprintf("range [%d,%d) not contained in {", rng.Start, rng.End) + for _, r := range par { + msg += fmt.Sprintf(" [%d,%d)", r.Start, r.End) + } + msg += " }" + return false, msg +} + +func rangesContainsAll(parent, child []dwarf.Range) (bool, string) { + for _, r := range child { + c, m := rangesContains(parent, r) + if !c { + return false, m + } + } + return true, "" +} + +// checkInlCall verifies that the PC ranges for inline info 'idx' are +// enclosed/contained within the ranges of its parent inline (or if +// this is a root/toplevel inline, checks that the ranges fall within +// the extent of the top level function). A panic is issued if a +// malformed range is found. +func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, parentIdx int) { + + // Callee + ic := inlCalls.Calls[idx] + callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name + calleeRanges := ic.Ranges + + // Caller + caller := funcName + parentRanges := []dwarf.Range{dwarf.Range{Start: int64(0), End: funcSize}} + if parentIdx != -1 { + pic := inlCalls.Calls[parentIdx] + caller = base.Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name + parentRanges = pic.Ranges + } + + // Callee ranges contained in caller ranges? + c, m := rangesContainsAll(parentRanges, calleeRanges) + if !c { + base.Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m) + } + + // Now visit kids + for _, k := range ic.Children { + checkInlCall(funcName, inlCalls, funcSize, k, idx) + } +} + +// unifyCallRanges ensures that the ranges for a given inline +// transitively include all of the ranges for its child inlines. +func unifyCallRanges(inlcalls dwarf.InlCalls, idx int) { + ic := &inlcalls.Calls[idx] + for _, childIdx := range ic.Children { + // First make sure child ranges are unified. + unifyCallRanges(inlcalls, childIdx) + + // Then merge child ranges into ranges for this inline. + cic := inlcalls.Calls[childIdx] + ic.Ranges = dwarf.MergeRanges(ic.Ranges, cic.Ranges) + } +} diff --git a/src/cmd/compile/internal/dwarfgen/scope.go b/src/cmd/compile/internal/dwarfgen/scope.go new file mode 100644 index 0000000000..1c040edc28 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/scope.go @@ -0,0 +1,112 @@ +// Copyright 2017 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 dwarfgen + +import ( + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/src" +) + +// See golang.org/issue/20390. +func xposBefore(p, q src.XPos) bool { + return base.Ctxt.PosTable.Pos(p).Before(base.Ctxt.PosTable.Pos(q)) +} + +func findScope(marks []ir.Mark, pos src.XPos) ir.ScopeID { + i := sort.Search(len(marks), func(i int) bool { + return xposBefore(pos, marks[i].Pos) + }) + if i == 0 { + return 0 + } + return marks[i-1].Scope +} + +func assembleScopes(fnsym *obj.LSym, fn *ir.Func, dwarfVars []*dwarf.Var, varScopes []ir.ScopeID) []dwarf.Scope { + // Initialize the DWARF scope tree based on lexical scopes. + dwarfScopes := make([]dwarf.Scope, 1+len(fn.Parents)) + for i, parent := range fn.Parents { + dwarfScopes[i+1].Parent = int32(parent) + } + + scopeVariables(dwarfVars, varScopes, dwarfScopes) + scopePCs(fnsym, fn.Marks, dwarfScopes) + return compactScopes(dwarfScopes) +} + +// scopeVariables assigns DWARF variable records to their scopes. +func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ir.ScopeID, dwarfScopes []dwarf.Scope) { + sort.Stable(varsByScopeAndOffset{dwarfVars, varScopes}) + + i0 := 0 + for i := range dwarfVars { + if varScopes[i] == varScopes[i0] { + continue + } + dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:i] + i0 = i + } + if i0 < len(dwarfVars) { + dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:] + } +} + +// scopePCs assigns PC ranges to their scopes. +func scopePCs(fnsym *obj.LSym, marks []ir.Mark, dwarfScopes []dwarf.Scope) { + // If there aren't any child scopes (in particular, when scope + // tracking is disabled), we can skip a whole lot of work. + if len(marks) == 0 { + return + } + p0 := fnsym.Func().Text + scope := findScope(marks, p0.Pos) + for p := p0; p != nil; p = p.Link { + if p.Pos == p0.Pos { + continue + } + dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: p.Pc}) + p0 = p + scope = findScope(marks, p0.Pos) + } + if p0.Pc < fnsym.Size { + dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: fnsym.Size}) + } +} + +func compactScopes(dwarfScopes []dwarf.Scope) []dwarf.Scope { + // Reverse pass to propagate PC ranges to parent scopes. + for i := len(dwarfScopes) - 1; i > 0; i-- { + s := &dwarfScopes[i] + dwarfScopes[s.Parent].UnifyRanges(s) + } + + return dwarfScopes +} + +type varsByScopeAndOffset struct { + vars []*dwarf.Var + scopes []ir.ScopeID +} + +func (v varsByScopeAndOffset) Len() int { + return len(v.vars) +} + +func (v varsByScopeAndOffset) Less(i, j int) bool { + if v.scopes[i] != v.scopes[j] { + return v.scopes[i] < v.scopes[j] + } + return v.vars[i].StackOffset < v.vars[j].StackOffset +} + +func (v varsByScopeAndOffset) Swap(i, j int) { + v.vars[i], v.vars[j] = v.vars[j], v.vars[i] + v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i] +} diff --git a/src/cmd/compile/internal/dwarfgen/scope_test.go b/src/cmd/compile/internal/dwarfgen/scope_test.go new file mode 100644 index 0000000000..fcfcf85f84 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/scope_test.go @@ -0,0 +1,539 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarfgen + +import ( + "debug/dwarf" + "fmt" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "testing" + + "cmd/internal/objfile" +) + +type testline struct { + // line is one line of go source + line string + + // scopes is a list of scope IDs of all the lexical scopes that this line + // of code belongs to. + // Scope IDs are assigned by traversing the tree of lexical blocks of a + // function in pre-order + // Scope IDs are function specific, i.e. scope 0 is always the root scope + // of the function that this line belongs to. Empty scopes are not assigned + // an ID (because they are not saved in debug_info). + // Scope 0 is always omitted from this list since all lines always belong + // to it. + scopes []int + + // vars is the list of variables that belong in scopes[len(scopes)-1]. + // Local variables are prefixed with "var ", formal parameters with "arg ". + // Must be ordered alphabetically. + // Set to nil to skip the check. + vars []string + + // decl is the list of variables declared at this line. + decl []string + + // declBefore is the list of variables declared at or before this line. + declBefore []string +} + +var testfile = []testline{ + {line: "package main"}, + {line: "func f1(x int) { }"}, + {line: "func f2(x int) { }"}, + {line: "func f3(x int) { }"}, + {line: "func f4(x int) { }"}, + {line: "func f5(x int) { }"}, + {line: "func f6(x int) { }"}, + {line: "func fi(x interface{}) { if a, ok := x.(error); ok { a.Error() } }"}, + {line: "func gret1() int { return 2 }"}, + {line: "func gretbool() bool { return true }"}, + {line: "func gret3() (int, int, int) { return 0, 1, 2 }"}, + {line: "var v = []int{ 0, 1, 2 }"}, + {line: "var ch = make(chan int)"}, + {line: "var floatch = make(chan float64)"}, + {line: "var iface interface{}"}, + {line: "func TestNestedFor() {", vars: []string{"var a int"}}, + {line: " a := 0", decl: []string{"a"}}, + {line: " f1(a)"}, + {line: " for i := 0; i < 5; i++ {", scopes: []int{1}, vars: []string{"var i int"}, decl: []string{"i"}}, + {line: " f2(i)", scopes: []int{1}}, + {line: " for i := 0; i < 5; i++ {", scopes: []int{1, 2}, vars: []string{"var i int"}, decl: []string{"i"}}, + {line: " f3(i)", scopes: []int{1, 2}}, + {line: " }"}, + {line: " f4(i)", scopes: []int{1}}, + {line: " }"}, + {line: " f5(a)"}, + {line: "}"}, + {line: "func TestOas2() {", vars: []string{}}, + {line: " if a, b, c := gret3(); a != 1 {", scopes: []int{1}, vars: []string{"var a int", "var b int", "var c int"}}, + {line: " f1(a)", scopes: []int{1}}, + {line: " f1(b)", scopes: []int{1}}, + {line: " f1(c)", scopes: []int{1}}, + {line: " }"}, + {line: " for i, x := range v {", scopes: []int{2}, vars: []string{"var i int", "var x int"}}, + {line: " f1(i)", scopes: []int{2}}, + {line: " f1(x)", scopes: []int{2}}, + {line: " }"}, + {line: " if a, ok := <- ch; ok {", scopes: []int{3}, vars: []string{"var a int", "var ok bool"}}, + {line: " f1(a)", scopes: []int{3}}, + {line: " }"}, + {line: " if a, ok := iface.(int); ok {", scopes: []int{4}, vars: []string{"var a int", "var ok bool"}}, + {line: " f1(a)", scopes: []int{4}}, + {line: " }"}, + {line: "}"}, + {line: "func TestIfElse() {"}, + {line: " if x := gret1(); x != 0 {", scopes: []int{1}, vars: []string{"var x int"}}, + {line: " a := 0", scopes: []int{1, 2}, vars: []string{"var a int"}}, + {line: " f1(a); f1(x)", scopes: []int{1, 2}}, + {line: " } else {"}, + {line: " b := 1", scopes: []int{1, 3}, vars: []string{"var b int"}}, + {line: " f1(b); f1(x+1)", scopes: []int{1, 3}}, + {line: " }"}, + {line: "}"}, + {line: "func TestSwitch() {", vars: []string{}}, + {line: " switch x := gret1(); x {", scopes: []int{1}, vars: []string{"var x int"}}, + {line: " case 0:", scopes: []int{1, 2}}, + {line: " i := x + 5", scopes: []int{1, 2}, vars: []string{"var i int"}}, + {line: " f1(x); f1(i)", scopes: []int{1, 2}}, + {line: " case 1:", scopes: []int{1, 3}}, + {line: " j := x + 10", scopes: []int{1, 3}, vars: []string{"var j int"}}, + {line: " f1(x); f1(j)", scopes: []int{1, 3}}, + {line: " case 2:", scopes: []int{1, 4}}, + {line: " k := x + 2", scopes: []int{1, 4}, vars: []string{"var k int"}}, + {line: " f1(x); f1(k)", scopes: []int{1, 4}}, + {line: " }"}, + {line: "}"}, + {line: "func TestTypeSwitch() {", vars: []string{}}, + {line: " switch x := iface.(type) {"}, + {line: " case int:", scopes: []int{1}}, + {line: " f1(x)", scopes: []int{1}, vars: []string{"var x int"}}, + {line: " case uint8:", scopes: []int{2}}, + {line: " f1(int(x))", scopes: []int{2}, vars: []string{"var x uint8"}}, + {line: " case float64:", scopes: []int{3}}, + {line: " f1(int(x)+1)", scopes: []int{3}, vars: []string{"var x float64"}}, + {line: " }"}, + {line: "}"}, + {line: "func TestSelectScope() {"}, + {line: " select {"}, + {line: " case i := <- ch:", scopes: []int{1}}, + {line: " f1(i)", scopes: []int{1}, vars: []string{"var i int"}}, + {line: " case f := <- floatch:", scopes: []int{2}}, + {line: " f1(int(f))", scopes: []int{2}, vars: []string{"var f float64"}}, + {line: " }"}, + {line: "}"}, + {line: "func TestBlock() {", vars: []string{"var a int"}}, + {line: " a := 1"}, + {line: " {"}, + {line: " b := 2", scopes: []int{1}, vars: []string{"var b int"}}, + {line: " f1(b)", scopes: []int{1}}, + {line: " f1(a)", scopes: []int{1}}, + {line: " }"}, + {line: "}"}, + {line: "func TestDiscontiguousRanges() {", vars: []string{"var a int"}}, + {line: " a := 0"}, + {line: " f1(a)"}, + {line: " {"}, + {line: " b := 0", scopes: []int{1}, vars: []string{"var b int"}}, + {line: " f2(b)", scopes: []int{1}}, + {line: " if gretbool() {", scopes: []int{1}}, + {line: " c := 0", scopes: []int{1, 2}, vars: []string{"var c int"}}, + {line: " f3(c)", scopes: []int{1, 2}}, + {line: " } else {"}, + {line: " c := 1.1", scopes: []int{1, 3}, vars: []string{"var c float64"}}, + {line: " f4(int(c))", scopes: []int{1, 3}}, + {line: " }"}, + {line: " f5(b)", scopes: []int{1}}, + {line: " }"}, + {line: " f6(a)"}, + {line: "}"}, + {line: "func TestClosureScope() {", vars: []string{"var a int", "var b int", "var f func(int)"}}, + {line: " a := 1; b := 1"}, + {line: " f := func(c int) {", scopes: []int{0}, vars: []string{"arg c int", "var &b *int", "var a int", "var d int"}, declBefore: []string{"&b", "a"}}, + {line: " d := 3"}, + {line: " f1(c); f1(d)"}, + {line: " if e := 3; e != 0 {", scopes: []int{1}, vars: []string{"var e int"}}, + {line: " f1(e)", scopes: []int{1}}, + {line: " f1(a)", scopes: []int{1}}, + {line: " b = 2", scopes: []int{1}}, + {line: " }"}, + {line: " }"}, + {line: " f(3); f1(b)"}, + {line: "}"}, + {line: "func TestEscape() {"}, + {line: " a := 1", vars: []string{"var a int"}}, + {line: " {"}, + {line: " b := 2", scopes: []int{1}, vars: []string{"var &b *int", "var p *int"}}, + {line: " p := &b", scopes: []int{1}}, + {line: " f1(a)", scopes: []int{1}}, + {line: " fi(p)", scopes: []int{1}}, + {line: " }"}, + {line: "}"}, + {line: "func TestCaptureVar(flag bool) func() int {"}, + {line: " a := 1", vars: []string{"arg flag bool", "arg ~r1 func() int", "var a int"}}, + {line: " if flag {"}, + {line: " b := 2", scopes: []int{1}, vars: []string{"var b int", "var f func() int"}}, + {line: " f := func() int {", scopes: []int{1, 0}}, + {line: " return b + 1"}, + {line: " }"}, + {line: " return f", scopes: []int{1}}, + {line: " }"}, + {line: " f1(a)"}, + {line: " return nil"}, + {line: "}"}, + {line: "func main() {"}, + {line: " TestNestedFor()"}, + {line: " TestOas2()"}, + {line: " TestIfElse()"}, + {line: " TestSwitch()"}, + {line: " TestTypeSwitch()"}, + {line: " TestSelectScope()"}, + {line: " TestBlock()"}, + {line: " TestDiscontiguousRanges()"}, + {line: " TestClosureScope()"}, + {line: " TestEscape()"}, + {line: " TestCaptureVar(true)"}, + {line: "}"}, +} + +const detailOutput = false + +// Compiles testfile checks that the description of lexical blocks emitted +// by the linker in debug_info, for each function in the main package, +// corresponds to what we expect it to be. +func TestScopeRanges(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + if runtime.GOOS == "plan9" { + t.Skip("skipping on plan9; no DWARF symbol table in executables") + } + + dir, err := ioutil.TempDir("", "TestScopeRanges") + if err != nil { + t.Fatalf("could not create directory: %v", err) + } + defer os.RemoveAll(dir) + + src, f := gobuild(t, dir, false, testfile) + defer f.Close() + + // the compiler uses forward slashes for paths even on windows + src = strings.Replace(src, "\\", "/", -1) + + pcln, err := f.PCLineTable() + if err != nil { + t.Fatal(err) + } + dwarfData, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + dwarfReader := dwarfData.Reader() + + lines := make(map[line][]*lexblock) + + for { + entry, err := dwarfReader.Next() + if err != nil { + t.Fatal(err) + } + if entry == nil { + break + } + + if entry.Tag != dwarf.TagSubprogram { + continue + } + + name, ok := entry.Val(dwarf.AttrName).(string) + if !ok || !strings.HasPrefix(name, "main.Test") { + continue + } + + var scope lexblock + ctxt := scopexplainContext{ + dwarfData: dwarfData, + dwarfReader: dwarfReader, + scopegen: 1, + } + + readScope(&ctxt, &scope, entry) + + scope.markLines(pcln, lines) + } + + anyerror := false + for i := range testfile { + tgt := testfile[i].scopes + out := lines[line{src, i + 1}] + + if detailOutput { + t.Logf("%s // %v", testfile[i].line, out) + } + + scopesok := checkScopes(tgt, out) + if !scopesok { + t.Logf("mismatch at line %d %q: expected: %v got: %v\n", i, testfile[i].line, tgt, scopesToString(out)) + } + + varsok := true + if testfile[i].vars != nil { + if len(out) > 0 { + varsok = checkVars(testfile[i].vars, out[len(out)-1].vars) + if !varsok { + t.Logf("variable mismatch at line %d %q for scope %d: expected: %v got: %v\n", i+1, testfile[i].line, out[len(out)-1].id, testfile[i].vars, out[len(out)-1].vars) + } + for j := range testfile[i].decl { + if line := declLineForVar(out[len(out)-1].vars, testfile[i].decl[j]); line != i+1 { + t.Errorf("wrong declaration line for variable %s, expected %d got: %d", testfile[i].decl[j], i+1, line) + } + } + + for j := range testfile[i].declBefore { + if line := declLineForVar(out[len(out)-1].vars, testfile[i].declBefore[j]); line > i+1 { + t.Errorf("wrong declaration line for variable %s, expected %d (or less) got: %d", testfile[i].declBefore[j], i+1, line) + } + } + } + } + + anyerror = anyerror || !scopesok || !varsok + } + + if anyerror { + t.Fatalf("mismatched output") + } +} + +func scopesToString(v []*lexblock) string { + r := make([]string, len(v)) + for i, s := range v { + r[i] = strconv.Itoa(s.id) + } + return "[ " + strings.Join(r, ", ") + " ]" +} + +func checkScopes(tgt []int, out []*lexblock) bool { + if len(out) > 0 { + // omit scope 0 + out = out[1:] + } + if len(tgt) != len(out) { + return false + } + for i := range tgt { + if tgt[i] != out[i].id { + return false + } + } + return true +} + +func checkVars(tgt []string, out []variable) bool { + if len(tgt) != len(out) { + return false + } + for i := range tgt { + if tgt[i] != out[i].expr { + return false + } + } + return true +} + +func declLineForVar(scope []variable, name string) int { + for i := range scope { + if scope[i].name() == name { + return scope[i].declLine + } + } + return -1 +} + +type lexblock struct { + id int + ranges [][2]uint64 + vars []variable + scopes []lexblock +} + +type variable struct { + expr string + declLine int +} + +func (v *variable) name() string { + return strings.Split(v.expr, " ")[1] +} + +type line struct { + file string + lineno int +} + +type scopexplainContext struct { + dwarfData *dwarf.Data + dwarfReader *dwarf.Reader + scopegen int +} + +// readScope reads the DW_TAG_lexical_block or the DW_TAG_subprogram in +// entry and writes a description in scope. +// Nested DW_TAG_lexical_block entries are read recursively. +func readScope(ctxt *scopexplainContext, scope *lexblock, entry *dwarf.Entry) { + var err error + scope.ranges, err = ctxt.dwarfData.Ranges(entry) + if err != nil { + panic(err) + } + for { + e, err := ctxt.dwarfReader.Next() + if err != nil { + panic(err) + } + switch e.Tag { + case 0: + sort.Slice(scope.vars, func(i, j int) bool { + return scope.vars[i].expr < scope.vars[j].expr + }) + return + case dwarf.TagFormalParameter: + typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) + if err != nil { + panic(err) + } + scope.vars = append(scope.vars, entryToVar(e, "arg", typ)) + case dwarf.TagVariable: + typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) + if err != nil { + panic(err) + } + scope.vars = append(scope.vars, entryToVar(e, "var", typ)) + case dwarf.TagLexDwarfBlock: + scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen}) + ctxt.scopegen++ + readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e) + } + } +} + +func entryToVar(e *dwarf.Entry, kind string, typ dwarf.Type) variable { + return variable{ + fmt.Sprintf("%s %s %s", kind, e.Val(dwarf.AttrName).(string), typ.String()), + int(e.Val(dwarf.AttrDeclLine).(int64)), + } +} + +// markLines marks all lines that belong to this scope with this scope +// Recursively calls markLines for all children scopes. +func (scope *lexblock) markLines(pcln objfile.Liner, lines map[line][]*lexblock) { + for _, r := range scope.ranges { + for pc := r[0]; pc < r[1]; pc++ { + file, lineno, _ := pcln.PCToLine(pc) + l := line{file, lineno} + if len(lines[l]) == 0 || lines[l][len(lines[l])-1] != scope { + lines[l] = append(lines[l], scope) + } + } + } + + for i := range scope.scopes { + scope.scopes[i].markLines(pcln, lines) + } +} + +func gobuild(t *testing.T, dir string, optimized bool, testfile []testline) (string, *objfile.File) { + src := filepath.Join(dir, "test.go") + dst := filepath.Join(dir, "out.o") + + f, err := os.Create(src) + if err != nil { + t.Fatal(err) + } + for i := range testfile { + f.Write([]byte(testfile[i].line)) + f.Write([]byte{'\n'}) + } + f.Close() + + args := []string{"build"} + if !optimized { + args = append(args, "-gcflags=-N -l") + } + args = append(args, "-o", dst, src) + + cmd := exec.Command(testenv.GoToolPath(t), args...) + if b, err := cmd.CombinedOutput(); err != nil { + t.Logf("build: %s\n", string(b)) + t.Fatal(err) + } + + pkg, err := objfile.Open(dst) + if err != nil { + t.Fatal(err) + } + return src, pkg +} + +// TestEmptyDwarfRanges tests that no list entry in debug_ranges has start == end. +// See issue #23928. +func TestEmptyDwarfRanges(t *testing.T) { + testenv.MustHaveGoRun(t) + t.Parallel() + + if runtime.GOOS == "plan9" { + t.Skip("skipping on plan9; no DWARF symbol table in executables") + } + + dir, err := ioutil.TempDir("", "TestEmptyDwarfRanges") + if err != nil { + t.Fatalf("could not create directory: %v", err) + } + defer os.RemoveAll(dir) + + _, f := gobuild(t, dir, true, []testline{{line: "package main"}, {line: "func main(){ println(\"hello\") }"}}) + defer f.Close() + + dwarfData, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + dwarfReader := dwarfData.Reader() + + for { + entry, err := dwarfReader.Next() + if err != nil { + t.Fatal(err) + } + if entry == nil { + break + } + + ranges, err := dwarfData.Ranges(entry) + if err != nil { + t.Fatal(err) + } + if ranges == nil { + continue + } + + for _, rng := range ranges { + if rng[0] == rng[1] { + t.Errorf("range entry with start == end: %v", rng) + } + } + } +} diff --git a/src/cmd/compile/internal/gc/dwarf.go b/src/cmd/compile/internal/gc/dwarf.go deleted file mode 100644 index e853c51422..0000000000 --- a/src/cmd/compile/internal/gc/dwarf.go +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "sort" - - "cmd/compile/internal/base" - "cmd/compile/internal/ir" - "cmd/compile/internal/ssa" - "cmd/compile/internal/ssagen" - "cmd/compile/internal/types" - "cmd/internal/dwarf" - "cmd/internal/obj" - "cmd/internal/objabi" - "cmd/internal/src" -) - -func debuginfo(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) { - fn := curfn.(*ir.Func) - - if fn.Nname != nil { - expect := fn.Sym().Linksym() - if fnsym.ABI() == obj.ABI0 { - expect = fn.Sym().LinksymABI0() - } - if fnsym != expect { - base.Fatalf("unexpected fnsym: %v != %v", fnsym, expect) - } - } - - // Back when there were two different *Funcs for a function, this code - // was not consistent about whether a particular *Node being processed - // was an ODCLFUNC or ONAME node. Partly this is because inlined function - // bodies have no ODCLFUNC node, which was it's own inconsistency. - // In any event, the handling of the two different nodes for DWARF purposes - // was subtly different, likely in unintended ways. CL 272253 merged the - // two nodes' Func fields, so that code sees the same *Func whether it is - // holding the ODCLFUNC or the ONAME. This resulted in changes in the - // DWARF output. To preserve the existing DWARF output and leave an - // intentional change for a future CL, this code does the following when - // fn.Op == ONAME: - // - // 1. Disallow use of createComplexVars in createDwarfVars. - // It was not possible to reach that code for an ONAME before, - // because the DebugInfo was set only on the ODCLFUNC Func. - // Calling into it in the ONAME case causes an index out of bounds panic. - // - // 2. Do not populate apdecls. fn.Func.Dcl was in the ODCLFUNC Func, - // not the ONAME Func. Populating apdecls for the ONAME case results - // in selected being populated after createSimpleVars is called in - // createDwarfVars, and then that causes the loop to skip all the entries - // in dcl, meaning that the RecordAutoType calls don't happen. - // - // These two adjustments keep toolstash -cmp working for now. - // Deciding the right answer is, as they say, future work. - // - // We can tell the difference between the old ODCLFUNC and ONAME - // cases by looking at the infosym.Name. If it's empty, DebugInfo is - // being called from (*obj.Link).populateDWARF, which used to use - // the ODCLFUNC. If it's non-empty (the name will end in $abstract), - // DebugInfo is being called from (*obj.Link).DwarfAbstractFunc, - // which used to use the ONAME form. - isODCLFUNC := infosym.Name == "" - - var apdecls []*ir.Name - // Populate decls for fn. - if isODCLFUNC { - for _, n := range fn.Dcl { - if n.Op() != ir.ONAME { // might be OTYPE or OLITERAL - continue - } - switch n.Class_ { - case ir.PAUTO: - if !n.Used() { - // Text == nil -> generating abstract function - if fnsym.Func().Text != nil { - base.Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)") - } - continue - } - case ir.PPARAM, ir.PPARAMOUT: - default: - continue - } - apdecls = append(apdecls, n) - fnsym.Func().RecordAutoType(ngotype(n).Linksym()) - } - } - - decls, dwarfVars := createDwarfVars(fnsym, isODCLFUNC, fn, apdecls) - - // For each type referenced by the functions auto vars but not - // already referenced by a dwarf var, attach an R_USETYPE relocation to - // the function symbol to insure that the type included in DWARF - // processing during linking. - typesyms := []*obj.LSym{} - for t, _ := range fnsym.Func().Autot { - typesyms = append(typesyms, t) - } - sort.Sort(obj.BySymName(typesyms)) - for _, sym := range typesyms { - r := obj.Addrel(infosym) - r.Sym = sym - r.Type = objabi.R_USETYPE - } - fnsym.Func().Autot = nil - - var varScopes []ir.ScopeID - for _, decl := range decls { - pos := declPos(decl) - varScopes = append(varScopes, findScope(fn.Marks, pos)) - } - - scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes) - var inlcalls dwarf.InlCalls - if base.Flag.GenDwarfInl > 0 { - inlcalls = assembleInlines(fnsym, dwarfVars) - } - return scopes, inlcalls -} - -func declPos(decl *ir.Name) src.XPos { - if decl.Name().Defn != nil && (decl.Name().Captured() || decl.Name().Byval()) { - // It's not clear which position is correct for captured variables here: - // * decl.Pos is the wrong position for captured variables, in the inner - // function, but it is the right position in the outer function. - // * decl.Name.Defn is nil for captured variables that were arguments - // on the outer function, however the decl.Pos for those seems to be - // correct. - // * decl.Name.Defn is the "wrong" thing for variables declared in the - // header of a type switch, it's their position in the header, rather - // than the position of the case statement. In principle this is the - // right thing, but here we prefer the latter because it makes each - // instance of the header variable local to the lexical block of its - // case statement. - // This code is probably wrong for type switch variables that are also - // captured. - return decl.Name().Defn.Pos() - } - return decl.Pos() -} - -// createDwarfVars process fn, returning a list of DWARF variables and the -// Nodes they represent. -func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var) { - // Collect a raw list of DWARF vars. - var vars []*dwarf.Var - var decls []*ir.Name - var selected map[*ir.Name]bool - if base.Ctxt.Flag_locationlists && base.Ctxt.Flag_optimize && fn.DebugInfo != nil && complexOK { - decls, vars, selected = createComplexVars(fnsym, fn) - } else { - decls, vars, selected = createSimpleVars(fnsym, apDecls) - } - - dcl := apDecls - if fnsym.WasInlined() { - dcl = preInliningDcls(fnsym) - } - - // If optimization is enabled, the list above will typically be - // missing some of the original pre-optimization variables in the - // function (they may have been promoted to registers, folded into - // constants, dead-coded away, etc). Input arguments not eligible - // for SSA optimization are also missing. Here we add back in entries - // for selected missing vars. Note that the recipe below creates a - // conservative location. The idea here is that we want to - // communicate to the user that "yes, there is a variable named X - // in this function, but no, I don't have enough information to - // reliably report its contents." - // For non-SSA-able arguments, however, the correct information - // is known -- they have a single home on the stack. - for _, n := range dcl { - if _, found := selected[n]; found { - continue - } - c := n.Sym().Name[0] - if c == '.' || n.Type().IsUntyped() { - continue - } - if n.Class_ == ir.PPARAM && !ssagen.TypeOK(n.Type()) { - // SSA-able args get location lists, and may move in and - // out of registers, so those are handled elsewhere. - // Autos and named output params seem to get handled - // with VARDEF, which creates location lists. - // Args not of SSA-able type are treated here; they - // are homed on the stack in a single place for the - // entire call. - vars = append(vars, createSimpleVar(fnsym, n)) - decls = append(decls, n) - continue - } - typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) - decls = append(decls, n) - abbrev := dwarf.DW_ABRV_AUTO_LOCLIST - isReturnValue := (n.Class_ == ir.PPARAMOUT) - if n.Class_ == ir.PPARAM || n.Class_ == ir.PPARAMOUT { - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - } else if n.Class_ == ir.PAUTOHEAP { - // If dcl in question has been promoted to heap, do a bit - // of extra work to recover original class (auto or param); - // see issue 30908. This insures that we get the proper - // signature in the abstract function DIE, but leaves a - // misleading location for the param (we want pointer-to-heap - // and not stack). - // TODO(thanm): generate a better location expression - stackcopy := n.Name().Stackcopy - if stackcopy != nil && (stackcopy.Class_ == ir.PPARAM || stackcopy.Class_ == ir.PPARAMOUT) { - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - isReturnValue = (stackcopy.Class_ == ir.PPARAMOUT) - } - } - inlIndex := 0 - if base.Flag.GenDwarfInl > 1 { - if n.Name().InlFormal() || n.Name().InlLocal() { - inlIndex = posInlIndex(n.Pos()) + 1 - if n.Name().InlFormal() { - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - } - } - } - declpos := base.Ctxt.InnermostPos(n.Pos()) - vars = append(vars, &dwarf.Var{ - Name: n.Sym().Name, - IsReturnValue: isReturnValue, - Abbrev: abbrev, - StackOffset: int32(n.FrameOffset()), - Type: base.Ctxt.Lookup(typename), - DeclFile: declpos.RelFilename(), - DeclLine: declpos.RelLine(), - DeclCol: declpos.Col(), - InlIndex: int32(inlIndex), - ChildIndex: -1, - }) - // Record go type of to insure that it gets emitted by the linker. - fnsym.Func().RecordAutoType(ngotype(n).Linksym()) - } - - return decls, vars -} - -// Given a function that was inlined at some point during the -// compilation, return a sorted list of nodes corresponding to the -// autos/locals in that function prior to inlining. If this is a -// function that is not local to the package being compiled, then the -// names of the variables may have been "versioned" to avoid conflicts -// with local vars; disregard this versioning when sorting. -func preInliningDcls(fnsym *obj.LSym) []*ir.Name { - fn := base.Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*ir.Func) - var rdcl []*ir.Name - for _, n := range fn.Inl.Dcl { - c := n.Sym().Name[0] - // Avoid reporting "_" parameters, since if there are more than - // one, it can result in a collision later on, as in #23179. - if unversion(n.Sym().Name) == "_" || c == '.' || n.Type().IsUntyped() { - continue - } - rdcl = append(rdcl, n) - } - return rdcl -} - -// createSimpleVars creates a DWARF entry for every variable declared in the -// function, claiming that they are permanently on the stack. -func createSimpleVars(fnsym *obj.LSym, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, map[*ir.Name]bool) { - var vars []*dwarf.Var - var decls []*ir.Name - selected := make(map[*ir.Name]bool) - for _, n := range apDecls { - if ir.IsAutoTmp(n) { - continue - } - - decls = append(decls, n) - vars = append(vars, createSimpleVar(fnsym, n)) - selected[n] = true - } - return decls, vars, selected -} - -func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var { - var abbrev int - var offs int64 - - switch n.Class_ { - case ir.PAUTO: - offs = n.FrameOffset() - abbrev = dwarf.DW_ABRV_AUTO - if base.Ctxt.FixedFrameSize() == 0 { - offs -= int64(types.PtrSize) - } - if objabi.Framepointer_enabled || objabi.GOARCH == "arm64" { - // There is a word space for FP on ARM64 even if the frame pointer is disabled - offs -= int64(types.PtrSize) - } - - case ir.PPARAM, ir.PPARAMOUT: - abbrev = dwarf.DW_ABRV_PARAM - offs = n.FrameOffset() + base.Ctxt.FixedFrameSize() - default: - base.Fatalf("createSimpleVar unexpected class %v for node %v", n.Class_, n) - } - - typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) - delete(fnsym.Func().Autot, ngotype(n).Linksym()) - inlIndex := 0 - if base.Flag.GenDwarfInl > 1 { - if n.Name().InlFormal() || n.Name().InlLocal() { - inlIndex = posInlIndex(n.Pos()) + 1 - if n.Name().InlFormal() { - abbrev = dwarf.DW_ABRV_PARAM - } - } - } - declpos := base.Ctxt.InnermostPos(declPos(n)) - return &dwarf.Var{ - Name: n.Sym().Name, - IsReturnValue: n.Class_ == ir.PPARAMOUT, - IsInlFormal: n.Name().InlFormal(), - Abbrev: abbrev, - StackOffset: int32(offs), - Type: base.Ctxt.Lookup(typename), - DeclFile: declpos.RelFilename(), - DeclLine: declpos.RelLine(), - DeclCol: declpos.Col(), - InlIndex: int32(inlIndex), - ChildIndex: -1, - } -} - -// createComplexVars creates recomposed DWARF vars with location lists, -// suitable for describing optimized code. -func createComplexVars(fnsym *obj.LSym, fn *ir.Func) ([]*ir.Name, []*dwarf.Var, map[*ir.Name]bool) { - debugInfo := fn.DebugInfo.(*ssa.FuncDebug) - - // Produce a DWARF variable entry for each user variable. - var decls []*ir.Name - var vars []*dwarf.Var - ssaVars := make(map[*ir.Name]bool) - - for varID, dvar := range debugInfo.Vars { - n := dvar - ssaVars[n] = true - for _, slot := range debugInfo.VarSlots[varID] { - ssaVars[debugInfo.Slots[slot].N] = true - } - - if dvar := createComplexVar(fnsym, fn, ssa.VarID(varID)); dvar != nil { - decls = append(decls, n) - vars = append(vars, dvar) - } - } - - return decls, vars, ssaVars -} - -// createComplexVar builds a single DWARF variable entry and location list. -func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var { - debug := fn.DebugInfo.(*ssa.FuncDebug) - n := debug.Vars[varID] - - var abbrev int - switch n.Class_ { - case ir.PAUTO: - abbrev = dwarf.DW_ABRV_AUTO_LOCLIST - case ir.PPARAM, ir.PPARAMOUT: - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - default: - return nil - } - - gotype := ngotype(n).Linksym() - delete(fnsym.Func().Autot, gotype) - typename := dwarf.InfoPrefix + gotype.Name[len("type."):] - inlIndex := 0 - if base.Flag.GenDwarfInl > 1 { - if n.Name().InlFormal() || n.Name().InlLocal() { - inlIndex = posInlIndex(n.Pos()) + 1 - if n.Name().InlFormal() { - abbrev = dwarf.DW_ABRV_PARAM_LOCLIST - } - } - } - declpos := base.Ctxt.InnermostPos(n.Pos()) - dvar := &dwarf.Var{ - Name: n.Sym().Name, - IsReturnValue: n.Class_ == ir.PPARAMOUT, - IsInlFormal: n.Name().InlFormal(), - Abbrev: abbrev, - Type: base.Ctxt.Lookup(typename), - // The stack offset is used as a sorting key, so for decomposed - // variables just give it the first one. It's not used otherwise. - // This won't work well if the first slot hasn't been assigned a stack - // location, but it's not obvious how to do better. - StackOffset: ssagen.StackOffset(debug.Slots[debug.VarSlots[varID][0]]), - DeclFile: declpos.RelFilename(), - DeclLine: declpos.RelLine(), - DeclCol: declpos.Col(), - InlIndex: int32(inlIndex), - ChildIndex: -1, - } - list := debug.LocationLists[varID] - if len(list) != 0 { - dvar.PutLocationList = func(listSym, startPC dwarf.Sym) { - debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym)) - } - } - return dvar -} diff --git a/src/cmd/compile/internal/gc/dwinl.go b/src/cmd/compile/internal/gc/dwinl.go deleted file mode 100644 index d9eb930037..0000000000 --- a/src/cmd/compile/internal/gc/dwinl.go +++ /dev/null @@ -1,452 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/base" - "cmd/compile/internal/ir" - "cmd/internal/dwarf" - "cmd/internal/obj" - "cmd/internal/src" - "fmt" - "strings" -) - -// To identify variables by original source position. -type varPos struct { - DeclName string - DeclFile string - DeclLine uint - DeclCol uint -} - -// This is the main entry point for collection of raw material to -// drive generation of DWARF "inlined subroutine" DIEs. See proposal -// 22080 for more details and background info. -func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls { - var inlcalls dwarf.InlCalls - - if base.Debug.DwarfInl != 0 { - base.Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name) - } - - // This maps inline index (from Ctxt.InlTree) to index in inlcalls.Calls - imap := make(map[int]int) - - // Walk progs to build up the InlCalls data structure - var prevpos src.XPos - for p := fnsym.Func().Text; p != nil; p = p.Link { - if p.Pos == prevpos { - continue - } - ii := posInlIndex(p.Pos) - if ii >= 0 { - insertInlCall(&inlcalls, ii, imap) - } - prevpos = p.Pos - } - - // This is used to partition DWARF vars by inline index. Vars not - // produced by the inliner will wind up in the vmap[0] entry. - vmap := make(map[int32][]*dwarf.Var) - - // Now walk the dwarf vars and partition them based on whether they - // were produced by the inliner (dwv.InlIndex > 0) or were original - // vars/params from the function (dwv.InlIndex == 0). - for _, dwv := range dwVars { - - vmap[dwv.InlIndex] = append(vmap[dwv.InlIndex], dwv) - - // Zero index => var was not produced by an inline - if dwv.InlIndex == 0 { - continue - } - - // Look up index in our map, then tack the var in question - // onto the vars list for the correct inlined call. - ii := int(dwv.InlIndex) - 1 - idx, ok := imap[ii] - if !ok { - // We can occasionally encounter a var produced by the - // inliner for which there is no remaining prog; add a new - // entry to the call list in this scenario. - idx = insertInlCall(&inlcalls, ii, imap) - } - inlcalls.Calls[idx].InlVars = - append(inlcalls.Calls[idx].InlVars, dwv) - } - - // Post process the map above to assign child indices to vars. - // - // A given variable is treated differently depending on whether it - // is part of the top-level function (ii == 0) or if it was - // produced as a result of an inline (ii != 0). - // - // If a variable was not produced by an inline and its containing - // function was not inlined, then we just assign an ordering of - // based on variable name. - // - // If a variable was not produced by an inline and its containing - // function was inlined, then we need to assign a child index - // based on the order of vars in the abstract function (in - // addition, those vars that don't appear in the abstract - // function, such as "~r1", are flagged as such). - // - // If a variable was produced by an inline, then we locate it in - // the pre-inlining decls for the target function and assign child - // index accordingly. - for ii, sl := range vmap { - var m map[varPos]int - if ii == 0 { - if !fnsym.WasInlined() { - for j, v := range sl { - v.ChildIndex = int32(j) - } - continue - } - m = makePreinlineDclMap(fnsym) - } else { - ifnlsym := base.Ctxt.InlTree.InlinedFunction(int(ii - 1)) - m = makePreinlineDclMap(ifnlsym) - } - - // Here we assign child indices to variables based on - // pre-inlined decls, and set the "IsInAbstract" flag - // appropriately. In addition: parameter and local variable - // names are given "middle dot" version numbers as part of the - // writing them out to export data (see issue 4326). If DWARF - // inlined routine generation is turned on, we want to undo - // this versioning, since DWARF variables in question will be - // parented by the inlined routine and not the top-level - // caller. - synthCount := len(m) - for _, v := range sl { - canonName := unversion(v.Name) - vp := varPos{ - DeclName: canonName, - DeclFile: v.DeclFile, - DeclLine: v.DeclLine, - DeclCol: v.DeclCol, - } - synthesized := strings.HasPrefix(v.Name, "~r") || canonName == "_" || strings.HasPrefix(v.Name, "~b") - if idx, found := m[vp]; found { - v.ChildIndex = int32(idx) - v.IsInAbstract = !synthesized - v.Name = canonName - } else { - // Variable can't be found in the pre-inline dcl list. - // In the top-level case (ii=0) this can happen - // because a composite variable was split into pieces, - // and we're looking at a piece. We can also see - // return temps (~r%d) that were created during - // lowering, or unnamed params ("_"). - v.ChildIndex = int32(synthCount) - synthCount++ - } - } - } - - // Make a second pass through the progs to compute PC ranges for - // the various inlined calls. - start := int64(-1) - curii := -1 - var prevp *obj.Prog - for p := fnsym.Func().Text; p != nil; prevp, p = p, p.Link { - if prevp != nil && p.Pos == prevp.Pos { - continue - } - ii := posInlIndex(p.Pos) - if ii == curii { - continue - } - // Close out the current range - if start != -1 { - addRange(inlcalls.Calls, start, p.Pc, curii, imap) - } - // Begin new range - start = p.Pc - curii = ii - } - if start != -1 { - addRange(inlcalls.Calls, start, fnsym.Size, curii, imap) - } - - // Issue 33188: if II foo is a child of II bar, then ensure that - // bar's ranges include the ranges of foo (the loop above will produce - // disjoint ranges). - for k, c := range inlcalls.Calls { - if c.Root { - unifyCallRanges(inlcalls, k) - } - } - - // Debugging - if base.Debug.DwarfInl != 0 { - dumpInlCalls(inlcalls) - dumpInlVars(dwVars) - } - - // Perform a consistency check on inlined routine PC ranges - // produced by unifyCallRanges above. In particular, complain in - // cases where you have A -> B -> C (e.g. C is inlined into B, and - // B is inlined into A) and the ranges for B are not enclosed - // within the ranges for A, or C within B. - for k, c := range inlcalls.Calls { - if c.Root { - checkInlCall(fnsym.Name, inlcalls, fnsym.Size, k, -1) - } - } - - return inlcalls -} - -// Secondary hook for DWARF inlined subroutine generation. This is called -// late in the compilation when it is determined that we need an -// abstract function DIE for an inlined routine imported from a -// previously compiled package. -func genAbstractFunc(fn *obj.LSym) { - ifn := base.Ctxt.DwFixups.GetPrecursorFunc(fn) - if ifn == nil { - base.Ctxt.Diag("failed to locate precursor fn for %v", fn) - return - } - _ = ifn.(*ir.Func) - if base.Debug.DwarfInl != 0 { - base.Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name) - } - base.Ctxt.DwarfAbstractFunc(ifn, fn, base.Ctxt.Pkgpath) -} - -// Undo any versioning performed when a name was written -// out as part of export data. -func unversion(name string) string { - if i := strings.Index(name, "·"); i > 0 { - name = name[:i] - } - return name -} - -// Given a function that was inlined as part of the compilation, dig -// up the pre-inlining DCL list for the function and create a map that -// supports lookup of pre-inline dcl index, based on variable -// position/name. NB: the recipe for computing variable pos/file/line -// needs to be kept in sync with the similar code in gc.createSimpleVars -// and related functions. -func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int { - dcl := preInliningDcls(fnsym) - m := make(map[varPos]int) - for i, n := range dcl { - pos := base.Ctxt.InnermostPos(n.Pos()) - vp := varPos{ - DeclName: unversion(n.Sym().Name), - DeclFile: pos.RelFilename(), - DeclLine: pos.RelLine(), - DeclCol: pos.Col(), - } - if _, found := m[vp]; found { - base.Fatalf("child dcl collision on symbol %s within %v\n", n.Sym().Name, fnsym.Name) - } - m[vp] = i - } - return m -} - -func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int { - callIdx, found := imap[inlIdx] - if found { - return callIdx - } - - // Haven't seen this inline yet. Visit parent of inline if there - // is one. We do this first so that parents appear before their - // children in the resulting table. - parCallIdx := -1 - parInlIdx := base.Ctxt.InlTree.Parent(inlIdx) - if parInlIdx >= 0 { - parCallIdx = insertInlCall(dwcalls, parInlIdx, imap) - } - - // Create new entry for this inline - inlinedFn := base.Ctxt.InlTree.InlinedFunction(inlIdx) - callXPos := base.Ctxt.InlTree.CallPos(inlIdx) - absFnSym := base.Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn) - pb := base.Ctxt.PosTable.Pos(callXPos).Base() - callFileSym := base.Ctxt.Lookup(pb.SymFilename()) - ic := dwarf.InlCall{ - InlIndex: inlIdx, - CallFile: callFileSym, - CallLine: uint32(callXPos.Line()), - AbsFunSym: absFnSym, - Root: parCallIdx == -1, - } - dwcalls.Calls = append(dwcalls.Calls, ic) - callIdx = len(dwcalls.Calls) - 1 - imap[inlIdx] = callIdx - - if parCallIdx != -1 { - // Add this inline to parent's child list - dwcalls.Calls[parCallIdx].Children = append(dwcalls.Calls[parCallIdx].Children, callIdx) - } - - return callIdx -} - -// Given a src.XPos, return its associated inlining index if it -// corresponds to something created as a result of an inline, or -1 if -// there is no inline info. Note that the index returned will refer to -// the deepest call in the inlined stack, e.g. if you have "A calls B -// calls C calls D" and all three callees are inlined (B, C, and D), -// the index for a node from the inlined body of D will refer to the -// call to D from C. Whew. -func posInlIndex(xpos src.XPos) int { - pos := base.Ctxt.PosTable.Pos(xpos) - if b := pos.Base(); b != nil { - ii := b.InliningIndex() - if ii >= 0 { - return ii - } - } - return -1 -} - -func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) { - if start == -1 { - panic("bad range start") - } - if end == -1 { - panic("bad range end") - } - if ii == -1 { - return - } - if start == end { - return - } - // Append range to correct inlined call - callIdx, found := imap[ii] - if !found { - base.Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start) - } - call := &calls[callIdx] - call.Ranges = append(call.Ranges, dwarf.Range{Start: start, End: end}) -} - -func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) { - for i := 0; i < ilevel; i++ { - base.Ctxt.Logf(" ") - } - ic := inlcalls.Calls[idx] - callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex) - base.Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name) - for _, f := range ic.InlVars { - base.Ctxt.Logf(" %v", f.Name) - } - base.Ctxt.Logf(" ) C: (") - for _, k := range ic.Children { - base.Ctxt.Logf(" %v", k) - } - base.Ctxt.Logf(" ) R:") - for _, r := range ic.Ranges { - base.Ctxt.Logf(" [%d,%d)", r.Start, r.End) - } - base.Ctxt.Logf("\n") - for _, k := range ic.Children { - dumpInlCall(inlcalls, k, ilevel+1) - } - -} - -func dumpInlCalls(inlcalls dwarf.InlCalls) { - for k, c := range inlcalls.Calls { - if c.Root { - dumpInlCall(inlcalls, k, 0) - } - } -} - -func dumpInlVars(dwvars []*dwarf.Var) { - for i, dwv := range dwvars { - typ := "local" - if dwv.Abbrev == dwarf.DW_ABRV_PARAM_LOCLIST || dwv.Abbrev == dwarf.DW_ABRV_PARAM { - typ = "param" - } - ia := 0 - if dwv.IsInAbstract { - ia = 1 - } - base.Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ) - } -} - -func rangesContains(par []dwarf.Range, rng dwarf.Range) (bool, string) { - for _, r := range par { - if rng.Start >= r.Start && rng.End <= r.End { - return true, "" - } - } - msg := fmt.Sprintf("range [%d,%d) not contained in {", rng.Start, rng.End) - for _, r := range par { - msg += fmt.Sprintf(" [%d,%d)", r.Start, r.End) - } - msg += " }" - return false, msg -} - -func rangesContainsAll(parent, child []dwarf.Range) (bool, string) { - for _, r := range child { - c, m := rangesContains(parent, r) - if !c { - return false, m - } - } - return true, "" -} - -// checkInlCall verifies that the PC ranges for inline info 'idx' are -// enclosed/contained within the ranges of its parent inline (or if -// this is a root/toplevel inline, checks that the ranges fall within -// the extent of the top level function). A panic is issued if a -// malformed range is found. -func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, parentIdx int) { - - // Callee - ic := inlCalls.Calls[idx] - callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name - calleeRanges := ic.Ranges - - // Caller - caller := funcName - parentRanges := []dwarf.Range{dwarf.Range{Start: int64(0), End: funcSize}} - if parentIdx != -1 { - pic := inlCalls.Calls[parentIdx] - caller = base.Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name - parentRanges = pic.Ranges - } - - // Callee ranges contained in caller ranges? - c, m := rangesContainsAll(parentRanges, calleeRanges) - if !c { - base.Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m) - } - - // Now visit kids - for _, k := range ic.Children { - checkInlCall(funcName, inlCalls, funcSize, k, idx) - } -} - -// unifyCallRanges ensures that the ranges for a given inline -// transitively include all of the ranges for its child inlines. -func unifyCallRanges(inlcalls dwarf.InlCalls, idx int) { - ic := &inlcalls.Calls[idx] - for _, childIdx := range ic.Children { - // First make sure child ranges are unified. - unifyCallRanges(inlcalls, childIdx) - - // Then merge child ranges into ranges for this inline. - cic := inlcalls.Calls[childIdx] - ic.Ranges = dwarf.MergeRanges(ic.Ranges, cic.Ranges) - } -} diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 154235f744..2a8012b462 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -10,6 +10,7 @@ import ( "bufio" "bytes" "cmd/compile/internal/base" + "cmd/compile/internal/dwarfgen" "cmd/compile/internal/escape" "cmd/compile/internal/inline" "cmd/compile/internal/ir" @@ -114,7 +115,7 @@ func Main(archInit func(*ssagen.ArchInfo)) { // Record flags that affect the build result. (And don't // record flags that don't, since that would cause spurious // changes in the binary.) - recordFlags("B", "N", "l", "msan", "race", "shared", "dynlink", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre") + dwarfgen.RecordFlags("B", "N", "l", "msan", "race", "shared", "dynlink", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre") if !base.EnableTrace && base.Flag.LowerT { log.Fatalf("compiler not built with support for -t") @@ -134,8 +135,8 @@ func Main(archInit func(*ssagen.ArchInfo)) { } if base.Flag.Dwarf { - base.Ctxt.DebugInfo = debuginfo - base.Ctxt.GenAbstractFunc = genAbstractFunc + base.Ctxt.DebugInfo = dwarfgen.Info + base.Ctxt.GenAbstractFunc = dwarfgen.AbstractFunc base.Ctxt.DwFixups = obj.NewDwarfFixupTable(base.Ctxt) } else { // turn off inline generation if no dwarf at all @@ -211,7 +212,7 @@ func Main(archInit func(*ssagen.ArchInfo)) { ssagen.CgoSymABIs() base.Timer.Stop() base.Timer.AddEvent(int64(lines), "lines") - recordPackageName() + dwarfgen.RecordPackageName() // Typecheck. typecheck.Package() @@ -364,73 +365,6 @@ func writebench(filename string) error { return f.Close() } -// recordFlags records the specified command-line flags to be placed -// in the DWARF info. -func recordFlags(flags ...string) { - if base.Ctxt.Pkgpath == "" { - // We can't record the flags if we don't know what the - // package name is. - return - } - - type BoolFlag interface { - IsBoolFlag() bool - } - type CountFlag interface { - IsCountFlag() bool - } - var cmd bytes.Buffer - for _, name := range flags { - f := flag.Lookup(name) - if f == nil { - continue - } - getter := f.Value.(flag.Getter) - if getter.String() == f.DefValue { - // Flag has default value, so omit it. - continue - } - if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() { - val, ok := getter.Get().(bool) - if ok && val { - fmt.Fprintf(&cmd, " -%s", f.Name) - continue - } - } - if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() { - val, ok := getter.Get().(int) - if ok && val == 1 { - fmt.Fprintf(&cmd, " -%s", f.Name) - continue - } - } - fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get()) - } - - if cmd.Len() == 0 { - return - } - s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + base.Ctxt.Pkgpath) - s.Type = objabi.SDWARFCUINFO - // Sometimes (for example when building tests) we can link - // together two package main archives. So allow dups. - s.Set(obj.AttrDuplicateOK, true) - base.Ctxt.Data = append(base.Ctxt.Data, s) - s.P = cmd.Bytes()[1:] -} - -// recordPackageName records the name of the package being -// compiled, so that the linker can save it in the compile unit's DIE. -func recordPackageName() { - s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + base.Ctxt.Pkgpath) - s.Type = objabi.SDWARFCUINFO - // Sometimes (for example when building tests) we can link - // together two package main archives. So allow dups. - s.Set(obj.AttrDuplicateOK, true) - base.Ctxt.Data = append(base.Ctxt.Data, s) - s.P = []byte(types.LocalPkg.Name) -} - func makePos(b *src.PosBase, line, col uint) src.XPos { return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col)) } diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index 4db2ad9d4a..f159256da6 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -319,7 +319,7 @@ func litsym(n *ir.Name, noff int64, c ir.Node, wid int) { func ggloblnod(nam ir.Node) { s := nam.Sym().Linksym() - s.Gotype = ngotype(nam).Linksym() + s.Gotype = reflectdata.TypeSym(nam.Type()).Linksym() flags := 0 if nam.Name().Readonly() { flags = obj.RODATA diff --git a/src/cmd/compile/internal/gc/scope.go b/src/cmd/compile/internal/gc/scope.go deleted file mode 100644 index 9ab33583c8..0000000000 --- a/src/cmd/compile/internal/gc/scope.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc - -import ( - "cmd/compile/internal/base" - "cmd/compile/internal/ir" - "cmd/internal/dwarf" - "cmd/internal/obj" - "cmd/internal/src" - "sort" -) - -// See golang.org/issue/20390. -func xposBefore(p, q src.XPos) bool { - return base.Ctxt.PosTable.Pos(p).Before(base.Ctxt.PosTable.Pos(q)) -} - -func findScope(marks []ir.Mark, pos src.XPos) ir.ScopeID { - i := sort.Search(len(marks), func(i int) bool { - return xposBefore(pos, marks[i].Pos) - }) - if i == 0 { - return 0 - } - return marks[i-1].Scope -} - -func assembleScopes(fnsym *obj.LSym, fn *ir.Func, dwarfVars []*dwarf.Var, varScopes []ir.ScopeID) []dwarf.Scope { - // Initialize the DWARF scope tree based on lexical scopes. - dwarfScopes := make([]dwarf.Scope, 1+len(fn.Parents)) - for i, parent := range fn.Parents { - dwarfScopes[i+1].Parent = int32(parent) - } - - scopeVariables(dwarfVars, varScopes, dwarfScopes) - scopePCs(fnsym, fn.Marks, dwarfScopes) - return compactScopes(dwarfScopes) -} - -// scopeVariables assigns DWARF variable records to their scopes. -func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ir.ScopeID, dwarfScopes []dwarf.Scope) { - sort.Stable(varsByScopeAndOffset{dwarfVars, varScopes}) - - i0 := 0 - for i := range dwarfVars { - if varScopes[i] == varScopes[i0] { - continue - } - dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:i] - i0 = i - } - if i0 < len(dwarfVars) { - dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:] - } -} - -// scopePCs assigns PC ranges to their scopes. -func scopePCs(fnsym *obj.LSym, marks []ir.Mark, dwarfScopes []dwarf.Scope) { - // If there aren't any child scopes (in particular, when scope - // tracking is disabled), we can skip a whole lot of work. - if len(marks) == 0 { - return - } - p0 := fnsym.Func().Text - scope := findScope(marks, p0.Pos) - for p := p0; p != nil; p = p.Link { - if p.Pos == p0.Pos { - continue - } - dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: p.Pc}) - p0 = p - scope = findScope(marks, p0.Pos) - } - if p0.Pc < fnsym.Size { - dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: fnsym.Size}) - } -} - -func compactScopes(dwarfScopes []dwarf.Scope) []dwarf.Scope { - // Reverse pass to propagate PC ranges to parent scopes. - for i := len(dwarfScopes) - 1; i > 0; i-- { - s := &dwarfScopes[i] - dwarfScopes[s.Parent].UnifyRanges(s) - } - - return dwarfScopes -} - -type varsByScopeAndOffset struct { - vars []*dwarf.Var - scopes []ir.ScopeID -} - -func (v varsByScopeAndOffset) Len() int { - return len(v.vars) -} - -func (v varsByScopeAndOffset) Less(i, j int) bool { - if v.scopes[i] != v.scopes[j] { - return v.scopes[i] < v.scopes[j] - } - return v.vars[i].StackOffset < v.vars[j].StackOffset -} - -func (v varsByScopeAndOffset) Swap(i, j int) { - v.vars[i], v.vars[j] = v.vars[j], v.vars[i] - v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i] -} diff --git a/src/cmd/compile/internal/gc/scope_test.go b/src/cmd/compile/internal/gc/scope_test.go deleted file mode 100644 index b0e038d27f..0000000000 --- a/src/cmd/compile/internal/gc/scope_test.go +++ /dev/null @@ -1,538 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gc_test - -import ( - "cmd/internal/objfile" - "debug/dwarf" - "fmt" - "internal/testenv" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "testing" -) - -type testline struct { - // line is one line of go source - line string - - // scopes is a list of scope IDs of all the lexical scopes that this line - // of code belongs to. - // Scope IDs are assigned by traversing the tree of lexical blocks of a - // function in pre-order - // Scope IDs are function specific, i.e. scope 0 is always the root scope - // of the function that this line belongs to. Empty scopes are not assigned - // an ID (because they are not saved in debug_info). - // Scope 0 is always omitted from this list since all lines always belong - // to it. - scopes []int - - // vars is the list of variables that belong in scopes[len(scopes)-1]. - // Local variables are prefixed with "var ", formal parameters with "arg ". - // Must be ordered alphabetically. - // Set to nil to skip the check. - vars []string - - // decl is the list of variables declared at this line. - decl []string - - // declBefore is the list of variables declared at or before this line. - declBefore []string -} - -var testfile = []testline{ - {line: "package main"}, - {line: "func f1(x int) { }"}, - {line: "func f2(x int) { }"}, - {line: "func f3(x int) { }"}, - {line: "func f4(x int) { }"}, - {line: "func f5(x int) { }"}, - {line: "func f6(x int) { }"}, - {line: "func fi(x interface{}) { if a, ok := x.(error); ok { a.Error() } }"}, - {line: "func gret1() int { return 2 }"}, - {line: "func gretbool() bool { return true }"}, - {line: "func gret3() (int, int, int) { return 0, 1, 2 }"}, - {line: "var v = []int{ 0, 1, 2 }"}, - {line: "var ch = make(chan int)"}, - {line: "var floatch = make(chan float64)"}, - {line: "var iface interface{}"}, - {line: "func TestNestedFor() {", vars: []string{"var a int"}}, - {line: " a := 0", decl: []string{"a"}}, - {line: " f1(a)"}, - {line: " for i := 0; i < 5; i++ {", scopes: []int{1}, vars: []string{"var i int"}, decl: []string{"i"}}, - {line: " f2(i)", scopes: []int{1}}, - {line: " for i := 0; i < 5; i++ {", scopes: []int{1, 2}, vars: []string{"var i int"}, decl: []string{"i"}}, - {line: " f3(i)", scopes: []int{1, 2}}, - {line: " }"}, - {line: " f4(i)", scopes: []int{1}}, - {line: " }"}, - {line: " f5(a)"}, - {line: "}"}, - {line: "func TestOas2() {", vars: []string{}}, - {line: " if a, b, c := gret3(); a != 1 {", scopes: []int{1}, vars: []string{"var a int", "var b int", "var c int"}}, - {line: " f1(a)", scopes: []int{1}}, - {line: " f1(b)", scopes: []int{1}}, - {line: " f1(c)", scopes: []int{1}}, - {line: " }"}, - {line: " for i, x := range v {", scopes: []int{2}, vars: []string{"var i int", "var x int"}}, - {line: " f1(i)", scopes: []int{2}}, - {line: " f1(x)", scopes: []int{2}}, - {line: " }"}, - {line: " if a, ok := <- ch; ok {", scopes: []int{3}, vars: []string{"var a int", "var ok bool"}}, - {line: " f1(a)", scopes: []int{3}}, - {line: " }"}, - {line: " if a, ok := iface.(int); ok {", scopes: []int{4}, vars: []string{"var a int", "var ok bool"}}, - {line: " f1(a)", scopes: []int{4}}, - {line: " }"}, - {line: "}"}, - {line: "func TestIfElse() {"}, - {line: " if x := gret1(); x != 0 {", scopes: []int{1}, vars: []string{"var x int"}}, - {line: " a := 0", scopes: []int{1, 2}, vars: []string{"var a int"}}, - {line: " f1(a); f1(x)", scopes: []int{1, 2}}, - {line: " } else {"}, - {line: " b := 1", scopes: []int{1, 3}, vars: []string{"var b int"}}, - {line: " f1(b); f1(x+1)", scopes: []int{1, 3}}, - {line: " }"}, - {line: "}"}, - {line: "func TestSwitch() {", vars: []string{}}, - {line: " switch x := gret1(); x {", scopes: []int{1}, vars: []string{"var x int"}}, - {line: " case 0:", scopes: []int{1, 2}}, - {line: " i := x + 5", scopes: []int{1, 2}, vars: []string{"var i int"}}, - {line: " f1(x); f1(i)", scopes: []int{1, 2}}, - {line: " case 1:", scopes: []int{1, 3}}, - {line: " j := x + 10", scopes: []int{1, 3}, vars: []string{"var j int"}}, - {line: " f1(x); f1(j)", scopes: []int{1, 3}}, - {line: " case 2:", scopes: []int{1, 4}}, - {line: " k := x + 2", scopes: []int{1, 4}, vars: []string{"var k int"}}, - {line: " f1(x); f1(k)", scopes: []int{1, 4}}, - {line: " }"}, - {line: "}"}, - {line: "func TestTypeSwitch() {", vars: []string{}}, - {line: " switch x := iface.(type) {"}, - {line: " case int:", scopes: []int{1}}, - {line: " f1(x)", scopes: []int{1}, vars: []string{"var x int"}}, - {line: " case uint8:", scopes: []int{2}}, - {line: " f1(int(x))", scopes: []int{2}, vars: []string{"var x uint8"}}, - {line: " case float64:", scopes: []int{3}}, - {line: " f1(int(x)+1)", scopes: []int{3}, vars: []string{"var x float64"}}, - {line: " }"}, - {line: "}"}, - {line: "func TestSelectScope() {"}, - {line: " select {"}, - {line: " case i := <- ch:", scopes: []int{1}}, - {line: " f1(i)", scopes: []int{1}, vars: []string{"var i int"}}, - {line: " case f := <- floatch:", scopes: []int{2}}, - {line: " f1(int(f))", scopes: []int{2}, vars: []string{"var f float64"}}, - {line: " }"}, - {line: "}"}, - {line: "func TestBlock() {", vars: []string{"var a int"}}, - {line: " a := 1"}, - {line: " {"}, - {line: " b := 2", scopes: []int{1}, vars: []string{"var b int"}}, - {line: " f1(b)", scopes: []int{1}}, - {line: " f1(a)", scopes: []int{1}}, - {line: " }"}, - {line: "}"}, - {line: "func TestDiscontiguousRanges() {", vars: []string{"var a int"}}, - {line: " a := 0"}, - {line: " f1(a)"}, - {line: " {"}, - {line: " b := 0", scopes: []int{1}, vars: []string{"var b int"}}, - {line: " f2(b)", scopes: []int{1}}, - {line: " if gretbool() {", scopes: []int{1}}, - {line: " c := 0", scopes: []int{1, 2}, vars: []string{"var c int"}}, - {line: " f3(c)", scopes: []int{1, 2}}, - {line: " } else {"}, - {line: " c := 1.1", scopes: []int{1, 3}, vars: []string{"var c float64"}}, - {line: " f4(int(c))", scopes: []int{1, 3}}, - {line: " }"}, - {line: " f5(b)", scopes: []int{1}}, - {line: " }"}, - {line: " f6(a)"}, - {line: "}"}, - {line: "func TestClosureScope() {", vars: []string{"var a int", "var b int", "var f func(int)"}}, - {line: " a := 1; b := 1"}, - {line: " f := func(c int) {", scopes: []int{0}, vars: []string{"arg c int", "var &b *int", "var a int", "var d int"}, declBefore: []string{"&b", "a"}}, - {line: " d := 3"}, - {line: " f1(c); f1(d)"}, - {line: " if e := 3; e != 0 {", scopes: []int{1}, vars: []string{"var e int"}}, - {line: " f1(e)", scopes: []int{1}}, - {line: " f1(a)", scopes: []int{1}}, - {line: " b = 2", scopes: []int{1}}, - {line: " }"}, - {line: " }"}, - {line: " f(3); f1(b)"}, - {line: "}"}, - {line: "func TestEscape() {"}, - {line: " a := 1", vars: []string{"var a int"}}, - {line: " {"}, - {line: " b := 2", scopes: []int{1}, vars: []string{"var &b *int", "var p *int"}}, - {line: " p := &b", scopes: []int{1}}, - {line: " f1(a)", scopes: []int{1}}, - {line: " fi(p)", scopes: []int{1}}, - {line: " }"}, - {line: "}"}, - {line: "func TestCaptureVar(flag bool) func() int {"}, - {line: " a := 1", vars: []string{"arg flag bool", "arg ~r1 func() int", "var a int"}}, - {line: " if flag {"}, - {line: " b := 2", scopes: []int{1}, vars: []string{"var b int", "var f func() int"}}, - {line: " f := func() int {", scopes: []int{1, 0}}, - {line: " return b + 1"}, - {line: " }"}, - {line: " return f", scopes: []int{1}}, - {line: " }"}, - {line: " f1(a)"}, - {line: " return nil"}, - {line: "}"}, - {line: "func main() {"}, - {line: " TestNestedFor()"}, - {line: " TestOas2()"}, - {line: " TestIfElse()"}, - {line: " TestSwitch()"}, - {line: " TestTypeSwitch()"}, - {line: " TestSelectScope()"}, - {line: " TestBlock()"}, - {line: " TestDiscontiguousRanges()"}, - {line: " TestClosureScope()"}, - {line: " TestEscape()"}, - {line: " TestCaptureVar(true)"}, - {line: "}"}, -} - -const detailOutput = false - -// Compiles testfile checks that the description of lexical blocks emitted -// by the linker in debug_info, for each function in the main package, -// corresponds to what we expect it to be. -func TestScopeRanges(t *testing.T) { - testenv.MustHaveGoBuild(t) - t.Parallel() - - if runtime.GOOS == "plan9" { - t.Skip("skipping on plan9; no DWARF symbol table in executables") - } - - dir, err := ioutil.TempDir("", "TestScopeRanges") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) - - src, f := gobuild(t, dir, false, testfile) - defer f.Close() - - // the compiler uses forward slashes for paths even on windows - src = strings.Replace(src, "\\", "/", -1) - - pcln, err := f.PCLineTable() - if err != nil { - t.Fatal(err) - } - dwarfData, err := f.DWARF() - if err != nil { - t.Fatal(err) - } - dwarfReader := dwarfData.Reader() - - lines := make(map[line][]*lexblock) - - for { - entry, err := dwarfReader.Next() - if err != nil { - t.Fatal(err) - } - if entry == nil { - break - } - - if entry.Tag != dwarf.TagSubprogram { - continue - } - - name, ok := entry.Val(dwarf.AttrName).(string) - if !ok || !strings.HasPrefix(name, "main.Test") { - continue - } - - var scope lexblock - ctxt := scopexplainContext{ - dwarfData: dwarfData, - dwarfReader: dwarfReader, - scopegen: 1, - } - - readScope(&ctxt, &scope, entry) - - scope.markLines(pcln, lines) - } - - anyerror := false - for i := range testfile { - tgt := testfile[i].scopes - out := lines[line{src, i + 1}] - - if detailOutput { - t.Logf("%s // %v", testfile[i].line, out) - } - - scopesok := checkScopes(tgt, out) - if !scopesok { - t.Logf("mismatch at line %d %q: expected: %v got: %v\n", i, testfile[i].line, tgt, scopesToString(out)) - } - - varsok := true - if testfile[i].vars != nil { - if len(out) > 0 { - varsok = checkVars(testfile[i].vars, out[len(out)-1].vars) - if !varsok { - t.Logf("variable mismatch at line %d %q for scope %d: expected: %v got: %v\n", i+1, testfile[i].line, out[len(out)-1].id, testfile[i].vars, out[len(out)-1].vars) - } - for j := range testfile[i].decl { - if line := declLineForVar(out[len(out)-1].vars, testfile[i].decl[j]); line != i+1 { - t.Errorf("wrong declaration line for variable %s, expected %d got: %d", testfile[i].decl[j], i+1, line) - } - } - - for j := range testfile[i].declBefore { - if line := declLineForVar(out[len(out)-1].vars, testfile[i].declBefore[j]); line > i+1 { - t.Errorf("wrong declaration line for variable %s, expected %d (or less) got: %d", testfile[i].declBefore[j], i+1, line) - } - } - } - } - - anyerror = anyerror || !scopesok || !varsok - } - - if anyerror { - t.Fatalf("mismatched output") - } -} - -func scopesToString(v []*lexblock) string { - r := make([]string, len(v)) - for i, s := range v { - r[i] = strconv.Itoa(s.id) - } - return "[ " + strings.Join(r, ", ") + " ]" -} - -func checkScopes(tgt []int, out []*lexblock) bool { - if len(out) > 0 { - // omit scope 0 - out = out[1:] - } - if len(tgt) != len(out) { - return false - } - for i := range tgt { - if tgt[i] != out[i].id { - return false - } - } - return true -} - -func checkVars(tgt []string, out []variable) bool { - if len(tgt) != len(out) { - return false - } - for i := range tgt { - if tgt[i] != out[i].expr { - return false - } - } - return true -} - -func declLineForVar(scope []variable, name string) int { - for i := range scope { - if scope[i].name() == name { - return scope[i].declLine - } - } - return -1 -} - -type lexblock struct { - id int - ranges [][2]uint64 - vars []variable - scopes []lexblock -} - -type variable struct { - expr string - declLine int -} - -func (v *variable) name() string { - return strings.Split(v.expr, " ")[1] -} - -type line struct { - file string - lineno int -} - -type scopexplainContext struct { - dwarfData *dwarf.Data - dwarfReader *dwarf.Reader - scopegen int -} - -// readScope reads the DW_TAG_lexical_block or the DW_TAG_subprogram in -// entry and writes a description in scope. -// Nested DW_TAG_lexical_block entries are read recursively. -func readScope(ctxt *scopexplainContext, scope *lexblock, entry *dwarf.Entry) { - var err error - scope.ranges, err = ctxt.dwarfData.Ranges(entry) - if err != nil { - panic(err) - } - for { - e, err := ctxt.dwarfReader.Next() - if err != nil { - panic(err) - } - switch e.Tag { - case 0: - sort.Slice(scope.vars, func(i, j int) bool { - return scope.vars[i].expr < scope.vars[j].expr - }) - return - case dwarf.TagFormalParameter: - typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) - if err != nil { - panic(err) - } - scope.vars = append(scope.vars, entryToVar(e, "arg", typ)) - case dwarf.TagVariable: - typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) - if err != nil { - panic(err) - } - scope.vars = append(scope.vars, entryToVar(e, "var", typ)) - case dwarf.TagLexDwarfBlock: - scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen}) - ctxt.scopegen++ - readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e) - } - } -} - -func entryToVar(e *dwarf.Entry, kind string, typ dwarf.Type) variable { - return variable{ - fmt.Sprintf("%s %s %s", kind, e.Val(dwarf.AttrName).(string), typ.String()), - int(e.Val(dwarf.AttrDeclLine).(int64)), - } -} - -// markLines marks all lines that belong to this scope with this scope -// Recursively calls markLines for all children scopes. -func (scope *lexblock) markLines(pcln objfile.Liner, lines map[line][]*lexblock) { - for _, r := range scope.ranges { - for pc := r[0]; pc < r[1]; pc++ { - file, lineno, _ := pcln.PCToLine(pc) - l := line{file, lineno} - if len(lines[l]) == 0 || lines[l][len(lines[l])-1] != scope { - lines[l] = append(lines[l], scope) - } - } - } - - for i := range scope.scopes { - scope.scopes[i].markLines(pcln, lines) - } -} - -func gobuild(t *testing.T, dir string, optimized bool, testfile []testline) (string, *objfile.File) { - src := filepath.Join(dir, "test.go") - dst := filepath.Join(dir, "out.o") - - f, err := os.Create(src) - if err != nil { - t.Fatal(err) - } - for i := range testfile { - f.Write([]byte(testfile[i].line)) - f.Write([]byte{'\n'}) - } - f.Close() - - args := []string{"build"} - if !optimized { - args = append(args, "-gcflags=-N -l") - } - args = append(args, "-o", dst, src) - - cmd := exec.Command(testenv.GoToolPath(t), args...) - if b, err := cmd.CombinedOutput(); err != nil { - t.Logf("build: %s\n", string(b)) - t.Fatal(err) - } - - pkg, err := objfile.Open(dst) - if err != nil { - t.Fatal(err) - } - return src, pkg -} - -// TestEmptyDwarfRanges tests that no list entry in debug_ranges has start == end. -// See issue #23928. -func TestEmptyDwarfRanges(t *testing.T) { - testenv.MustHaveGoRun(t) - t.Parallel() - - if runtime.GOOS == "plan9" { - t.Skip("skipping on plan9; no DWARF symbol table in executables") - } - - dir, err := ioutil.TempDir("", "TestEmptyDwarfRanges") - if err != nil { - t.Fatalf("could not create directory: %v", err) - } - defer os.RemoveAll(dir) - - _, f := gobuild(t, dir, true, []testline{{line: "package main"}, {line: "func main(){ println(\"hello\") }"}}) - defer f.Close() - - dwarfData, err := f.DWARF() - if err != nil { - t.Fatal(err) - } - dwarfReader := dwarfData.Reader() - - for { - entry, err := dwarfReader.Next() - if err != nil { - t.Fatal(err) - } - if entry == nil { - break - } - - ranges, err := dwarfData.Ranges(entry) - if err != nil { - t.Fatal(err) - } - if ranges == nil { - continue - } - - for _, rng := range ranges { - if rng[0] == rng[1] { - t.Errorf("range entry with start == end: %v", rng) - } - } - } -} diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go index 02a4c0a688..17bbd1c3a2 100644 --- a/src/cmd/compile/internal/gc/subr.go +++ b/src/cmd/compile/internal/gc/subr.go @@ -7,7 +7,6 @@ package gc import ( "cmd/compile/internal/base" "cmd/compile/internal/ir" - "cmd/compile/internal/reflectdata" "cmd/compile/internal/ssagen" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" @@ -305,13 +304,6 @@ func cheapexpr(n ir.Node, init *ir.Nodes) ir.Node { return copyexpr(n, n.Type(), init) } -func ngotype(n ir.Node) *types.Sym { - if n.Type() != nil { - return reflectdata.TypeSym(n.Type()) - } - return nil -} - // itabType loads the _type field from a runtime.itab struct. func itabType(itab ir.Node) ir.Node { typ := ir.NewSelectorExpr(base.Pos, ir.ODOTPTR, itab, nil) -- cgit v1.3-5-g9baa