closure implementation: first steps
This commit is contained in:
parent
3af91064e5
commit
2633e3fb27
10 changed files with 330 additions and 299 deletions
|
|
@ -220,7 +220,7 @@ type
|
||||||
sfDiscriminant, # field is a discriminant in a record/object
|
sfDiscriminant, # field is a discriminant in a record/object
|
||||||
sfDeprecated, # symbol is deprecated
|
sfDeprecated, # symbol is deprecated
|
||||||
sfError, # usage of symbol should trigger a compile-time error
|
sfError, # usage of symbol should trigger a compile-time error
|
||||||
sfInClosure, # variable is accessed by a closure
|
sfInnerProc, # proc is an inner proc
|
||||||
sfThread, # proc will run as a thread
|
sfThread, # proc will run as a thread
|
||||||
# variable is a thread variable
|
# variable is a thread variable
|
||||||
sfCompileTime, # proc can be evaluated at compile time
|
sfCompileTime, # proc can be evaluated at compile time
|
||||||
|
|
@ -976,6 +976,13 @@ proc hasSonWith(n: PNode, kind: TNodeKind): bool =
|
||||||
return true
|
return true
|
||||||
result = false
|
result = false
|
||||||
|
|
||||||
|
proc containsNode*(n: PNode, kinds: TNodeKinds): bool =
|
||||||
|
case n.kind
|
||||||
|
of nkEmpty..nkNilLit: result = n.kind in kinds
|
||||||
|
else:
|
||||||
|
for i in countup(0, sonsLen(n) - 1):
|
||||||
|
if containsNode(n.sons[i], kinds): return true
|
||||||
|
|
||||||
proc hasSubnodeWith(n: PNode, kind: TNodeKind): bool =
|
proc hasSubnodeWith(n: PNode, kind: TNodeKind): bool =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkEmpty..nkNilLit: result = n.kind == kind
|
of nkEmpty..nkNilLit: result = n.kind == kind
|
||||||
|
|
@ -1030,3 +1037,6 @@ proc isGenericRoutine*(s: PSym): bool =
|
||||||
result = s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty
|
result = s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty
|
||||||
else: nil
|
else: nil
|
||||||
|
|
||||||
|
iterator items*(n: PNode): PNode =
|
||||||
|
for i in 0.. <n.len: yield n.sons[i]
|
||||||
|
|
||||||
|
|
|
||||||
201
compiler/lambdalifting.nim
Normal file
201
compiler/lambdalifting.nim
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# The Nimrod Compiler
|
||||||
|
# (c) Copyright 2012 Andreas Rumpf
|
||||||
|
#
|
||||||
|
# See the file "copying.txt", included in this
|
||||||
|
# distribution, for details about the copyright.
|
||||||
|
#
|
||||||
|
|
||||||
|
# This include file implements lambda lifting for the transformator.
|
||||||
|
|
||||||
|
# - Things to consider: Does capturing of 'result' work? (unknown)
|
||||||
|
# - Do generic inner procs work? (should)
|
||||||
|
# - Does nesting of closures work? (not yet)
|
||||||
|
# - Test that iterators within closures work etc.
|
||||||
|
|
||||||
|
const
|
||||||
|
procDefs = {nkLambda, nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef,
|
||||||
|
nkConverterDef}
|
||||||
|
|
||||||
|
proc indirectAccess(a, b: PSym): PNode =
|
||||||
|
# returns a[].b as a node
|
||||||
|
var x = newSymNode(a)
|
||||||
|
var y = newSymNode(b)
|
||||||
|
var deref = newNodeI(nkHiddenDeref, x.info)
|
||||||
|
deref.typ = x.typ.sons[0]
|
||||||
|
addSon(deref, x)
|
||||||
|
result = newNodeI(nkDotExpr, x.info)
|
||||||
|
addSon(result, deref)
|
||||||
|
addSon(result, y)
|
||||||
|
result.typ = y.typ
|
||||||
|
|
||||||
|
proc Incl(container: PNode, s: PSym) =
|
||||||
|
for x in container:
|
||||||
|
if x.sym.id == s.id: return
|
||||||
|
container.add(newSymNode(s))
|
||||||
|
|
||||||
|
proc gatherVars(c: PTransf, n: PNode, owner: PSym, container: PNode) =
|
||||||
|
# gather used vars for closure generation
|
||||||
|
case n.kind
|
||||||
|
of nkSym:
|
||||||
|
var s = n.sym
|
||||||
|
var found = false
|
||||||
|
case s.kind
|
||||||
|
of skVar, skLet: found = sfGlobal notin s.flags
|
||||||
|
of skTemp, skForVar, skParam, skResult: found = true
|
||||||
|
else: nil
|
||||||
|
if found and owner.id != s.owner.id:
|
||||||
|
incl(container, s)
|
||||||
|
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
|
||||||
|
else:
|
||||||
|
for i in countup(0, sonsLen(n) - 1):
|
||||||
|
gatherVars(c, n.sons[i], owner, container)
|
||||||
|
|
||||||
|
proc replaceVars(c: PTransf, n: PNode, owner, env: PSym) =
|
||||||
|
for i in countup(0, safeLen(n) - 1):
|
||||||
|
if n.kind == nkSym:
|
||||||
|
let s = n.sym
|
||||||
|
var found = false
|
||||||
|
case s.kind
|
||||||
|
of skVar, skLet: found = sfGlobal notin s.flags
|
||||||
|
of skTemp, skForVar, skParam, skResult: found = true
|
||||||
|
else: nil
|
||||||
|
if found and owner.id != s.owner.id:
|
||||||
|
# access through the closure param:
|
||||||
|
n.sons[i] = indirectAccess(env, s)
|
||||||
|
else:
|
||||||
|
replaceVars(c, n.sons[i], owner, env)
|
||||||
|
|
||||||
|
proc addFormalParam(routine: PSym, param: PSym) =
|
||||||
|
addSon(routine.typ, param.typ)
|
||||||
|
addSon(routine.ast.sons[paramsPos], newSymNode(param))
|
||||||
|
|
||||||
|
proc isInnerProc(s, owner: PSym): bool {.inline.} =
|
||||||
|
result = s.kind in {skProc, skMacro, skIterator, skMethod, skConverter} and
|
||||||
|
s.owner.id == owner.id and not isGenericRoutine(s)
|
||||||
|
|
||||||
|
proc searchForInnerProcs(c: PTransf, n: PNode, owner: PSym, container: PNode) =
|
||||||
|
case n.kind
|
||||||
|
of nkSym:
|
||||||
|
let s = n.sym
|
||||||
|
if isInnerProc(s, owner):
|
||||||
|
gatherVars(c, s.getBody, owner, container)
|
||||||
|
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
|
||||||
|
else:
|
||||||
|
for i in 0.. <len(n):
|
||||||
|
searchForInnerProcs(c, n.sons[i], owner, container)
|
||||||
|
|
||||||
|
proc makeClosure(c: PTransf, prc, env: PSym): PNode =
|
||||||
|
var tup = newType(tyTuple, c.module)
|
||||||
|
tup.addson(prc.typ)
|
||||||
|
tup.addson(env.typ)
|
||||||
|
result = newNodeIT(nkPar, prc.info, tup)
|
||||||
|
result.add(newSymNode(prc))
|
||||||
|
result.add(newSymNode(env))
|
||||||
|
|
||||||
|
proc transformInnerProcs(c: PTransf, n: PNode, owner, env: PSym) =
|
||||||
|
case n.kind
|
||||||
|
of nkSym:
|
||||||
|
let innerProc = n.sym
|
||||||
|
if isInnerProc(innerProc, owner):
|
||||||
|
# inner proc could capture outer vars:
|
||||||
|
var param = newTemp(c, env.typ, n.info)
|
||||||
|
param.kind = skParam
|
||||||
|
addFormalParam(innerProc, param)
|
||||||
|
# 'anon' should be replaced by '(anon, env)':
|
||||||
|
IdNodeTablePut(c.transCon.mapping, innerProc,
|
||||||
|
makeClosure(c, innerProc, env))
|
||||||
|
|
||||||
|
# access all non-local vars through the 'env' param:
|
||||||
|
var body = innerProc.getBody
|
||||||
|
replaceVars(c, body, innerProc, param)
|
||||||
|
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
|
||||||
|
else:
|
||||||
|
for i in 0.. <len(n):
|
||||||
|
transformInnerProcs(c, n.sons[i], owner, env)
|
||||||
|
|
||||||
|
proc newCall(a, b: PSym): PNode =
|
||||||
|
result = newNodeI(nkCall, a.info)
|
||||||
|
result.add newSymNode(a)
|
||||||
|
result.add newSymNode(b)
|
||||||
|
|
||||||
|
proc createEnvStmt(c: PTransf, varList: PNode, 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)
|
||||||
|
var v = newNodeI(nkVarSection, env.info)
|
||||||
|
addVar(v, newSymNode(env))
|
||||||
|
result.add(v.ptransNode)
|
||||||
|
# add 'new' statement:
|
||||||
|
result.add(newCall(getSysSym"new", env).ptransnode)
|
||||||
|
|
||||||
|
# add assignment statements:
|
||||||
|
for v in varList:
|
||||||
|
assert v.kind == nkSym
|
||||||
|
let fieldAccess = indirectAccess(env, v.sym)
|
||||||
|
if v.sym.kind == skParam:
|
||||||
|
# add ``env.param = param``
|
||||||
|
result.add(newAsgnStmt(c, fieldAccess, v.ptransNode))
|
||||||
|
IdNodeTablePut(c.transCon.mapping, v.sym, fieldAccess)
|
||||||
|
|
||||||
|
proc transformProc(c: PTransf, n: PNode): PTransNode =
|
||||||
|
# don't process generics:
|
||||||
|
if n.sons[genericParamsPos].kind != nkEmpty:
|
||||||
|
return PTransNode(n)
|
||||||
|
|
||||||
|
var s = n.sons[namePos].sym
|
||||||
|
var body = s.getBody
|
||||||
|
if not containsNode(body, procDefs):
|
||||||
|
# fast path: no inner procs, so no closure needed:
|
||||||
|
n.sons[bodyPos] = PNode(transform(c, body))
|
||||||
|
if n.kind == nkMethodDef: methodDef(s, false)
|
||||||
|
return PTransNode(n)
|
||||||
|
|
||||||
|
var closure = newNodeI(nkRecList, n.info)
|
||||||
|
searchForInnerProcs(c, body, s, closure)
|
||||||
|
|
||||||
|
if closure.len == 0:
|
||||||
|
# fast path: no captured variables, so no closure needed:
|
||||||
|
n.sons[bodyPos] = PNode(transform(c, body))
|
||||||
|
if n.kind == nkMethodDef: methodDef(s, false)
|
||||||
|
return PTransNode(n)
|
||||||
|
|
||||||
|
# create environment:
|
||||||
|
var envDesc = newType(tyObject, s)
|
||||||
|
envDesc.n = closure
|
||||||
|
addSon(envDesc, nil) # no super class
|
||||||
|
var envType = newType(tyRef, s)
|
||||||
|
addSon(envType, envDesc)
|
||||||
|
|
||||||
|
# XXX currently we always do a heap allocation. A simple escape analysis
|
||||||
|
# could turn the closure into a stack allocation. Later versions will
|
||||||
|
# implement that.
|
||||||
|
var envSym = newTemp(c, envType, s.info)
|
||||||
|
|
||||||
|
var newBody = createEnvStmt(c, closure, 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.
|
||||||
|
# Careful this transforms the inner procs too!
|
||||||
|
newBody.add(transform(c, body))
|
||||||
|
n.sons[bodyPos] = newBody.pnode
|
||||||
|
if n.kind == nkMethodDef: methodDef(s, false)
|
||||||
|
result = newBody
|
||||||
|
|
||||||
|
proc generateThunk(c: PTransf, prc: PNode, closure: PType): PNode =
|
||||||
|
## Converts 'prc' into '(thunk, nil)' so that it's compatible with
|
||||||
|
## a closure.
|
||||||
|
|
||||||
|
# XXX we hack around here by generating a 'cast' instead of a proper thunk.
|
||||||
|
result = newNodeIT(nkPar, prc.info, closure)
|
||||||
|
var conv = newNodeIT(nkHiddenStdConv, prc.info, closure.sons[0])
|
||||||
|
conv.add(emptyNode)
|
||||||
|
conv.add(prc)
|
||||||
|
result.add(conv)
|
||||||
|
result.add(newNodeIT(nkNilLit, prc.info, closure.sons[1]))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -213,7 +213,7 @@ proc procTypeRel(mapping: var TIdTable, f, a: PType): TTypeRelation =
|
||||||
case a.kind
|
case a.kind
|
||||||
of tyNil: result = isSubtype
|
of tyNil: result = isSubtype
|
||||||
of tyProc:
|
of tyProc:
|
||||||
if sonsLen(f) != sonsLen(a) or f.callconv != a.callconv: return
|
if sonsLen(f) != sonsLen(a): return
|
||||||
# Note: We have to do unification for the parameters before the
|
# Note: We have to do unification for the parameters before the
|
||||||
# return type!
|
# return type!
|
||||||
result = isEqual # start with maximum; also correct for no
|
result = isEqual # start with maximum; also correct for no
|
||||||
|
|
@ -240,6 +240,12 @@ proc procTypeRel(mapping: var TIdTable, f, a: PType): TTypeRelation =
|
||||||
elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}:
|
elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {}:
|
||||||
# noSideEffect implies ``tfThread``! XXX really?
|
# noSideEffect implies ``tfThread``! XXX really?
|
||||||
result = isNone
|
result = isNone
|
||||||
|
elif f.callconv != a.callconv:
|
||||||
|
# valid to pass a 'nimcall' thingie to 'closure':
|
||||||
|
if f.callconv == ccClosure and a.callconv == ccDefault:
|
||||||
|
result = isConvertible
|
||||||
|
else:
|
||||||
|
result = isNone
|
||||||
else: nil
|
else: nil
|
||||||
|
|
||||||
proc typeRel(mapping: var TIdTable, f, a: PType): TTypeRelation =
|
proc typeRel(mapping: var TIdTable, f, a: PType): TTypeRelation =
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ type
|
||||||
transCon: PTransCon # top of a TransCon stack
|
transCon: PTransCon # top of a TransCon stack
|
||||||
inlining: int # > 0 if we are in inlining context (copy vars)
|
inlining: int # > 0 if we are in inlining context (copy vars)
|
||||||
blocksyms: seq[PSym]
|
blocksyms: seq[PSym]
|
||||||
|
procToEnv: TIdTable # mapping from a proc to its generated explicit
|
||||||
|
# 'env' var (for closure generation)
|
||||||
PTransf = ref TTransfContext
|
PTransf = ref TTransfContext
|
||||||
|
|
||||||
proc newTransNode(a: PNode): PTransNode {.inline.} =
|
proc newTransNode(a: PNode): PTransNode {.inline.} =
|
||||||
|
|
@ -502,7 +503,6 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
|
||||||
popTransCon(c)
|
popTransCon(c)
|
||||||
#echo "transformed: ", renderTree(n)
|
#echo "transformed: ", renderTree(n)
|
||||||
|
|
||||||
|
|
||||||
proc getMagicOp(call: PNode): TMagic =
|
proc getMagicOp(call: PNode): TMagic =
|
||||||
if call.sons[0].kind == nkSym and
|
if call.sons[0].kind == nkSym and
|
||||||
call.sons[0].sym.kind in {skProc, skMethod, skConverter}:
|
call.sons[0].sym.kind in {skProc, skMethod, skConverter}:
|
||||||
|
|
@ -510,69 +510,7 @@ proc getMagicOp(call: PNode): TMagic =
|
||||||
else:
|
else:
|
||||||
result = mNone
|
result = mNone
|
||||||
|
|
||||||
proc gatherVars(c: PTransf, n: PNode, marked: var TIntSet, owner: PSym,
|
include lambdalifting
|
||||||
container: PNode) =
|
|
||||||
# gather used vars for closure generation
|
|
||||||
case n.kind
|
|
||||||
of nkSym:
|
|
||||||
var s = n.sym
|
|
||||||
var found = false
|
|
||||||
case s.kind
|
|
||||||
of skVar, skLet: found = sfGlobal notin s.flags
|
|
||||||
of skTemp, skForVar, skParam, skResult: found = true
|
|
||||||
else: nil
|
|
||||||
if found and owner.id != s.owner.id and not ContainsOrIncl(marked, s.id):
|
|
||||||
incl(s.flags, sfInClosure)
|
|
||||||
addSon(container, copyNode(n)) # DON'T make a copy of the symbol!
|
|
||||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
|
|
||||||
nil
|
|
||||||
else:
|
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
|
||||||
gatherVars(c, n.sons[i], marked, owner, container)
|
|
||||||
|
|
||||||
proc addFormalParam(routine: PSym, param: PSym) =
|
|
||||||
addSon(routine.typ, param.typ)
|
|
||||||
addSon(routine.ast.sons[paramsPos], newSymNode(param))
|
|
||||||
|
|
||||||
proc indirectAccess(a, b: PSym): PNode =
|
|
||||||
# returns a[].b as a node
|
|
||||||
var x = newSymNode(a)
|
|
||||||
var y = newSymNode(b)
|
|
||||||
var deref = newNodeI(nkHiddenDeref, x.info)
|
|
||||||
deref.typ = x.typ.sons[0]
|
|
||||||
addSon(deref, x)
|
|
||||||
result = newNodeI(nkDotExpr, x.info)
|
|
||||||
addSon(result, deref)
|
|
||||||
addSon(result, y)
|
|
||||||
result.typ = y.typ
|
|
||||||
|
|
||||||
proc transformLambda(c: PTransf, n: PNode): PNode =
|
|
||||||
var marked = initIntSet()
|
|
||||||
result = n
|
|
||||||
if n.sons[namePos].kind != nkSym: InternalError(n.info, "transformLambda")
|
|
||||||
var s = n.sons[namePos].sym
|
|
||||||
var closure = newNodeI(nkRecList, n.info)
|
|
||||||
var body = s.getBody
|
|
||||||
gatherVars(c, body, marked, s, closure)
|
|
||||||
# add closure type to the param list (even if closure is empty!):
|
|
||||||
var cl = newType(tyObject, s)
|
|
||||||
cl.n = closure
|
|
||||||
addSon(cl, nil) # no super class
|
|
||||||
var p = newType(tyRef, s)
|
|
||||||
addSon(p, cl)
|
|
||||||
var param = newSym(skParam, getIdent(genPrefix & "Cl"), s)
|
|
||||||
param.typ = p
|
|
||||||
addFormalParam(s, param)
|
|
||||||
# all variables that are accessed should be accessed by the new closure
|
|
||||||
# parameter:
|
|
||||||
if sonsLen(closure) > 0:
|
|
||||||
var newC = newTransCon(c.transCon.owner)
|
|
||||||
for i in countup(0, sonsLen(closure) - 1):
|
|
||||||
IdNodeTablePut(newC.mapping, closure.sons[i].sym,
|
|
||||||
indirectAccess(param, closure.sons[i].sym))
|
|
||||||
pushTransCon(c, newC)
|
|
||||||
n.sons[bodyPos] = transform(c, body).pnode
|
|
||||||
popTransCon(c)
|
|
||||||
|
|
||||||
proc transformCase(c: PTransf, n: PNode): PTransNode =
|
proc transformCase(c: PTransf, n: PNode): PTransNode =
|
||||||
# removes `elif` branches of a case stmt
|
# removes `elif` branches of a case stmt
|
||||||
|
|
@ -670,23 +608,10 @@ proc transform(c: PTransf, n: PNode): PTransNode =
|
||||||
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
|
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
|
||||||
# nothing to be done for leaves:
|
# nothing to be done for leaves:
|
||||||
result = PTransNode(n)
|
result = PTransNode(n)
|
||||||
of nkBracketExpr:
|
of nkBracketExpr: result = transformArrayAccess(c, n)
|
||||||
result = transformArrayAccess(c, n)
|
of procDefs: result = transformProc(c, n)
|
||||||
of nkLambda:
|
of nkForStmt: result = transformFor(c, n)
|
||||||
var s = n.sons[namePos].sym
|
of nkCaseStmt: result = transformCase(c, n)
|
||||||
n.sons[bodyPos] = PNode(transform(c, s.getBody))
|
|
||||||
result = PTransNode(n)
|
|
||||||
when false: result = transformLambda(c, n)
|
|
||||||
of nkForStmt:
|
|
||||||
result = transformFor(c, n)
|
|
||||||
of nkCaseStmt:
|
|
||||||
result = transformCase(c, n)
|
|
||||||
of nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkConverterDef:
|
|
||||||
if n.sons[genericParamsPos].kind == nkEmpty:
|
|
||||||
var s = n.sons[namePos].sym
|
|
||||||
n.sons[bodyPos] = PNode(transform(c, s.getBody))
|
|
||||||
if n.kind == nkMethodDef: methodDef(s, false)
|
|
||||||
result = PTransNode(n)
|
|
||||||
of nkContinueStmt:
|
of nkContinueStmt:
|
||||||
result = PTransNode(newNode(nkBreakStmt))
|
result = PTransNode(newNode(nkBreakStmt))
|
||||||
var labl = c.blockSyms[c.blockSyms.high]
|
var labl = c.blockSyms[c.blockSyms.high]
|
||||||
|
|
@ -748,6 +673,7 @@ proc openTransf(module: PSym, filename: string): PPassContext =
|
||||||
new(n)
|
new(n)
|
||||||
n.blocksyms = @[]
|
n.blocksyms = @[]
|
||||||
n.module = module
|
n.module = module
|
||||||
|
initIdTable(n.procToEnv)
|
||||||
result = n
|
result = n
|
||||||
|
|
||||||
proc openTransfCached(module: PSym, filename: string,
|
proc openTransfCached(module: PSym, filename: string,
|
||||||
|
|
|
||||||
179
doc/gramcurl.txt
179
doc/gramcurl.txt
|
|
@ -1,179 +0,0 @@
|
||||||
module ::= stmt*
|
|
||||||
|
|
||||||
comma ::= ',' [COMMENT] [IND]
|
|
||||||
operator ::= OP0 | OR | XOR | AND | OP3 | OP4 | OP5 | OP6 | OP7
|
|
||||||
| 'is' | 'isnot' | 'in' | 'notin'
|
|
||||||
| 'div' | 'mod' | 'shl' | 'shr' | 'not'
|
|
||||||
|
|
||||||
prefixOperator ::= OP0 | OP3 | OP4 | OP5 | OP6 | OP7 | 'not'
|
|
||||||
|
|
||||||
optInd ::= [COMMENT] [IND]
|
|
||||||
|
|
||||||
|
|
||||||
lowestExpr ::= orExpr (OP0 optInd orExpr)*
|
|
||||||
orExpr ::= andExpr (OR | 'xor' optInd andExpr)*
|
|
||||||
andExpr ::= cmpExpr ('and' optInd cmpExpr)*
|
|
||||||
cmpExpr ::= ampExpr (OP3 | 'is' | 'isnot' | 'in' | 'notin' optInd ampExpr)*
|
|
||||||
ampExpr ::= plusExpr (OP4 optInd plusExpr)*
|
|
||||||
plusExpr ::= mulExpr (OP5 optInd mulExpr)*
|
|
||||||
mulExpr ::= dollarExpr (OP6 | 'div' | 'mod' | 'shl' | 'shr' optInd dollarExpr)*
|
|
||||||
dollarExpr ::= primary (OP7 optInd primary)*
|
|
||||||
|
|
||||||
indexExpr ::= '..' [expr] | expr ['=' expr | '..' expr]
|
|
||||||
|
|
||||||
castExpr ::= 'cast' '[' optInd typeDesc [SAD] ']' '(' optInd expr [SAD] ')'
|
|
||||||
addrExpr ::= 'addr' '(' optInd expr ')'
|
|
||||||
symbol ::= '`' (KEYWORD | IDENT | operator | '(' ')'
|
|
||||||
| '[' ']' | '=' | literal)+ '`'
|
|
||||||
| IDENT
|
|
||||||
|
|
||||||
primaryPrefix ::= (prefixOperator | 'bind') optInd
|
|
||||||
primarySuffix ::= '.' optInd symbol
|
|
||||||
| '(' optInd namedExprList [SAD] ')'
|
|
||||||
| '[' optInd [indexExpr (comma indexExpr)* [comma]] [SAD] ']'
|
|
||||||
| '^'
|
|
||||||
| pragma
|
|
||||||
|
|
||||||
primary ::= primaryPrefix* (symbol | constructor | castExpr | addrExpr)
|
|
||||||
primarySuffix*
|
|
||||||
|
|
||||||
literal ::= INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT
|
|
||||||
| FLOAT_LIT | FLOAT32_LIT | FLOAT64_LIT
|
|
||||||
| STR_LIT | RSTR_LIT | TRIPLESTR_LIT
|
|
||||||
| CHAR_LIT
|
|
||||||
| NIL
|
|
||||||
|
|
||||||
constructor ::= literal
|
|
||||||
| '[' optInd colonExprList [SAD] ']'
|
|
||||||
| '{' optInd sliceExprList [SAD] '}'
|
|
||||||
| '(' optInd colonExprList [SAD] ')'
|
|
||||||
|
|
||||||
colonExpr ::= expr [':' expr]
|
|
||||||
colonExprList ::= [colonExpr (comma colonExpr)* [comma]]
|
|
||||||
|
|
||||||
namedExpr ::= expr ['=' expr]
|
|
||||||
namedExprList ::= [namedExpr (comma namedExpr)* [comma]]
|
|
||||||
|
|
||||||
sliceExpr ::= expr ['..' expr]
|
|
||||||
sliceExprList ::= [sliceExpr (comma sliceExpr)* [comma]]
|
|
||||||
|
|
||||||
exprOrType ::= lowestExpr
|
|
||||||
| 'if' '(' expr ')' expr ('elif' '(' expr ')' expr)* 'else' expr
|
|
||||||
| 'var' exprOrType
|
|
||||||
| 'ref' exprOrType
|
|
||||||
| 'ptr' exprOrType
|
|
||||||
| 'type' exprOrType
|
|
||||||
| 'tuple' tupleDesc
|
|
||||||
|
|
||||||
expr ::= exprOrType
|
|
||||||
| 'proc' paramList [pragma] ['=' stmt]
|
|
||||||
|
|
||||||
qualifiedIdent ::= symbol ['.' symbol]
|
|
||||||
|
|
||||||
typeDesc ::= exprOrType
|
|
||||||
| 'proc' paramList [pragma]
|
|
||||||
|
|
||||||
macroStmt ::= '{' [stmt] '}' ('of' [sliceExprList] stmt
|
|
||||||
|'elif' '(' expr ')' stmt
|
|
||||||
|'except' '(' exceptList ')' stmt )*
|
|
||||||
['else' stmt]
|
|
||||||
|
|
||||||
simpleStmt ::= returnStmt
|
|
||||||
| yieldStmt
|
|
||||||
| discardStmt
|
|
||||||
| raiseStmt
|
|
||||||
| breakStmt
|
|
||||||
| continueStmt
|
|
||||||
| pragma
|
|
||||||
| importStmt
|
|
||||||
| fromStmt
|
|
||||||
| includeStmt
|
|
||||||
| exprStmt
|
|
||||||
complexStmt ::= ifStmt | whileStmt | caseStmt | tryStmt | forStmt
|
|
||||||
| blockStmt | asmStmt
|
|
||||||
| procDecl | iteratorDecl | macroDecl | templateDecl | methodDecl
|
|
||||||
| constSection | typeSection | whenStmt | varSection
|
|
||||||
|
|
||||||
stmt ::= simpleStmt
|
|
||||||
| indPush (complexStmt | simpleStmt) (';' (complexStmt | simpleStmt))*
|
|
||||||
DED indPop
|
|
||||||
|
|
||||||
exprStmt ::= lowestExpr ['=' expr | [expr (comma expr)*] [macroStmt]]
|
|
||||||
returnStmt ::= 'return' [expr]
|
|
||||||
yieldStmt ::= 'yield' expr
|
|
||||||
discardStmt ::= 'discard' expr
|
|
||||||
raiseStmt ::= 'raise' [expr]
|
|
||||||
breakStmt ::= 'break' [symbol]
|
|
||||||
continueStmt ::= 'continue'
|
|
||||||
ifStmt ::= 'if' '(' expr ')' stmt ('elif' '(' expr ')' stmt)* ['else' stmt]
|
|
||||||
whenStmt ::= 'when' '(' expr ')' stmt ('elif' '(' expr ')' stmt)* ['else' stmt]
|
|
||||||
caseStmt ::= 'case' '(' expr ')' ('of' sliceExprList ':' stmt)*
|
|
||||||
('elif' '(' expr ')' stmt)*
|
|
||||||
['else' stmt]
|
|
||||||
whileStmt ::= 'while' '(' expr ')' stmt
|
|
||||||
forStmt ::= 'for' '(' symbol (comma symbol)* 'in' expr ['..' expr] ')' stmt
|
|
||||||
exceptList ::= [qualifiedIdent (comma qualifiedIdent)*]
|
|
||||||
|
|
||||||
tryStmt ::= 'try' stmt
|
|
||||||
('except' '(' exceptList ')' stmt)*
|
|
||||||
['finally' stmt]
|
|
||||||
asmStmt ::= 'asm' [pragma] (STR_LIT | RSTR_LIT | TRIPLESTR_LIT)
|
|
||||||
blockStmt ::= 'block' [symbol] stmt
|
|
||||||
filename ::= symbol | STR_LIT | RSTR_LIT | TRIPLESTR_LIT
|
|
||||||
importStmt ::= 'import' filename (comma filename)*
|
|
||||||
includeStmt ::= 'include' filename (comma filename)*
|
|
||||||
fromStmt ::= 'from' filename 'import' symbol (comma symbol)*
|
|
||||||
|
|
||||||
pragma ::= '{.' optInd (colonExpr [comma])* [SAD] ('.}' | '}')
|
|
||||||
|
|
||||||
param ::= symbol (comma symbol)* (':' typeDesc ['=' expr] | '=' expr)
|
|
||||||
paramList ::= ['(' [param (comma param)*] [SAD] ')'] [':' typeDesc]
|
|
||||||
|
|
||||||
genericParam ::= symbol [':' typeDesc] ['=' expr]
|
|
||||||
genericParams ::= '[' genericParam (comma genericParam)* [SAD] ']'
|
|
||||||
|
|
||||||
|
|
||||||
routineDecl := symbol ['*'] [genericParams] paramList [pragma] ['=' stmt]
|
|
||||||
procDecl ::= 'proc' routineDecl
|
|
||||||
macroDecl ::= 'macro' routineDecl
|
|
||||||
iteratorDecl ::= 'iterator' routineDecl
|
|
||||||
templateDecl ::= 'template' routineDecl
|
|
||||||
methodDecl ::= 'method' routineDecl
|
|
||||||
|
|
||||||
colonAndEquals ::= [':' typeDesc] '=' expr
|
|
||||||
|
|
||||||
constDecl ::= symbol ['*'] [pragma] colonAndEquals ';' [COMMENT]
|
|
||||||
constSection ::= 'const' [COMMENT] (constDecl | '{' constDecl+ '}')
|
|
||||||
|
|
||||||
typeDef ::= typeDesc | objectDef | enumDef | 'distinct' typeDesc
|
|
||||||
|
|
||||||
objectField ::= symbol ['*'] [pragma]
|
|
||||||
objectIdentPart ::= objectField (comma objectField)* ':' typeDesc
|
|
||||||
[COMMENT|IND COMMENT]
|
|
||||||
|
|
||||||
objectWhen ::= 'when' expr ':' [COMMENT] objectPart
|
|
||||||
('elif' expr ':' [COMMENT] objectPart)*
|
|
||||||
['else' ':' [COMMENT] objectPart]
|
|
||||||
objectCase ::= 'case' expr ':' typeDesc [COMMENT]
|
|
||||||
('of' sliceExprList ':' [COMMENT] objectPart)*
|
|
||||||
['else' ':' [COMMENT] objectPart]
|
|
||||||
|
|
||||||
objectPart ::= objectWhen | objectCase | objectIdentPart | 'nil'
|
|
||||||
| indPush objectPart (SAD objectPart)* DED indPop
|
|
||||||
tupleDesc ::= '[' optInd [param (comma param)*] [SAD] ']'
|
|
||||||
|
|
||||||
objectDef ::= 'object' [pragma] ['of' typeDesc] objectPart
|
|
||||||
enumField ::= symbol ['=' expr]
|
|
||||||
enumDef ::= 'enum' ['of' typeDesc] (enumField [comma] [COMMENT | IND COMMENT])+
|
|
||||||
|
|
||||||
typeDecl ::= COMMENT
|
|
||||||
| symbol ['*'] [genericParams] ['=' typeDef] [COMMENT | IND COMMENT]
|
|
||||||
|
|
||||||
typeSection ::= 'type' indPush typeDecl (SAD typeDecl)* DED indPop
|
|
||||||
|
|
||||||
colonOrEquals ::= ':' typeDesc ['=' expr] | '=' expr
|
|
||||||
varField ::= symbol ['*'] [pragma]
|
|
||||||
varPart ::= symbol (comma symbol)* colonOrEquals [COMMENT | IND COMMENT]
|
|
||||||
varSection ::= 'var' (varPart
|
|
||||||
| indPush (COMMENT|varPart)
|
|
||||||
(SAD (COMMENT|varPart))* DED indPop)
|
|
||||||
101
doc/intern.txt
101
doc/intern.txt
|
|
@ -218,7 +218,7 @@ Backend issues
|
||||||
|
|
||||||
However the biggest problem is that dead code elimination breaks modularity!
|
However the biggest problem is that dead code elimination breaks modularity!
|
||||||
To see why, consider this scenario: The module ``G`` (for example the huge
|
To see why, consider this scenario: The module ``G`` (for example the huge
|
||||||
Gtk2 module...) is compiled with dead code elimination turned on. So no
|
Gtk2 module...) is compiled with dead code elimination turned on. So none
|
||||||
of ``G``'s procs is generated at all.
|
of ``G``'s procs is generated at all.
|
||||||
|
|
||||||
Then module ``B`` is compiled that requires ``G.P1``. Ok, no problem,
|
Then module ``B`` is compiled that requires ``G.P1``. Ok, no problem,
|
||||||
|
|
@ -366,11 +366,27 @@ comparisons).
|
||||||
Code generation for closures
|
Code generation for closures
|
||||||
============================
|
============================
|
||||||
|
|
||||||
|
Code generation for closures is implemented by `lambda lifting`:idx:.
|
||||||
|
|
||||||
|
Design
|
||||||
|
------
|
||||||
|
|
||||||
|
A ``closure`` proc var can call ordinary procs of the default Nimrod calling
|
||||||
|
convention. But not the other way round! A closure is implemented as a
|
||||||
|
``tuple[prc, data]``. ``data`` can be nil implying a call without a closure.
|
||||||
|
This means that a call through a closure generates an ``if`` but the
|
||||||
|
interoperability is worth the cost of the ``if``. Thunk generation would be
|
||||||
|
possible too, but it's slightly more effort to implement.
|
||||||
|
|
||||||
|
Tests with GCC on Amd64 showed that it's really beneficical if the
|
||||||
|
'environment' pointer is passed as the last argument, not as the first argument.
|
||||||
|
|
||||||
|
|
||||||
Example code:
|
Example code:
|
||||||
|
|
||||||
.. code-block:: nimrod
|
.. code-block:: nimrod
|
||||||
proc add(x: int): proc (y: int): int {.closure.} =
|
proc add(x: int): proc (y: int): int {.closure.} =
|
||||||
return lambda (y: int): int =
|
return proc (y: int): int =
|
||||||
return x + y
|
return x + y
|
||||||
|
|
||||||
var add2 = add(2)
|
var add2 = add(2)
|
||||||
|
|
@ -380,21 +396,21 @@ This should produce roughly this code:
|
||||||
|
|
||||||
.. code-block:: nimrod
|
.. code-block:: nimrod
|
||||||
type
|
type
|
||||||
PClosure = ref object
|
PEnv = ref object
|
||||||
fn: proc (x: int, c: PClosure): int
|
|
||||||
x: int # data
|
x: int # data
|
||||||
|
|
||||||
proc wasLambda(y: int, c: PClosure): int =
|
proc anon(y: int, c: PClosure): int =
|
||||||
return y + c.x
|
return y + c.x
|
||||||
|
|
||||||
proc add(x: int): PClosure =
|
proc add(x: int): tuple[prc, data] =
|
||||||
var c: PClosure
|
var env: PEnv
|
||||||
new(c)
|
new env
|
||||||
c.x = x
|
env.x = x
|
||||||
c.fn = wasLambda
|
result = (anon, env)
|
||||||
|
|
||||||
var add2 = add(2)
|
var add2 = add(2)
|
||||||
echo add2.fn(5, add2)
|
let tmp = if add2.data == nil: add2.prc(5) else: add2.prc(5, add2.data)
|
||||||
|
echo tmp
|
||||||
|
|
||||||
|
|
||||||
Beware of nesting:
|
Beware of nesting:
|
||||||
|
|
@ -412,36 +428,46 @@ This should produce roughly this code:
|
||||||
|
|
||||||
.. code-block:: nimrod
|
.. code-block:: nimrod
|
||||||
type
|
type
|
||||||
PClosure1 = ref object
|
PEnvX = ref object
|
||||||
fn: proc (x: int, c: PClosure1): int
|
|
||||||
x: int # data
|
x: int # data
|
||||||
|
|
||||||
PClosure2 = ref object
|
PEnvY = ref object
|
||||||
fn: proc (x: int, c: PClosure2): int
|
|
||||||
y: int
|
y: int
|
||||||
c1: PClosure1
|
ex: PEnvX
|
||||||
|
|
||||||
|
proc lambdaZ(z: int, ey: PEnvY): int =
|
||||||
|
return ey.ex.x + ey.y + z
|
||||||
|
|
||||||
proc innerLambda(z: int, c2: PClosure2): int =
|
proc lambdaY(y: int, ex: PEnvX): tuple[prc, data: PEnvY] =
|
||||||
return c2.c1.x + c2.y + z
|
var ey: PEnvY
|
||||||
|
new ey
|
||||||
|
ey.y = y
|
||||||
|
ey.ex = ex
|
||||||
|
result = (lambdaZ, ey)
|
||||||
|
|
||||||
proc outerLambda1(y: int, c1: PClosure1): PClosure2 =
|
proc add(x: int): tuple[prc, data: PEnvX] =
|
||||||
new(result)
|
var ex: PEnvX
|
||||||
result.c1 = c1
|
ex.x = x
|
||||||
result.y = y
|
result = (labmdaY, ex)
|
||||||
result.fn = innerLambda
|
|
||||||
|
|
||||||
proc add(x: int): PClosure1 =
|
|
||||||
new(result)
|
|
||||||
result.x = x
|
|
||||||
result.fn = outerLambda
|
|
||||||
|
|
||||||
var tmp = add(2)
|
var tmp = add(2)
|
||||||
var tmp2 = tmp.fn(4, tmp)
|
var tmp2 = tmp.fn(4, tmp.data)
|
||||||
var add24 = tmp2.fn(4, tmp2)
|
var add24 = tmp2.fn(4, tmp2.data)
|
||||||
echo add24(5)
|
echo add24(5)
|
||||||
|
|
||||||
|
|
||||||
|
We could get rid of nesting environments by always inlining inner anon procs.
|
||||||
|
More useful is escape analysis and stack allocation of the environment,
|
||||||
|
however.
|
||||||
|
|
||||||
|
|
||||||
|
Alternative
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Process the closure of all inner procs in one pass and accumulate the
|
||||||
|
environments. This is however not always possible.
|
||||||
|
|
||||||
|
|
||||||
Accumulator
|
Accumulator
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
@ -451,3 +477,18 @@ Accumulator
|
||||||
return lambda: int =
|
return lambda: int =
|
||||||
inc i
|
inc i
|
||||||
return i
|
return i
|
||||||
|
|
||||||
|
proc p =
|
||||||
|
var delta = 7
|
||||||
|
proc accumulator(start: int): proc(): int =
|
||||||
|
var x = start-1
|
||||||
|
result = proc (): int =
|
||||||
|
x = x + delta
|
||||||
|
inc delta
|
||||||
|
return x
|
||||||
|
|
||||||
|
var a = accumulator(3)
|
||||||
|
var b = accumulator(4)
|
||||||
|
echo a() + b()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,24 @@ proc reverse*[T](a: var openArray[T]) =
|
||||||
## reverses the array `a`.
|
## reverses the array `a`.
|
||||||
reverse(a, 0, a.high)
|
reverse(a, 0, a.high)
|
||||||
|
|
||||||
|
proc binarySearch*[T](a: openarray[T], key: T): int =
|
||||||
|
## binary search for `key` in `a`. Returns -1 if not found.
|
||||||
|
var b = len(a)
|
||||||
|
while result < b:
|
||||||
|
var mid = (result + b) div 2
|
||||||
|
if a[mid] < key: result = mid + 1
|
||||||
|
else: b = mid
|
||||||
|
if result >= len(x) or a[result] != key: result = -1
|
||||||
|
|
||||||
|
proc smartBinarySearch*[T](a: openArray[T], key: T): int =
|
||||||
|
## ``a.len`` must be a power of 2 for this to work.
|
||||||
|
var step = a.len div 2
|
||||||
|
while step > 0:
|
||||||
|
if a[result or step] <= key:
|
||||||
|
result = result or step
|
||||||
|
step = step shr 1
|
||||||
|
if a[result] != key: result = -1
|
||||||
|
|
||||||
const
|
const
|
||||||
onlySafeCode = false
|
onlySafeCode = false
|
||||||
|
|
||||||
|
|
|
||||||
10
todo.txt
10
todo.txt
|
|
@ -1,8 +1,9 @@
|
||||||
version 0.8.14
|
version 0.8.14
|
||||||
==============
|
==============
|
||||||
|
|
||||||
- bug: tsortdev does not run with native GC?
|
- implement closures
|
||||||
- object {.pure, final.} does not work again!
|
- object {.pure, final.} does not work again!
|
||||||
|
- bug: tsortdev does not run with native GC?
|
||||||
- ``=`` should be overloadable; requires specialization for ``=``?
|
- ``=`` should be overloadable; requires specialization for ``=``?
|
||||||
|
|
||||||
version 0.9.0
|
version 0.9.0
|
||||||
|
|
@ -18,7 +19,7 @@ version 0.9.0
|
||||||
use tyInt+node for that
|
use tyInt+node for that
|
||||||
- implement the high level optimizer
|
- implement the high level optimizer
|
||||||
- change overloading resolution
|
- change overloading resolution
|
||||||
- implement closures; implement proper coroutines
|
- implement proper coroutines
|
||||||
- implement ``partial`` pragma for partial evaluation
|
- implement ``partial`` pragma for partial evaluation
|
||||||
- we need to support iteration of 2 different data structures in parallel
|
- we need to support iteration of 2 different data structures in parallel
|
||||||
- make exceptions compatible with C++ exceptions
|
- make exceptions compatible with C++ exceptions
|
||||||
|
|
@ -56,6 +57,11 @@ version 0.9.XX
|
||||||
- implicit ref/ptr->var conversion; the compiler may store an object
|
- implicit ref/ptr->var conversion; the compiler may store an object
|
||||||
implicitly on the heap for write barrier efficiency; better:
|
implicitly on the heap for write barrier efficiency; better:
|
||||||
proc specialization in the code gen
|
proc specialization in the code gen
|
||||||
|
- shared memory heap: ``shared ref`` etc. The only hard part in the GC is to
|
||||||
|
"stop the world". However, it may be worthwhile to generate explicit
|
||||||
|
(or implicit) syncGC() calls in loops. Automatic loop injection seems
|
||||||
|
troublesome, but maybe we can come up with a simple heuristic. (All procs
|
||||||
|
that `new` shared memory are syncGC() candidates...)
|
||||||
- EcmaScript needs a new and better code gen: simply adapt the C code gen to it
|
- EcmaScript needs a new and better code gen: simply adapt the C code gen to it
|
||||||
- tlastmod returns wrong results on BSD (Linux, MacOS X: works)
|
- tlastmod returns wrong results on BSD (Linux, MacOS X: works)
|
||||||
- nested tuple unpacking; tuple unpacking in non-var-context
|
- nested tuple unpacking; tuple unpacking in non-var-context
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@
|
||||||
|
|
||||||
Here you can download the latest version of the Nimrod Compiler.
|
Here you can download the latest version of the Nimrod Compiler.
|
||||||
Please choose your platform:
|
Please choose your platform:
|
||||||
* source-based installation: `<download/nimrod_0.8.12.zip>`_
|
* source-based installation: `<download/nimrod_0.8.14.zip>`_
|
||||||
* installer for Windows XP/Vista (i386): `<download/nimrod_0.8.12.exe>`_
|
* installer for Windows XP/Vista (i386): `<download/nimrod_0.8.14.exe>`_
|
||||||
(includes GCC and everything else you need)
|
(includes GCC and everything else you need)
|
||||||
|
|
||||||
The source-based installation has been tested on these systems:
|
The source-based installation has been tested on these systems:
|
||||||
* Linux: i386, AMD64
|
* Linux: i386, AMD64, PowerPC
|
||||||
* Mac OS X: i386
|
* Mac OS X: i386
|
||||||
|
|
||||||
Other UNIX-based operating systems may work. An older version was tested on
|
Other UNIX-based operating systems may work. An older version was tested on
|
||||||
FreeBSD (i386).
|
FreeBSD (i386).
|
||||||
|
|
||||||
.. include:: ../install.txt
|
.. include:: ../install.txt
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ Bugfixes
|
||||||
- Some more bugfixes for macros and compile-time evaluation.
|
- Some more bugfixes for macros and compile-time evaluation.
|
||||||
- The GC now takes into account interior pointers on the stack which may be
|
- The GC now takes into account interior pointers on the stack which may be
|
||||||
introduced by aggressive C optimizers.
|
introduced by aggressive C optimizers.
|
||||||
|
- Nimrod's native allocator/GC now works on PowerPC.
|
||||||
- Lots of other bugfixes: Too many to list them all.
|
- Lots of other bugfixes: Too many to list them all.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue