first implementation of new lambda lifting
This commit is contained in:
parent
70c283ac64
commit
15dad22a0a
5 changed files with 411 additions and 215 deletions
|
|
@ -257,6 +257,9 @@ const
|
|||
sfShadowed* = sfInnerProc
|
||||
# a variable that was shadowed in some inner scope
|
||||
|
||||
sfByCopy* = sfBorrow
|
||||
# a variable is to be captured by value in a closure
|
||||
|
||||
const
|
||||
# getting ready for the future expr/stmt merge
|
||||
nkWhen* = nkWhenStmt
|
||||
|
|
|
|||
|
|
@ -849,6 +849,10 @@ proc IdNodeTablePut(t: var TIdNodeTable, key: PIdObj, val: PNode) =
|
|||
IdNodeTableRawInsert(t.data, key, val)
|
||||
inc(t.counter)
|
||||
|
||||
iterator pairs*(t: TIdNodeTable): tuple[key: PIdObj, val: PNode] =
|
||||
for i in 0 .. high(t.data):
|
||||
if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val)
|
||||
|
||||
proc initIITable(x: var TIITable) =
|
||||
x.counter = 0
|
||||
newSeq(x.data, startSize)
|
||||
|
|
|
|||
|
|
@ -10,27 +10,157 @@
|
|||
# This include file implements lambda lifting for the transformator.
|
||||
# included from transf.nim
|
||||
|
||||
import
|
||||
intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os,
|
||||
idents, renderer, types, magicsys, rodread
|
||||
|
||||
discard """
|
||||
The basic approach is that captured vars need to be put on the heap and
|
||||
that the calling chain needs to be explicitely modelled. Things to consider:
|
||||
|
||||
proc a =
|
||||
var v = 0
|
||||
proc b =
|
||||
var w = 2
|
||||
|
||||
for x in 0..3:
|
||||
proc c = capture v, w, x
|
||||
c()
|
||||
b()
|
||||
|
||||
for x in 0..4:
|
||||
proc d = capture x
|
||||
d()
|
||||
|
||||
Needs to be translated into:
|
||||
|
||||
proc a =
|
||||
var cl: *
|
||||
new cl
|
||||
cl.v = 0
|
||||
|
||||
proc b(cl) =
|
||||
var bcl: *
|
||||
new bcl
|
||||
bcl.w = 2
|
||||
bcl.up = cl
|
||||
|
||||
for x in 0..3:
|
||||
var bcl2: *
|
||||
new bcl2
|
||||
bcl2.up = bcl
|
||||
bcl2.x = x
|
||||
|
||||
proc c(cl) = capture cl.up.up.v, cl.up.w, cl.x
|
||||
c(bcl2)
|
||||
|
||||
c(bcl)
|
||||
|
||||
b(cl)
|
||||
|
||||
for x in 0..4:
|
||||
var acl2: *
|
||||
new acl2
|
||||
acl2.x = x
|
||||
proc d(cl) = capture cl.x
|
||||
d(acl2)
|
||||
|
||||
Closures as interfaces:
|
||||
|
||||
proc outer: T =
|
||||
var captureMe: TObject # value type required for efficiency
|
||||
proc getter(): int = result = captureMe.x
|
||||
proc setter(x: int) = captureMe.x = x
|
||||
|
||||
result = (getter, setter)
|
||||
|
||||
Is translated to:
|
||||
|
||||
proc outer: T =
|
||||
var cl: *
|
||||
new cl
|
||||
|
||||
proc getter(cl): int = result = cl.captureMe.x
|
||||
proc setter(cl: *, x: int) = cl.captureMe.x = x
|
||||
|
||||
result = ((cl, getter), (cl, setter))
|
||||
|
||||
|
||||
For 'byref' capture, the outer proc needs to access the captured var through
|
||||
the indirection too. For 'bycopy' capture, the outer proc accesses the var
|
||||
not through the indirection.
|
||||
|
||||
Possible optimizations:
|
||||
|
||||
1) If the closure contains a single 'ref' and this
|
||||
reference is not re-assigned (check ``sfAddrTaken`` flag) make this the
|
||||
closure. This is an important optimization if closures are used as
|
||||
interfaces.
|
||||
2) If the closure does not escape, put it onto the stack, not on the heap.
|
||||
3) Dataflow analysis would help to eliminate the 'up' indirections.
|
||||
4) If the captured var is not actually used in the outer proc (common?),
|
||||
put it into an inner proc.
|
||||
|
||||
"""
|
||||
|
||||
const
|
||||
declarativeDefs = {nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef}
|
||||
procDefs = nkLambdaKinds + declarativeDefs
|
||||
declarativeDefs* = {nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef}
|
||||
procDefs* = nkLambdaKinds + declarativeDefs
|
||||
upName* = ":up" # field name for the 'up' reference
|
||||
envName* = ":env"
|
||||
|
||||
type
|
||||
TCapture = seq[PSym]
|
||||
|
||||
TLLShared {.final.} = object
|
||||
transformedInnerProcs: TIntSet
|
||||
c: PTransf
|
||||
upField: PSym
|
||||
|
||||
TLLContext {.final.} = object
|
||||
outerProc, innerProc: PSym
|
||||
mapping: TIdNodeTable # mapping from symbols to nodes
|
||||
shared: ref TLLShared
|
||||
PInnerContext = ref TInnerContext
|
||||
POuterContext = ref TOuterContext
|
||||
PLLShared = ref TLLShared
|
||||
PBlock = ref TBlock
|
||||
|
||||
PLLContext = ref TLLContext
|
||||
TBlock {.final.} = object
|
||||
body: PNode
|
||||
closure: PSym
|
||||
used: bool
|
||||
|
||||
TInnerContext {.final.} = object
|
||||
fn: PSym
|
||||
closureParam: PSym
|
||||
localsToAccess: TIdNodeTable
|
||||
up: POuterContext # used for chaining
|
||||
levelsUp: int # counts how many "up levels" are accessed
|
||||
tup: PType
|
||||
|
||||
TOuterContext {.final.} = object
|
||||
fn: PSym
|
||||
currentBlock: PNode
|
||||
capturedVars: TIntSet
|
||||
localsToEnclosingScope: TIdNodeTable
|
||||
localsToAccess: TIdNodeTable
|
||||
lambdasToEnclosingScope: TIdNodeTable
|
||||
|
||||
shared: PLLShared
|
||||
up: POuterContext
|
||||
|
||||
proc indirectAccess(a, b: PSym, info: TLineInfo): PNode =
|
||||
proc newOuterContext(fn: PSym, shared: PLLShared,
|
||||
up: POuterContext = nil): POuterContext =
|
||||
new(result)
|
||||
result.fn = fn
|
||||
result.shared = shared
|
||||
result.capturedVars = initIntSet()
|
||||
initIdNodeTable(result.localsToAccess)
|
||||
initIdNodeTable(result.localsToEnclosingScope)
|
||||
initIdNodeTable(result.lambdasToEnclosingScope)
|
||||
|
||||
proc newInnerContext(fn: PSym, outer: POuterContext): PInnerContext =
|
||||
new(result)
|
||||
result.up = outer
|
||||
result.fn = fn
|
||||
initIdNodeTable(result.localsToAccess)
|
||||
|
||||
proc indirectAccess(a: PNode, b: PSym, info: TLineInfo): PNode =
|
||||
# returns a[].b as a node
|
||||
let x = newSymNode(a)
|
||||
let x = a
|
||||
var deref = newNodeI(nkHiddenDeref, info)
|
||||
deref.typ = x.typ.sons[0]
|
||||
|
||||
|
|
@ -41,54 +171,20 @@ proc indirectAccess(a, b: PSym, info: TLineInfo): PNode =
|
|||
addSon(result, newSymNode(field))
|
||||
result.typ = field.typ
|
||||
|
||||
proc Capture(cap: var TCapture, s: PSym) =
|
||||
for x in cap:
|
||||
if x.name.id == s.name.id: return
|
||||
cap.add(s)
|
||||
proc indirectAccess(a, b: PSym, info: TLineInfo): PNode =
|
||||
result = indirectAccess(newSymNode(a), b, info)
|
||||
|
||||
proc captureToTuple(cap: TCapture, owner: PSym): PType =
|
||||
result = newType(tyTuple, owner)
|
||||
result.n = newNodeI(nkRecList, owner.info)
|
||||
for s in cap:
|
||||
var field = newSym(skField, s.name, s.owner)
|
||||
|
||||
let typ = s.typ
|
||||
field.typ = typ
|
||||
field.position = sonsLen(result)
|
||||
|
||||
addSon(result.n, newSymNode(field))
|
||||
addSon(result, typ)
|
||||
proc newCall(a, b: PSym): PNode =
|
||||
result = newNodeI(nkCall, a.info)
|
||||
result.add newSymNode(a)
|
||||
result.add newSymNode(b)
|
||||
|
||||
proc interestingVar(s: PSym): bool {.inline.} =
|
||||
result = s.kind in {skVar, skLet, skTemp, skForVar, skParam, skResult} and
|
||||
sfGlobal notin s.flags
|
||||
|
||||
proc gatherVars(c: PLLContext, n: PNode, cap: var TCapture) =
|
||||
# gather used vars for closure generation into 'cap'
|
||||
case n.kind
|
||||
of nkSym:
|
||||
var s = n.sym
|
||||
if interestingVar(s) and c.innerProc.id != s.owner.id:
|
||||
# we need to compute the path here:
|
||||
var
|
||||
|
||||
#echo "captured: ", s.name.s
|
||||
Capture(cap, s)
|
||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
|
||||
else:
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
gatherVars(c, n.sons[i], cap)
|
||||
|
||||
proc replaceVars(c: PTransf, n: PNode, outerProc, env: PSym) =
|
||||
for i in countup(0, safeLen(n) - 1):
|
||||
let a = n.sons[i]
|
||||
if a.kind == nkSym:
|
||||
let s = a.sym
|
||||
if interestingVar(s) and outerProc == s.owner:
|
||||
# access through the closure param:
|
||||
n.sons[i] = indirectAccess(env, s, n.info)
|
||||
else:
|
||||
replaceVars(c, a, outerProc, env)
|
||||
proc addField(tup: PType, s: PSym) =
|
||||
var field = newSym(skField, s.name, s.owner)
|
||||
field.typ = s.typ
|
||||
field.position = sonsLen(tup)
|
||||
addSon(tup.n, newSymNode(field))
|
||||
addSon(tup, s.typ)
|
||||
|
||||
proc addHiddenParam(routine: PSym, param: PSym) =
|
||||
var params = routine.ast.sons[paramsPos]
|
||||
|
|
@ -97,174 +193,248 @@ proc addHiddenParam(routine: PSym, param: PSym) =
|
|||
#echo "produced environment: ", param.id, " for ", routine.name.s
|
||||
|
||||
proc isInnerProc(s, outerProc: PSym): bool {.inline.} =
|
||||
result = s.kind in {skProc, skMacro, skIterator, skMethod, skConverter} and
|
||||
result = s.kind in {skProc, skIterator, skMethod, skConverter} and
|
||||
s.owner == outerProc and not isGenericRoutine(s)
|
||||
#s.typ.callConv == ccClosure
|
||||
|
||||
proc searchForInnerProcs(c: PTransf, n: PNode, outerProc: PSym,
|
||||
cap: var TCapture) =
|
||||
proc captureVar(o: POuterContext, i: PInnerContext, local: PSym,
|
||||
info: TLineInfo) =
|
||||
discard """
|
||||
Consider:
|
||||
var x = 0
|
||||
var y = 2
|
||||
capture x, y
|
||||
|
||||
block:
|
||||
var z = 3
|
||||
capture z
|
||||
|
||||
We need to merge x, y into a closure, but not z!
|
||||
"""
|
||||
# we need to remember which outer closure belongs to this lambda; we also
|
||||
# use this check to prevent multiple runs over the same inner proc:
|
||||
if IdNodeTableGet(o.lambdasToEnclosingScope, i.fn) != nil: return
|
||||
IdNodeTablePut(o.lambdasToEnclosingScope, i.fn, o.currentBlock)
|
||||
|
||||
if IdNodeTableGet(i.localsToAccess, local) != nil: return
|
||||
if i.closureParam == nil:
|
||||
var cp = newSym(skParam, getIdent(upname), i.fn)
|
||||
cp.info = i.fn.info
|
||||
incl(cp.flags, sfFromGeneric)
|
||||
i.tup = newType(tyTuple, i.fn)
|
||||
i.tup.n = newNodeI(nkRecList, i.fn.info)
|
||||
cp.typ = i.tup
|
||||
i.closureParam = cp
|
||||
addField(i.tup, local)
|
||||
var it = i.up
|
||||
var access = newSymNode(i.closureParam)
|
||||
var levelsUp = 0
|
||||
while it.fn.id != local.owner.id:
|
||||
access = indirectAccess(access, o.shared.upField, info)
|
||||
it = it.up
|
||||
assert it != nil
|
||||
inc levelsUp
|
||||
i.levelsUp = max(i.levelsUp, levelsUp)
|
||||
access = indirectAccess(access, local, info)
|
||||
IdNodeTablePut(i.localsToAccess, local, access)
|
||||
incl(o.capturedVars, local.id)
|
||||
|
||||
proc interestingVar(s: PSym): bool {.inline.} =
|
||||
result = s.kind in {skVar, skLet, skTemp, skForVar, skParam, skResult} and
|
||||
sfGlobal notin s.flags
|
||||
|
||||
proc gatherVars(o: POuterContext, i: PInnerContext, n: PNode) =
|
||||
# gather used vars for closure generation
|
||||
case n.kind
|
||||
of nkSym:
|
||||
if isInnerProc(n.sym, outerProc):
|
||||
gatherVars(c, n.sym.getBody, outerProc, cap)
|
||||
var s = n.sym
|
||||
if interestingVar(s) and i.fn.id != s.owner.id:
|
||||
captureVar(o, i, s, n.info)
|
||||
#echo "captured: ", s.name.s
|
||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
|
||||
else:
|
||||
for i in 0.. <len(n):
|
||||
searchForInnerProcs(c, n.sons[i], outerProc, cap)
|
||||
|
||||
proc makeClosure(c: PTransf, prc, env: PSym, info: TLineInfo): PNode =
|
||||
for k in countup(0, sonsLen(n) - 1):
|
||||
gatherVars(o, i, n.sons[k])
|
||||
|
||||
proc makeClosure(prc, env: PSym, info: TLineInfo): PNode =
|
||||
result = newNodeIT(nkClosure, info, prc.typ)
|
||||
result.add(newSymNode(prc))
|
||||
if env == nil:
|
||||
result.add(newNodeIT(nkNilLit, info, getSysType(tyNil)))
|
||||
else:
|
||||
result.add(newSymNode(env))
|
||||
|
||||
proc transformInnerProcs(c: PTransf, n: PNode, outerProc, env: PSym) =
|
||||
|
||||
proc transformInnerProc(o: POuterContext, i: PInnerContext, n: PNode): PNode =
|
||||
case n.kind
|
||||
of nkSym:
|
||||
let innerProc = n.sym
|
||||
if isInnerProc(innerProc, outerProc) and not
|
||||
containsOrIncl(c.transformedInnerProcs, innerProc.id):
|
||||
if env == nil:
|
||||
innerProc.ast.sons[bodyPos] = transform(c, innerProc.getBody).pnode
|
||||
else:
|
||||
# inner proc could capture outer vars:
|
||||
var param = newTemp(c, env.typ, n.info)
|
||||
param.kind = skParam
|
||||
|
||||
# recursive calls go through (f, hiddenParam):
|
||||
IdNodeTablePut(c.transCon.mapping, innerProc,
|
||||
makeClosure(c, innerProc, param, n.info))
|
||||
# access all non-local vars through the 'env' param:
|
||||
replaceVars(c, innerProc.getBody, outerProc, param)
|
||||
|
||||
innerProc.ast.sons[bodyPos] = transform(c, innerProc.getBody).pnode
|
||||
addHiddenParam(innerProc, param)
|
||||
|
||||
# 'anon' should be replaced by '(anon, env)' in the outer proc:
|
||||
IdNodeTablePut(c.transCon.mapping, innerProc,
|
||||
makeClosure(c, innerProc, env, n.info))
|
||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
|
||||
of nkSym:
|
||||
if n.sym == i.fn:
|
||||
# recursive calls go through (lambda, hiddenParam):
|
||||
assert i.closureParam != nil
|
||||
result = makeClosure(n.sym, i.closureParam, n.info)
|
||||
else:
|
||||
# captured symbol?
|
||||
result = IdNodeTableGet(i.localsToAccess, n.sym)
|
||||
of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef,
|
||||
nkIteratorDef, nkLambdaKinds:
|
||||
# don't recurse here:
|
||||
nil
|
||||
else:
|
||||
for i in 0.. <len(n):
|
||||
transformInnerProcs(c, n.sons[i], outerProc, env)
|
||||
|
||||
template checkInvariant(n: PNode, s: PSym) =
|
||||
when false:
|
||||
if s.ast != n:
|
||||
echo renderTree(s.ast)
|
||||
echo " -------------- "
|
||||
echo n.renderTree
|
||||
assert s.ast == n
|
||||
for j in countup(0, sonsLen(n) - 1):
|
||||
let x = transformInnerProc(o, i, n.sons[j])
|
||||
if x != nil: n.sons[j] = x
|
||||
|
||||
proc newCall(a, b: PSym): PNode =
|
||||
result = newNodeI(nkCall, a.info)
|
||||
result.add newSymNode(a)
|
||||
result.add newSymNode(b)
|
||||
proc searchForInnerProcs(o: POuterContext, n: PNode) =
|
||||
case n.kind
|
||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||
nil
|
||||
of nkSym:
|
||||
if isInnerProc(n.sym, o.fn):
|
||||
var inner = newInnerContext(n.sym, o)
|
||||
let body = n.sym.getBody
|
||||
gatherVars(o, inner, body)
|
||||
let ti = transformInnerProc(o, inner, body)
|
||||
if ti != nil: n.sym.ast.sons[bodyPos] = ti
|
||||
of nkWhileStmt, nkForStmt, nkParForStmt, nkBlockStmt:
|
||||
# some nodes open a new scope, so they are candidates for the insertion
|
||||
# of closure creation; however for simplicity we merge closures between
|
||||
# branches, in fact, only loops bodies are of interest here as only they
|
||||
# yield observable changes in semantics. For Zahary we also
|
||||
# include ``nkBlock``.
|
||||
var body = n.len-1
|
||||
for i in countup(0, body - 1): searchForInnerProcs(o, n.sons[i])
|
||||
# special handling for the loop body:
|
||||
let oldBlock = o.currentBlock
|
||||
var ex = newNodeI(nkStmtList, n.info)
|
||||
ex.add(emptyNode)
|
||||
ex.add(n.sons[body])
|
||||
o.currentBlock = ex
|
||||
searchForInnerProcs(o, n.sons[body])
|
||||
n.sons[body] = ex
|
||||
o.currentBlock = oldBlock
|
||||
of nkVarSection, nkLetSection:
|
||||
# we need to compute a mapping var->declaredBlock. Note: The definition
|
||||
# counts, not the block where it is captured!
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
var it = n.sons[i]
|
||||
if it.kind == nkCommentStmt: nil
|
||||
elif it.kind == nkIdentDefs:
|
||||
if it.sons[0].kind != nkSym: InternalError(it.info, "transformOuter")
|
||||
IdNodeTablePut(o.localsToEnclosingScope, it.sons[0].sym, o.currentBlock)
|
||||
elif it.kind == nkVarTuple:
|
||||
var L = sonsLen(it)
|
||||
for j in countup(0, L-3):
|
||||
IdNodeTablePut(o.localsToEnclosingScope, it.sons[j].sym,
|
||||
o.currentBlock)
|
||||
else:
|
||||
InternalError(it.info, "transformOuter")
|
||||
of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef,
|
||||
nkIteratorDef, nkLambdaKinds:
|
||||
# don't recurse here:
|
||||
# XXX recurse here and setup 'up' pointers
|
||||
nil
|
||||
else:
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
searchForInnerProcs(o, n.sons[i])
|
||||
|
||||
proc createEnvStmt(c: PTransf, varList: TCapture, env: PSym): PTransNode =
|
||||
# 'varlist' can contain parameters or variables. We don't eliminate yet
|
||||
# local vars that end up in an environment. This could even be a for loop
|
||||
# var!
|
||||
result = newTransNode(nkStmtList, env.info, 0)
|
||||
proc newAsgnStmt(le, ri: PNode): PNode =
|
||||
result = newNodeI(nkFastAsgn, ri.info)
|
||||
result.add(le)
|
||||
result.add(ri)
|
||||
|
||||
proc addVar*(father, v: PNode) =
|
||||
var vpart = newNodeI(nkIdentDefs, v.info)
|
||||
addSon(vpart, v)
|
||||
addSon(vpart, ast.emptyNode)
|
||||
addSon(vpart, ast.emptyNode)
|
||||
addSon(father, vpart)
|
||||
|
||||
proc generateClosureCreation(o: POuterContext, scope: PNode): PNode =
|
||||
# add assignment if its a parameter that has been captured:
|
||||
var env = newSym(skParam, getIdent(envName), o.fn)
|
||||
env.info = scope.info
|
||||
env.typ = newType(tyTuple, o.fn)
|
||||
env.typ.n = newNodeI(nkRecList, scope.info)
|
||||
|
||||
result = newNodeI(nkStmtList, env.info)
|
||||
var v = newNodeI(nkVarSection, env.info)
|
||||
addVar(v, newSymNode(env))
|
||||
result.add(v.ptransNode)
|
||||
result.add(v)
|
||||
# add 'new' statement:
|
||||
result.add(newCall(getSysSym"internalNew", env).ptransnode)
|
||||
result.add(newCall(getSysSym"internalNew", env))
|
||||
|
||||
# add assignment statements:
|
||||
for v in varList:
|
||||
let fieldAccess = indirectAccess(env, v, env.info)
|
||||
if v.kind == skParam:
|
||||
# add ``env.param = param``
|
||||
result.add(newAsgnStmt(c, fieldAccess, newSymNode(v).ptransNode))
|
||||
IdNodeTablePut(c.transCon.mapping, v, fieldAccess)
|
||||
|
||||
proc transformProcFin(c: PTransf, n: PNode, s: PSym): PTransNode =
|
||||
if n.kind in nkLambdaKinds:
|
||||
# for lambdas we transformed 'n.sons[bodyPos]', but not 'ast.n[bodyPos]'!
|
||||
s.ast.sons[bodyPos] = n.sons[bodyPos]
|
||||
else:
|
||||
assert s.ast == n
|
||||
|
||||
if n.kind == nkMethodDef: methodDef(s, false)
|
||||
# should 's' be replaced by a tuple ('s', env)?
|
||||
var tc = c.transCon
|
||||
var repl: PNode = nil
|
||||
while tc != nil:
|
||||
repl = IdNodeTableGet(tc.mapping, s)
|
||||
if repl != nil: break
|
||||
tc = tc.next
|
||||
if repl != nil:
|
||||
result = PTransNode(repl)
|
||||
else:
|
||||
result = PTransNode(n)
|
||||
for v, scope2 in pairs(o.localsToEnclosingScope):
|
||||
if scope2 == scope:
|
||||
let local = PSym(v)
|
||||
addField(env.typ, local)
|
||||
let fieldAccess = indirectAccess(env, local, env.info)
|
||||
if sfByCopy in local.flags or local.kind == skParam:
|
||||
# add ``env.param = param``
|
||||
result.add(newAsgnStmt(fieldAccess, newSymNode(local)))
|
||||
IdNodeTablePut(o.localsToAccess, local, fieldAccess)
|
||||
# XXX add support for 'up' references!
|
||||
|
||||
proc transformProc(c: PTransf, n: PNode): PTransNode =
|
||||
# don't process generics:
|
||||
if n.sons[genericParamsPos].kind != nkEmpty:
|
||||
return PTransNode(n)
|
||||
proc transformOuterProc(o: POuterContext, n: PNode): PNode =
|
||||
case n.kind
|
||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
|
||||
of nkSym:
|
||||
var local = n.sym
|
||||
var envBlock = IdNodeTableGet(o.lambdasToEnclosingScope, local)
|
||||
if envBlock != nil:
|
||||
# we need to replace the lambda with '(lambda, env)':
|
||||
let a = envBlock.sons[0]
|
||||
assert a.kind == nkStmtList
|
||||
assert a.sons[0].kind == nkVarSection
|
||||
assert a.sons[0].sons[0].kind == nkIdentDefs
|
||||
var env = a.sons[0].sons[0].sons[0].sym
|
||||
return makeClosure(local, env, n.info)
|
||||
|
||||
var s = n.sons[namePos].sym
|
||||
var body = s.getBody
|
||||
if body.kind == nkEmpty or n.sons[bodyPos].kind == nkEmpty or
|
||||
containsOrIncl(c.transformedInnerProcs, s.id):
|
||||
return PTransNode(n)
|
||||
if not o.capturedVars.contains(local.id): return
|
||||
var scope = IdNodeTableGet(o.localsToEnclosingScope, local)
|
||||
if scope == nil: return
|
||||
|
||||
checkInvariant(n, s)
|
||||
|
||||
if not containsNode(body, procDefs) and s.typ.callConv != ccClosure:
|
||||
assert scope.kind == nkStmtList
|
||||
if scope.sons[0].kind == nkEmpty:
|
||||
# change the empty node to contain the closure construction; we need to
|
||||
# gather all variables here that belong to the closure which is a bit
|
||||
# expensive:
|
||||
scope.sons[0] = generateClosureCreation(o, scope)
|
||||
|
||||
# change 'local' to 'closure.local', unless it's a 'byCopy' variable:
|
||||
if sfByCopy notin local.flags:
|
||||
result = IdNodeTableGet(o.localsToAccess, local)
|
||||
assert result != nil
|
||||
# else it is captured by copy and this means that 'outer' should continue
|
||||
# to access the local as a local.
|
||||
of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef,
|
||||
nkIteratorDef, nkLambdaKinds:
|
||||
# don't recurse here:
|
||||
nil
|
||||
else:
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
let x = transformOuterProc(o, n.sons[i])
|
||||
if x != nil: n.sons[i] = x
|
||||
|
||||
proc liftLambdas(fn: PSym, shared: PLLShared, body: PNode): PNode =
|
||||
if body.kind == nkEmpty:
|
||||
# ignore forward declaration:
|
||||
result = body
|
||||
elif not containsNode(body, procDefs) and fn.typ.callConv != ccClosure:
|
||||
# fast path: no inner procs, so no closure needed:
|
||||
n.sons[bodyPos] = PNode(transform(c, body))
|
||||
checkInvariant(n, s)
|
||||
return transformProcFin(c, n, s)
|
||||
|
||||
# create environment:
|
||||
var cap: TCapture = @[]
|
||||
searchForInnerProcs(c, body, s, cap)
|
||||
|
||||
var envType = newType(tyRef, s)
|
||||
addSon(envType, captureToTuple(cap, s))
|
||||
if s.typ.callConv == ccClosure:
|
||||
addHiddenParam(s, newTemp(c, envType, n.info))
|
||||
IdNodeTablePut(c.transCon.mapping, s,
|
||||
makeClosure(c, s, nil, n.info))
|
||||
|
||||
if cap.len == 0:
|
||||
# fast path: no captured variables, so no closure needed:
|
||||
transformInnerProcs(c, body, s, nil)
|
||||
n.sons[bodyPos] = PNode(transform(c, body))
|
||||
return transformProcFin(c, n, s)
|
||||
|
||||
# Currently we always do a heap allocation. A simple escape analysis
|
||||
# could turn the closure into a stack allocation. Later versions might
|
||||
# implement that. This would require backend changes too though.
|
||||
var envSym = newTemp(c, envType, s.info)
|
||||
|
||||
var newBody = createEnvStmt(c, cap, envSym)
|
||||
# modify any local proc to gain a new parameter; this also creates the
|
||||
# mapping entries that turn (localProc) into (localProc, env):
|
||||
transformInnerProcs(c, body, s, envSym)
|
||||
|
||||
# now we can transform 'body' as all rewriting entries have been created:
|
||||
newBody.add(transform(c, body))
|
||||
n.sons[bodyPos] = newBody.pnode
|
||||
result = transformProcFin(c, n, s)
|
||||
checkInvariant(n, s)
|
||||
|
||||
proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode =
|
||||
## Converts 'prc' into '(thunk, nil)' so that it's compatible with
|
||||
## a closure.
|
||||
|
||||
# we cannot generate a proper thunk here for GC-safety reasons (see internal
|
||||
# documentation):
|
||||
result = newNodeIT(nkClosure, prc.info, dest)
|
||||
var conv = newNodeIT(nkHiddenStdConv, prc.info, dest)
|
||||
conv.add(emptyNode)
|
||||
conv.add(prc)
|
||||
result.add(conv)
|
||||
result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil)))
|
||||
result = body
|
||||
else:
|
||||
var o = newOuterContext(fn, shared)
|
||||
searchForInnerProcs(o, body)
|
||||
result = transformOuterProc(o, body)
|
||||
if result == nil: result = body
|
||||
|
||||
# XXX should 's' be replaced by a tuple ('s', env)?
|
||||
|
||||
proc liftLambdas*(n: PNode): PNode =
|
||||
assert n.kind in procDefs
|
||||
var s = n.sons[namePos].sym
|
||||
var shared: ref TLLShared
|
||||
new shared
|
||||
shared.upField = newSym(skField, upName.getIdent, s)
|
||||
result = liftLambdas(s, shared, s.getBody)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
|
||||
import
|
||||
intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os,
|
||||
idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread
|
||||
idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread,
|
||||
lambdalifting
|
||||
|
||||
const
|
||||
genPrefix* = ":tmp" # prefix for generated names
|
||||
|
|
@ -193,7 +194,7 @@ proc transformSym(c: PTransf, n: PNode): PTransNode =
|
|||
|
||||
proc transformVarSection(c: PTransf, v: PNode): PTransNode =
|
||||
result = newTransNode(v)
|
||||
for i in countup(0, sonsLen(v)-1):
|
||||
for i in countup(0, sonsLen(v)-1):
|
||||
var it = v.sons[i]
|
||||
if it.kind == nkCommentStmt:
|
||||
result[i] = PTransNode(it)
|
||||
|
|
@ -349,13 +350,6 @@ proc transformYield(c: PTransf, n: PNode): PTransNode =
|
|||
# we need to introduce new local variables:
|
||||
add(result, introduceNewLocalVars(c, c.transCon.forLoopBody.pnode))
|
||||
|
||||
proc addVar(father, v: PNode) =
|
||||
var vpart = newNodeI(nkIdentDefs, v.info)
|
||||
addSon(vpart, v)
|
||||
addSon(vpart, ast.emptyNode)
|
||||
addSon(vpart, ast.emptyNode)
|
||||
addSon(father, vpart)
|
||||
|
||||
proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
|
||||
result = transformSons(c, n)
|
||||
var n = result.pnode
|
||||
|
|
@ -377,7 +371,18 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
|
|||
# addr ( deref ( x )) --> x
|
||||
result = PTransNode(n.sons[0].sons[0])
|
||||
|
||||
include lambdalifting
|
||||
proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode =
|
||||
## Converts 'prc' into '(thunk, nil)' so that it's compatible with
|
||||
## a closure.
|
||||
|
||||
# we cannot generate a proper thunk here for GC-safety reasons (see internal
|
||||
# documentation):
|
||||
result = newNodeIT(nkClosure, prc.info, dest)
|
||||
var conv = newNodeIT(nkHiddenStdConv, prc.info, dest)
|
||||
conv.add(emptyNode)
|
||||
conv.add(prc)
|
||||
result.add(conv)
|
||||
result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil)))
|
||||
|
||||
proc transformConv(c: PTransf, n: PNode): PTransNode =
|
||||
# numeric types need range checks:
|
||||
|
|
@ -638,7 +643,14 @@ proc transform(c: PTransf, n: PNode): PTransNode =
|
|||
# nothing to be done for leaves:
|
||||
result = PTransNode(n)
|
||||
of nkBracketExpr: result = transformArrayAccess(c, n)
|
||||
of procDefs, nkMacroDef:
|
||||
of procDefs:
|
||||
if n.sons[genericParamsPos].kind == nkEmpty:
|
||||
var s = n.sons[namePos].sym
|
||||
n.sons[bodyPos] = PNode(transform(c, s.getBody))
|
||||
n.sons[bodyPos] = liftLambdas(n)
|
||||
if n.kind == nkMethodDef: methodDef(s, false)
|
||||
result = PTransNode(n)
|
||||
of nkMacroDef:
|
||||
# XXX no proper closure support yet:
|
||||
if n.sons[genericParamsPos].kind == nkEmpty:
|
||||
var s = n.sons[namePos].sym
|
||||
|
|
@ -737,10 +749,9 @@ proc transfPass(): TPass =
|
|||
result.close = processTransf # we need to process generics too!
|
||||
|
||||
proc transform*(module: PSym, n: PNode): PNode =
|
||||
if nfTransf in n.flags:
|
||||
if nfTransf in n.flags:
|
||||
result = n
|
||||
else:
|
||||
var c = openTransf(module, "")
|
||||
result = processTransf(c, n)
|
||||
incl(result.flags, nfTransf)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,14 @@ myData.each do (x: int):
|
|||
|
||||
#OUT 2 4 6 8 10
|
||||
|
||||
type
|
||||
ITest = tuple[
|
||||
setter: proc(v: Int),
|
||||
getter: proc(): int]
|
||||
|
||||
|
||||
proc getInterf(): ITest =
|
||||
var shared: int
|
||||
|
||||
return (setter: proc (x) = shared = x,
|
||||
getter: proc (): int = return shared)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue