Files
gyehoek-hs/doc/closure-conversion.org
2026-08-20 01:05:16 -06:00

4.3 KiB

closure-conversion

the closure-conversion phase makes closed-over variables explicit by addition of the primitive make-closure, taking a code pointer (in the CPS language, bare lambda) and the environment.

nice testable properties of closure-converted code:

  • code pointers only appear in function position
  • no function has free variables

multiple env-ref calls could probably be replaced with a primitive that loads the entire environment at once, returning multiple variables.

scratchpad

example

  (letrec ((curried-add (λ (n)
                          (λ (m)
                            (+ n m)))))
    ((curried-add 3) 4))
scheme source
  (letrec ((curried-add
            (λ (n ktail0)
              (letrec ((curried-add-in
                        (λ (m ktail1)
                          (prim (+ n m)
                                (κ (x0) (continue ktail1 x0))))))
                (continue ktail0 curried-add-in)))))
    (letrec ((k0 (κ (adder) (adder 4 halt))))
      (curried-add 3 k0)))
cps
  (letrec ((curried-add
            (λ (n ktail0)
              (letrec ((curried-add-in-code
                        (λ (env m ktail1)
                          (prim (env-ref 0 env)
                                (κ (n)
                                   (prim (+ n m)
                                         (κ (x0) (continue ktail1 x0))))))))
                (prim (make-closure curried-add-in-code n)
                      (κ (curried-add-in)
                         (continue ktail0 curried-add-in)))))))
    (letrec ((k0 (κ (adder-closure)
                    (prim (closure-code adder-closure)
                          (κ (adder)
                             (adder adder-closure 4 halt))))))
      (curried-add 3 k0)))
closure-converted

wasm

(letrec ((make-adder
        (lambda (n)
          (lambda (x)
            (+ n x)))))
((make-adder 3) 2))
(define add-code
  (lambda (n env)
    (+ n (env-ref env 'x))))

(define make-adder-code
  (lambda (n)
    (make-closure add-code ('x n))))

(define make-adder (make-closure make-adder-code))

(apply-closure (apply-closure make-addder 3) 2)
(module
 (type $heap-object (sub (struct (field $hash (mut i32)))))
 (type $closure (sub $heap-object
                     (struct (field $hash (mut i32))
                             (field $code (ref $cont-type)))))
 (type $closure1 (sub $closure
                      (struct (field $hash (mut i32))
                              (field $code (ref $cont-type))
                              (field $env0 (ref eq)))))
 (global $arg0 (mut (ref null eq)) (ref.null eq))
 (global $arg1 (mut (ref null eq)) (ref.null eq))
 (global $arg2 (mut (ref null eq)) (ref.null eq))
 (global $arg3 (mut (ref null eq)) (ref.null eq))
 (global $arg4 (mut (ref null eq)) (ref.null eq))
 (global $arg5 (mut (ref null eq)) (ref.null eq))
 ;; ⋮
 ;; (global $argn (mut (ref null eq)) (ref.null eq))

 (global $current-closure (mut (ref null $closure)) (ref.null $closure))

 (func $add-code (param $nargs i32)
       (local $n (ref eq))
       (local $x (ref eq))
       (local.set $n (global.get $arg0))
       (local.set $x (struct.get $closure1
                                 (global.get $current-closure)
                                 $env0))
       (return (i32.add $n $x)))

 (func $make-adder-code (param $nargs i32)
       (local $n (ref eq))
       (local.set $n (global.get $arg0))
       (return (struct.new $closure1
                           0
                           $add-code)))

 (func $main
       (local.set $make-adder
                  (struct.new $closure
                              0
                              $make-adder-code))
       (global.set $current-closure $make-adder)
       (global.set $arg0 (i32.const 3))
       (local.set $f (call (struct.get $closure
                                       $make-adder
                                       $code)
                           1))
       (global.set $current-closure $f)
       (global.set $arg0 (i32.const 2))
       (return (call (struct.get $closure
                                 $f
                                 $code)
                     1))))