]> Cypherpunks repositories - gostls13.git/commitdiff
Implement labels, goto, labeled break, and labeled continue.
authorAustin Clements <aclements@csail.mit.edu>
Thu, 30 Jul 2009 21:39:27 +0000 (14:39 -0700)
committerAustin Clements <aclements@csail.mit.edu>
Thu, 30 Jul 2009 21:39:27 +0000 (14:39 -0700)
Return checking is now done as a general flow check at the end
of function compilation, since break and goto complicated the
way I was doing return checking before.  Goto-over-declaration
checking is also done as a final flow check.

Temporary variables used for effect extraction are now
actually temporary.  Otherwise "op=", "++", and "--" appear as
declarations that cannot be jumped over.

R=rsc
APPROVED=rsc
DELTA=421  (344 added, 38 deleted, 39 changed)
OCL=32527
CL=32535

usr/austin/eval/compiler.go
usr/austin/eval/decls.go
usr/austin/eval/expr.go
usr/austin/eval/scope.go
usr/austin/eval/stmt.go

index 641e6a293b7de07d410559b03a5d53894daf6c98..59858c800b18c602e8c0dddfb3a3787c464af1b5 100644 (file)
@@ -44,7 +44,27 @@ func (a *compiler) compileFuncType(b *block, typ *ast.FuncType) *FuncDecl
 func (a *compiler) compileArrayLen(b *block, expr ast.Expr) (int64, bool)
 
 
+type label struct {
+       name string;
+       desc string;
+       // The PC goto statements should jump to, or nil if this label
+       // cannot be goto'd (such as an anonymous for loop label).
+       gotoPC *uint;
+       // The PC break statements should jump to, or nil if a break
+       // statement is invalid.
+       breakPC *uint;
+       // The PC continue statements should jump to, or nil if a
+       // continue statement is invalid.
+       continuePC *uint;
+       // The position where this label was resolved.  If it has not
+       // been resolved yet, an invalid position.
+       resolved token.Position;
+       // The position where this label was first jumped to.
+       used token.Position;
+}
+
 type codeBuf struct
+type flowBuf struct
 type FuncType struct
 // A funcCompiler captures information used throughout the compilation
 // of a single function body.
@@ -55,22 +75,21 @@ type funcCompiler struct {
        // kinds of return statements are legal.
        outVarsNamed bool;
        *codeBuf;
+       flow *flowBuf;
+       labels map[string] *label;
        err bool;
 }
 
