mirror of https://github.com/golang/go.git
1517 lines
42 KiB
Go
1517 lines
42 KiB
Go
// 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 { runtime.panicrangestate(#stateN) }
|
|
#stateN = abi.RF_PANIC
|
|
|
|
(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 { 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 { runtime.panicrangestate(#state1) }
|
|
#state1 = abi.RF_PANIC
|
|
var #state2 = abi.RF_READY
|
|
g(func() bool {
|
|
if #state2 != abi.RF_READY { 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 { runtime.panicrangestate(#state1) }
|
|
#state1 = abi.RF_PANIC
|
|
var #state2 = abi.RF_READY
|
|
g(func() { // 3,4
|
|
if #state2 != abi.RF_READY { runtime.panicrangestate(#state2) }
|
|
#state2 = abi.RF_PANIC
|
|
var #state3 = abi.RF_READY
|
|
h(func() { // 5,6
|
|
if #state3 != abi.RF_READY { 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{ runtime.panicrangestate(#state1) }
|
|
#state1 = abi.RF_PANIC
|
|
var #state2 = abi.RF_READY
|
|
g(func() {
|
|
if #state2 != abi.RF_READY { runtime.panicrangestate(#state2) }
|
|
#state2 = abi.RF_PANIC
|
|
...
|
|
var #state3 bool = abi.RF_READY
|
|
h(func() {
|
|
if #state3 != abi.RF_READY { 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 distingushed 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() {
|
|
bodyFunc.Body.List = append(bodyFunc.Body.List, r.assertReady(start, loop))
|
|
bodyFunc.Body.List = append(bodyFunc.Body.List, r.setState(abi.RF_PANIC, start))
|
|
}
|
|
|
|
// 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 #stateK != abi.RF_READY { runtime.panicrangestate(#stateK) }
|
|
//
|
|
// where #stateK is the state variable for loop.
|
|
func (r *rewriter) assertReady(start syntax.Pos, loop *forLoop) syntax.Stmt {
|
|
nif := &syntax.IfStmt{
|
|
Cond: r.cond(syntax.Neq, r.useObj(loop.stateVar), r.stateConst(abi.RF_READY)),
|
|
Then: &syntax.BlockStmt{
|
|
List: []syntax.Stmt{r.callPanic(start, r.useObj(loop.stateVar))},
|
|
},
|
|
}
|
|
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
|
|
})
|
|
}
|