closure implementation: first steps

This commit is contained in:
Araq 2012-02-04 15:47:48 +01:00
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 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
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

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

View file

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

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! 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()

View file

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

View file

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

View file

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

View file

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