| // Copyright 2023 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 rangefunc rewrites range-over-func to code that doesn't use range-over-funcs. | |
| Rewriting the construct in the front end, before noder, means the functions generated during | |
| the rewrite are available in a noder-generated representation for inlining by the back end. | |
| # Theory of Operation | |
| The basic idea is to rewrite | |
| for x := range f { | |
| ... | |
| } | |
| into | |
| f(func(x T) bool { | |
| ... | |
| }) | |
| But it's not usually that easy. | |
| # Range variables | |
| For a range not using :=, the assigned variables cannot be function parameters | |
| in the generated body function. Instead, we allocate fake parameters and | |
| start the body with an assignment. For example: | |
| for expr1, expr2 = range f { | |
| ... | |
| } | |
| becomes | |
| f(func(#p1 T1, #p2 T2) bool { | |
| expr1, expr2 = #p1, #p2 | |
| ... | |
| }) | |
| (All the generated variables have a # at the start to signal that they | |
| are internal variables when looking at the generated code in a | |
| debugger. Because variables have all been resolved to the specific | |
| objects they represent, there is no danger of using plain "p1" and | |
| colliding with a Go variable named "p1"; the # is just nice to have, | |
| not for correctness.) | |
| It can also happen that there are fewer range variables than function | |
| arguments, in which case we end up with something like | |
| f(func(x T1, _ T2) bool { | |
| ... | |
| }) | |
| or | |
| f(func(#p1 T1, #p2 T2, _ T3) bool { | |
| expr1, expr2 = #p1, #p2 | |
| ... | |
| }) | |
| # Return | |
| If the body contains a "break", that break turns into "return false", | |
| to tell f to stop. And if the body contains a "continue", that turns | |
| into "return true", to tell f to proceed with the next value. | |
| Those are the easy cases. | |
| If the body contains a return or a break/continue/goto L, then we need | |
| to rewrite that into code that breaks out of the loop and then | |
| triggers that control flow. In general we rewrite | |
| for x := range f { | |
| ... | |
| } | |
| into | |
| { | |
| var #next int | |
| f(func(x T1) bool { | |
| ... | |
| return true | |
| }) | |
| ... check #next ... | |
| } | |
| The variable #next is an integer code that says what to do when f | |
| returns. Each difficult statement sets #next and then returns false to | |
| stop f. | |
| A plain "return" rewrites to {#next = -1; return false}. | |
| The return false breaks the loop. Then when f returns, the "check | |
| #next" section includes | |
| if #next == -1 { return } | |
| which causes the return we want. | |
| Return with arguments is more involved, and has to deal with | |
| corner cases involving panic, defer, and recover. The results | |
| of the enclosing function or closure are rewritten to give them | |
| names if they don't have them already, and the names are assigned | |
| at the return site. | |
| func foo() (#rv1 A, #rv2 B) { | |
| { | |
| var ( | |
| #next int | |
| ) | |
| f(func(x T1) bool { | |
| ... | |
| { | |
| // return a, b | |
| #rv1, #rv2 = a, b | |
| #next = -1 | |
| return false | |
| } | |
| ... | |
| return true | |
| }) | |
| if #next == -1 { return } | |
| } | |
| # Checking | |
| To permit checking that an iterator is well-behaved -- that is, that | |
| it does not call the loop body again after it has returned false or | |
| after the entire loop has exited (it might retain a copy of the body | |
| function, or pass it to another goroutine) -- each generated loop has | |
| its own #stateK variable that is used to check for permitted call | |
| patterns to the yield function for a loop body. | |
| The state values are: | |
| abi.RF_DONE = 0 // body of loop has exited in a non-panic way | |
| abi.RF_READY = 1 // body of loop has not exited yet, is not running | |
| abi.RF_PANIC = 2 // body of loop is either currently running, or has panicked | |
| abi.RF_EXHAUSTED = 3 // iterator function call, e.g. f(func(x t){...}), returned so the sequence is "exhausted". | |
| abi.RF_MISSING_PANIC = 4 // used to report errors. | |
| The value of #stateK transitions | |
| (1) before calling the iterator function, | |
| var #stateN = abi.RF_READY | |
| (2) after the iterator function call returns, | |
| if #stateN == abi.RF_PANIC { | |
| panic(runtime.panicrangestate(abi.RF_MISSING_PANIC)) | |
| } | |
| #stateN = abi.RF_EXHAUSTED | |
| (3) at the beginning of the iteration of the loop body, | |
| if #stateN != abi.RF_READY { #stateN = abi.RF_PANIC ; runtime.panicrangestate(#stateN) } | |
| #stateN = abi.RF_PANIC | |
| // This is slightly rearranged below for better code generation. | |
| (4) when loop iteration continues, | |
| #stateN = abi.RF_READY | |
| [return true] | |
| (5) when control flow exits the loop body. | |
| #stateN = abi.RF_DONE | |
| [return false] | |
| For example: | |
| for x := range f { | |
| ... | |
| if ... { break } | |
| ... | |
| } | |
| becomes | |
| { | |
| var #state1 = abi.RF_READY | |
| f(func(x T1) bool { | |
| if #state1 != abi.RF_READY { #state1 = abi.RF_PANIC; runtime.panicrangestate(#state1) } | |
| #state1 = abi.RF_PANIC | |
| ... | |
| if ... { #state1 = abi.RF_DONE ; return false } | |
| ... | |
| #state1 = abi.RF_READY | |
| return true | |
| }) | |
| if #state1 == abi.RF_PANIC { | |
| // the code for the loop body did not return normally | |
| panic(runtime.panicrangestate(abi.RF_MISSING_PANIC)) | |
| } | |
| #state1 = abi.RF_EXHAUSTED | |
| } | |
| # Nested Loops | |
| So far we've only considered a single loop. If a function contains a | |
| sequence of loops, each can be translated individually. But loops can | |
| be nested. It would work to translate the innermost loop and then | |
| translate the loop around it, and so on, except that there'd be a lot | |
| of rewriting of rewritten code and the overall traversals could end up | |
| taking time quadratic in the depth of the nesting. To avoid all that, | |
| we use a single rewriting pass that handles a top-most range-over-func | |
| loop and all the range-over-func loops it contains at the same time. | |
| If we need to return from inside a doubly-nested loop, the rewrites | |
| above stay the same, but the check after the inner loop only says | |
| if #next < 0 { return false } | |
| to stop the outer loop so it can do the actual return. That is, | |
| for range f { | |
| for range g { | |
| ... | |
| return a, b | |
| ... | |
| } | |
| } | |
| becomes | |
| { | |
| var ( | |
| #next int | |
| ) | |
| var #state1 = abi.RF_READY | |
| f(func() bool { | |
| if #state1 != abi.RF_READY { #state1 = abi.RF_PANIC; runtime.panicrangestate(#state1) } | |
| #state1 = abi.RF_PANIC | |
| var #state2 = abi.RF_READY | |
| g(func() bool { | |
| if #state2 != abi.RF_READY { #state2 = abi.RF_PANIC; runtime.panicrangestate(#state2) } | |
| ... | |
| { | |
| // return a, b | |
| #rv1, #rv2 = a, b | |
| #next = -1 | |
| #state2 = abi.RF_DONE | |
| return false | |
| } | |
| ... | |
| #state2 = abi.RF_READY | |
| return true | |
| }) | |
| if #state2 == abi.RF_PANIC { | |
| panic(runtime.panicrangestate(abi.RF_MISSING_PANIC)) | |
| } | |
| #state2 = abi.RF_EXHAUSTED | |
| if #next < 0 { | |
| #state1 = abi.RF_DONE | |
| return false | |
| } | |
| #state1 = abi.RF_READY | |
| return true | |
| }) | |
| if #state1 == abi.RF_PANIC { | |
| panic(runtime.panicrangestate(abi.RF_MISSING_PANIC)) | |
| } | |
| #state1 = abi.RF_EXHAUSTED | |
| if #next == -1 { | |
| return | |
| } | |
| } | |
| # Labeled break/continue of range-over-func loops | |
| For a labeled break or continue of an outer range-over-func, we | |
| use positive #next values. | |
| Any such labeled break or continue | |
| really means "do N breaks" or "do N breaks and 1 continue". | |
| The positive #next value tells which level of loop N to target | |
| with a break or continue, where perLoopStep*N means break out of | |
| level N and perLoopStep*N-1 means continue into level N. The | |
| outermost loop has level 1, therefore #next == perLoopStep means | |
| to break from the outermost loop, and #next == perLoopStep-1 means | |
| to continue the outermost loop. | |
| Loops that might need to propagate a labeled break or continue | |
| add one or both of these to the #next checks: | |
| // N == depth of this loop, one less than the one just exited. | |
| if #next != 0 { | |
| if #next >= perLoopStep*N-1 { // break or continue this loop | |
| if #next >= perLoopStep*N+1 { // error checking | |
| // TODO reason about what exactly can appear | |
| // here given full or partial checking. | |
| runtime.panicrangestate(abi.RF_DONE) | |
| } | |
| rv := #next & 1 == 1 // code generates into #next&1 | |
| #next = 0 | |
| return rv | |
| } | |
| return false // or handle returns and gotos | |
| } | |
| For example (with perLoopStep == 2) | |
| F: for range f { // 1, 2 | |
| for range g { // 3, 4 | |
| for range h { | |
| ... | |
| break F | |
| ... | |
| ... | |
| continue F | |
| ... | |
| } | |
| } | |
| ... | |
| } | |
| becomes | |
| { | |
| var #next int | |
| var #state1 = abi.RF_READY | |
| f(func() { // 1,2 | |
| if #state1 != abi.RF_READY { #state1 = abi.RF_PANIC; runtime.panicrangestate(#state1) } | |
| #state1 = abi.RF_PANIC | |
| var #state2 = abi.RF_READY | |
| g(func() { // 3,4 | |
| if #state2 != abi.RF_READY { #state2 = abi.RF_PANIC; runtime.panicrangestate(#state2) } | |
| #state2 = abi.RF_PANIC | |
| var #state3 = abi.RF_READY | |
| h(func() { // 5,6 | |
| if #state3 != abi.RF_READY { #state3 = abi.RF_PANIC; runtime.panicrangestate(#state3) } | |
| #state3 = abi.RF_PANIC | |
| ... | |
| { | |
| // break F | |
| #next = 2 | |
| #state3 = abi.RF_DONE | |
| return false | |
| } | |
| ... | |
| { | |
| // continue F | |
| #next = 1 | |
| #state3 = abi.RF_DONE | |
| return false | |
| } | |
| ... | |
| #state3 = abi.RF_READY | |
| return true | |
| }) | |
| if #state3 == abi.RF_PANIC { | |
| panic(runtime.panicrangestate(abi.RF_MISSING_PANIC)) | |
| } | |
| #state3 = abi.RF_EXHAUSTED | |
| if #next != 0 { | |
| // no breaks or continues targeting this loop | |
| #state2 = abi.RF_DONE | |
| return false | |
| } | |
| return true | |
| }) | |
| if #state2 == abi.RF_PANIC { | |
| panic(runtime.panicrangestate(abi.RF_MISSING_PANIC)) | |
| } | |
| #state2 = abi.RF_EXHAUSTED | |
| if #next != 0 { // just exited g, test for break/continue applied to f/F | |
| if #next >= 1 { | |
| if #next >= 3 { runtime.panicrangestate(abi.RF_DONE) } // error | |
| rv := #next&1 == 1 | |
| #next = 0 | |
| return rv | |
| } | |
| #state1 = abi.RF_DONE | |
| return false | |
| } | |
| ... | |
| return true | |
| }) | |
| if #state1 == abi.RF_PANIC { | |
| panic(runtime.panicrangestate(abi.RF_MISSING_PANIC)) | |
| } | |
| #state1 = abi.RF_EXHAUSTED | |
| } | |
| Note that the post-h checks only consider a break, | |
| since no generated code tries to continue g. | |
| # Gotos and other labeled break/continue | |
| The final control flow translations are goto and break/continue of a | |
| non-range-over-func statement. In both cases, we may need to break | |
| out of one or more range-over-func loops before we can do the actual | |
| control flow statement. Each such break/continue/goto L statement is | |
| assigned a unique negative #next value (since -1 is return). Then | |
| the post-checks for a given loop test for the specific codes that | |
| refer to labels directly targetable from that block. Otherwise, the | |
| generic | |
| if #next < 0 { return false } | |
| check handles stopping the next loop to get one step closer to the label. | |
| For example | |
| Top: print("start\n") | |
| for range f { | |
| for range g { | |
| ... | |
| for range h { | |
| ... | |
| goto Top | |
| ... | |
| } | |
| } | |
| } | |
| becomes | |
| Top: print("start\n") | |
| { | |
| var #next int | |
| var #state1 = abi.RF_READY | |
| f(func() { | |
| if #state1 != abi.RF_READY{ #state1 = abi.RF_PANIC; runtime.panicrangestate(#state1) } | |
| #state1 = abi.RF_PANIC | |
| var #state2 = abi.RF_READY | |
| g(func() { | |
| if #state2 != abi.RF_READY { #state2 = abi.RF_PANIC; runtime.panicrangestate(#state2) } | |
| #state2 = abi.RF_PANIC | |
| ... | |
| var #state3 bool = abi.RF_READY | |
| h(func() { | |
| if #state3 != abi.RF_READY { #state3 = abi.RF_PANIC; runtime.panicrangestate(#state3) } | |
| #state3 = abi.RF_PANIC | |
| ... | |
| { | |
| // goto Top | |
| #next = -3 | |
| #state3 = abi.RF_DONE | |
| return false | |
| } | |
| ... | |
| #state3 = abi.RF_READY | |
| return true | |
| }) | |
| if #state3 == abi.RF_PANIC {runtime.panicrangestate(abi.RF_MISSING_PANIC)} | |
| #state3 = abi.RF_EXHAUSTED | |
| if #next < 0 { | |
| #state2 = abi.RF_DONE | |
| return false | |
| } | |
| #state2 = abi.RF_READY | |
| return true | |
| }) | |
| if #state2 == abi.RF_PANIC {runtime.panicrangestate(abi.RF_MISSING_PANIC)} | |
| #state2 = abi.RF_EXHAUSTED | |
| if #next < 0 { | |
| #state1 = abi.RF_DONE | |
| return false | |
| } | |
| #state1 = abi.RF_READY | |
| return true | |
| }) | |
| if #state1 == abi.RF_PANIC {runtime.panicrangestate(abi.RF_MISSING_PANIC)} | |
| #state1 = abi.RF_EXHAUSTED | |
| if #next == -3 { | |
| #next = 0 | |
| goto Top | |
| } | |
| } | |
| Labeled break/continue to non-range-over-funcs are handled the same | |
| way as goto. | |
| # Defers | |
| The last wrinkle is handling defer statements. If we have | |
| for range f { | |
| defer print("A") | |
| } | |
| we cannot rewrite that into | |
| f(func() { | |
| defer print("A") | |
| }) | |
| because the deferred code will run at the end of the iteration, not | |
| the end of the containing function. To fix that, the runtime provides | |
| a special hook that lets us obtain a defer "token" representing the | |
| outer function and then use it in a later defer to attach the deferred | |
| code to that outer function. | |
| Normally, | |
| defer print("A") | |
| compiles to | |
| runtime.deferproc(func() { print("A") }) | |
| This changes in a range-over-func. For example: | |
| for range f { | |
| defer print("A") | |
| } | |
| compiles to | |
| var #defers = runtime.deferrangefunc() | |
| f(func() { | |
| runtime.deferprocat(func() { print("A") }, #defers) | |
| }) | |
| For this rewriting phase, we insert the explicit initialization of | |
| #defers and then attach the #defers variable to the CallStmt | |
| representing the defer. That variable will be propagated to the | |
| backend and will cause the backend to compile the defer using | |
| deferprocat instead of an ordinary deferproc. | |
| TODO: Could call runtime.deferrangefuncend after f. | |
| */ | |
| package rangefunc | |
| import ( | |
| "cmd/compile/internal/base" | |
| "cmd/compile/internal/syntax" | |
| "cmd/compile/internal/types2" | |
| "fmt" | |
| "go/constant" | |
| "internal/abi" | |
| "os" | |
| ) | |
| // nopos is the zero syntax.Pos. | |
| var nopos syntax.Pos | |
| // A rewriter implements rewriting the range-over-funcs in a given function. | |
| type rewriter struct { | |
| pkg *types2.Package | |
| info *types2.Info | |
| sig *types2.Signature | |
| outer *syntax.FuncType | |
| body *syntax.BlockStmt | |
| // References to important types and values. | |
| any types2.Object | |
| bool types2.Object | |
| int types2.Object | |
| true types2.Object | |
| false types2.Object | |
| // Branch numbering, computed as needed. | |
| branchNext map[branch]int // branch -> #next value | |
| labelLoop map[string]*syntax.ForStmt // label -> innermost rangefunc loop it is declared inside (nil for no loop) | |
| // Stack of nodes being visited. | |
| stack []syntax.Node // all nodes | |
| forStack []*forLoop // range-over-func loops | |
| rewritten map[*syntax.ForStmt]syntax.Stmt | |
| // Declared variables in generated code for outermost loop. | |
| declStmt *syntax.DeclStmt | |
| nextVar types2.Object | |
| defers types2.Object | |
| stateVarCount int // stateVars are referenced from their respective loops | |
| bodyClosureCount int // to help the debugger, the closures generated for loop bodies get names | |
| rangefuncBodyClosures map[*syntax.FuncLit]bool | |
| } | |
| // A branch is a single labeled branch. | |
| type branch struct { | |
| tok syntax.Token | |
| label string | |
| } | |
| // A forLoop describes a single range-over-func loop being processed. | |
| type forLoop struct { | |
| nfor *syntax.ForStmt // actual syntax | |
| stateVar *types2.Var // #state variable for this loop | |
| stateVarDecl *syntax.VarDecl | |
| depth int // outermost loop has depth 1, otherwise depth = depth(parent)+1 | |
| checkRet bool // add check for "return" after loop | |
| checkBreak bool // add check for "break" after loop | |
| checkContinue bool // add check for "continue" after loop | |
| checkBranch []branch // add check for labeled branch after loop | |
| } | |
| type State int | |
| // Rewrite rewrites all the range-over-funcs in the files. | |
| // It returns the set of function literals generated from rangefunc loop bodies. | |
| // This allows for rangefunc loop bodies to be distinguished by debuggers. | |
| func Rewrite(pkg *types2.Package, info *types2.Info, files []*syntax.File) map[*syntax.FuncLit]bool { | |
| ri := make(map[*syntax.FuncLit]bool) | |
| for _, file := range files { | |
| syntax.Inspect(file, func(n syntax.Node) bool { | |
| switch n := n.(type) { | |
| case *syntax.FuncDecl: | |
| sig, _ := info.Defs[n.Name].Type().(*types2.Signature) | |
| rewriteFunc(pkg, info, n.Type, n.Body, sig, ri) | |
| return false | |
| case *syntax.FuncLit: | |
| sig, _ := info.Types[n].Type.(*types2.Signature) | |
| if sig == nil { | |
| tv := n.GetTypeInfo() | |
| sig = tv.Type.(*types2.Signature) | |
| } | |
| rewriteFunc(pkg, info, n.Type, n.Body, sig, ri) | |
| return false | |
| } | |
| return true | |
| }) | |
| } | |
| return ri | |
| } | |
| // rewriteFunc rewrites all the range-over-funcs in a single function (a top-level func or a func literal). | |
| // The typ and body are the function's type and body. | |
| func rewriteFunc(pkg *types2.Package, info *types2.Info, typ *syntax.FuncType, body *syntax.BlockStmt, sig *types2.Signature, ri map[*syntax.FuncLit]bool) { | |
| if body == nil { | |
| return | |
| } | |
| r := &rewriter{ | |
| pkg: pkg, | |
| info: info, | |
| outer: typ, | |
| body: body, | |
| sig: sig, | |
| rangefuncBodyClosures: ri, | |
| } | |
| syntax.Inspect(body, r.inspect) | |
| if (base.Flag.W != 0) && r.forStack != nil { | |
| syntax.Fdump(os.Stderr, body) | |
| } | |
| } | |
| // checkFuncMisuse reports whether to check for misuse of iterator callbacks functions. | |
| func (r *rewriter) checkFuncMisuse() bool { | |
| return base.Debug.RangeFuncCheck != 0 | |
| } | |
| // inspect is a callback for syntax.Inspect that drives the actual rewriting. | |
| // If it sees a func literal, it kicks off a separate rewrite for that literal. | |
| // Otherwise, it maintains a stack of range-over-func loops and | |
| // converts each in turn. | |
| func (r *rewriter) inspect(n syntax.Node) bool { | |
| switch n := n.(type) { | |
| case *syntax.FuncLit: | |
| sig, _ := r.info.Types[n].Type.(*types2.Signature) | |
| if sig == nil { | |
| tv := n.GetTypeInfo() | |
| sig = tv.Type.(*types2.Signature) | |
| } | |
| rewriteFunc(r.pkg, r.info, n.Type, n.Body, sig, r.rangefuncBodyClosures) | |
| return false | |
| default: | |
| // Push n onto stack. | |
| r.stack = append(r.stack, n) | |
| if nfor, ok := forRangeFunc(n); ok { | |
| loop := &forLoop{nfor: nfor, depth: 1 + len(r.forStack)} | |
| r.forStack = append(r.forStack, loop) | |
| r.startLoop(loop) | |
| } | |
| case nil: | |
| // n == nil signals that we are done visiting | |
| // the top-of-stack node's children. Find it. | |
| n = r.stack[len(r.stack)-1] | |
| // If we are inside a range-over-func, | |
| // take this moment to replace any break/continue/goto/return | |
| // statements directly contained in this node. | |
| // Also replace any converted for statements | |
| // with the rewritten block. | |
| switch n := n.(type) { | |
| case *syntax.BlockStmt: | |
| for i, s := range n.List { | |
| n.List[i] = r.editStmt(s) | |
| } | |
| case *syntax.CaseClause: | |
| for i, s := range n.Body { | |
| n.Body[i] = r.editStmt(s) | |
| } | |
| case *syntax.CommClause: | |
| for i, s := range n.Body { | |
| n.Body[i] = r.editStmt(s) | |
| } | |
| case *syntax.LabeledStmt: | |
| n.Stmt = r.editStmt(n.Stmt) | |
| } | |
| // Pop n. | |
| if len(r.forStack) > 0 && r.stack[len(r.stack)-1] == r.forStack[len(r.forStack)-1].nfor { | |
| r.endLoop(r.forStack[len(r.forStack)-1]) | |
| r.forStack = r.forStack[:len(r.forStack)-1] | |
| } | |
| r.stack = r.stack[:len(r.stack)-1] | |
| } | |
| return true | |
| } | |
| // startLoop sets up for converting a range-over-func loop. | |
| func (r *rewriter) startLoop(loop *forLoop) { | |
| // For first loop in function, allocate syntax for any, bool, int, true, and false. | |
| if r.any == nil { | |
| r.any = types2.Universe.Lookup("any") | |
| r.bool = types2.Universe.Lookup("bool") | |
| r.int = types2.Universe.Lookup("int") | |
| r.true = types2.Universe.Lookup("true") | |
| r.false = types2.Universe.Lookup("false") | |
| r.rewritten = make(map[*syntax.ForStmt]syntax.Stmt) | |
| } | |
| if r.checkFuncMisuse() { | |
| // declare the state flag for this loop's body | |
| loop.stateVar, loop.stateVarDecl = r.stateVar(loop.nfor.Pos()) | |
| } | |
| } | |
| // editStmt returns the replacement for the statement x, | |
| // or x itself if it should be left alone. | |
| // This includes the for loops we are converting, | |
| // as left in x.rewritten by r.endLoop. | |
| func (r *rewriter) editStmt(x syntax.Stmt) syntax.Stmt { | |
| if x, ok := x.(*syntax.ForStmt); ok { | |
| if s := r.rewritten[x]; s != nil { | |
| return s | |
| } | |
| } | |
| if len(r.forStack) > 0 { | |
| switch x := x.(type) { | |
| case *syntax.BranchStmt: | |
| return r.editBranch(x) | |
| case *syntax.CallStmt: | |
| if x.Tok == syntax.Defer { | |
| return r.editDefer(x) | |
| } | |
| case *syntax.ReturnStmt: | |
| return r.editReturn(x) | |
| } | |
| } | |
| return x | |
| } | |
| // editDefer returns the replacement for the defer statement x. | |
| // See the "Defers" section in the package doc comment above for more context. | |
| func (r *rewriter) editDefer(x *syntax.CallStmt) syntax.Stmt { | |
| if r.defers == nil { | |
| // Declare and initialize the #defers token. | |
| init := &syntax.CallExpr{ | |
| Fun: runtimeSym(r.info, "deferrangefunc"), | |
| } | |
| tv := syntax.TypeAndValue{Type: r.any.Type()} | |
| tv.SetIsValue() | |
| init.SetTypeInfo(tv) | |
| r.defers = r.declOuterVar("#defers", r.any.Type(), init) | |
| } | |
| // Attach the token as an "extra" argument to the defer. | |
| x.DeferAt = r.useObj(r.defers) | |
| setPos(x.DeferAt, x.Pos()) | |
| return x | |
| } | |
| func (r *rewriter) stateVar(pos syntax.Pos) (*types2.Var, *syntax.VarDecl) { | |
| r.stateVarCount++ | |
| name := fmt.Sprintf("#state%d", r.stateVarCount) | |
| typ := r.int.Type() | |
| obj := types2.NewVar(pos, r.pkg, name, typ) | |
| n := syntax.NewName(pos, name) | |
| setValueType(n, typ) | |
| r.info.Defs[n] = obj | |
| return obj, &syntax.VarDecl{NameList: []*syntax.Name{n}, Values: r.stateConst(abi.RF_READY)} | |
| } | |
| // editReturn returns the replacement for the return statement x. | |
| // See the "Return" section in the package doc comment above for more context. | |
| func (r *rewriter) editReturn(x *syntax.ReturnStmt) syntax.Stmt { | |
| bl := &syntax.BlockStmt{} | |
| if x.Results != nil { | |
| // rewrite "return val" into "assign to named result; return" | |
| if len(r.outer.ResultList) > 0 { | |
| // Make sure that result parameters all have names | |
| for i, a := range r.outer.ResultList { | |
| if a.Name == nil || a.Name.Value == "_" { | |
| r.generateParamName(r.outer.ResultList, i) // updates a.Name | |
| } | |
| } | |
| } | |
| // Assign to named results | |
| results := []types2.Object{} | |
| for _, a := range r.outer.ResultList { | |
| results = append(results, r.info.Defs[a.Name]) | |
| } | |
| bl.List = append(bl.List, &syntax.AssignStmt{Lhs: r.useList(results), Rhs: x.Results}) | |
| x.Results = nil | |
| } | |
| next := -1 // return | |
| // Tell the loops along the way to check for a return. | |
| for _, loop := range r.forStack { | |
| loop.checkRet = true | |
| } | |
| // Set #next, and return false. | |
| bl.List = append(bl.List, &syntax.AssignStmt{Lhs: r.next(), Rhs: r.intConst(next)}) | |
| if r.checkFuncMisuse() { | |
| // mark this loop as exited, the others (which will be exited if iterators do not interfere) have not, yet. | |
| bl.List = append(bl.List, r.setState(abi.RF_DONE, x.Pos())) | |
| } | |
| bl.List = append(bl.List, &syntax.ReturnStmt{Results: r.useObj(r.false)}) | |
| setPos(bl, x.Pos()) | |
| return bl | |
| } | |
| // perLoopStep is part of the encoding of loop-spanning control flow | |
| // for function range iterators. Each multiple of two encodes a "return false" | |
| // passing control to an enclosing iterator; a terminal value of 1 encodes | |
| // "return true" (i.e., local continue) from the body function, and a terminal | |
| // value of 0 encodes executing the remainder of the body function. | |
| const perLoopStep = 2 | |
| // editBranch returns the replacement for the branch statement x, | |
| // or x itself if it should be left alone. | |
| // See the package doc comment above for more context. | |
| func (r *rewriter) editBranch(x *syntax.BranchStmt) syntax.Stmt { | |
| if x.Tok == syntax.Fallthrough { | |
| // Fallthrough is unaffected by the rewrite. | |
| return x | |
| } | |
| // Find target of break/continue/goto in r.forStack. | |
| // (The target may not be in r.forStack at all.) | |
| targ := x.Target | |
| i := len(r.forStack) - 1 | |
| if x.Label == nil && r.forStack[i].nfor != targ { | |
| // Unlabeled break or continue that's not nfor must be inside nfor. Leave alone. | |
| return x | |
| } | |
| for i >= 0 && r.forStack[i].nfor != targ { | |
| i-- | |
| } | |
| // exitFrom is the index of the loop interior to the target of the control flow, | |
| // if such a loop exists (it does not if i == len(r.forStack) - 1) | |
| exitFrom := i + 1 | |
| // Compute the value to assign to #next and the specific return to use. | |
| var next int | |
| var ret *syntax.ReturnStmt | |
| if x.Tok == syntax.Goto || i < 0 { | |
| // goto Label | |
| // or break/continue of labeled non-range-over-func loop (x.Label != nil). | |
| // We may be able to leave it alone, or we may have to break | |
| // out of one or more nested loops and then use #next to signal | |
| // to complete the break/continue/goto. | |
| // Figure out which range-over-func loop contains the label. | |
| r.computeBranchNext() | |
| nfor := r.forStack[len(r.forStack)-1].nfor | |
| label := x.Label.Value | |
| targ := r.labelLoop[label] | |
| if nfor == targ { | |
| // Label is in the innermost range-over-func loop; use it directly. | |
| return x | |
| } | |
| // Set #next to the code meaning break/continue/goto label. | |
| next = r.branchNext[branch{x.Tok, label}] | |
| // Break out of nested loops up to targ. | |
| i := len(r.forStack) - 1 | |
| for i >= 0 && r.forStack[i].nfor != targ { | |
| i-- | |
| } | |
| exitFrom = i + 1 | |
| // Mark loop we exit to get to targ to check for that branch. | |
| // When i==-1 / exitFrom == 0 that's the outermost func body. | |
| top := r.forStack[exitFrom] | |
| top.checkBranch = append(top.checkBranch, branch{x.Tok, label}) | |
| // Mark loops along the way to check for a plain return, so they break. | |
| for j := exitFrom + 1; j < len(r.forStack); j++ { | |
| r.forStack[j].checkRet = true | |
| } | |
| // In the innermost loop, use a plain "return false". | |
| ret = &syntax.ReturnStmt{Results: r.useObj(r.false)} | |
| } else { | |
| // break/continue of labeled range-over-func loop. | |
| if exitFrom == len(r.forStack) { | |
| // Simple break or continue. | |
| // Continue returns true, break returns false, optionally both adjust state, | |
| // neither modifies #next. | |
| var state abi.RF_State | |
| if x.Tok == syntax.Continue { | |
| ret = &syntax.ReturnStmt{Results: r.useObj(r.true)} | |
| state = abi.RF_READY | |
| } else { | |
| ret = &syntax.ReturnStmt{Results: r.useObj(r.false)} | |
| state = abi.RF_DONE | |
| } | |
| var stmts []syntax.Stmt | |
| if r.checkFuncMisuse() { | |
| stmts = []syntax.Stmt{r.setState(state, x.Pos()), ret} | |
| } else { | |
| stmts = []syntax.Stmt{ret} | |
| } | |
| bl := &syntax.BlockStmt{ | |
| List: stmts, | |
| } | |
| setPos(bl, x.Pos()) | |
| return bl | |
| } | |
| ret = &syntax.ReturnStmt{Results: r.useObj(r.false)} | |
| // The loop inside the one we are break/continue-ing | |
| // needs to make that happen when we break out of it. | |
| if x.Tok == syntax.Continue { | |
| r.forStack[exitFrom].checkContinue = true | |
| } else { | |
| exitFrom = i // exitFrom-- | |
| r.forStack[exitFrom].checkBreak = true | |
| } | |
| // The loops along the way just need to break. | |
| for j := exitFrom + 1; j < len(r.forStack); j++ { | |
| r.forStack[j].checkBreak = true | |
| } | |
| // Set next to break the appropriate number of times; | |
| // the final time may be a continue, not a break. | |
| next = perLoopStep * (i + 1) | |
| if x.Tok == syntax.Continue { | |
| next-- | |
| } | |
| } | |
| // Assign #next = next and do the return. | |
| as := &syntax.AssignStmt{Lhs: r.next(), Rhs: r.intConst(next)} | |
| bl := &syntax.BlockStmt{ | |
| List: []syntax.Stmt{as}, | |
| } | |
| if r.checkFuncMisuse() { | |
| // Set #stateK for this loop. | |
| // The exterior loops have not exited yet, and the iterator might interfere. | |
| bl.List = append(bl.List, r.setState(abi.RF_DONE, x.Pos())) | |
| } | |
| bl.List = append(bl.List, ret) | |
| setPos(bl, x.Pos()) | |
| return bl | |
| } | |
| // computeBranchNext computes the branchNext numbering | |
| // and determines which labels end up inside which range-over-func loop bodies. | |
| func (r *rewriter) computeBranchNext() { | |
| if r.labelLoop != nil { | |
| return | |
| } | |
| r.labelLoop = make(map[string]*syntax.ForStmt) | |
| r.branchNext = make(map[branch]int) | |
| var labels []string | |
| var stack []syntax.Node | |
| var forStack []*syntax.ForStmt | |
| forStack = append(forStack, nil) | |
| syntax.Inspect(r.body, func(n syntax.Node) bool { | |
| if n != nil { | |
| stack = append(stack, n) | |
| if nfor, ok := forRangeFunc(n); ok { | |
| forStack = append(forStack, nfor) | |
| } | |
| if n, ok := n.(*syntax.LabeledStmt); ok { | |
| l := n.Label.Value | |
| labels = append(labels, l) | |
| f := forStack[len(forStack)-1] | |
| r.labelLoop[l] = f | |
| } | |
| } else { | |
| n := stack[len(stack)-1] | |
| stack = stack[:len(stack)-1] | |
| if n == forStack[len(forStack)-1] { | |
| forStack = forStack[:len(forStack)-1] | |
| } | |
| } | |
| return true | |
| }) | |
| // Assign numbers to all the labels we observed. | |
| used := -1 // returns use -1 | |
| for _, l := range labels { | |
| used -= 3 | |
| r.branchNext[branch{syntax.Break, l}] = used | |
| r.branchNext[branch{syntax.Continue, l}] = used + 1 | |
| r.branchNext[branch{syntax.Goto, l}] = used + 2 | |
| } | |
| } | |
| // endLoop finishes the conversion of a range-over-func loop. | |
| // We have inspected and rewritten the body of the loop and can now | |
| // construct the body function and rewrite the for loop into a call | |
| // bracketed by any declarations and checks it requires. | |
| func (r *rewriter) endLoop(loop *forLoop) { | |
| // Pick apart for range X { ... } | |
| nfor := loop.nfor | |
| start, end := nfor.Pos(), nfor.Body.Rbrace // start, end position of for loop | |
| rclause := nfor.Init.(*syntax.RangeClause) | |
| rfunc := types2.CoreType(rclause.X.GetTypeInfo().Type).(*types2.Signature) // type of X - func(func(...)bool) | |
| if rfunc.Params().Len() != 1 { | |
| base.Fatalf("invalid typecheck of range func") | |
| } | |
| ftyp := types2.CoreType(rfunc.Params().At(0).Type()).(*types2.Signature) // func(...) bool | |
| if ftyp.Results().Len() != 1 { | |
| base.Fatalf("invalid typecheck of range func") | |
| } | |
| // Give the closure generated for the body a name, to help the debugger connect it to its frame, if active. | |
| r.bodyClosureCount++ | |
| clo := r.bodyFunc(nfor.Body.List, syntax.UnpackListExpr(rclause.Lhs), rclause.Def, ftyp, start, end) | |
| cloDecl, cloVar := r.declSingleVar(fmt.Sprintf("#yield%d", r.bodyClosureCount), clo.GetTypeInfo().Type, clo) | |
| setPos(cloDecl, start) | |
| // Build X(bodyFunc) | |
| call := &syntax.ExprStmt{ | |
| X: &syntax.CallExpr{ | |
| Fun: rclause.X, | |
| ArgList: []syntax.Expr{ | |
| r.useObj(cloVar), | |
| }, | |
| }, | |
| } | |
| setPos(call, start) | |
| // Build checks based on #next after X(bodyFunc) | |
| checks := r.checks(loop, end) | |
| // Rewrite for vars := range X { ... } to | |
| // | |
| // { | |
| // r.declStmt | |
| // call | |
| // checks | |
| // } | |
| // | |
| // The r.declStmt can be added to by this loop or any inner loop | |
| // during the creation of r.bodyFunc; it is only emitted in the outermost | |
| // converted range loop. | |
| block := &syntax.BlockStmt{Rbrace: end} | |
| setPos(block, start) | |
| if len(r.forStack) == 1 && r.declStmt != nil { | |
| setPos(r.declStmt, start) | |
| block.List = append(block.List, r.declStmt) | |
| } | |
| // declare the state variable here so it has proper scope and initialization | |
| if r.checkFuncMisuse() { | |
| stateVarDecl := &syntax.DeclStmt{DeclList: []syntax.Decl{loop.stateVarDecl}} | |
| setPos(stateVarDecl, start) | |
| block.List = append(block.List, stateVarDecl) | |
| } | |
| // iteratorFunc(bodyFunc) | |
| block.List = append(block.List, cloDecl, call) | |
| if r.checkFuncMisuse() { | |
| // iteratorFunc has exited, check for swallowed panic, and set body state to abi.RF_EXHAUSTED | |
| nif := &syntax.IfStmt{ | |
| Cond: r.cond(syntax.Eql, r.useObj(loop.stateVar), r.stateConst(abi.RF_PANIC)), | |
| Then: &syntax.BlockStmt{ | |
| List: []syntax.Stmt{r.callPanic(start, r.stateConst(abi.RF_MISSING_PANIC))}, | |
| }, | |
| } | |
| setPos(nif, end) | |
| block.List = append(block.List, nif) | |
| block.List = append(block.List, r.setState(abi.RF_EXHAUSTED, end)) | |
| } | |
| block.List = append(block.List, checks...) | |
| if len(r.forStack) == 1 { // ending an outermost loop | |
| r.declStmt = nil | |
| r.nextVar = nil | |
| r.defers = nil | |
| } | |
| r.rewritten[nfor] = block | |
| } | |
| func (r *rewriter) cond(op syntax.Operator, x, y syntax.Expr) *syntax.Operation { | |
| cond := &syntax.Operation{Op: op, X: x, Y: y} | |
| tv := syntax.TypeAndValue{Type: r.bool.Type()} | |
| tv.SetIsValue() | |
| cond.SetTypeInfo(tv) | |
| return cond | |
| } | |
| func (r *rewriter) setState(val abi.RF_State, pos syntax.Pos) *syntax.AssignStmt { | |
| ss := r.setStateAt(len(r.forStack)-1, val) | |
| setPos(ss, pos) | |
| return ss | |
| } | |
| func (r *rewriter) setStateAt(index int, stateVal abi.RF_State) *syntax.AssignStmt { | |
| loop := r.forStack[index] | |
| return &syntax.AssignStmt{ | |
| Lhs: r.useObj(loop.stateVar), | |
| Rhs: r.stateConst(stateVal), | |
| } | |
| } | |
| // bodyFunc converts the loop body (control flow has already been updated) | |
| // to a func literal that can be passed to the range function. | |
| // | |
| // vars is the range variables from the range statement. | |
| // def indicates whether this is a := range statement. | |
| // ftyp is the type of the function we are creating | |
| // start and end are the syntax positions to use for new nodes | |
| // that should be at the start or end of the loop. | |
| func (r *rewriter) bodyFunc(body []syntax.Stmt, lhs []syntax.Expr, def bool, ftyp *types2.Signature, start, end syntax.Pos) *syntax.FuncLit { | |
| // Starting X(bodyFunc); build up bodyFunc first. | |
| var params, results []*types2.Var | |
| results = append(results, types2.NewVar(start, nil, "#r", r.bool.Type())) | |
| bodyFunc := &syntax.FuncLit{ | |
| // Note: Type is ignored but needs to be non-nil to avoid panic in syntax.Inspect. | |
| Type: &syntax.FuncType{}, | |
| Body: &syntax.BlockStmt{ | |
| List: []syntax.Stmt{}, | |
| Rbrace: end, | |
| }, | |
| } | |
| r.rangefuncBodyClosures[bodyFunc] = true | |
| setPos(bodyFunc, start) | |
| for i := 0; i < ftyp.Params().Len(); i++ { | |
| typ := ftyp.Params().At(i).Type() | |
| var paramVar *types2.Var | |
| if i < len(lhs) && def { | |
| // Reuse range variable as parameter. | |
| x := lhs[i] | |
| paramVar = r.info.Defs[x.(*syntax.Name)].(*types2.Var) | |
| } else { | |
| // Declare new parameter and assign it to range expression. | |
| paramVar = types2.NewVar(start, r.pkg, fmt.Sprintf("#p%d", 1+i), typ) | |
| if i < len(lhs) { | |
| x := lhs[i] | |
| as := &syntax.AssignStmt{Lhs: x, Rhs: r.useObj(paramVar)} | |
| as.SetPos(x.Pos()) | |
| setPos(as.Rhs, x.Pos()) | |
| bodyFunc.Body.List = append(bodyFunc.Body.List, as) | |
| } | |
| } | |
| params = append(params, paramVar) | |
| } | |
| tv := syntax.TypeAndValue{ | |
| Type: types2.NewSignatureType(nil, nil, nil, | |
| types2.NewTuple(params...), | |
| types2.NewTuple(results...), | |
| false), | |
| } | |
| tv.SetIsValue() | |
| bodyFunc.SetTypeInfo(tv) | |
| loop := r.forStack[len(r.forStack)-1] | |
| if r.checkFuncMisuse() { | |
| // #tmpState := #stateVarN | |
| // #stateVarN = abi.RF_PANIC | |
| // if #tmpState != abi.RF_READY { | |
| // runtime.panicrangestate(#tmpState) | |
| // } | |
| // | |
| // That is a slightly code-size-optimized version of | |
| // | |
| // if #stateVarN != abi.RF_READY { | |
| // #stateVarN = abi.RF_PANIC // If we ever need to specially detect "iterator swallowed checking panic" we put a different value here. | |
| // runtime.panicrangestate(#tmpState) | |
| // } | |
| // #stateVarN = abi.RF_PANIC | |
| // | |
| tmpDecl, tmpState := r.declSingleVar("#tmpState", r.int.Type(), r.useObj(loop.stateVar)) | |
| bodyFunc.Body.List = append(bodyFunc.Body.List, tmpDecl) | |
| bodyFunc.Body.List = append(bodyFunc.Body.List, r.setState(abi.RF_PANIC, start)) | |
| bodyFunc.Body.List = append(bodyFunc.Body.List, r.assertReady(start, tmpState)) | |
| } | |
| // Original loop body (already rewritten by editStmt during inspect). | |
| bodyFunc.Body.List = append(bodyFunc.Body.List, body...) | |
| // end of loop body, set state to abi.RF_READY and return true to continue iteration | |
| if r.checkFuncMisuse() { | |
| bodyFunc.Body.List = append(bodyFunc.Body.List, r.setState(abi.RF_READY, end)) | |
| } | |
| ret := &syntax.ReturnStmt{Results: r.useObj(r.true)} | |
| ret.SetPos(end) | |
| bodyFunc.Body.List = append(bodyFunc.Body.List, ret) | |
| return bodyFunc | |
| } | |
| // checks returns the post-call checks that need to be done for the given loop. | |
| func (r *rewriter) checks(loop *forLoop, pos syntax.Pos) []syntax.Stmt { | |
| var list []syntax.Stmt | |
| if len(loop.checkBranch) > 0 { | |
| did := make(map[branch]bool) | |
| for _, br := range loop.checkBranch { | |
| if did[br] { | |
| continue | |
| } | |
| did[br] = true | |
| doBranch := &syntax.BranchStmt{Tok: br.tok, Label: &syntax.Name{Value: br.label}} | |
| list = append(list, r.ifNext(syntax.Eql, r.branchNext[br], true, doBranch)) | |
| } | |
| } | |
| curLoop := loop.depth - 1 | |
| curLoopIndex := curLoop - 1 | |
| if len(r.forStack) == 1 { | |
| if loop.checkRet { | |
| list = append(list, r.ifNext(syntax.Eql, -1, false, retStmt(nil))) | |
| } | |
| } else { | |
| // Idealized check, implemented more simply for now. | |
| // // N == depth of this loop, one less than the one just exited. | |
| // if #next != 0 { | |
| // if #next >= perLoopStep*N-1 { // this loop | |
| // if #next >= perLoopStep*N+1 { // error checking | |
| // runtime.panicrangestate(abi.RF_DONE) | |
| // } | |
| // rv := #next & 1 == 1 // code generates into #next&1 | |
| // #next = 0 | |
| // return rv | |
| // } | |
| // return false // or handle returns and gotos | |
| // } | |
| if loop.checkRet { | |
| // Note: next < 0 also handles gotos handled by outer loops. | |
| // We set checkRet in that case to trigger this check. | |
| if r.checkFuncMisuse() { | |
| list = append(list, r.ifNext(syntax.Lss, 0, false, r.setStateAt(curLoopIndex, abi.RF_DONE), retStmt(r.useObj(r.false)))) | |
| } else { | |
| list = append(list, r.ifNext(syntax.Lss, 0, false, retStmt(r.useObj(r.false)))) | |
| } | |
| } | |
| depthStep := perLoopStep * (curLoop) | |
| if r.checkFuncMisuse() { | |
| list = append(list, r.ifNext(syntax.Gtr, depthStep, false, r.callPanic(pos, r.stateConst(abi.RF_DONE)))) | |
| } else { | |
| list = append(list, r.ifNext(syntax.Gtr, depthStep, true)) | |
| } | |
| if r.checkFuncMisuse() { | |
| if loop.checkContinue { | |
| list = append(list, r.ifNext(syntax.Eql, depthStep-1, true, r.setStateAt(curLoopIndex, abi.RF_READY), retStmt(r.useObj(r.true)))) | |
| } | |
| if loop.checkBreak { | |
| list = append(list, r.ifNext(syntax.Eql, depthStep, true, r.setStateAt(curLoopIndex, abi.RF_DONE), retStmt(r.useObj(r.false)))) | |
| } | |
| if loop.checkContinue || loop.checkBreak { | |
| list = append(list, r.ifNext(syntax.Gtr, 0, false, r.setStateAt(curLoopIndex, abi.RF_DONE), retStmt(r.useObj(r.false)))) | |
| } | |
| } else { | |
| if loop.checkContinue { | |
| list = append(list, r.ifNext(syntax.Eql, depthStep-1, true, retStmt(r.useObj(r.true)))) | |
| } | |
| if loop.checkBreak { | |
| list = append(list, r.ifNext(syntax.Eql, depthStep, true, retStmt(r.useObj(r.false)))) | |
| } | |
| if loop.checkContinue || loop.checkBreak { | |
| list = append(list, r.ifNext(syntax.Gtr, 0, false, retStmt(r.useObj(r.false)))) | |
| } | |
| } | |
| } | |
| for _, j := range list { | |
| setPos(j, pos) | |
| } | |
| return list | |
| } | |
| // retStmt returns a return statement returning the given return values. | |
| func retStmt(results syntax.Expr) *syntax.ReturnStmt { | |
| return &syntax.ReturnStmt{Results: results} | |
| } | |
| // ifNext returns the statement: | |
| // | |
| // if #next op c { [#next = 0;] thens... } | |
| func (r *rewriter) ifNext(op syntax.Operator, c int, zeroNext bool, thens ...syntax.Stmt) syntax.Stmt { | |
| var thenList []syntax.Stmt | |
| if zeroNext { | |
| clr := &syntax.AssignStmt{ | |
| Lhs: r.next(), | |
| Rhs: r.intConst(0), | |
| } | |
| thenList = append(thenList, clr) | |
| } | |
| for _, then := range thens { | |
| thenList = append(thenList, then) | |
| } | |
| nif := &syntax.IfStmt{ | |
| Cond: r.cond(op, r.next(), r.intConst(c)), | |
| Then: &syntax.BlockStmt{ | |
| List: thenList, | |
| }, | |
| } | |
| return nif | |
| } | |
| // setValueType marks x as a value with type typ. | |
| func setValueType(x syntax.Expr, typ syntax.Type) { | |
| tv := syntax.TypeAndValue{Type: typ} | |
| tv.SetIsValue() | |
| x.SetTypeInfo(tv) | |
| } | |
| // assertReady returns the statement: | |
| // | |
| // if #tmpState != abi.RF_READY { runtime.panicrangestate(#tmpState) } | |
| func (r *rewriter) assertReady(start syntax.Pos, tmpState *types2.Var) syntax.Stmt { | |
| nif := &syntax.IfStmt{ | |
| Cond: r.cond(syntax.Neq, r.useObj(tmpState), r.stateConst(abi.RF_READY)), | |
| Then: &syntax.BlockStmt{ | |
| List: []syntax.Stmt{ | |
| r.callPanic(start, r.useObj(tmpState))}, | |
| }, | |
| } | |
| setPos(nif, start) | |
| return nif | |
| } | |
| func (r *rewriter) callPanic(start syntax.Pos, arg syntax.Expr) syntax.Stmt { | |
| callPanicExpr := &syntax.CallExpr{ | |
| Fun: runtimeSym(r.info, "panicrangestate"), | |
| ArgList: []syntax.Expr{arg}, | |
| } | |
| setValueType(callPanicExpr, nil) // no result type | |
| return &syntax.ExprStmt{X: callPanicExpr} | |
| } | |
| // next returns a reference to the #next variable. | |
| func (r *rewriter) next() *syntax.Name { | |
| if r.nextVar == nil { | |
| r.nextVar = r.declOuterVar("#next", r.int.Type(), nil) | |
| } | |
| return r.useObj(r.nextVar) | |
| } | |
| // forRangeFunc checks whether n is a range-over-func. | |
| // If so, it returns n.(*syntax.ForStmt), true. | |
| // Otherwise it returns nil, false. | |
| func forRangeFunc(n syntax.Node) (*syntax.ForStmt, bool) { | |
| nfor, ok := n.(*syntax.ForStmt) | |
| if !ok { | |
| return nil, false | |
| } | |
| nrange, ok := nfor.Init.(*syntax.RangeClause) | |
| if !ok { | |
| return nil, false | |
| } | |
| _, ok = types2.CoreType(nrange.X.GetTypeInfo().Type).(*types2.Signature) | |
| if !ok { | |
| return nil, false | |
| } | |
| return nfor, true | |
| } | |
| // intConst returns syntax for an integer literal with the given value. | |
| func (r *rewriter) intConst(c int) *syntax.BasicLit { | |
| lit := &syntax.BasicLit{ | |
| Value: fmt.Sprint(c), | |
| Kind: syntax.IntLit, | |
| } | |
| tv := syntax.TypeAndValue{Type: r.int.Type(), Value: constant.MakeInt64(int64(c))} | |
| tv.SetIsValue() | |
| lit.SetTypeInfo(tv) | |
| return lit | |
| } | |
| func (r *rewriter) stateConst(s abi.RF_State) *syntax.BasicLit { | |
| return r.intConst(int(s)) | |
| } | |
| // useObj returns syntax for a reference to decl, which should be its declaration. | |
| func (r *rewriter) useObj(obj types2.Object) *syntax.Name { | |
| n := syntax.NewName(nopos, obj.Name()) | |
| tv := syntax.TypeAndValue{Type: obj.Type()} | |
| tv.SetIsValue() | |
| n.SetTypeInfo(tv) | |
| r.info.Uses[n] = obj | |
| return n | |
| } | |
| // useList is useVar for a list of decls. | |
| func (r *rewriter) useList(vars []types2.Object) syntax.Expr { | |
| var new []syntax.Expr | |
| for _, obj := range vars { | |
| new = append(new, r.useObj(obj)) | |
| } | |
| if len(new) == 1 { | |
| return new[0] | |
| } | |
| return &syntax.ListExpr{ElemList: new} | |
| } | |
| func (r *rewriter) makeVarName(pos syntax.Pos, name string, typ types2.Type) (*types2.Var, *syntax.Name) { | |
| obj := types2.NewVar(pos, r.pkg, name, typ) | |
| n := syntax.NewName(pos, name) | |
| tv := syntax.TypeAndValue{Type: typ} | |
| tv.SetIsValue() | |
| n.SetTypeInfo(tv) | |
| r.info.Defs[n] = obj | |
| return obj, n | |
| } | |
| func (r *rewriter) generateParamName(results []*syntax.Field, i int) { | |
| obj, n := r.sig.RenameResult(results, i) | |
| r.info.Defs[n] = obj | |
| } | |
| // declOuterVar declares a variable with a given name, type, and initializer value, | |
| // in the same scope as the outermost loop in a loop nest. | |
| func (r *rewriter) declOuterVar(name string, typ types2.Type, init syntax.Expr) *types2.Var { | |
| if r.declStmt == nil { | |
| r.declStmt = &syntax.DeclStmt{} | |
| } | |
| stmt := r.declStmt | |
| obj, n := r.makeVarName(stmt.Pos(), name, typ) | |
| stmt.DeclList = append(stmt.DeclList, &syntax.VarDecl{ | |
| NameList: []*syntax.Name{n}, | |
| // Note: Type is ignored | |
| Values: init, | |
| }) | |
| return obj | |
| } | |
| // declSingleVar declares a variable with a given name, type, and initializer value, | |
| // and returns both the declaration and variable, so that the declaration can be placed | |
| // in a specific scope. | |
| func (r *rewriter) declSingleVar(name string, typ types2.Type, init syntax.Expr) (*syntax.DeclStmt, *types2.Var) { | |
| stmt := &syntax.DeclStmt{} | |
| obj, n := r.makeVarName(stmt.Pos(), name, typ) | |
| stmt.DeclList = append(stmt.DeclList, &syntax.VarDecl{ | |
| NameList: []*syntax.Name{n}, | |
| // Note: Type is ignored | |
| Values: init, | |
| }) | |
| return stmt, obj | |
| } | |
| // runtimePkg is a fake runtime package that contains what we need to refer to in package runtime. | |
| var runtimePkg = func() *types2.Package { | |
| var nopos syntax.Pos | |
| pkg := types2.NewPackage("runtime", "runtime") | |
| anyType := types2.Universe.Lookup("any").Type() | |
| intType := types2.Universe.Lookup("int").Type() | |
| // func deferrangefunc() unsafe.Pointer | |
| obj := types2.NewFunc(nopos, pkg, "deferrangefunc", types2.NewSignatureType(nil, nil, nil, nil, types2.NewTuple(types2.NewParam(nopos, pkg, "extra", anyType)), false)) | |
| pkg.Scope().Insert(obj) | |
| // func panicrangestate() | |
| obj = types2.NewFunc(nopos, pkg, "panicrangestate", types2.NewSignatureType(nil, nil, nil, types2.NewTuple(types2.NewParam(nopos, pkg, "state", intType)), nil, false)) | |
| pkg.Scope().Insert(obj) | |
| return pkg | |
| }() | |
| // runtimeSym returns a reference to a symbol in the fake runtime package. | |
| func runtimeSym(info *types2.Info, name string) *syntax.Name { | |
| obj := runtimePkg.Scope().Lookup(name) | |
| n := syntax.NewName(nopos, "runtime."+name) | |
| tv := syntax.TypeAndValue{Type: obj.Type()} | |
| tv.SetIsValue() | |
| tv.SetIsRuntimeHelper() | |
| n.SetTypeInfo(tv) | |
| info.Uses[n] = obj | |
| return n | |
| } | |
| // setPos walks the top structure of x that has no position assigned | |
| // and assigns it all to have position pos. | |
| // When setPos encounters a syntax node with a position assigned, | |
| // setPos does not look inside that node. | |
| // setPos only needs to handle syntax we create in this package; | |
| // all other syntax should have positions assigned already. | |
| func setPos(x syntax.Node, pos syntax.Pos) { | |
| if x == nil { | |
| return | |
| } | |
| syntax.Inspect(x, func(n syntax.Node) bool { | |
| if n == nil || n.Pos() != nopos { | |
| return false | |
| } | |
| n.SetPos(pos) | |
| switch n := n.(type) { | |
| case *syntax.BlockStmt: | |
| if n.Rbrace == nopos { | |
| n.Rbrace = pos | |
| } | |
| } | |
| return true | |
| }) | |
| } | |