closure implementation: first steps

This commit is contained in:
Araq
2012-02-04 15:47:48 +01:00
parent 3af91064e5
commit 2633e3fb27
10 changed files with 330 additions and 299 deletions

View File

@@ -220,7 +220,7 @@ type
sfDiscriminant, # field is a discriminant in a record/object
sfDeprecated, # symbol is deprecated
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
# variable is a thread variable
sfCompileTime, # proc can be evaluated at compile time
@@ -976,6 +976,13 @@ proc hasSonWith(n: PNode, kind: TNodeKind): bool =
return true
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 =
case n.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
else: nil
iterator items*(n: PNode): PNode =
for i in 0.. <n.len: yield n.sons[i]

201
compiler/lambdalifting.nim Normal file
View 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]))

View File

@@ -212,8 +212,8 @@ proc procTypeRel(mapping: var TIdTable, f, a: PType): TTypeRelation =
case a.kind
of tyNil: result = isSubtype
of tyProc:
if sonsLen(f) != sonsLen(a) or f.callconv != a.callconv: return
of tyProc:
if sonsLen(f) != sonsLen(a): return
# Note: We have to do unification for the parameters before the
# return type!
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} == {}:
# noSideEffect implies ``tfThread``! XXX really?
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
proc typeRel(mapping: var TIdTable, f, a: PType): TTypeRelation =

View File

@@ -45,7 +45,8 @@ type
transCon: PTransCon # top of a TransCon stack
inlining: int # > 0 if we are in inlining context (copy vars)
blocksyms: seq[PSym]
procToEnv: TIdTable # mapping from a proc to its generated explicit
# 'env' var (for closure generation)
PTransf = ref TTransfContext
proc newTransNode(a: PNode): PTransNode {.inline.} =
@@ -502,77 +503,14 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
popTransCon(c)
#echo "transformed: ", renderTree(n)
proc getMagicOp(call: PNode): TMagic =
if call.sons[0].kind == nkSym and
call.sons[0].sym.kind in {skProc, skMethod, skConverter}:
result = call.sons[0].sym.magic
else:
result = mNone
proc gatherVars(c: PTransf, n: PNode, marked: var TIntSet, 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 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)
include lambdalifting
proc transformCase(c: PTransf, n: PNode): PTransNode =
# 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:
# nothing to be done for leaves:
result = PTransNode(n)
of nkBracketExpr:
result = transformArrayAccess(c, n)
of nkLambda:
var s = n.sons[namePos].sym
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 nkBracketExpr: result = transformArrayAccess(c, n)
of procDefs: result = transformProc(c, n)
of nkForStmt: result = transformFor(c, n)
of nkCaseStmt: result = transformCase(c, n)
of nkContinueStmt:
result = PTransNode(newNode(nkBreakStmt))
var labl = c.blockSyms[c.blockSyms.high]
@@ -748,6 +673,7 @@ proc openTransf(module: PSym, filename: string): PPassContext =
new(n)
n.blocksyms = @[]
n.module = module
initIdTable(n.procToEnv)
result = n
proc openTransfCached(module: PSym, filename: string,

View File

@@ -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)

View File

@@ -218,7 +218,7 @@ Backend issues
However the biggest problem is that dead code elimination breaks modularity!
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.
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 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:
.. code-block:: nimrod
proc add(x: int): proc (y: int): int {.closure.} =
return lambda (y: int): int =
return proc (y: int): int =
return x + y
var add2 = add(2)
@@ -380,21 +396,21 @@ This should produce roughly this code:
.. code-block:: nimrod
type
PClosure = ref object
fn: proc (x: int, c: PClosure): int
PEnv = ref object
x: int # data
proc wasLambda(y: int, c: PClosure): int =
proc anon(y: int, c: PClosure): int =
return y + c.x
proc add(x: int): PClosure =
var c: PClosure
new(c)
c.x = x
c.fn = wasLambda
proc add(x: int): tuple[prc, data] =
var env: PEnv
new env
env.x = x
result = (anon, env)
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:
@@ -412,36 +428,46 @@ This should produce roughly this code:
.. code-block:: nimrod
type
PClosure1 = ref object
fn: proc (x: int, c: PClosure1): int
PEnvX = ref object
x: int # data
PClosure2 = ref object
fn: proc (x: int, c: PClosure2): int
PEnvY = ref object
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 =
return c2.c1.x + c2.y + z
proc lambdaY(y: int, ex: PEnvX): tuple[prc, data: PEnvY] =
var ey: PEnvY
new ey
ey.y = y
ey.ex = ex
result = (lambdaZ, ey)
proc outerLambda1(y: int, c1: PClosure1): PClosure2 =
new(result)
result.c1 = c1
result.y = y
result.fn = innerLambda
proc add(x: int): PClosure1 =
new(result)
result.x = x
result.fn = outerLambda
proc add(x: int): tuple[prc, data: PEnvX] =
var ex: PEnvX
ex.x = x
result = (labmdaY, ex)
var tmp = add(2)
var tmp2 = tmp.fn(4, tmp)
var add24 = tmp2.fn(4, tmp2)
var tmp2 = tmp.fn(4, tmp.data)
var add24 = tmp2.fn(4, tmp2.data)
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
-----------
@@ -451,3 +477,18 @@ Accumulator
return lambda: int =
inc 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()

View File

@@ -34,6 +34,24 @@ proc reverse*[T](a: var openArray[T]) =
## reverses the array `a`.
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
onlySafeCode = false

View File

@@ -1,8 +1,9 @@
version 0.8.14
==============
- bug: tsortdev does not run with native GC?
- implement closures
- object {.pure, final.} does not work again!
- bug: tsortdev does not run with native GC?
- ``=`` should be overloadable; requires specialization for ``=``?
version 0.9.0
@@ -18,7 +19,7 @@ version 0.9.0
use tyInt+node for that
- implement the high level optimizer
- change overloading resolution
- implement closures; implement proper coroutines
- implement proper coroutines
- implement ``partial`` pragma for partial evaluation
- we need to support iteration of 2 different data structures in parallel
- 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
implicitly on the heap for write barrier efficiency; better:
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
- tlastmod returns wrong results on BSD (Linux, MacOS X: works)
- nested tuple unpacking; tuple unpacking in non-var-context

View File

@@ -3,15 +3,16 @@
Here you can download the latest version of the Nimrod Compiler.
Please choose your platform:
* source-based installation: `<download/nimrod_0.8.12.zip>`_
* installer for Windows XP/Vista (i386): `<download/nimrod_0.8.12.exe>`_
* source-based installation: `<download/nimrod_0.8.14.zip>`_
* installer for Windows XP/Vista (i386): `<download/nimrod_0.8.14.exe>`_
(includes GCC and everything else you need)
The source-based installation has been tested on these systems:
* Linux: i386, AMD64
* Linux: i386, AMD64, PowerPC
* Mac OS X: i386
Other UNIX-based operating systems may work. An older version was tested on
FreeBSD (i386).
.. include:: ../install.txt

View File

@@ -19,6 +19,7 @@ Bugfixes
- Some more bugfixes for macros and compile-time evaluation.
- The GC now takes into account interior pointers on the stack which may be
introduced by aggressive C optimizers.
- Nimrod's native allocator/GC now works on PowerPC.
- Lots of other bugfixes: Too many to list them all.