+func (a *funcCompiler) checkLabels()
 
 // A blockCompiler captures information used throughout the compilation
 // of a single block within a function.
 type blockCompiler struct {
        *funcCompiler;
        block *block;
-       returned bool;
-       // The PC break statements should jump to, or nil if a break
-       // statement is invalid.
-       breakPC *uint;
-       // The PC continue statements should jump to, or nil if a
-       // continue statement is invalid.
-       continuePC *uint;
+       // The label of this block, used for finding break and
+       // continue labels.
+       label *label;
        // The blockCompiler for the block enclosing this one, or nil
        // for a function-level block.
        parent *blockCompiler;
index 2f71f11f3b27cfa433450b797f8fe1b08dc43e06..439b8b2162069b6b525eb366a761e4cf550c87e1 100644 (file)
@@ -178,7 +178,7 @@ func (b *block) enterChild() *block
 func (b *block) exit()
 func (b *block) ChildScope() *Scope
 func (b *block) DefineVar(name string, t Type) *Variable
-func (b *block) DefineTemp(t Type) *Variable
+func (b *block) DefineSlot(t Type) *Variable
 func (b *block) DefineConst(name string, t Type, v Value) *Constant
 func (b *block) DefineType(name string, t Type) Type
 func (b *block) Lookup(name string) (level int, def Def)
index fbd4b5ac4eafd42fb0639cf387cb39ef9c7e48cf..1231e22586adfc8c657290e75a9397df5afe6baf 100644 (file)
@@ -1324,7 +1324,10 @@ func (a *compiler) compileExpr(b *block, expr ast.Expr, constant bool) *exprComp
 // extractEffect separates out any effects that the expression may
 // have, returning a function that will perform those effects and a
 // new exprCompiler that is guaranteed to be side-effect free.  These
-// are the moral equivalents of "temp := &expr" and "*temp".
+// are the moral equivalents of "temp := &expr" and "*temp".  Because
+// this creates a temporary variable, the caller should create a
+// temporary block for the compilation of this expression and the
+// evaluation of the results.
 //
 // Implementation limit: The expression must be addressable.
 func (a *exprCompiler) extractEffect() (func(f *Frame), *exprCompiler) {
@@ -1337,9 +1340,7 @@ func (a *exprCompiler) extractEffect() (func(f *Frame), *exprCompiler) {
        // Create temporary
        tempBlock := a.block;
        tempType := NewPtrType(a.t);
-       // TODO(austin) These temporaries accumulate in the scope.  We
-       // could enter a temporary block, but the caller has to exit it.
-       temp := tempBlock.DefineTemp(tempType);
+       temp := tempBlock.DefineSlot(tempType);
        tempIdx := temp.Index;
 
        // Generate "temp := &e"
index 6d89d00d789cda52ab613e0d74dc5c318a7f732b..aed896f9590ed1a86264a9044a92e54b599bfc76 100644 (file)
@@ -51,14 +51,14 @@ func (b *block) DefineVar(name string, t Type) *Variable {
        if _, ok := b.defs[name]; ok {
                return nil;
        }
-       v := b.DefineTemp(t);
+       v := b.DefineSlot(t);
        if v != nil {
                b.defs[name] = v;
        }
        return v;
 }
 
-func (b *block) DefineTemp(t Type) *Variable {
+func (b *block) DefineSlot(t Type) *Variable {
        if b.inner != nil {
                log.Crash("Failed to exit child block before defining variable");
        }
index 65d97ac3e5cae35357235096786c914312b01761..88febdc376f4ec2dbc4dc77dcadd77996aedf5c8 100644 (file)
@@ -15,6 +15,11 @@ import (
        "strconv";
 )
 
+const (
+       returnPC = ^uint(0);
+       badPC = ^uint(1);
+)
+
 /*
  * Statement compiler
  */
@@ -22,6 +27,8 @@ import (
 type stmtCompiler struct {
        *blockCompiler;
        pos token.Position;
+       // This statement's label, or nil if it is not labeled.
+       stmtLabel *label;
        // err should be initialized to true before visiting and set
        // to false when the statement is compiled successfully.  The
        // function invoking Visit should or this with
@@ -34,6 +41,183 @@ func (a *stmtCompiler) diag(format string, args ...) {
        a.diagAt(&a.pos, format, args);
 }
 
+/*
+ * Flow checker
+ */
+
+type flowEnt struct {
+       // Whether this flow entry is conditional.  If true, flow can
+       // continue to the next PC.
+       cond bool;
+       // True if this will terminate flow (e.g., a return statement).
+       // cond must be false and jumps must be nil if this is true.
+       term bool;
+       // PC's that can be reached from this flow entry.
+       jumps []*uint;
+       // Whether this flow entry has been visited by reachesEnd.
+       visited bool;
+}
+
+type flowBlock struct {
+       // If this is a goto, the target label.
+       target string;
+       // The inner-most block containing definitions.
+       block *block;
+       // The numVars from each block leading to the root of the
+       // scope, starting at block.
+       numVars []int;
+}
+
+type flowBuf struct {
+       cb *codeBuf;
+       // ents is a map from PC's to flow entries.  Any PC missing
+       // from this map is assumed to reach only PC+1.
+       ents map[uint] *flowEnt;
+       // gotos is a map from goto positions to information on the
+       // block at the point of the goto.
+       gotos map[*token.Position] *flowBlock;
+       // labels is a map from label name to information on the block
+       // at the point of the label.  labels are tracked by name,
+       // since mutliple labels at the same PC can have different
+       // blocks.
+       labels map[string] *flowBlock;
+}
+
+func newFlowBuf(cb *codeBuf) *flowBuf {
+       return &flowBuf{cb, make(map[uint] *flowEnt), make(map[*token.Position] *flowBlock), make(map[string] *flowBlock)};
+}
+
+// put creates a flow control point for the next PC in the code buffer.
+// This should be done before pushing the instruction into the code buffer.
+func (f *flowBuf) put(cond bool, term bool, jumps []*uint) {
+       pc := f.cb.nextPC();
+       if ent, ok := f.ents[pc]; ok {
+               log.Crashf("Flow entry already exists at PC %d: %+v", pc, ent);
+       }
+       f.ents[pc] = &flowEnt{cond, term, jumps, false};
+}
+
+// putTerm creates a flow control point at the next PC that
+// unconditionally terminates execution.
+func (f *flowBuf) putTerm() {
+       f.put(false, true, nil);
+}
+
+// put1 creates a flow control point at the next PC that jumps to one
+// PC and, if cond is true, can also continue to the PC following the
+// next PC.
+func (f *flowBuf) put1(cond bool, jumpPC *uint) {
+       f.put(cond, false, []*uint {jumpPC});
+}
+
+func newFlowBlock(target string, b *block) *flowBlock {
+       // Find the inner-most block containing definitions
+       for b.numVars == 0 && b.outer != nil && b.outer.scope == b.scope {
+               b = b.outer;
+       }
+
+       // Count parents leading to the root of the scope
+       n := 0;
+       for bp := b; bp.scope == b.scope; bp = bp.outer {
+               n++;
+       }
+
+       // Capture numVars from each block to the root of the scope
+       numVars := make([]int, n);
+       i := 0;
+       for bp := b; i < n; bp = bp.outer {
+               numVars[i] = bp.numVars;
+               i++;
+       }
+
+       return &flowBlock{target, b, numVars};
+}
+
+// putGoto captures the block at a goto statement.  This should be
+// called in addition to putting a flow control point.
+func (f *flowBuf) putGoto(pos token.Position, target string, b *block) {
+       f.gotos[&pos] = newFlowBlock(target, b);
+}
+
+// putLabel captures the block at a label.
+func (f *flowBuf) putLabel(name string, b *block) {
+       f.labels[name] = newFlowBlock("", b);
+}
+
+// reachesEnd returns true if the end of f's code buffer can be
+// reached from the given program counter.  Error reporting is the
+// caller's responsibility.
+func (f *flowBuf) reachesEnd(pc uint) bool {
+       endPC := f.cb.nextPC();
+       if pc > endPC {
+               log.Crashf("Reached bad PC %d past end PC %d", pc, endPC);
+       }
+
+       for ; pc < endPC; pc++ {
+               ent, ok := f.ents[pc];
+               if !ok {
+                       continue;
+               }
+
+               if ent.visited {
+                       return false;
+               }
+               ent.visited = true;
+
+               if ent.term {
+                       return false;
+               }
+
+               // If anything can reach the end, we can reach the end
+               // from pc.
+               for _, j := range ent.jumps {
+                       if f.reachesEnd(*j) {
+                               return true;
+                       }
+               }
+               // If the jump was conditional, we can reach the next
+               // PC, so try reaching the end from it.
+               if ent.cond {
+                       continue;
+               }
+               return false;
+       }
+       return true;
+}
+
+// gotosObeyScopes returns true if no goto statement causes any
+// variables to come into scope that were not in scope at the point of
+// the goto.  Reports any errors using the given compiler.
+func (f *flowBuf) gotosObeyScopes(a *compiler) bool {
+       for pos, src := range f.gotos {
+               tgt := f.labels[src.target];
+
+               // The target block must be a parent of this block
+               numVars := src.numVars;
+               b := src.block;
+               for len(numVars) > 0 && b != tgt.block {
+                       b = b.outer;
+                       numVars = numVars[1:len(numVars)];
+               }
+               if b != tgt.block {
+                       // We jumped into a deeper block
+                       a.diagAt(pos, "goto causes variables to come into scope");
+                       return false;
+               }
+
+               // There must be no variables in the target block that
+               // did not exist at the jump
+               tgtNumVars := tgt.numVars;
+               for i := range numVars {
+                       if tgtNumVars[i] > numVars[i] {
+                               a.diagAt(pos, "goto causes variables to come into scope");
+                               return false;
+                       }
+               }
+       }
+       return true;
+}
+
 /*
  * Statement visitors
  */
@@ -51,7 +235,36 @@ func (a *stmtCompiler) DoEmptyStmt(s *ast.EmptyStmt) {
 }
 
 func (a *stmtCompiler) DoLabeledStmt(s *ast.LabeledStmt) {
-       log.Crash("Not implemented");
+       bad := false;
+
+       // Define label
+       l, ok := a.labels[s.Label.Value];
+       if ok {
+               if l.resolved.IsValid() {
+                       a.diag("label %s redefined; previous definition at line %d", s.Label.Value, l.resolved.Line);
+                       bad = true;
+               }
+       } else {
+               pc := badPC;
+               l = &label{name: s.Label.Value, gotoPC: &pc};
+               a.labels[l.name] = l;
+       }
+       l.desc = "regular label";
+       l.resolved = s.Pos();
+
+       // Set goto PC
+       *l.gotoPC = a.nextPC();
+
+       // Define flow entry so we can check for jumps over declarations.
+       a.flow.putLabel(l.name, a.block);
+
+       // Compile the statement.  Reuse our stmtCompiler for simplicity.
+       a.pos = s.Stmt.Pos();
+       a.stmtLabel = l;
+       s.Stmt.Visit(a);
+       if bad {
+               a.err = true;
+       }
 }
 
 func (a *stmtCompiler) DoExprStmt(s *ast.ExprStmt) {
@@ -73,7 +286,11 @@ func (a *stmtCompiler) DoExprStmt(s *ast.ExprStmt) {
 }
 
 func (a *stmtCompiler) DoIncDecStmt(s *ast.IncDecStmt) {
-       l := a.compileExpr(a.block, s.X, false);
+       // Create temporary block for extractEffect
+       bc := a.enterChild();
+       defer bc.exit();
+
+       l := a.compileExpr(bc.block, s.X, false);
        if l == nil {
                return;
        }
@@ -330,8 +547,12 @@ func (a *stmtCompiler) doAssignOp(s *ast.AssignStmt) {
                return;
        }
 
-       l := a.compileExpr(a.block, s.Lhs[0], false);
-       r := a.compileExpr(a.block, s.Rhs[0], false);
+       // Create temporary block for extractEffect
+       bc := a.enterChild();
+       defer bc.exit();
+
+       l := a.compileExpr(bc.block, s.Lhs[0], false);
+       r := a.compileExpr(bc.block, s.Rhs[0], false);
        if l == nil || r == nil {
                return;
        }
@@ -387,13 +608,10 @@ func (a *stmtCompiler) DoReturnStmt(s *ast.ReturnStmt) {
                return;
        }
 
-       // Supress return errors even if we fail to compile this
-       // return statement.
-       a.returned = true;
-
        if len(s.Results) == 0 && (len(a.fnType.Out) == 0 || a.outVarsNamed) {
                // Simple case.  Simply exit from the function.
-               a.push(func(v *vm) { v.pc = ^uint(0) });
+               a.flow.putTerm();
+               a.push(func(v *vm) { v.pc = returnPC });
                a.err = false;
                return;
        }
@@ -429,49 +647,68 @@ func (a *stmtCompiler) DoReturnStmt(s *ast.ReturnStmt) {
        // Compile
        start := len(a.fnType.In);
        nout := len(a.fnType.Out);
+       a.flow.putTerm();
        a.push(func(v *vm) {
                assign(multiV(v.f.Vars[start:start+nout]), v.f);
-               v.pc = ^uint(0);
+               v.pc = returnPC;
        });
        a.err = false;
 }
 
+func (a *stmtCompiler) findLexicalLabel(name *ast.Ident, pred func(*label) bool, errOp, errCtx string) *label {
+       bc := a.blockCompiler;
+       for ; bc != nil; bc = bc.parent {
+               if bc.label == nil {
+                       continue;
+               }
+               l := bc.label;
+               if name == nil && pred(l) {
+                       return l;
+               }
+               if name != nil && l.name == name.Value {
+                       if !pred(l) {
+                               a.diag("cannot %s to %s %s", errOp, l.desc, l.name);
+                               return nil;
+                       }
+                       return l;
+               }
+       }
+       if name == nil {
+               a.diag("%s outside %s", errOp, errCtx);
+       } else {
+               a.diag("%s label %s not defined", errOp, name.Value);
+       }
+       return nil;
+}
+
 func (a *stmtCompiler) DoBranchStmt(s *ast.BranchStmt) {
+       var pc *uint;
+
        switch s.Tok {
        case token.BREAK:
-               if s.Label != nil {
-                       log.Crash("break with label not implemented");
-               }
-
-               bc := a.blockCompiler;
-               for ; bc != nil; bc = bc.parent {
-                       if bc.breakPC != nil {
-                               pc := bc.breakPC;
-                               a.push(func(v *vm) { v.pc = *pc });
-                               a.err = false;
-                               return;
-                       }
+               l := a.findLexicalLabel(s.Label, func(l *label) bool { return l.breakPC != nil }, "break", "for loop, switch, or select");
+               if l == nil {
+                       return;
                }
-               a.diag("break outside for loop, switch, or select");
+               pc = l.breakPC;
 
        case token.CONTINUE:
-               if s.Label != nil {
-                       log.Crash("continue with label not implemented");
+               l := a.findLexicalLabel(s.Label, func(l *label) bool { return l.continuePC != nil }, "continue", "for loop");
+               if l == nil {
+                       return;
                }
+               pc = l.continuePC;
 
-               bc := a.blockCompiler;
-               for ; bc != nil; bc = bc.parent {
-                       if bc.continuePC != nil {
-                               pc := bc.continuePC;
-                               a.push(func(v *vm) { v.pc = *pc });
-                               a.err = false;
-                               return;
-                       }
+       case token.GOTO:
+               l, ok := a.labels[s.Label.Value];
+               if !ok {
+                       pc := badPC;
+                       l = &label{name: s.Label.Value, desc: "unresolved label", gotoPC: &pc, used: s.Pos()};
+                       a.labels[l.name] = l;
                }
-               a.diag("continue outside for loop");
 
-       case token.GOTO:
-               log.Crash("goto not implemented");
+               pc = l.gotoPC;
+               a.flow.putGoto(s.Pos(), l.name, a.block);
 
        case token.FALLTHROUGH:
                log.Crash("fallthrough not implemented");
@@ -479,6 +716,10 @@ func (a *stmtCompiler) DoBranchStmt(s *ast.BranchStmt) {
        default:
                log.Crash("Unexpected branch token %v", s.Tok);
        }
+
+       a.flow.put1(false, pc);
+       a.push(func(v *vm) { v.pc = *pc });
+       a.err = false;
 }
 
 func (a *stmtCompiler) DoBlockStmt(s *ast.BlockStmt) {
@@ -486,7 +727,6 @@ func (a *stmtCompiler) DoBlockStmt(s *ast.BlockStmt) {
        bc.compileStmts(s);
        bc.exit();
 
-       a.returned = a.returned || bc.returned;
        a.err = false;
 }
 
@@ -509,7 +749,8 @@ func (a *stmtCompiler) DoIfStmt(s *ast.IfStmt) {
                bc.compileStmt(s.Init);
        }
 
-       var elsePC, endPC uint;
+       elsePC := badPC;
+       endPC := badPC;
 
        // Compile condition, if any.  If there is no condition, we
        // fall through to the body.
@@ -524,6 +765,7 @@ func (a *stmtCompiler) DoIfStmt(s *ast.IfStmt) {
                        bad = true;
                default:
                        eval := e.asBool();
+                       a.flow.put1(true, &elsePC);
                        a.push(func(v *vm) {
                                if !eval(v.f) {
                                        v.pc = elsePC;
@@ -540,15 +782,12 @@ func (a *stmtCompiler) DoIfStmt(s *ast.IfStmt) {
        // Compile else
        if s.Else != nil {
                // Skip over else if we executed the body
+               a.flow.put1(false, &endPC);
                a.push(func(v *vm) {
                        v.pc = endPC;
                });
                elsePC = a.nextPC();
                bc.compileStmt(s.Else);
-
-               if body.returned && bc.returned {
-                       a.returned = true;
-               }
        } else {
                elsePC = a.nextPC();
        }
@@ -593,17 +832,27 @@ func (a *stmtCompiler) DoForStmt(s *ast.ForStmt) {
                bc.compileStmt(s.Init);
        }
 
-       var bodyPC, postPC, checkPC, endPC uint;
+       bodyPC := badPC;
+       postPC := badPC;
+       checkPC := badPC;
+       endPC := badPC;
 
        // Jump to condition check.  We generate slightly less code by
        // placing the condition check after the body.
+       a.flow.put1(false, &checkPC);
        a.push(func(v *vm) { v.pc = checkPC });
 
        // Compile body
        bodyPC = a.nextPC();
        body := bc.enterChild();
-       body.breakPC = &endPC;
-       body.continuePC = &postPC;
+       if a.stmtLabel != nil {
+               body.label = a.stmtLabel;
+       } else {
+               body.label = &label{resolved: s.Pos()};
+       }
+       body.label.desc = "for loop";
+       body.label.breakPC = &endPC;
+       body.label.continuePC = &postPC;
        body.compileStmts(s.Body);
        body.exit();
 
@@ -620,6 +869,7 @@ func (a *stmtCompiler) DoForStmt(s *ast.ForStmt) {
        checkPC = a.nextPC();
        if s.Cond == nil {
                // If the condition is absent, it is equivalent to true.
+               a.flow.put1(false, &bodyPC);
                a.push(func(v *vm) { v.pc = bodyPC });
        } else {
                e := bc.compileExpr(bc.block, s.Cond, false);
@@ -631,6 +881,7 @@ func (a *stmtCompiler) DoForStmt(s *ast.ForStmt) {
                        bad = true;
                default:
                        eval := e.asBool();
+                       a.flow.put1(true, &bodyPC);
                        a.push(func(v *vm) {
                                if eval(v.f) {
                                        v.pc = bodyPC;
@@ -658,7 +909,7 @@ func (a *blockCompiler) compileStmt(s ast.Stmt) {
        if a.block.inner != nil {
                log.Crash("Child scope still entered");
        }
-       sc := &stmtCompiler{a, s.Pos(), true};
+       sc := &stmtCompiler{a, s.Pos(), nil, true};
        s.Visit(sc);
        if a.block.inner != nil {
                log.Crash("Forgot to exit child scope");
@@ -677,7 +928,6 @@ func (a *blockCompiler) enterChild() *blockCompiler {
        return &blockCompiler{
                funcCompiler: a.funcCompiler,
                block: block,
-               returned: false,
                parent: a,
        };
 }
@@ -701,33 +951,36 @@ func (a *compiler) compileFunc(b *block, decl *FuncDecl, body *ast.BlockStmt) (f
                if decl.InNames[i] != nil {
                        bodyScope.DefineVar(decl.InNames[i].Value, t);
                } else {
-                       // TODO(austin) Not technically a temp
-                       bodyScope.DefineTemp(t);
+                       bodyScope.DefineSlot(t);
                }
        }
        for i, t := range decl.Type.Out {
                if decl.OutNames[i] != nil {
                        bodyScope.DefineVar(decl.OutNames[i].Value, t);
                } else {
-                       bodyScope.DefineTemp(t);
+                       bodyScope.DefineSlot(t);
                }
        }
 
        // Create block context
-       fc := &funcCompiler{a, decl.Type, false, newCodeBuf(), false};
-       if len(decl.OutNames) > 0 && decl.OutNames[0] != nil {
-               fc.outVarsNamed = true;
-       }
+       cb := newCodeBuf();
+       fc := &funcCompiler{
+               compiler: a,
+               fnType: decl.Type,
+               outVarsNamed: len(decl.OutNames) > 0 && decl.OutNames[0] != nil,
+               codeBuf: cb,
+               flow: newFlowBuf(cb),
+               labels: make(map[string] *label),
+               err: false,
+       };
        bc := &blockCompiler{
                funcCompiler: fc,
                block: bodyScope.block,
-               returned: false,
        };
 
        // Compile body
        bc.compileStmts(body);
-
-       // TODO(austin) Check that all gotos were linked?
+       fc.checkLabels();
 
        if fc.err {
                return nil;
@@ -735,7 +988,7 @@ func (a *compiler) compileFunc(b *block, decl *FuncDecl, body *ast.BlockStmt) (f
 
        // Check that the body returned if necessary.  We only check
        // this if there were no errors compiling the body.
-       if len(decl.Type.Out) > 0 && !bc.returned {
+       if len(decl.Type.Out) > 0 && fc.flow.reachesEnd(0) {
                // XXX(Spec) Not specified.
                a.diagAt(&body.Rbrace, "function ends without a return statement");
                return nil;
@@ -746,6 +999,30 @@ func (a *compiler) compileFunc(b *block, decl *FuncDecl, body *ast.BlockStmt) (f
        return func(f *Frame) Func { return &evalFunc{f, maxVars, code} };
 }
 
+// Checks that labels were resolved and that all jumps obey scoping
+// rules.  Reports an error and set fc.err if any check fails.
+func (a *funcCompiler) checkLabels() {
+       bad := false;
+       for _, l := range a.labels {
+               if !l.resolved.IsValid() {
+                       a.diagAt(&l.used, "label %s not defined", l.name);
+                       bad = true;
+               }
+       }
+       if bad {
+               a.err = true;
+               // Don't check scopes if we have unresolved labels
+               return;
+       }
+
+       // Executing the "goto" statement must not cause any variables
+       // to come into scope that were not already in scope at the
+       // point of the goto.
+       if !a.flow.gotosObeyScopes(a.compiler) {
+               a.err = true;
+       }
+}
+
 /*
  * Testing interface
  */
@@ -761,16 +1038,25 @@ func (s *Stmt) Exec(f *Frame) {
 func CompileStmts(scope *Scope, stmts []ast.Stmt) (*Stmt, os.Error) {
        errors := scanner.NewErrorVector();
        cc := &compiler{errors};
-       fc := &funcCompiler{cc, nil, false, newCodeBuf(), false};
+       cb := newCodeBuf();
+       fc := &funcCompiler{
+               compiler: cc,
+               fnType: nil,
+               outVarsNamed: false,
+               codeBuf: cb,
+               flow: newFlowBuf(cb),
+               labels: make(map[string] *label),
+               err: false,
+       };
        bc := &blockCompiler{
                funcCompiler: fc,
                block: scope.block,
-               returned: false
        };
        out := make([]*Stmt, len(stmts));
        for i, stmt := range stmts {
                bc.compileStmt(stmt);
        }
+       fc.checkLabels();
        if fc.err {
                return nil, errors.GetError(scanner.Sorted);
        }