* rationale? previously, the VM's stack was used for storing local variables across blocks; a Scheme procedure was split into several low-level routines (one for the procedure itself and one for each continuation), and the stack was used as a communication channel for these separate routines. in contrast, registers were local to each routine. this aligns with Wasm's model of functions pretty well, with Wasm /locals/ acting as the VM's /registers/, and a global mutable stack serving as fallback. this worked quite well until it became time to implement ~call/cc~. we are considering making the following alterations to the VM: - explicitly segment the stack into frames. - passing procedures and return addresses on the stack. - new instructions: + ~(tail-call /n/)~ + ~(call /n/)~ + ~(load /r/ /n/)~ + ~(return /n/)~ * scratchpad #+begin_src scheme (letrec ((fac (λ (n) (if (zero? n) 1 (* n (fac (- n 1))))))) (fac 3)) #+end_src #+begin_src scheme (λ (ktail0) (letrec ((fac (λ (n ktail1) (zero? n (κ (x0) (if x0 (continue ktail1 1) (- n 1 (κ (x1) (fac x1 (κ (x2) (* n x2 ktail1))))))))))) (fac 3))) #+end_src #+begin_example n ktail1 | | | | x0 | | | | | ^ | | | | x1 | | | | | ^ | | | | x2 | | | ^ ^ ^ #+end_example #+begin_src scheme (define $fac-c0 (pop! %x0 0) ; [ x0 $fac-c0 n $fac ktail1 ] (if %x0 ; [ $fac-c0 n $fac ktail1 ] ;; every variable but `ktail1' is dead so we pop them all. ;; this probably means that `if' should take two continuations ;; rather than two blocks. (then (push! 1) ; [ $fac-c0 n $fac ktail1 ] (return 1)) ; [ 1 $fac-c0 n $fac ktail1 ] (else (load %n 1) ; [ $fac-c0 n $fac ktail1 ] (prim %x1 (- %n 1)) ; [ $fac-c0 n $fac ktail1 ] (push! $fac-c1) ; [ $fac-c0 n $fac ktail1 ] (push! $fac) ; [ $fac-c1 $fac-c0 n $fac ktail1 ] (push! %x1) ; [ $fac $fac-c1 $fac-c0 n $fac ktail1 ] (call 1) ; [ x1 $fac $fac-c1 $fac-c0 n $fac ktail1 ] ))) (define $fac-c1 (pop! %x2) ; [ x2 $fac-c1 $fac-c0 n $fac ktail1 ] (load %n 3) ; [ $fac-c1 $fac-c0 n $fac ktail1 ] (prim %x3 (* %n %x2)) (push! %x3) ; [ $fac-c1 $fac-c0 n $fac ktail1 ] (return 1) ; [ x3 $fac-c1 $fac-c0 n $fac ktail1 ] ) (define $fac (load %ktail1 2) ; [ n $fac ktail1 ] (load %n 0) ; [ n $fac ktail1 ] (push! $fac-c0) ; [ n $fac ktail1 ] (push! $zero?) ; [ $fac-c0 n $fac ktail1 ] (push! %n) ; [ $zero? $fac-c0 n $fac ktail1 ] (call 1) ; [ n $zero? $fac-c0 n $fac ktail1 ] ) (define $start (push! $fac) ; [ $start ktail0 ] (push! 3) ; [ $fac $start ktail0 ] (tail-call 1) ; [ 3 $fac $start ktail0 ] ;; ↑ `tail-call' knows how to dispose of the caller's stack frame. ) #+end_src