diff options
Diffstat (limited to 'doc/intern.txt')
-rwxr-xr-x | doc/intern.txt | 129 |
1 files changed, 99 insertions, 30 deletions
diff --git a/doc/intern.txt b/doc/intern.txt index 86afbb7ba..748b648fb 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,39 @@ 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, env]``. ``env`` 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. + +Proper thunk generation is harder because the proc that is to wrap +could stem from a complex expression: + +.. code-block:: nimrod + receivesClosure(returnsDefaultCC[i]) + +A thunk would need to call 'returnsDefaultCC[i]' somehow and that would require +an *additional* closure generation... Ok, not really, but it requires to pass +the function to call. So we'd end up with 2 indirect calls instead of one. +Another much more severe problem which this solution is that it's not GC-safe +to pass a proc pointer around via a generic ``ref`` type. + + 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 +408,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 +440,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 outerLambda1(y: int, c1: PClosure1): PClosure2 = - new(result) - result.c1 = c1 - result.y = y - result.fn = innerLambda + 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 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 +489,34 @@ 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() + + +Internals +--------- + +Lambda lifting is implemented as part of the ``transf`` pass. The ``transf`` +pass generates code to setup the environment and to pass it around. However, +this pass does not change the types! So we have some kind of mismatch here; on +the one hand the proc expression becomes an explicit tuple, on the other hand +the tyProc(ccClosure) type is not changed. For C code generation it's also +important the hidden formal param is ``void*`` and not something more +specialized. However the more specialized env type needs to passed to the +backend somehow. We deal with this by modifying ``s.ast[paramPos]`` to contain +the formal hidden parameter, but not ``s.typ``! + + + + |