diff --git a/compiler/ast.nim b/compiler/ast.nim index 76260b586c..45b1ebfd08 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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.. 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, diff --git a/doc/gramcurl.txt b/doc/gramcurl.txt deleted file mode 100755 index 3ac9294c87..0000000000 --- a/doc/gramcurl.txt +++ /dev/null @@ -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) diff --git a/doc/intern.txt b/doc/intern.txt index 86afbb7ba5..550976d6f3 100755 --- a/doc/intern.txt +++ b/doc/intern.txt @@ -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() + + diff --git a/lib/pure/algorithm.nim b/lib/pure/algorithm.nim index 1e9a0bb4ba..045b78250c 100755 --- a/lib/pure/algorithm.nim +++ b/lib/pure/algorithm.nim @@ -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 diff --git a/todo.txt b/todo.txt index c666f7903e..6e238b8119 100755 --- a/todo.txt +++ b/todo.txt @@ -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 diff --git a/web/download.txt b/web/download.txt index bd231dd547..47c2501e92 100755 --- a/web/download.txt +++ b/web/download.txt @@ -3,15 +3,16 @@ Here you can download the latest version of the Nimrod Compiler. Please choose your platform: -* source-based installation: ``_ -* installer for Windows XP/Vista (i386): ``_ +* source-based installation: ``_ +* installer for Windows XP/Vista (i386): ``_ (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 + diff --git a/web/news.txt b/web/news.txt index db46587223..61f84b70f2 100755 --- a/web/news.txt +++ b/web/news.txt @@ -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.