diff options
Diffstat (limited to 'usr/austin/eval/stmt.go')
| -rw-r--r-- | usr/austin/eval/stmt.go | 1313 |
1 files changed, 0 insertions, 1313 deletions
diff --git a/usr/austin/eval/stmt.go b/usr/austin/eval/stmt.go deleted file mode 100644 index 9ec6fb83d..000000000 --- a/usr/austin/eval/stmt.go +++ /dev/null @@ -1,1313 +0,0 @@ -// Copyright 2009 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 eval - -import ( - "bignum"; - "log"; - "go/ast"; - "go/token"; -) - -const ( - returnPC = ^uint(0); - badPC = ^uint(1); -) - -/* - * Statement compiler - */ - -type stmtCompiler struct { - *blockCompiler; - pos token.Position; - // This statement's label, or nil if it is not labeled. - stmtLabel *label; -} - -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) { - 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; - } - - // 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; - } - } - } -} - -/* - * Statement generation helpers - */ - -func (a *stmtCompiler) defineVar(ident *ast.Ident, t Type) *Variable { - v, prev := a.block.DefineVar(ident.Value, ident.Pos(), t); - if prev != nil { - // TODO(austin) It's silly that we have to capture - // Pos() in a variable. - pos := prev.Pos(); - if pos.IsValid() { - a.diagAt(ident, "variable %s redeclared in this block\n\tprevious declaration at %s", ident.Value, &pos); - } else { - a.diagAt(ident, "variable %s redeclared in this block", ident.Value); - } - return nil; - } - - // Initialize the variable - index := v.Index; - if v.Index >= 0 { - a.push(func(v *Thread) { - v.f.Vars[index] = t.Zero(); - }); - } - return v; -} - -// TODO(austin) Move doAssign to here - -/* - * Statement compiler - */ - -func (a *stmtCompiler) compile(s ast.Stmt) { - if a.block.inner != nil { - log.Crash("Child scope still entered"); - } - - notimpl := false; - switch s := s.(type) { - case *ast.BadStmt: - // Error already reported by parser. - a.silentErrors++; - - case *ast.DeclStmt: - a.compileDeclStmt(s); - - case *ast.EmptyStmt: - // Do nothing. - - case *ast.LabeledStmt: - a.compileLabeledStmt(s); - - case *ast.ExprStmt: - a.compileExprStmt(s); - - case *ast.IncDecStmt: - a.compileIncDecStmt(s); - - case *ast.AssignStmt: - a.compileAssignStmt(s); - - case *ast.GoStmt: - notimpl = true; - - case *ast.DeferStmt: - notimpl = true; - - case *ast.ReturnStmt: - a.compileReturnStmt(s); - - case *ast.BranchStmt: - a.compileBranchStmt(s); - - case *ast.BlockStmt: - a.compileBlockStmt(s); - - case *ast.IfStmt: - a.compileIfStmt(s); - - case *ast.CaseClause: - a.diag("case clause outside switch"); - - case *ast.SwitchStmt: - a.compileSwitchStmt(s); - - case *ast.TypeCaseClause: - notimpl = true; - - case *ast.TypeSwitchStmt: - notimpl = true; - - case *ast.CommClause: - notimpl = true; - - case *ast.SelectStmt: - notimpl = true; - - case *ast.ForStmt: - a.compileForStmt(s); - - case *ast.RangeStmt: - notimpl = true; - - default: - log.Crashf("unexpected ast node type %T", s); - } - - if notimpl { - a.diag("%T statment node not implemented", s); - } - - if a.block.inner != nil { - log.Crash("Forgot to exit child scope"); - } -} - -func (a *stmtCompiler) compileDeclStmt(s *ast.DeclStmt) { - switch decl := s.Decl.(type) { - case *ast.BadDecl: - // Do nothing. Already reported by parser. - a.silentErrors++; - - case *ast.FuncDecl: - if !a.block.global { - log.Crash("FuncDecl at statement level"); - } - - case *ast.GenDecl: - if decl.Tok == token.IMPORT && !a.block.global { - log.Crash("import at statement level"); - } - - default: - log.Crashf("Unexpected Decl type %T", s.Decl); - } - a.compileDecl(s.Decl); -} - -func (a *stmtCompiler) compileVarDecl(decl *ast.GenDecl) { - for _, spec := range decl.Specs { - spec := spec.(*ast.ValueSpec); - if spec.Values == nil { - // Declaration without assignment - if spec.Type == nil { - // Parser should have caught - log.Crash("Type and Values nil"); - } - t := a.compileType(a.block, spec.Type); - // Define placeholders even if type compile failed - for _, n := range spec.Names { - a.defineVar(n, t); - } - } else { - // Declaration with assignment - lhs := make([]ast.Expr, len(spec.Names)); - for i, n := range spec.Names { - lhs[i] = n; - } - a.doAssign(lhs, spec.Values, decl.Tok, spec.Type); - } - } -} - -func (a *stmtCompiler) compileDecl(decl ast.Decl) { - switch d := decl.(type) { - case *ast.BadDecl: - // Do nothing. Already reported by parser. - a.silentErrors++; - - case *ast.FuncDecl: - decl := a.compileFuncType(a.block, d.Type); - if decl == nil { - return; - } - // Declare and initialize v before compiling func - // so that body can refer to itself. - c, prev := a.block.DefineConst(d.Name.Value, a.pos, decl.Type, decl.Type.Zero()); - if prev != nil { - pos := prev.Pos(); - if pos.IsValid() { - a.diagAt(d.Name, "identifier %s redeclared in this block\n\tprevious declaration at %s", d.Name.Value, &pos); - } else { - a.diagAt(d.Name, "identifier %s redeclared in this block", d.Name.Value); - } - } - fn := a.compileFunc(a.block, decl, d.Body); - if c == nil || fn == nil { - return; - } - var zeroThread Thread; - c.Value.(FuncValue).Set(nil, fn(&zeroThread)); - - case *ast.GenDecl: - switch d.Tok { - case token.IMPORT: - log.Crashf("%v not implemented", d.Tok); - case token.CONST: - log.Crashf("%v not implemented", d.Tok); - case token.TYPE: - a.compileTypeDecl(a.block, d); - case token.VAR: - a.compileVarDecl(d); - } - - default: - log.Crashf("Unexpected Decl type %T", decl); - } -} - -func (a *stmtCompiler) compileLabeledStmt(s *ast.LabeledStmt) { - // Define label - l, ok := a.labels[s.Label.Value]; - if ok { - if l.resolved.IsValid() { - a.diag("label %s redeclared in this block\n\tprevious declaration at %s", s.Label.Value, &l.resolved); - } - } 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. - sc := &stmtCompiler{a.blockCompiler, s.Stmt.Pos(), l}; - sc.compile(s.Stmt); -} - -func (a *stmtCompiler) compileExprStmt(s *ast.ExprStmt) { - bc := a.enterChild(); - defer bc.exit(); - - e := a.compileExpr(bc.block, false, s.X); - if e == nil { - return; - } - - if e.exec == nil { - a.diag("%s cannot be used as expression statement", e.desc); - return; - } - - a.push(e.exec); -} - -func (a *stmtCompiler) compileIncDecStmt(s *ast.IncDecStmt) { - // Create temporary block for extractEffect - bc := a.enterChild(); - defer bc.exit(); - - l := a.compileExpr(bc.block, false, s.X); - if l == nil { - return; - } - - if l.evalAddr == nil { - l.diag("cannot assign to %s", l.desc); - return; - } - if !(l.t.isInteger() || l.t.isFloat()) { - l.diagOpType(s.Tok, l.t); - return; - } - - var op token.Token; - var desc string; - switch s.Tok { - case token.INC: - op = token.ADD; - desc = "increment statement"; - case token.DEC: - op = token.SUB; - desc = "decrement statement"; - default: - log.Crashf("Unexpected IncDec token %v", s.Tok); - } - - effect, l := l.extractEffect(bc.block, desc); - - one := l.newExpr(IdealIntType, "constant"); - one.pos = s.Pos(); - one.eval = func() *bignum.Integer { return bignum.Int(1) }; - - binop := l.compileBinaryExpr(op, l, one); - if binop == nil { - return; - } - - assign := a.compileAssign(s.Pos(), bc.block, l.t, []*expr{binop}, "", ""); - if assign == nil { - log.Crashf("compileAssign type check failed"); - } - - lf := l.evalAddr; - a.push(func(v *Thread) { - effect(v); - assign(lf(v), v); - }); -} - -func (a *stmtCompiler) doAssign(lhs []ast.Expr, rhs []ast.Expr, tok token.Token, declTypeExpr ast.Expr) { - nerr := a.numError(); - - // Compile right side first so we have the types when - // compiling the left side and so we don't see definitions - // made on the left side. - rs := make([]*expr, len(rhs)); - for i, re := range rhs { - rs[i] = a.compileExpr(a.block, false, re); - } - - errOp := "assignment"; - if tok == token.DEFINE || tok == token.VAR { - errOp = "declaration"; - } - ac, ok := a.checkAssign(a.pos, rs, errOp, "value"); - ac.allowMapForms(len(lhs)); - - // If this is a definition and the LHS is too big, we won't be - // able to produce the usual error message because we can't - // begin to infer the types of the LHS. - if (tok == token.DEFINE || tok == token.VAR) && len(lhs) > len(ac.rmt.Elems) { - a.diag("not enough values for definition"); - } - - // Compile left type if there is one - var declType Type; - if declTypeExpr != nil { - declType = a.compileType(a.block, declTypeExpr); - } - - // Compile left side - ls := make([]*expr, len(lhs)); - nDefs := 0; - for i, le := range lhs { - // If this is a definition, get the identifier and its type - var ident *ast.Ident; - var lt Type; - switch tok { - case token.DEFINE: - // Check that it's an identifier - ident, ok = le.(*ast.Ident); - if !ok { - a.diagAt(le, "left side of := must be a name"); - // Suppress new defitions errors - nDefs++; - continue; - } - - // Is this simply an assignment? - if _, ok := a.block.defs[ident.Value]; ok { - ident = nil; - break; - } - nDefs++; - - case token.VAR: - ident = le.(*ast.Ident); - } - - // If it's a definition, get or infer its type. - if ident != nil { - // Compute the identifier's type from the RHS - // type. We use the computed MultiType so we - // don't have to worry about unpacking. - switch { - case declTypeExpr != nil: - // We have a declaration type, use it. - // If declType is nil, we gave an - // error when we compiled it. - lt = declType; - - case i >= len(ac.rmt.Elems): - // Define a placeholder. We already - // gave the "not enough" error above. - lt = nil; - - case ac.rmt.Elems[i] == nil: - // We gave the error when we compiled - // the RHS. - lt = nil; - - case ac.rmt.Elems[i].isIdeal(): - // If the type is absent and the - // corresponding expression is a - // constant expression of ideal - // integer or ideal float type, the - // type of the declared variable is - // int or float respectively. - switch { - case ac.rmt.Elems[i].isInteger(): - lt = IntType; - case ac.rmt.Elems[i].isFloat(): - lt = FloatType; - default: - log.Crashf("unexpected ideal type %v", rs[i].t); - } - - default: - lt = ac.rmt.Elems[i]; - } - } - - // If it's a definition, define the identifier - if ident != nil { - if a.defineVar(ident, lt) == nil { - continue; - } - } - - // Compile LHS - ls[i] = a.compileExpr(a.block, false, le); - if ls[i] == nil { - continue; - } - - if ls[i].evalMapValue != nil { - // Map indexes are not generally addressable, - // but they are assignable. - // - // TODO(austin) Now that the expression - // compiler uses semantic values, this might - // be easier to implement as a function call. - sub := ls[i]; - ls[i] = ls[i].newExpr(sub.t, sub.desc); - ls[i].evalMapValue = sub.evalMapValue; - mvf := sub.evalMapValue; - et := sub.t; - ls[i].evalAddr = func(t *Thread) Value { - m, k := mvf(t); - e := m.Elem(t, k); - if e == nil { - e = et.Zero(); - m.SetElem(t, k, e); - } - return e; - }; - } else if ls[i].evalAddr == nil { - ls[i].diag("cannot assign to %s", ls[i].desc); - continue; - } - } - - // A short variable declaration may redeclare variables - // provided they were originally declared in the same block - // with the same type, and at least one of the variables is - // new. - if tok == token.DEFINE && nDefs == 0 { - a.diag("at least one new variable must be declared"); - return; - } - - // If there have been errors, our arrays are full of nil's so - // get out of here now. - if nerr != a.numError() { - return; - } - - // Check for 'a[x] = r, ok' - if len(ls) == 1 && len(rs) == 2 && ls[0].evalMapValue != nil { - a.diag("a[x] = r, ok form not implemented"); - return; - } - - // Create assigner - var lt Type; - n := len(lhs); - if n == 1 { - lt = ls[0].t; - } else { - lts := make([]Type, len(ls)); - for i, l := range ls { - if l != nil { - lts[i] = l.t; - } - } - lt = NewMultiType(lts); - } - bc := a.enterChild(); - defer bc.exit(); - assign := ac.compile(bc.block, lt); - if assign == nil { - return; - } - - // Compile - if n == 1 { - // Don't need temporaries and can avoid []Value. - lf := ls[0].evalAddr; - a.push(func(t *Thread) { assign(lf(t), t) }); - } else if tok == token.VAR || (tok == token.DEFINE && nDefs == n) { - // Don't need temporaries - lfs := make([]func(*Thread) Value, n); - for i, l := range ls { - lfs[i] = l.evalAddr; - } - a.push(func(t *Thread) { - dest := make([]Value, n); - for i, lf := range lfs { - dest[i] = lf(t); - } - assign(multiV(dest), t); - }); - } else { - // Need temporaries - lmt := lt.(*MultiType); - lfs := make([]func(*Thread) Value, n); - for i, l := range ls { - lfs[i] = l.evalAddr; - } - a.push(func(t *Thread) { - temp := lmt.Zero().(multiV); - assign(temp, t); - // Copy to destination - for i := 0; i < n; i ++ { - // TODO(austin) Need to evaluate LHS - // before RHS - lfs[i](t).Assign(t, temp[i]); - } - }); - } -} - -var assignOpToOp = map[token.Token] token.Token { - token.ADD_ASSIGN : token.ADD, - token.SUB_ASSIGN : token.SUB, - token.MUL_ASSIGN : token.MUL, - token.QUO_ASSIGN : token.QUO, - token.REM_ASSIGN : token.REM, - - token.AND_ASSIGN : token.AND, - token.OR_ASSIGN : token.OR, - token.XOR_ASSIGN : token.XOR, - token.SHL_ASSIGN : token.SHL, - token.SHR_ASSIGN : token.SHR, - token.AND_NOT_ASSIGN : token.AND_NOT, -} - -func (a *stmtCompiler) doAssignOp(s *ast.AssignStmt) { - if len(s.Lhs) != 1 || len(s.Rhs) != 1 { - a.diag("tuple assignment cannot be combined with an arithmetic operation"); - return; - } - - // Create temporary block for extractEffect - bc := a.enterChild(); - defer bc.exit(); - - l := a.compileExpr(bc.block, false, s.Lhs[0]); - r := a.compileExpr(bc.block, false, s.Rhs[0]); - if l == nil || r == nil { - return; - } - - if l.evalAddr == nil { - l.diag("cannot assign to %s", l.desc); - return; - } - - effect, l := l.extractEffect(bc.block, "operator-assignment"); - - binop := r.compileBinaryExpr(assignOpToOp[s.Tok], l, r); - if binop == nil { - return; - } - - assign := a.compileAssign(s.Pos(), bc.block, l.t, []*expr{binop}, "assignment", "value"); - if assign == nil { - log.Crashf("compileAssign type check failed"); - } - - lf := l.evalAddr; - a.push(func(t *Thread) { - effect(t); - assign(lf(t), t); - }); -} - -func (a *stmtCompiler) compileAssignStmt(s *ast.AssignStmt) { - switch s.Tok { - case token.ASSIGN, token.DEFINE: - a.doAssign(s.Lhs, s.Rhs, s.Tok, nil); - - default: - a.doAssignOp(s); - } -} - -func (a *stmtCompiler) compileReturnStmt(s *ast.ReturnStmt) { - if a.fnType == nil { - a.diag("cannot return at the top level"); - return; - } - - if len(s.Results) == 0 && (len(a.fnType.Out) == 0 || a.outVarsNamed) { - // Simple case. Simply exit from the function. - a.flow.putTerm(); - a.push(func(v *Thread) { v.pc = returnPC }); - return; - } - - bc := a.enterChild(); - defer bc.exit(); - - // Compile expressions - bad := false; - rs := make([]*expr, len(s.Results)); - for i, re := range s.Results { - rs[i] = a.compileExpr(bc.block, false, re); - if rs[i] == nil { - bad = true; - } - } - if bad { - return; - } - - // Create assigner - - // However, if the expression list in the "return" statement - // is a single call to a multi-valued function, the values - // returned from the called function will be returned from - // this one. - assign := a.compileAssign(s.Pos(), bc.block, NewMultiType(a.fnType.Out), rs, "return", "value"); - - // XXX(Spec) "The result types of the current function and the - // called function must match." Match is fuzzy. It should - // say that they must be assignment compatible. - - // Compile - start := len(a.fnType.In); - nout := len(a.fnType.Out); - a.flow.putTerm(); - a.push(func(t *Thread) { - assign(multiV(t.f.Vars[start:start+nout]), t); - t.pc = returnPC; - }); -} - -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) compileBranchStmt(s *ast.BranchStmt) { - var pc *uint; - - switch s.Tok { - case token.BREAK: - l := a.findLexicalLabel(s.Label, func(l *label) bool { return l.breakPC != nil }, "break", "for loop, switch, or select"); - if l == nil { - return; - } - pc = l.breakPC; - - case token.CONTINUE: - l := a.findLexicalLabel(s.Label, func(l *label) bool { return l.continuePC != nil }, "continue", "for loop"); - if l == nil { - return; - } - pc = l.continuePC; - - 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; - } - - pc = l.gotoPC; - a.flow.putGoto(s.Pos(), l.name, a.block); - - case token.FALLTHROUGH: - a.diag("fallthrough outside switch"); - return; - - default: - log.Crash("Unexpected branch token %v", s.Tok); - } - - a.flow.put1(false, pc); - a.push(func(v *Thread) { v.pc = *pc }); -} - -func (a *stmtCompiler) compileBlockStmt(s *ast.BlockStmt) { - bc := a.enterChild(); - bc.compileStmts(s); - bc.exit(); -} - -func (a *stmtCompiler) compileIfStmt(s *ast.IfStmt) { - // The scope of any variables declared by [the init] statement - // extends to the end of the "if" statement and the variables - // are initialized once before the statement is entered. - // - // XXX(Spec) What this really wants to say is that there's an - // implicit scope wrapping every if, for, and switch - // statement. This is subtly different from what it actually - // says when there's a non-block else clause, because that - // else claus has to execute in a scope that is *not* the - // surrounding scope. - bc := a.enterChild(); - defer bc.exit(); - - // Compile init statement, if any - if s.Init != nil { - bc.compileStmt(s.Init); - } - - elsePC := badPC; - endPC := badPC; - - // Compile condition, if any. If there is no condition, we - // fall through to the body. - if s.Cond != nil { - e := bc.compileExpr(bc.block, false, s.Cond); - switch { - case e == nil: - // Error reported by compileExpr - case !e.t.isBoolean(): - e.diag("'if' condition must be boolean\n\t%v", e.t); - default: - eval := e.asBool(); - a.flow.put1(true, &elsePC); - a.push(func(t *Thread) { - if !eval(t) { - t.pc = elsePC; - } - }); - } - } - - // Compile body - body := bc.enterChild(); - body.compileStmts(s.Body); - body.exit(); - - // Compile else - if s.Else != nil { - // Skip over else if we executed the body - a.flow.put1(false, &endPC); - a.push(func(v *Thread) { - v.pc = endPC; - }); - elsePC = a.nextPC(); - bc.compileStmt(s.Else); - } else { - elsePC = a.nextPC(); - } - endPC = a.nextPC(); -} - -func (a *stmtCompiler) compileSwitchStmt(s *ast.SwitchStmt) { - // Create implicit scope around switch - bc := a.enterChild(); - defer bc.exit(); - - // Compile init statement, if any - if s.Init != nil { - bc.compileStmt(s.Init); - } - - // Compile condition, if any, and extract its effects - var cond *expr; - condbc := bc.enterChild(); - if s.Tag != nil { - e := condbc.compileExpr(condbc.block, false, s.Tag); - if e != nil { - var effect func(*Thread); - effect, cond = e.extractEffect(condbc.block, "switch"); - a.push(effect); - } - } - - // Count cases - ncases := 0; - hasDefault := false; - for _, c := range s.Body.List { - clause, ok := c.(*ast.CaseClause); - if !ok { - a.diagAt(clause, "switch statement must contain case clauses"); - continue; - } - if clause.Values == nil { - if hasDefault { - a.diagAt(clause, "switch statement contains more than one default case"); - } - hasDefault = true; - } else { - ncases += len(clause.Values); - } - } - - // Compile case expressions - cases := make([]func(*Thread) bool, ncases); - i := 0; - for _, c := range s.Body.List { - clause, ok := c.(*ast.CaseClause); - if !ok { - continue; - } - for _, v := range clause.Values { - e := condbc.compileExpr(condbc.block, false, v); - switch { - case e == nil: - // Error reported by compileExpr - case cond == nil && !e.t.isBoolean(): - a.diagAt(v, "'case' condition must be boolean"); - case cond == nil: - cases[i] = e.asBool(); - case cond != nil: - // Create comparison - // TOOD(austin) This produces bad error messages - compare := e.compileBinaryExpr(token.EQL, cond, e); - if compare != nil { - cases[i] = compare.asBool(); - } - } - i++; - } - } - - // Emit condition - casePCs := make([]*uint, ncases+1); - endPC := badPC; - - a.flow.put(false, false, casePCs); - a.push(func(t *Thread) { - for i, c := range cases { - if c(t) { - t.pc = *casePCs[i]; - return; - } - } - t.pc = *casePCs[ncases]; - }); - condbc.exit(); - - // Compile cases - i = 0; - for _, c := range s.Body.List { - clause, ok := c.(*ast.CaseClause); - if !ok { - continue; - } - - // Save jump PC's - pc := a.nextPC(); - if clause.Values != nil { - for _ = range clause.Values { - casePCs[i] = &pc; - i++; - } - } else { - // Default clause - casePCs[ncases] = &pc; - } - - // Compile body - fall := false; - for j, s := range clause.Body { - if br, ok := s.(*ast.BranchStmt); ok && br.Tok == token.FALLTHROUGH { - // println("Found fallthrough"); - // It may be used only as the final - // non-empty statement in a case or - // default clause in an expression - // "switch" statement. - for _, s2 := range clause.Body[j+1:len(clause.Body)] { - // XXX(Spec) 6g also considers - // empty blocks to be empty - // statements. - if _, ok := s2.(*ast.EmptyStmt); !ok { - a.diagAt(s, "fallthrough statement must be final statement in case"); - break; - } - } - fall = true; - } else { - bc.compileStmt(s); - } - } - // Jump out of switch, unless there was a fallthrough - if !fall { - a.flow.put1(false, &endPC); - a.push(func(v *Thread) { v.pc = endPC }); - } - } - - // Get end PC - endPC = a.nextPC(); - if !hasDefault { - casePCs[ncases] = &endPC; - } -} - -func (a *stmtCompiler) compileForStmt(s *ast.ForStmt) { - // Wrap the entire for in a block. - bc := a.enterChild(); - defer bc.exit(); - - // Compile init statement, if any - if s.Init != nil { - bc.compileStmt(s.Init); - } - - 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 *Thread) { v.pc = checkPC }); - - // Compile body - bodyPC = a.nextPC(); - body := bc.enterChild(); - 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(); - - // Compile post, if any - postPC = a.nextPC(); - if s.Post != nil { - // TODO(austin) Does the parser disallow short - // declarations in s.Post? - bc.compileStmt(s.Post); - } - - // Compile condition check, if any - 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 *Thread) { v.pc = bodyPC }); - } else { - e := bc.compileExpr(bc.block, false, s.Cond); - switch { - case e == nil: - // Error reported by compileExpr - case !e.t.isBoolean(): - a.diag("'for' condition must be boolean\n\t%v", e.t); - default: - eval := e.asBool(); - a.flow.put1(true, &bodyPC); - a.push(func(t *Thread) { - if eval(t) { - t.pc = bodyPC; - } - }); - } - } - - endPC = a.nextPC(); -} - -/* - * Block compiler - */ - -func (a *blockCompiler) compileStmt(s ast.Stmt) { - sc := &stmtCompiler{a, s.Pos(), nil}; - sc.compile(s); -} - -func (a *blockCompiler) compileStmts(block *ast.BlockStmt) { - for _, sub := range block.List { - a.compileStmt(sub); - } -} - -func (a *blockCompiler) enterChild() *blockCompiler { - block := a.block.enterChild(); - return &blockCompiler{ - funcCompiler: a.funcCompiler, - block: block, - parent: a, - }; -} - -func (a *blockCompiler) exit() { - a.block.exit(); -} - -/* - * Function compiler - */ - -func (a *compiler) compileFunc(b *block, decl *FuncDecl, body *ast.BlockStmt) (func (*Thread) Func) { - // Create body scope - // - // The scope of a parameter or result is the body of the - // corresponding function. - bodyScope := b.ChildScope(); - defer bodyScope.exit(); - for i, t := range decl.Type.In { - if decl.InNames[i] != nil { - bodyScope.DefineVar(decl.InNames[i].Value, decl.InNames[i].Pos(), t); - } else { - bodyScope.DefineTemp(t); - } - } - for i, t := range decl.Type.Out { - if decl.OutNames[i] != nil { - bodyScope.DefineVar(decl.OutNames[i].Value, decl.OutNames[i].Pos(), t); - } else { - bodyScope.DefineTemp(t); - } - } - - // Create block context - 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), - }; - bc := &blockCompiler{ - funcCompiler: fc, - block: bodyScope.block, - }; - - // Compile body - nerr := a.numError(); - bc.compileStmts(body); - fc.checkLabels(); - if nerr != a.numError() { - return nil; - } - - // 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 && fc.flow.reachesEnd(0) { - // XXX(Spec) Not specified. - a.diagAt(&body.Rbrace, "function ends without a return statement"); - return nil; - } - - code := fc.get(); - maxVars := bodyScope.maxVars; - return func(t *Thread) Func { return &evalFunc{t.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() { - nerr := a.numError(); - for _, l := range a.labels { - if !l.resolved.IsValid() { - a.diagAt(&l.used, "label %s not defined", l.name); - } - } - if nerr != a.numError() { - // 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. - a.flow.gotosObeyScopes(a.compiler); -} |
