Files
gyehoek-hs/doc/make-vm-stackier.org
T
2026-08-28 11:39:37 -06:00

1.9 KiB

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.

scratchpad

Scheme source

(* 2 (call/cc
      (λ (cc)
        (begin (cc 6)
               3))))

CPS

(λ (ktail0)
  (letrec ((with-cc
            (λ (cc ktail1)
              (letrec ((k0 (κ (_)
                              (continue ktail1 3))))
                (cc 6 k0)))))
    (prim (call/cc with-cc)
          (κ (x1)
             (prim (* 2 x1)
                   (κ (x2) (continue ktail0 x2)))))))

stack VM

;; (call n) expects `n' values on the stack as arguments. then the
;; procedure is expected at index `n', and the return continuation
;; should be at `n+1'.

(define $k0
  (pop! %_) ; [ _ ret ]
  (push! 3) ; [ ret ]
  (tail-call 1) ; [ 3 ret ]
  )

(define $with-cc
  (pop! %cc) ; [ cc ret ]
  (push! $k0) ; [ ret ]
  (push! %cc) ; [ $k0 ret ]
  (push! 6) ; [ cc $k0 ret ]
  ;; call a procedure with one argument.
  (call 1) ; [ 6 cc $k0 ret ]
  )

(define $main
  (pop! %ktail0) ; [ ret ]
  (prim %x1 (call/cc $with-cc)) ; []
  (prim %x2 (* 2 %x1)) ; []
  (push! %ktail0) ; []
  (push! %x2) ; [ ret ]
  (tail-call 1) ; [ %x2 ret ]
  )