summary refs log tree commit diff stats
path: root/doc/intern.txt
diff options
context:
space:
mode:
Diffstat (limited to 'doc/intern.txt')
-rwxr-xr-xdoc/intern.txt129
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``!
+
+
+
+