func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) { if fn.Body == nil { return } blocks := block.Build(pkg.FileSet, fn.Body) ast.Inspect(fn.Body, func(x ast.Node) bool { var list []ast.Stmt switch x := x.(type) { default: return true case *ast.BlockStmt: list = x.List case *ast.CommClause: list = x.Body case *ast.CaseClause: list = x.Body } for i := 0; i < len(list); i++ { x := list[i] if grinder.IsTerminatingStmt(blocks, x) { end := i + 1 for end < len(list) && !isGotoTarget(blocks, list[end]) { end++ } if end > i+1 { edit.Delete(edit.End(x), edit.End(list[end-1])) i = end - 1 // after i++, next iteration starts at end } } } return true }) }
func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) { if fn.Name.Name == "evconst" { old := debug debug = true defer func() { debug = old }() } if pkg.TypesError != nil { // Without scoping information, we can't be sure code moves are okay. fmt.Printf("%s: cannot inline gotos without type information\n", fn.Name) return } if fn.Body == nil { return } blocks := block.Build(pkg.FileSet, fn.Body) for labelname, gotos := range blocks.Goto { target, ok := findTargetBlock(pkg, edit, fn, blocks, labelname) if debug { println("TARGET", ok, labelname, len(gotos), target.dead, target.short) } if ok && (len(gotos) == 1 && target.dead || target.short) { numReplaced := 0 for _, g := range gotos { code := edit.TextAt(target.comment, target.start) + target.code if !objsMatch(pkg, fn, g.Pos(), target.objs, target.start, target.end) { if debug { println("OBJS DO NOT MATCH") } // Cannot inline code here; needed identifiers have different meanings. continue } if target.needReturn { // NOTE: Should really check to see if function results are shadowed. // If we screw up, the code won't compile, so we can put it off. code += "; return" } if target.needGoto != "" { code += "; goto " + target.needGoto } edit.Replace(g.Pos(), g.End(), code) numReplaced++ } if numReplaced == len(gotos) { if len(gotos) == 1 && target.dead { edit.Delete(target.comment, target.end) } else { edit.DeleteLine(target.start, target.endLabel) } } // The code we move might itself have gotos to inline, // and we can't make that change until we get new line // number position, so return after each label change. if numReplaced > 0 { return } } } }
func DeleteUnusedLabels(ctxt *grinder.Context, pkg *grinder.Package) { grinder.GrindFuncDecls(ctxt, pkg, func(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) { if fn.Body == nil { return } blocks := block.Build(pkg.FileSet, fn.Body) ast.Inspect(fn.Body, func(x ast.Node) bool { switch x := x.(type) { case *ast.LabeledStmt: if len(blocks.Goto[x.Label.Name])+len(blocks.Break[x.Label.Name])+len(blocks.Continue[x.Label.Name]) == 0 { edit.DeleteLine(x.Pos(), x.Colon+1) } case ast.Expr: return false } return true }) }) }
func analyzeFunc(pkg *grinder.Package, edit *grinder.EditBuffer, body *ast.BlockStmt) []*Var { const debug = false // Build list of candidate var declarations. inClosure := make(map[*ast.Object]bool) var objs []*ast.Object vardecl := make(map[*ast.Object]*ast.DeclStmt) ast.Inspect(body, func(x ast.Node) bool { switch x := x.(type) { case *ast.DeclStmt: decl := x.Decl.(*ast.GenDecl) if len(decl.Specs) != 1 || decl.Tok != token.VAR { break } spec := decl.Specs[0].(*ast.ValueSpec) if len(spec.Values) > 0 { break } for _, id := range spec.Names { if id.Obj != nil { objs = append(objs, id.Obj) vardecl[id.Obj] = x } } case *ast.FuncLit: ast.Inspect(x, func(x ast.Node) bool { switch x := x.(type) { case *ast.Ident: if x.Obj != nil { inClosure[x.Obj] = true } } return true }) return false } return true }) // Compute block information for entire AST. blocks := block.Build(pkg.FileSet, body) var vars []*Var // Handle each variable separately. for _, obj := range objs { // For now, refuse to touch variables shared with closures. // Could instead treat those variables as having their addresses // taken at the point where the closure appears in the source code. if inClosure[obj] { continue } // Build flow graph of nodes relevant to v. g := flow.Build(pkg.FileSet, body, func(x ast.Node) bool { return needForObj(pkg, obj, x) }) // Find reaching definitions. m := newIdentMatcher(pkg, g, obj) g.Dataflow(m) // If an instance of v can refer to multiple definitions, merge them. t := newUnionFind() for _, x := range m.list { for _, def := range m.out[x].list { t.Add(def) } } for _, x := range m.list { defs := m.out[x].list if len(defs) > 1 { for _, def := range defs[1:] { t.Merge(defs[0], def) } } } // Build list of candidate definitions. var defs []*Def nodedef := make(map[ast.Node]*Def) for _, list := range t.Sets() { x := list[0].(ast.Node) d := &Def{} defs = append(defs, d) nodedef[x] = d } // Build map from uses to candidate definitions. idToDef := make(map[ast.Node]*Def) for _, x := range m.list { if _, ok := x.(*ast.Ident); ok { if debug { fmt.Printf("ID:IN %s\n", m.nodeIn(x)) fmt.Printf("ID:OUT %s\n", m.nodeOut(x)) } defs := m.out[x].list if len(defs) > 0 { idToDef[x] = nodedef[t.Find(defs[0]).(ast.Node)] } } } // Compute start/end of where defn is needed, // along with block where defn must be placed. for _, x := range m.list { // Skip declaration without initializer. // We can move the zero initialization forward. switch x := x.(type) { case *ast.DeclStmt: if x.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values == nil { continue } } // Must use all entries in list. // Although most defs have been merged in previous passes, // the implicit zero definition of a var decl has not been. for _, def := range m.out[x].list { d := nodedef[t.Find(def).(ast.Node)] bx := blocks.Map[x] if debug { ddepth := -1 if d.Block != nil { ddepth = d.Block.Depth } fmt.Printf("ID:X %s | d=%p %p b=%p bxdepth=%d ddepth=%d\n", m.nodeIn(x), d, d.Block, bx, bx.Depth, ddepth) } if d.Block == nil { d.Block = blocks.Map[x] } else { // Hoist into block containing both preliminary d.Block and x. for bx.Depth > d.Block.Depth { bx = bx.Parent } for d.Block.Depth > bx.Depth { d.Start = d.Block.Root.Pos() d.Block = d.Block.Parent } for d.Block != bx { d.Start = d.Block.Root.Pos() d.Block = d.Block.Parent bx = bx.Parent } } if pos := x.Pos(); d.Start == 0 || pos < d.Start { d.Start = pos } if end := x.End(); end > d.End { d.End = end } if debug { fmt.Printf("ID:X -> %s:%d,%d (%d,%d) ddepth=%d\n", pkg.FileSet.Position(d.Start).Filename, pkg.FileSet.Position(d.Start).Line, pkg.FileSet.Position(d.End).Line, d.Start, d.End, d.Block.Depth) } } } // Move tentative declaration sites up as required. for { changed := false for di, d := range defs { if d == nil { continue } orig := blocks.Map[vardecl[obj]].Depth if d.Block == nil { continue } // Cannot move declarations into loops. // Without liveness analysis we cannot be sure the variable is dead on entry. for b := d.Block; b.Depth > orig; b = b.Parent { switch b.Root.(type) { case *ast.ForStmt, *ast.RangeStmt: for d.Block != b { d.Start = d.Block.Root.Pos() d.End = d.Block.Root.End() d.Block = d.Block.Parent changed = true } } } // Gotos. for labelname, list := range blocks.Goto { label := blocks.Label[labelname] for _, g := range list { // Cannot declare between backward goto (possibly in nested block) // and target label in same or outer block; without liveness information, // we can't be sure the variable is dead at the label. if vardecl[obj].Pos() < label.Pos() && label.Pos() < d.Start && d.Start < g.Pos() { for label.Pos() < d.Block.Root.Pos() { d.Block = d.Block.Parent } d.Start = label.Pos() changed = true } // Cannot declare between forward goto (possibly in nested block) // and target label in same block; Go disallows jumping over declaration. if g.Pos() < d.Start && d.Start <= label.Pos() && blocks.Map[label] == d.Block { if false { fmt.Printf("%s:%d: goto %s blocks declaration of %s here\n", pkg.FileSet.Position(d.Start).Filename, pkg.FileSet.Position(d.Start).Line, labelname, obj.Name) } d.Start = g.Pos() changed = true } } } // If we've decided on an implicit if/for/switch block, // make sure we can actually put the declaration there. // If we can't initialize the variable with := in the initializer, // must move it up out of the loop. // Need to do this now, so that the move is visible to the // goto and "one definition per block" checks below. // TODO(rsc): This should be done for all variables simultaneously, // to allow // for x, y := range z // instead of // var x, y int // var x, y = range z if !canDeclare(d.Block.Root, obj) { d.Start = d.Block.Root.Pos() d.Block = d.Block.Parent changed = true } // From a purely flow control point of view, in something like: // var x int // { // x = 2 // y = x+x // x = y // } // use(x) // The declaration 'x = 2' could be turned into a :=, since that value // is only used on the next line, except that the := will make the assignment // on the next line no longer refer to the outer x. // For each instance of x, if the proposed declaration shadows the // actual target of that x, merge the proposal into the outer declaration. for x, xDef := range idToDef { if xDef != d && xDef.Block.Depth < d.Block.Depth && d.Start <= x.Pos() && x.Pos() < d.Block.Root.End() { // xDef is an outer definition, so its start is already earlier than d's. // No need to update xDef.Start. // Not clear we care about xDef.End. // Update idToDef mappings to redirect d to xDef. for y, yDef := range idToDef { if yDef == d { idToDef[y] = xDef } } defs[di] = nil changed = true // because idToDef changed } } } // There can only be one definition (with a given name) per block. // Merge as needed. blockdef := make(map[*block.Block]*Def) for di, d := range defs { if d == nil { continue } dd := blockdef[d.Block] if dd == nil { blockdef[d.Block] = d continue } //fmt.Printf("merge defs %p %p\n", d, dd) if d.Start < dd.Start { dd.Start = d.Start } if d.End > dd.End { dd.End = d.End } for y, yDef := range idToDef { if yDef == d { idToDef[y] = dd } } defs[di] = nil changed = true } if changed { continue } // Find place to put declaration. // We established canDeclare(d.Block, obj) above. for _, d := range defs { if d == nil || d.Block == nil { continue } switch x := d.Block.Root.(type) { default: panic(fmt.Sprintf("unexpected declaration block root %T", d.Block.Root)) case *ast.BlockStmt: d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.List) case *ast.CaseClause: d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.Body) case *ast.CommClause: d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.Body) case *ast.IfStmt: if x.Init == nil { panic("if without init") } d.Init = x.Init case *ast.ForStmt: if x.Init == nil { panic("for without init") } d.Init = x.Init case *ast.RangeStmt: d.Init = x case *ast.SwitchStmt: if x.Init == nil { panic("switch without init") } d.Init = x case *ast.TypeSwitchStmt: if x.Init == nil { panic("type switch without init") } d.Init = x } if d.Init != nil && d.Init.Pos() < d.Start { d.Start = d.Init.Pos() changed = true } } if !changed { break } } // Build report. v := &Var{Obj: obj, Decl: vardecl[obj]} for _, d := range defs { if d == nil || d.Block == nil { continue } if debug { fset := pkg.FileSet fmt.Printf("\tdepth %d: %s:%d,%d (%d,%d)\n", d.Block.Depth, fset.Position(d.Start).Filename, fset.Position(d.Start).Line, fset.Position(d.End).Line, d.Start, d.End) for _, x := range m.list { if len(m.out[x].list) > 0 { if d.Block == nodedef[t.Find(m.out[x].list[0]).(ast.Node)].Block { fmt.Printf("\t%s:%d %T (%d)\n", fset.Position(x.Pos()).Filename, fset.Position(x.Pos()).Line, x, x.Pos()) } } } } v.Defs = append(v.Defs, d) } if len(v.Defs) == 1 && v.Defs[0].Init == vardecl[obj] { // No changes suggested. continue } vars = append(vars, v) } return vars }