b36cb61805d4

THE CONCATENING

This patch does something I've been dreading since I started: it concatenates
the registers, stack, and heap into one single big-ass array called the store.
This is how the original WAM was laid out (actually the original WAM has
EVERYTHING in one giant block of memory, but let's not get carried away here).

I was hoping I wouldn't have to do this, because the code reads a lot nicer when
these things are separate, but after reading ahead in the book I think I'm
pretty sure it had to be done.

The upside here is that now dereferencing things can be done without caring
where they live -- it's all just pointers into this giant array.  For example:
a register could refer to a stack cell, or a heap cell could point at a stack
cell.  The downside is that the stack is no longer adjustable (and things are
a bit less safe).
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sun, 08 May 2016 21:25:08 +0000
parents d26fa87611c0
children 4050f38d9715
branches/tags (none)
files Makefile examples/bench.lisp examples/ggp-paip.lisp examples/ggp.lisp package.lisp src/utils.lisp src/wam/cells.lisp src/wam/constants.lisp src/wam/dump.lisp src/wam/types.lisp src/wam/vm.lisp src/wam/wam.lisp

Changes

--- a/Makefile	Mon May 02 19:29:36 2016 +0000
+++ b/Makefile	Sun May 08 21:25:08 2016 +0000
@@ -25,6 +25,4 @@
 	hg -R ~/src/sjl.bitbucket.org push
 
 bench:
-	# jesus christ quicklisp
-	rm -rf ~/.cache/common-lisp
 	sbcl-rlwrap --noinform --load examples/bench.lisp  --eval '(quit)'
--- a/examples/bench.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/examples/bench.lisp	Sun May 08 21:25:08 2016 +0000
@@ -1,14 +1,37 @@
-(declaim (optimize (speed 3) (safety 1) (debug 0)))
-
 (ql:quickload 'bones)
 
 (load "examples/ggp-paip.lisp")
 (load "examples/ggp.lisp")
 
-(in-package :bones.paip)
-(format t "PAIP ------------------------------~%")
-(time (dfs-exhaust))
+(in-package :bones)
+
+(defun reload ()
+  (let ((*standard-output* (make-broadcast-stream))
+        (*debug-io* (make-broadcast-stream))
+        (*terminal-io* (make-broadcast-stream))
+        (*error-output* (make-broadcast-stream)))
+    (asdf:load-system 'bones :force t)))
+
+(defun run-test ()
+  (reload)
+
+  (format t "PAIP ------------------------------~%")
+  (time (bones.paip::dfs-exhaust))
 
-(in-package :bones.wam)
-(format t "WAM -------------------------------~%")
-(time (dfs-exhaust))
+  (format t "WAM -------------------------------~%")
+  (time (bones.wam::dfs-exhaust)))
+
+(format t "~%~%====================================~%")
+(format t "(speed 3) (safety 1) (debug 1)~%")
+(declaim (optimize (speed 3) (safety 1) (debug 1)))
+(run-test)
+
+(format t "~%~%====================================~%")
+(format t "(speed 3) (safety 1) (debug 0)~%")
+(declaim (optimize (speed 3) (safety 1) (debug 0)))
+(run-test)
+
+(format t "~%~%====================================~%")
+(format t "(speed 3) (safety 0) (debug 0)~%")
+(declaim (optimize (speed 3) (safety 0) (debug 0)))
+(run-test)
--- a/examples/ggp-paip.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/examples/ggp-paip.lisp	Sun May 08 21:25:08 2016 +0000
@@ -1,8 +1,5 @@
 (in-package #:bones.paip)
 
-(declaim (optimize (speed 1) (safety 3) (debug 1)))
-; (declaim (optimize (speed 3) (safety 1) (debug 0)))
-
 (clear-db)
 
 (rule (member ?thing (cons ?thing ?rest)))
--- a/examples/ggp.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/examples/ggp.lisp	Sun May 08 21:25:08 2016 +0000
@@ -1,8 +1,5 @@
 (in-package #:bones.wam)
 
-; (declaim (optimize (speed 1) (safety 3) (debug 1)))
-(declaim (optimize (speed 3) (safety 1) (debug 0)))
-
 (defparameter *d* (make-database))
 
 (with-database *d*
--- a/package.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/package.lisp	Sun May 08 21:25:08 2016 +0000
@@ -9,6 +9,7 @@
     #:bones.quickutils)
   (:export
     #:repeat
+    #:hex
     #:topological-sort
     #:push-if-new))
 
--- a/src/utils.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/src/utils.lisp	Sun May 08 21:25:08 2016 +0000
@@ -34,6 +34,9 @@
   `(dotimes (,(gensym) ,n)
      ,@body))
 
+(defun hex (d)
+  (format nil "~X" d))
+
 
 ;;;; Topological Sort
 ;;; Adapted from the AMOP book to add some flexibility (and remove the
--- a/src/wam/cells.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/src/wam/cells.lisp	Sun May 08 21:25:08 2016 +0000
@@ -65,12 +65,12 @@
           (eswitch ((cell-type cell))
             (+tag-null+ "")
             (+tag-structure+
-              (format nil " ~D" (cell-value cell)))
+              (format nil " ~X" (cell-value cell)))
             (+tag-functor+
-              (format nil " ~D"
+              (format nil " ~X"
                       (cell-functor-index cell)))
             (+tag-reference+
-              (format nil " ~D" (cell-value cell))))))
+              (format nil " ~X" (cell-value cell))))))
 
 
 (defun* cell-null-p ((cell heap-cell))
--- a/src/wam/constants.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/src/wam/constants.lisp	Sun May 08 21:25:08 2016 +0000
@@ -13,11 +13,6 @@
   :documentation "Bitmask for masking the cell type tags.")
 
 
-(define-constant +heap-limit+ (expt 2 +cell-value-width+)
-  ;; We can only address 2^value-bits cells.
-  :documentation "Maximum size of the WAM heap.")
-
-
 (define-constant +code-word-size+ 16
   :documentation "Size (in bits) of each word in the code store.")
 
@@ -44,37 +39,53 @@
 (define-constant +register-count+ 2048
   :documentation "The number of registers the WAM has available.")
 
+
 (define-constant +maximum-arity+ 1024
   :documentation "The maximum allowed arity of functors.")
 
-
 (define-constant +maximum-query-size+ 1024
   :documentation
   "The maximum size (in bytes of bytecode) a query may compile to.")
 
 
-(define-constant +stack-word-size+ 16
-  :documentation "Size (in bits) of each word in WAM stack.")
-
-(define-constant +stack-limit+ (expt 2 +stack-word-size+)
-  ;; We can only address 2^value-bits cells, and since stack address are
-  ;; themselves stored on the stack (the environment continuation pointer) they
-  ;; can only reference so much memory.
-  ;;
-  ;; todo: we might want to limit this further to prevent the stack from growing
-  ;; too large.
+(define-constant +stack-limit+ 2048
   :documentation "Maximum size of the WAM stack.")
 
 (define-constant +stack-frame-size-limit+ (+ 7 +register-count+)
   :documentation "The maximum size, in stack frame words, that a stack frame could be.")
 
 
-(define-constant +trail-limit+ (expt 2 +stack-word-size+)
+(define-constant +stack-start+ +register-count+
+  :documentation "The address in the store of the first cell of the stack.")
+
+(define-constant +stack-end+ (+ +stack-start+ +stack-limit+)
+  :documentation "The address in the store one past the last cell in the stack.")
+
+(define-constant +heap-start+ +stack-end+
+  :documentation "The address in the store of the first cell of the heap.")
+
+(define-constant +trail-limit+ (expt 2 +cell-width+)
   ;; The trail's fill pointer is stored inside choice frames on the stack, so it
-  ;; needs to be able to fit inside a stack word.
+  ;; needs to be able to fit inside a stack word.  We don't tag it, though, so
+  ;; we can technically use all of the cell bits if we want.
   :documentation "The maximum number of variables that may exist in the trail.")
 
 
+(define-constant +store-limit+ (expt 2 16)
+  :documentation "Maximum size of the WAM store.")
+
+(define-constant +heap-limit+ (- +store-limit+ +register-count+ +stack-limit+)
+  ;; The heap gets whatever's left over after the registers and stack have taken
+  ;; their chunk of memory.
+  :documentation "Maximum size of the WAM heap.")
+
+
+(define-constant +functor-limit+ (expt 2 +cell-value-width+)
+  ;; Functors are referred to by their index into the functor array.  This index
+  ;; is stored in the value part of functor cells.
+  :documentation "The maximum number of functors the WAM can keep track of.")
+
+
 ;;;; Opcodes
 ;;; Program
 (define-constant +opcode-noop+ 0)
--- a/src/wam/dump.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/src/wam/dump.lisp	Sun May 08 21:25:08 2016 +0000
@@ -1,10 +1,11 @@
 (in-package #:bones.wam)
 
 (defun registers-pointing-to (wam addr)
-  (loop :for reg :across (wam-local-registers wam)
-        :for i :from 0
-        :when (= reg addr)
-        :collect i))
+  (loop
+    :for r :from 0 :below +register-count+
+    :for reg = (wam-local-register wam r)
+    :when (= reg addr)
+    :collect r))
 
 (defun heap-debug (wam addr cell indent-p)
   (format
@@ -28,12 +29,12 @@
 
 (defun dump-heap (wam from to highlight)
   ;; This code is awful, sorry.
-  (let ((heap (wam-heap wam)))
+  (let ((store (wam-store wam)))
     (format t "HEAP~%")
     (format t "  +------+-----+----------+--------------------------------------+~%")
     (format t "  | ADDR | TYP |    VALUE | DEBUG                                |~%")
     (format t "  +------+-----+----------+--------------------------------------+~%")
-    (when (> from 0)
+    (when (> from +heap-start+)
       (format t "  |    ⋮ |  ⋮  |        ⋮ |                                      |~%"))
     (flet ((print-cell (i cell indent)
              (let ((hi (= i highlight)))
@@ -46,7 +47,7 @@
                        (if hi "<===" "|")))))
       (loop :for i :from from :below to
             :with indent = 0
-            :for cell = (aref heap i)
+            :for cell = (aref store i)
             :do
             (progn
               (print-cell i cell indent)
@@ -54,7 +55,7 @@
                 (setf indent (wam-functor-arity wam (cell-functor-index cell)))
                 (when (not (zerop indent))
                   (decf indent))))))
-    (when (< to (length heap))
+    (when (< to (wam-heap-pointer wam))
       (format t "  |    ⋮ |  ⋮  |        ⋮ |                                      |~%"))
     (format t "  +------+-----+----------+--------------------------------------+~%")
     (values)))
@@ -65,76 +66,69 @@
   (format t "  +------+----------+-------------------------------+~%")
   (format t "  | ADDR |    VALUE |                               |~%")
   (format t "  +------+----------+-------------------------------+~%")
-  (with-accessors ((stack wam-stack)
-                   (e wam-environment-pointer)
+  (with-accessors ((e wam-environment-pointer)
                    (b wam-backtrack-pointer))
       wam
-    (when (not (= 0 e b))
-      (loop :with nargs = nil
-            :with limit = (max (+ e 3) (+ b 7 2)) ; todo fix this limiting
-            :with arg = 0
-            :with currently-in = nil
-            :for addr :from 0 :below limit
-            :for cell = (aref (wam-stack wam) addr)
-            :for offset = 0 :then (1+ offset)
-            :do
-            (when (not (zerop addr))
-              (switch (addr :test #'=)
-                (e (setf currently-in :frame offset 0 arg 0))
-                (b (setf currently-in :choice offset 0 arg 0))))
-            (format t "  | ~4,'0X | ~8,'0X | ~30A|~A~A~%"
-                    addr
-                    cell
-                    (case currently-in ; jesus christ this needs to get fixed
-                      (:frame
-                       (cond
-                         ((= addr 0) "")
-                         ((= offset 0) "CE ===========================")
-                         ((= offset 1) "CP")
-                         ((= offset 2)
-                          (if (zerop cell)
-                            (progn
-                              (setf currently-in nil)
-                              "N: EMPTY")
-                            (progn
-                              (setf nargs cell)
-                              (format nil "N: ~D" cell))))
-                         ((< arg nargs)
-                          (prog1
-                              (format nil " Y~D: ~4,'0X"
-                                      arg
-                                      ;; look up the actual cell in the heap
-                                      (cell-aesthetic (wam-heap-cell wam cell)))
-                            (when (= nargs (incf arg))
-                              (setf currently-in nil))))))
-                      (:choice ; sweet lord make it stop
-                       (cond
-                         ((= addr 0) "")
-                         ((= offset 0)
-                          (if (zerop cell)
-                            (progn
-                              (setf currently-in nil)
-                              "N: EMPTY =================")
-                            (progn
-                              (setf nargs cell)
-                              (format nil "N: ~D =============" cell))))
-                         ((= offset 1) "CE saved env pointer")
-                         ((= offset 2) "CP saved cont pointer")
-                         ((= offset 3) "CB previous choice")
-                         ((= offset 4) "BP next clause")
-                         ((= offset 5) "TR saved trail pointer")
-                         ((= offset 6) "H  saved heap pointer")
-                         ((< arg nargs)
-                          (prog1
-                              (format nil " Y~D: ~4,'0X"
-                                      arg
-                                      ;; look up the actual cell in the heap
-                                      (cell-aesthetic (wam-heap-cell wam cell)))
-                            (when (= nargs (incf arg))
-                              (setf currently-in nil))))))
-                      (t ""))
-                    (if (= addr e) " <- E" "")
-                    (if (= addr b) " <- B" "")))))
+    (when (not (= +stack-start+ e b))
+      (loop
+        :with nargs = nil
+        :with arg = 0
+        :with currently-in = nil
+        :for addr :from (1+ +stack-start+) :below (wam-stack-top wam)
+        :for cell = (wam-stack-word wam addr)
+        :for offset = 0 :then (1+ offset)
+        :do
+        (when (not (zerop addr))
+          (switch (addr :test #'=)
+            (e (setf currently-in :frame offset 0 arg 0))
+            (b (setf currently-in :choice offset 0 arg 0))))
+        (format t "  | ~4,'0X | ~8,'0X | ~30A|~A~A~%"
+                addr
+                cell
+                (case currently-in ; jesus christ this needs to get fixed
+                  (:frame
+                   (cond
+                     ((= addr +stack-start+) "")
+                     ((= offset 0) "CE ===========================")
+                     ((= offset 1) "CP")
+                     ((= offset 2)
+                      (if (zerop cell)
+                        (progn
+                          (setf currently-in nil)
+                          "N: EMPTY")
+                        (progn
+                          (setf nargs cell)
+                          (format nil "N: ~D" cell))))
+                     ((< arg nargs)
+                      (prog1
+                          (format nil " Y~D: ~8,'0X" arg cell)
+                        (when (= nargs (incf arg))
+                          (setf currently-in nil))))))
+                  (:choice ; sweet lord make it stop
+                   (cond
+                     ((= addr +stack-start+) "")
+                     ((= offset 0)
+                      (if (zerop cell)
+                        (progn
+                          (setf currently-in nil)
+                          "N: EMPTY =================")
+                        (progn
+                          (setf nargs cell)
+                          (format nil "N: ~D =============" cell))))
+                     ((= offset 1) "CE saved env pointer")
+                     ((= offset 2) "CP saved cont pointer")
+                     ((= offset 3) "CB previous choice")
+                     ((= offset 4) "BP next clause")
+                     ((= offset 5) "TR saved trail pointer")
+                     ((= offset 6) "H  saved heap pointer")
+                     ((< arg nargs)
+                      (prog1
+                          (format nil " Y~D: ~8,'0X" arg cell)
+                        (when (= nargs (incf arg))
+                          (setf currently-in nil))))))
+                  (t ""))
+                (if (= addr e) " <- E" "")
+                (if (= addr b) " <- B" "")))))
   (format t "  +------+----------+-------------------------------+~%"))
 
 
@@ -282,20 +276,20 @@
 
 (defun dump-wam-registers (wam)
   (format t "REGISTERS:~%")
-  (format t  "~5@A ->~6@A~%" "S" (wam-subterm wam))
-  (loop :for i :from 0
-        :for reg :across (wam-local-registers wam)
-        :for contents = (when (not (= reg (1- +heap-limit+)))
+  (format t  "~5@A -> ~8@A~%" "S" (wam-subterm wam))
+  (loop :for i :from 0 :to +register-count+
+        :for reg :across (wam-store wam)
+        :for contents = (when (not (zerop reg))
                           (wam-heap-cell wam reg))
         :when contents
-        :do (format t "~5@A ->~6@A ~10A ~A~%"
+        :do (format t "~5@A -> ~8,'0X ~10A ~A~%"
                     (format nil "X~D" i)
                     reg
                     (cell-aesthetic contents)
                     (format nil "; ~A" (first (extract-things wam (list reg)))))))
 
 (defun dump-wam-functors (wam)
-  (format t " FUNCTORS: ~S~%" (wam-functors wam)))
+  (format t "        FUNCTORS: ~S~%" (wam-functors wam)))
 
 (defun dump-wam-trail (wam)
   (format t "    TRAIL: ")
@@ -316,13 +310,15 @@
 
 
 (defun dump-wam (wam from to highlight)
-  (format t "     FAIL: ~A~%" (wam-fail wam))
-  (format t "     MODE: ~S~%" (wam-mode wam))
+  (format t "            FAIL: ~A~%" (wam-fail wam))
+  (format t "            MODE: ~S~%" (wam-mode wam))
   (dump-wam-functors wam)
-  (format t "HEAP SIZE: ~A~%" (length (wam-heap wam)))
-  (format t "PROGRAM C: ~4,'0X~%" (wam-program-counter wam))
-  (format t "CONT  PTR: ~4,'0X~%" (wam-continuation-pointer wam))
-  (format t "ENVIR PTR: ~4,'0X~%" (wam-environment-pointer wam))
+  (format t "       HEAP SIZE: ~A~%" (- (wam-heap-pointer wam) +heap-start+))
+  (format t " PROGRAM COUNTER: ~4,'0X~%" (wam-program-counter wam))
+  (format t "CONTINUATION PTR: ~4,'0X~%" (wam-continuation-pointer wam))
+  (format t " ENVIRONMENT PTR: ~4,'0X~%" (wam-environment-pointer wam))
+  (format t "   BACKTRACK PTR: ~4,'0X~%" (wam-backtrack-pointer wam))
+  (format t "HEAP BCKTRCK PTR: ~4,'0X~%" (wam-heap-backtrack-pointer wam))
   (dump-wam-trail wam)
   (dump-wam-registers wam)
   (format t "~%")
@@ -342,12 +338,12 @@
     (dump-code-store wam code +maximum-query-size+ (length code))))
 
 (defun dump-wam-full (wam)
-  (dump-wam wam 0 (length (wam-heap wam)) -1))
+  (dump-wam wam +heap-start+ (wam-heap-pointer wam) -1))
 
 (defun dump-wam-around (wam addr width)
   (dump-wam wam
-            (max 0 (- addr width))
-            (min (length (wam-heap wam))
+            (max +heap-start+ (- addr width))
+            (min (wam-heap-pointer wam)
                  (+ addr width 1))
             addr))
 
--- a/src/wam/types.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/src/wam/types.lisp	Sun May 08 21:25:08 2016 +0000
@@ -10,11 +10,14 @@
   `(unsigned-byte ,+cell-value-width+))
 
 
+(deftype store-index ()
+  `(integer 0 ,(1- +store-limit+)))
+
 (deftype heap-index ()
-  `(integer 0 ,(1- +heap-limit+)))
+  `(integer ,+heap-start+ ,(1- +store-limit+)))
 
 (deftype stack-index ()
-  `(integer 0 ,(1- +stack-limit+)))
+  `(integer ,+stack-start+ ,(1- +stack-end+)))
 
 (deftype trail-index ()
   `(integer 0 ,(1- +trail-limit+)))
@@ -23,7 +26,7 @@
   `(integer 0 ,(1- +register-count+)))
 
 (deftype functor-index ()
-  `(integer 0 ,(1- array-total-size-limit)))
+  `(integer 0 ,(1- +functor-limit+)))
 
 
 (deftype arity ()
@@ -37,7 +40,7 @@
   `(unsigned-byte ,+code-word-size+))
 
 (deftype code-index ()
-  ; either an address or the sentinal
+  ;; either an address or the sentinal
   `(integer 0 ,(1- +code-limit+)))
 
 
--- a/src/wam/vm.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/src/wam/vm.lisp	Sun May 08 21:25:08 2016 +0000
@@ -70,7 +70,7 @@
   "
   (when *break-on-fail*
     (break "FAIL: ~A" reason))
-  (if (zerop (wam-backtrack-pointer wam))
+  (if (wam-backtrack-pointer-unset-p wam)
     (setf (wam-fail wam) t)
     (setf (wam-program-counter wam) (wam-stack-choice-bp wam)
           (wam-backtracked wam) t))
@@ -306,13 +306,13 @@
         ;; few instructions (which will be unify-*'s, executed in write mode).
         ((cell-reference-p cell)
          (let ((structure-address (nth-value 1 (push-new-structure! wam)))
-               (functor-address (push-new-functor! wam functor)))
+               (functor-address (nth-value 1 (push-new-functor! wam functor))))
            (bind! wam addr structure-address)
            (setf mode :write
                  s (1+ functor-address))))
 
-        ;; If the register points at a structure cell, then we look at where that
-        ;; cell points (which will be the functor cell for the structure):
+        ;; If the register points at a structure cell, then we look at where
+        ;; that cell points (which will be the functor cell for the structure):
         ;;
         ;;     |   N | STR | M   | points at the structure, not necessarily contiguous
         ;;     |       ...       |
@@ -325,15 +325,15 @@
         ;; (M+1 in the example above).
         ;;
         ;; What about if it's a 0-arity functor?  The S register will be set to
-        ;; garbage.  But that's okay, because we know the next thing in the stream
-        ;; of instructions will be another get-structure and we'll just blow away
-        ;; the S register there.
+        ;; garbage.  But that's okay, because we know the next thing in the
+        ;; stream of instructions will be another get-structure and we'll just
+        ;; blow away the S register there.
         ((cell-structure-p cell)
          (let* ((functor-addr (cell-value cell))
                 (functor-cell (wam-heap-cell wam functor-addr)))
            (if (matching-functor-p functor-cell functor)
-             (setf s (1+ functor-addr)
-                   mode :read)
+             (setf mode :read
+                   s (1+ functor-addr))
              (backtrack! wam "Functors don't match in get-struct"))))
         (t (backtrack! wam (format nil "get-struct on a non-ref/struct cell ~A"
                                    (cell-aesthetic cell))))))))
@@ -398,13 +398,12 @@
         (wam-continuation-pointer wam)))
 
 (define-instruction %allocate ((wam wam) (n stack-frame-argcount))
-  (let ((stack (wam-stack wam))
-        (old-e (wam-environment-pointer wam))
+  (let ((old-e (wam-environment-pointer wam))
         (new-e (wam-stack-top wam)))
-    (wam-stack-ensure-size! wam (+ new-e 3 n))
-    (setf (aref stack new-e) old-e ; CE
-          (aref stack (+ new-e 1)) (wam-continuation-pointer wam) ; CP
-          (aref stack (+ new-e 2)) n ; N
+    (wam-stack-ensure-size wam (+ new-e 3 n))
+    (setf (wam-stack-word wam new-e) old-e ; CE
+          (wam-stack-word wam (+ new-e 1)) (wam-continuation-pointer wam) ; CP
+          (wam-stack-word wam (+ new-e 2)) n ; N
           (wam-environment-pointer wam) new-e))) ; E <- new-e
 
 (define-instruction %deallocate ((wam wam))
@@ -416,17 +415,16 @@
 
 ;;;; Choice Instructions
 (define-instruction %try ((wam wam) (next-clause code-index))
-  (let ((stack (wam-stack wam))
-        (new-b (wam-stack-top wam))
+  (let ((new-b (wam-stack-top wam))
         (nargs (wam-number-of-arguments wam)))
-    (wam-stack-ensure-size! wam (+ new-b 7 nargs))
-    (setf (aref stack new-b) nargs ; N
-          (aref stack (+ new-b 1)) (wam-environment-pointer wam) ; CE
-          (aref stack (+ new-b 2)) (wam-continuation-pointer wam) ; CP
-          (aref stack (+ new-b 3)) (wam-backtrack-pointer wam) ; CB
-          (aref stack (+ new-b 4)) next-clause ; BP
-          (aref stack (+ new-b 5)) (wam-trail-pointer wam) ; TR
-          (aref stack (+ new-b 6)) (wam-heap-pointer wam) ; H
+    (wam-stack-ensure-size wam (+ new-b 7 nargs))
+    (setf (wam-stack-word wam new-b) nargs ; N
+          (wam-stack-word wam (+ new-b 1)) (wam-environment-pointer wam) ; CE
+          (wam-stack-word wam (+ new-b 2)) (wam-continuation-pointer wam) ; CP
+          (wam-stack-word wam (+ new-b 3)) (wam-backtrack-pointer wam) ; CB
+          (wam-stack-word wam (+ new-b 4)) next-clause ; BP
+          (wam-stack-word wam (+ new-b 5)) (wam-trail-pointer wam) ; TR
+          (wam-stack-word wam (+ new-b 6)) (wam-heap-pointer wam) ; H
           (wam-heap-backtrack-pointer wam) (wam-heap-pointer wam) ; HB
           (wam-backtrack-pointer wam) new-b) ; B
     (loop :for i :from 0 :below nargs :do ; A_i
@@ -443,13 +441,14 @@
     (setf (wam-environment-pointer wam) (wam-stack-choice-ce wam b)
           (wam-continuation-pointer wam) (wam-stack-choice-cp wam b)
           ;; overwrite the next clause address in the choice point
-          (aref (wam-stack wam) (+ b 4)) next-clause
+          (wam-stack-word wam (+ b 4)) next-clause
           (wam-trail-pointer wam) (wam-stack-choice-tr wam b)
           (wam-heap-pointer wam) (wam-stack-choice-h wam b)
           (wam-heap-backtrack-pointer wam) (wam-heap-pointer wam))))
 
 (define-instruction %trust ((wam wam))
-  (let ((b (wam-backtrack-pointer wam)))
+  (let* ((b (wam-backtrack-pointer wam))
+         (old-b (wam-stack-choice-cb wam b)))
     ;; Restore argument registers
     (loop :for i :from 0 :below (wam-stack-choice-n wam b) :do
           (setf (wam-local-register wam i)
@@ -459,13 +458,22 @@
           (wam-continuation-pointer wam) (wam-stack-choice-cp wam b)
           (wam-trail-pointer wam) (wam-stack-choice-tr wam b)
           (wam-heap-pointer wam) (wam-stack-choice-h wam b)
-          (wam-backtrack-pointer wam) (wam-stack-choice-cb wam b)
-          ;; Note that this last one uses the NEW value of b, so the heap
-          ;; backtrack pointer gets set to the heap pointer saved in the
-          ;; PREVIOUS choice point.
+          (wam-backtrack-pointer wam) old-b
+
+          ;; The book is wrong here: this last one uses the NEW value of b, so
+          ;; the heap backtrack pointer gets set to the heap pointer saved in
+          ;; the PREVIOUS choice point.  Thanks to the errata at
+          ;; https://github.com/a-yiorgos/wambook/blob/master/wamerratum.txt for
+          ;; pointing this out.
           ;;
-          ;; TODO: What if we just popped off the last stack frame?
-          (wam-heap-backtrack-pointer wam) (wam-stack-choice-h wam))))
+          ;; ... well, almost.  The errata is also wrong here.  If we're popping
+          ;; the FIRST choice point, then just using the "previous choice
+          ;; point"'s HB is going to give us garbage, so we should check for that
+          ;; edge case too.  Please kill me.
+          (wam-heap-backtrack-pointer wam)
+          (if (wam-backtrack-pointer-unset-p wam old-b)
+            +heap-start+
+            (wam-stack-choice-h wam old-b)))))
 
 
 ;;;; Running
--- a/src/wam/wam.lisp	Mon May 02 19:29:36 2016 +0000
+++ b/src/wam/wam.lisp	Sun May 08 21:25:08 2016 +0000
@@ -3,12 +3,10 @@
 ;;;; WAM
 (declaim
   ;; Inline all these struct accessors, otherwise things get REAL slow.
-  (inline wam-heap
+  (inline wam-store
           wam-code
           wam-functors
           wam-code-labels
-          wam-local-registers
-          wam-stack
           wam-fail
           wam-backtracked
           wam-unification-stack
@@ -22,10 +20,26 @@
           wam-heap-backtrack-pointer
           wam-mode))
 
-(defstruct (wam (:type vector) :named)
-  (heap
-    (make-array 1024
-      :fill-pointer 0
+(defstruct (wam
+             (:print-function
+              (lambda (wam stream depth)
+                (declare (ignore depth))
+                (print-unreadable-object
+                  (wam stream :type t :identity t)
+                  (format stream "an wam")))))
+  (store
+    ;; The main WAM store contains three separate blocks of values:
+    ;;
+    ;;     [0, +register-count+)        -> the local X_n registers
+    ;;     [+stack-start+, +stack-end+) -> the stack
+    ;;     [+heap-start+, ...)          -> the heap
+    ;;
+    ;; `+register-count+` and `+stack-start+` are the same number, and
+    ;; `+stack-end+` and `+heap-start+` are the same number as well.
+    (make-array (+ +register-count+ ; TODO: make all these configurable per-WAM
+                   +stack-limit+
+                   4096)
+      :fill-pointer +stack-end+
       :adjustable t
       :initial-element (make-cell-null)
       :element-type 'heap-cell)
@@ -53,27 +67,6 @@
   (code-labels
     (make-hash-table)
     :read-only t)
-  (local-registers
-    (make-array +register-count+
-      ;; Initialize to the last element in the heap for debugging.
-      ;; todo: don't do this
-      :initial-element (1- +heap-limit+)
-      :element-type 'heap-index)
-    :type (simple-array heap-index)
-    :read-only t)
-  (stack
-    (make-array 1024
-      :adjustable t
-      :initial-element 0
-      :element-type 'stack-word)
-    :type (vector stack-word)
-    :read-only t)
-  (fail
-    nil
-    :type boolean)
-  (backtracked
-    nil
-    :type boolean)
   (unification-stack
     (make-array 16
       :fill-pointer 0
@@ -89,37 +82,27 @@
       :element-type 'heap-index)
     :type (vector heap-index)
     :read-only t)
-  (number-of-arguments
-    0
-    :type arity)
-  (subterm
-    nil
-    :type (or null heap-index))
-  (program-counter ; P
-    0
-    :type code-index)
-  (continuation-pointer ; CP
-    0
-    :type code-index)
-  (environment-pointer ; E
-    0
-    :type environment-pointer)
-  (backtrack-pointer ; B
-    0
-    :type backtrack-pointer)
-  (heap-backtrack-pointer ; HB
-    0
-    :type heap-index)
-  (mode
-    nil
-    :type (or null (member :read :write))))
 
-(deftype wam ()
-  ; todo lol
-  '(simple-vector 19))
+  ;; Unique registers
+  (number-of-arguments    0             :type arity)                ; NARGS
+  (subterm                nil           :type (or null heap-index)) ; S
+  (program-counter        0             :type code-index)           ; P
+  (stack-pointer          +stack-start+ :type stack-index)          ; SP
+  (continuation-pointer   0             :type code-index)           ; CP
+  (environment-pointer    +stack-start+ :type environment-pointer)  ; E
+  (backtrack-pointer      +stack-start+ :type backtrack-pointer)    ; B
+  (heap-backtrack-pointer +heap-start+  :type heap-index)           ; HB
+
+  ;; Other global "registers"
+  (fail        nil :type boolean)
+  (backtracked nil :type boolean)
+  (mode        nil :type (or null (member :read :write))))
 
 
 ;;;; Heap
+;;; TODO: Should we privilege heap address 0 to mean "unset" so we have a good
+;;; sentinal value for HB, S, etc?
+
 (defun* wam-heap-push! ((wam wam) (cell heap-cell))
   (:returns (values heap-cell heap-index))
   "Push the cell onto the WAM heap and increment the heap pointer.
@@ -127,27 +110,27 @@
   Returns the cell and the address it was pushed to.
 
   "
-  (let ((heap (wam-heap wam)))
-    (if (= +heap-limit+ (fill-pointer heap))
+  (let ((store (wam-store wam)))
+    (if (= +store-limit+ (fill-pointer store))
       (error "WAM heap exhausted.")
-      (values cell (vector-push-extend cell heap)))))
+      (values cell (vector-push-extend cell store)))))
 
 (defun* wam-heap-pointer ((wam wam))
   (:returns heap-index)
   "Return the current heap pointer of the WAM."
-  (fill-pointer (wam-heap wam)))
+  (fill-pointer (wam-store wam)))
 
 (defun (setf wam-heap-pointer) (new-value wam)
-  (setf (fill-pointer (wam-heap wam)) new-value))
+  (setf (fill-pointer (wam-store wam)) new-value))
 
 
 (defun* wam-heap-cell ((wam wam) (address heap-index))
   (:returns heap-cell)
   "Return the heap cell at the given address."
-  (aref (wam-heap wam) address))
+  (aref (wam-store wam) address))
 
 (defun (setf wam-heap-cell) (new-value wam address)
-  (setf (aref (wam-heap wam) address) new-value))
+  (setf (aref (wam-store wam) address) new-value))
 
 
 ;;;; Trail
@@ -186,36 +169,55 @@
 
 
 ;;;; Stack
-;;; The stack is stored as a big ol' hunk of memory in a Lisp array with one
-;;; small glitch: we reserve the first word of the stack (address 0) to mean
-;;; "uninitialized", so we have a nice sentinal value for the various pointers
-;;; into the stack.
+;;; The stack is stored as a fixed-length hunk of the main WAM store array,
+;;; between the local register and the heap, with small glitch: we reserve the
+;;; first word of the stack (address `+stack-start`) to mean "uninitialized", so
+;;; we have a nice sentinal value for the various pointers into the stack.
+
+(declaim (inline wam-stack-word))
+
+(defun assert-inside-stack (wam address action)
+  (declare (ignore wam))
+  (assert (<= +stack-start+ address (1- +stack-end+)) ()
+    "Cannot ~A stack cell at address ~X (outside the stack range ~X to ~X)"
+    action address +stack-start+ +stack-end+)
+  (assert (not (= +stack-start+ address)) ()
+    "Cannot ~A stack address zero."
+    action))
+
+(defun* wam-stack-ensure-size ((wam wam) (address stack-index))
+  (:returns :void)
+  "Ensure the WAM stack is large enough to be able to write to `address`."
+  (assert-inside-stack wam address "write")
+  (values))
+
 
 (defun* wam-stack-word ((wam wam) (address stack-index))
   (:returns stack-word)
   "Return the stack word at the given address."
-  (assert (not (zerop address)) (address)
-          "Cannot write to stack address zero.")
-  (aref (wam-stack wam) address))
+  (assert-inside-stack wam address "read")
+  (aref (wam-store wam) address))
 
 (defun (setf wam-stack-word) (new-value wam address)
-  (setf (aref (wam-stack wam) address) new-value))
+  (assert-inside-stack wam address "write")
+  (setf (aref (wam-store wam) address) new-value))
 
-(defun* wam-stack-ensure-size! ((wam wam)
-                                (address stack-index))
-  (:returns :void)
-  "Ensure the WAM stack is large enough to be able to write to `address`.
-
-  It will be adjusted (but not beyond the limit) if necessary.
 
-  "
-  (let ((stack (wam-stack wam)))
-    (if (>= address +stack-limit+)
-      (error "WAM stack exhausted.")
-      (while (>= address (array-total-size stack))
-        ;; i uh, let's just hope this never executes more than once...
-        (adjust-array stack (* 2 (array-total-size stack))))))
-  (values))
+(defun* wam-backtrack-pointer-unset-p
+  ((wam wam)
+   &optional
+   ((backtrack-pointer backtrack-pointer)
+    (wam-backtrack-pointer wam)))
+  (:returns boolean)
+  (= backtrack-pointer +stack-start+))
+
+(defun* wam-environment-pointer-unset-p
+  ((wam wam)
+   &optional
+   ((environment-pointer environment-pointer)
+    (wam-environment-pointer wam)))
+  (:returns boolean)
+  (= environment-pointer +stack-start+))
 
 
 ;;; Stack frames are laid out like so:
@@ -257,7 +259,8 @@
     ((wam wam)
      (n register-index)
      &optional
-     ((e environment-pointer) (wam-environment-pointer wam)))
+     ((e environment-pointer)
+      (wam-environment-pointer wam)))
   (:returns heap-index)
   (wam-stack-word wam (+ 3 n e)))
 
@@ -405,7 +408,9 @@
   (let ((e (wam-environment-pointer wam))
         (b (wam-backtrack-pointer wam)))
     (cond
-      ((= 0 b e) 1) ; first allocation
+      ((and (wam-backtrack-pointer-unset-p wam b)
+            (wam-environment-pointer-unset-p wam e)) ; first allocation 
+       (1+ +stack-start+))
       ((> e b) ; the last thing on the stack is a frame
        (+ e (wam-stack-frame-size wam e)))
       (t ; the last thing on the stack is a choice point
@@ -414,7 +419,7 @@
 
 ;;;; Resetting
 (defun* wam-truncate-heap! ((wam wam))
-  (setf (fill-pointer (wam-heap wam)) 0))
+  (setf (fill-pointer (wam-store wam)) +heap-start+))
 
 (defun* wam-truncate-trail! ((wam wam))
   (setf (fill-pointer (wam-trail wam)) 0))
@@ -424,8 +429,7 @@
 
 (defun* wam-reset-local-registers! ((wam wam))
   (loop :for i :from 0 :below +register-count+ :do
-        (setf (wam-local-register wam i)
-              (1- +heap-limit+)))
+        (setf (wam-local-register wam i) (make-cell-null)))
   (setf (wam-subterm wam) nil))
 
 (defun* wam-reset! ((wam wam))
@@ -435,9 +439,9 @@
   (wam-reset-local-registers! wam)
   (setf (wam-program-counter wam) 0
         (wam-continuation-pointer wam) 0
-        (wam-environment-pointer wam) 0
-        (wam-backtrack-pointer wam) 0
-        (wam-heap-backtrack-pointer wam) 0
+        (wam-environment-pointer wam) +stack-start+
+        (wam-backtrack-pointer wam) +stack-start+
+        (wam-heap-backtrack-pointer wam) +heap-start+
         (wam-backtracked wam) nil
         (wam-fail wam) nil
         (wam-subterm wam) nil
@@ -526,8 +530,8 @@
 ;;; The WAM has two types of registers.  A register (regardless of type) always
 ;;; contains an index into the heap (basically a pointer to a heap cell).
 ;;;
-;;; Local/temporary/arguments registers live in a small, fixed, preallocated
-;;; array called `registers` in the WAM object.
+;;; Local/temporary/arguments registers live at the beginning of the WAM memory
+;;; store.
 ;;;
 ;;; Stack/permanent registers live on the stack, and need some extra math to
 ;;; find their location.
@@ -539,16 +543,16 @@
 ;;; instead of runtime, and register references happen A LOT at runtime.
 
 (defun* wam-local-register ((wam wam) (register register-index))
-  (:returns heap-index)
+  (:returns (or (eql 0) heap-index))
   "Return the value of the WAM local register with the given index."
-  (aref (wam-local-registers wam) register))
+  (aref (wam-store wam) register))
 
 (defun (setf wam-local-register) (new-value wam register)
-  (setf (aref (wam-local-registers wam) register) new-value))
+  (setf (aref (wam-store wam) register) new-value))
 
 
 (defun* wam-stack-register ((wam wam) (register register-index))
-  (:returns heap-index)
+  (:returns (or (eql 0) heap-index))
   "Return the value of the WAM stack register with the given index."
   (wam-stack-frame-arg wam register))