[dev.ssa] cmd/compile: implement control flow handling
Add label and goto checks and improve test coverage.
Implement OSWITCH and OSELECT.
Implement OBREAK and OCONTINUE.
Allow generation of code in dead blocks.
Change-Id: Ibebb7c98b4b2344f46d38db7c9dce058c56beaac
Reviewed-on: https://go-review.googlesource.com/12445
Reviewed-by: Keith Randall <khr@golang.org>
diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go
index a77e788..96756b1 100644
--- a/src/cmd/compile/internal/gc/ssa.go
+++ b/src/cmd/compile/internal/gc/ssa.go
@@ -62,7 +62,8 @@
// Allocate starting values
s.vars = map[*Node]*ssa.Value{}
- s.labels = map[string]*ssa.Block{}
+ s.labels = map[string]*ssaLabel{}
+ s.labeledNodes = map[*Node]*ssaLabel{}
s.startmem = s.entryNewValue0(ssa.OpArg, ssa.TypeMem)
s.sp = s.entryNewValue0(ssa.OpSP, s.config.Uintptr) // TODO: use generic pointer type (unsafe.Pointer?) instead
s.sb = s.entryNewValue0(ssa.OpSB, s.config.Uintptr)
@@ -105,6 +106,31 @@
s.exit.Control = s.mem()
s.endBlock()
+ // Check that we used all labels
+ for name, lab := range s.labels {
+ if !lab.used() && !lab.reported {
+ yyerrorl(int(lab.defNode.Lineno), "label %v defined and not used", name)
+ lab.reported = true
+ }
+ if lab.used() && !lab.defined() && !lab.reported {
+ yyerrorl(int(lab.useNode.Lineno), "label %v not defined", name)
+ lab.reported = true
+ }
+ }
+
+ // Check any forward gotos. Non-forward gotos have already been checked.
+ for _, n := range s.fwdGotos {
+ lab := s.labels[n.Left.Sym.Name]
+ // If the label is undefined, we have already have printed an error.
+ if lab.defined() {
+ s.checkgoto(n, lab.defNode)
+ }
+ }
+
+ if nerrors > 0 {
+ return nil, false
+ }
+
// Link up variable uses to variable definitions
s.linkForwardReferences()
@@ -132,8 +158,16 @@
// exit block that "return" jumps to (and panics jump to)
exit *ssa.Block
- // the target block for each label in f
- labels map[string]*ssa.Block
+ // labels and labeled control flow nodes (OFOR, OSWITCH, OSELECT) in f
+ labels map[string]*ssaLabel
+ labeledNodes map[*Node]*ssaLabel
+
+ // gotos that jump forward; required for deferred checkgoto calls
+ fwdGotos []*Node
+
+ // unlabeled break and continue statement tracking
+ breakTo *ssa.Block // current target for plain break statement
+ continueTo *ssa.Block // current target for plain continue statement
// current location where we're interpreting the AST
curBlock *ssa.Block
@@ -157,6 +191,34 @@
line []int32
}
+type ssaLabel struct {
+ target *ssa.Block // block identified by this label
+ breakTarget *ssa.Block // block to break to in control flow node identified by this label
+ continueTarget *ssa.Block // block to continue to in control flow node identified by this label
+ defNode *Node // label definition Node (OLABEL)
+ // Label use Node (OGOTO, OBREAK, OCONTINUE).
+ // Used only for error detection and reporting.
+ // There might be multiple uses, but we only need to track one.
+ useNode *Node
+ reported bool // reported indicates whether an error has already been reported for this label
+}
+
+// defined reports whether the label has a definition (OLABEL node).
+func (l *ssaLabel) defined() bool { return l.defNode != nil }
+
+// used reports whether the label has a use (OGOTO, OBREAK, or OCONTINUE node).
+func (l *ssaLabel) used() bool { return l.useNode != nil }
+
+// label returns the label associated with sym, creating it if necessary.
+func (s *state) label(sym *Sym) *ssaLabel {
+ lab := s.labels[sym.Name]
+ if lab == nil {
+ lab = new(ssaLabel)
+ s.labels[sym.Name] = lab
+ }
+ return lab
+}
+
func (s *state) Logf(msg string, args ...interface{}) { s.config.Logf(msg, args...) }
func (s *state) Fatalf(msg string, args ...interface{}) { s.config.Fatalf(msg, args...) }
func (s *state) Unimplementedf(msg string, args ...interface{}) { s.config.Unimplementedf(msg, args...) }
@@ -206,6 +268,10 @@
return s.line[len(s.line)-1]
}
+func (s *state) Error(msg string, args ...interface{}) {
+ yyerrorl(int(s.peekLine()), msg, args...)
+}
+
// newValue0 adds a new value with no arguments to the current block.
func (s *state) newValue0(op ssa.Op, t ssa.Type) *ssa.Value {
return s.curBlock.NewValue0(s.peekLine(), op, t)
@@ -293,6 +359,16 @@
s.pushLine(n.Lineno)
defer s.popLine()
+ // If s.curBlock is nil, then we're about to generate dead code.
+ // We can't just short-circuit here, though,
+ // because we check labels and gotos as part of SSA generation.
+ // Provide a block for the dead code so that we don't have
+ // to add special cases everywhere else.
+ if s.curBlock == nil {
+ dead := s.f.NewBlock(ssa.BlockPlain)
+ s.startBlock(dead)
+ }
+
s.stmtList(n.Ninit)
switch n.Op {
@@ -325,32 +401,61 @@
}
s.assign(OAS, n.Left.Name.Heapaddr, palloc)
- case OLABEL, OGOTO:
- if n.Op == OLABEL && isblanksym(n.Left.Sym) {
+ case OLABEL:
+ sym := n.Left.Sym
+
+ if isblanksym(sym) {
// Empty identifier is valid but useless.
// See issues 11589, 11593.
return
}
- // get block at label, or make one
- t := s.labels[n.Left.Sym.Name]
- if t == nil {
- t = s.f.NewBlock(ssa.BlockPlain)
- s.labels[n.Left.Sym.Name] = t
- }
- // go to that label (we pretend "label:" is preceded by "goto label")
- if b := s.endBlock(); b != nil {
- addEdge(b, t)
+
+ lab := s.label(sym)
+
+ // Associate label with its control flow node, if any
+ if ctl := n.Name.Defn; ctl != nil {
+ switch ctl.Op {
+ case OFOR, OSWITCH, OSELECT:
+ s.labeledNodes[ctl] = lab
+ }
}
- if n.Op == OLABEL {
- // next we work on the label's target block
- s.startBlock(t)
+ if !lab.defined() {
+ lab.defNode = n
+ } else {
+ s.Error("label %v already defined at %v", sym, Ctxt.Line(int(lab.defNode.Lineno)))
+ lab.reported = true
}
- if n.Op == OGOTO && s.curBlock == nil {
- s.Unimplementedf("goto at start of function; see test/goto.go")
- panic("stop compiling here, on pain of infinite loops")
+ // The label might already have a target block via a goto.
+ if lab.target == nil {
+ lab.target = s.f.NewBlock(ssa.BlockPlain)
}
+ // go to that label (we pretend "label:" is preceded by "goto label")
+ b := s.endBlock()
+ addEdge(b, lab.target)
+ s.startBlock(lab.target)
+
+ case OGOTO:
+ sym := n.Left.Sym
+
+ lab := s.label(sym)
+ if lab.target == nil {
+ lab.target = s.f.NewBlock(ssa.BlockPlain)
+ }
+ if !lab.used() {
+ lab.useNode = n
+ }
+
+ if lab.defined() {
+ s.checkgoto(n, lab.defNode)
+ } else {
+ s.fwdGotos = append(s.fwdGotos, n)
+ }
+
+ b := s.endBlock()
+ addEdge(b, lab.target)
+
case OAS, OASWB:
s.assign(n.Op, n.Left, n.Right)
@@ -396,6 +501,58 @@
b := s.endBlock()
addEdge(b, s.exit)
+ case OCONTINUE, OBREAK:
+ var op string
+ var to *ssa.Block
+ switch n.Op {
+ case OCONTINUE:
+ op = "continue"
+ to = s.continueTo
+ case OBREAK:
+ op = "break"
+ to = s.breakTo
+ }
+ if n.Left == nil {
+ // plain break/continue
+ if to == nil {
+ s.Error("%s is not in a loop", op)
+ return
+ }
+ // nothing to do; "to" is already the correct target
+ } else {
+ // labeled break/continue; look up the target
+ sym := n.Left.Sym
+ lab := s.label(sym)
+ if !lab.used() {
+ lab.useNode = n.Left
+ }
+ if !lab.defined() {
+ s.Error("%s label not defined: %v", op, sym)
+ lab.reported = true
+ return
+ }
+ switch n.Op {
+ case OCONTINUE:
+ to = lab.continueTarget
+ case OBREAK:
+ to = lab.breakTarget
+ }
+ if to == nil {
+ // Valid label but not usable with a break/continue here, e.g.:
+ // for {
+ // continue abc
+ // }
+ // abc:
+ // for {}
+ s.Error("invalid %s label %v", op, sym)
+ lab.reported = true
+ return
+ }
+ }
+
+ b := s.endBlock()
+ addEdge(b, to)
+
case OFOR:
// OFOR: for Ninit; Left; Right { Nbody }
bCond := s.f.NewBlock(ssa.BlockPlain)
@@ -422,9 +579,31 @@
addEdge(b, bBody)
addEdge(b, bEnd)
+ // set up for continue/break in body
+ prevContinue := s.continueTo
+ prevBreak := s.breakTo
+ s.continueTo = bIncr
+ s.breakTo = bEnd
+ lab := s.labeledNodes[n]
+ if lab != nil {
+ // labeled for loop
+ lab.continueTarget = bIncr
+ lab.breakTarget = bEnd
+ }
+
// generate body
s.startBlock(bBody)
s.stmtList(n.Nbody)
+
+ // tear down continue/break
+ s.continueTo = prevContinue
+ s.breakTo = prevBreak
+ if lab != nil {
+ lab.continueTarget = nil
+ lab.breakTarget = nil
+ }
+
+ // done with body, goto incr
if b := s.endBlock(); b != nil {
addEdge(b, bIncr)
}
@@ -439,6 +618,32 @@
}
s.startBlock(bEnd)
+ case OSWITCH, OSELECT:
+ // These have been mostly rewritten by the front end into their Nbody fields.
+ // Our main task is to correctly hook up any break statements.
+ bEnd := s.f.NewBlock(ssa.BlockPlain)
+
+ prevBreak := s.breakTo
+ s.breakTo = bEnd
+ lab := s.labeledNodes[n]
+ if lab != nil {
+ // labeled
+ lab.breakTarget = bEnd
+ }
+
+ // generate body code
+ s.stmtList(n.Nbody)
+
+ s.breakTo = prevBreak
+ if lab != nil {
+ lab.breakTarget = nil
+ }
+
+ if b := s.endBlock(); b != nil {
+ addEdge(b, bEnd)
+ }
+ s.startBlock(bEnd)
+
case OVARKILL:
// TODO(khr): ??? anything to do here? Only for addrtaken variables?
// Maybe just link it in the store chain?
@@ -924,14 +1129,66 @@
s.startBlock(bNext)
}
+// checkgoto checks that a goto from from to to does not
+// jump into a block or jump over variable declarations.
+// It is a copy of checkgoto in the pre-SSA backend,
+// modified only for line number handling.
+// TODO: document how this works and why it is designed the way it is.
+func (s *state) checkgoto(from *Node, to *Node) {
+ if from.Sym == to.Sym {
+ return
+ }
+
+ nf := 0
+ for fs := from.Sym; fs != nil; fs = fs.Link {
+ nf++
+ }
+ nt := 0
+ for fs := to.Sym; fs != nil; fs = fs.Link {
+ nt++
+ }
+ fs := from.Sym
+ for ; nf > nt; nf-- {
+ fs = fs.Link
+ }
+ if fs != to.Sym {
+ // decide what to complain about.
+ // prefer to complain about 'into block' over declarations,
+ // so scan backward to find most recent block or else dcl.
+ var block *Sym
+
+ var dcl *Sym
+ ts := to.Sym
+ for ; nt > nf; nt-- {
+ if ts.Pkg == nil {
+ block = ts
+ } else {
+ dcl = ts
+ }
+ ts = ts.Link
+ }
+
+ for ts != fs {
+ if ts.Pkg == nil {
+ block = ts
+ } else {
+ dcl = ts
+ }
+ ts = ts.Link
+ fs = fs.Link
+ }
+
+ lno := int(from.Left.Lineno)
+ if block != nil {
+ yyerrorl(lno, "goto %v jumps into block starting at %v", from.Left.Sym, Ctxt.Line(int(block.Lastlineno)))
+ } else {
+ yyerrorl(lno, "goto %v jumps over declaration of %v at %v", from.Left.Sym, dcl, Ctxt.Line(int(dcl.Lastlineno)))
+ }
+ }
+}
+
// variable returns the value of a variable at the current location.
func (s *state) variable(name *Node, t ssa.Type) *ssa.Value {
- if s.curBlock == nil {
- // Unimplemented instead of Fatal because fixedbugs/bug303.go
- // demonstrates a case in which this appears to happen legitimately.
- // TODO: decide on the correct behavior here.
- s.Unimplementedf("nil curblock adding variable %v (%v)", name, t)
- }
v := s.vars[name]
if v == nil {
// TODO: get type? Take Sym as arg?
@@ -989,8 +1246,13 @@
vals = append(vals, s.lookupVarOutgoing(p, t, name))
}
if len(vals) == 0 {
- s.Unimplementedf("TODO: Handle fixedbugs/bug076.go")
- return nil
+ // This block is dead; we have no predecessors and we're not the entry block.
+ // It doesn't matter what we use here as long as it is well-formed,
+ // so use the default/zero value.
+ if name == &memvar {
+ return s.startmem
+ }
+ return s.zeroVal(name.Type)
}
v0 := vals[0]
for i := 1; i < len(vals); i++ {