summary refs log tree commit diff
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-04-24 06:01:56 -0700
committerIrene Knapp <ireneista@irenes.space>2026-04-24 06:03:11 -0700
commit151d1cd962b8f936a1f97438f677b897adc8e740 (patch)
tree6ab9647c96e5def62a77d1299a0fec7e1259686e
parent272df1c32b0ef1ed8ed71e945981b16bdeed26d7 (diff)
refactor all the stack-string stuff to work the right way round
yeahhhhhhh the strings tried to grow downwards in memory. strings don't do that.

on the plus side, the new implementation feels way more elegant.

Force-Push: yes
Change-Id: I836e1348273035299afbd3a793a77327180666f0
-rw-r--r--quine.asm391
1 files changed, 257 insertions, 134 deletions
diff --git a/quine.asm b/quine.asm
index c5148ef..0a5d96d 100644
--- a/quine.asm
+++ b/quine.asm
@@ -4,6 +4,12 @@
 ;;;
 ;;; There's some tabular information, but diagrams have been avoided, in an
 ;;; attempt to make this manageable in screen readers. Feedback welcome.
+;;;
+;;; First the hex
+;;; Second the spark
+;;; Third the words
+;;; Then tie the knot
+;;;   [draft]
 
 
 ;;;;;;;;;;;;;;;;;;;;;
@@ -2902,6 +2908,24 @@ cold_start:
   dq hlt
   dq lit, 8, packalign, early_here_store
 
+  ; This was "align_size".
+  dq litstring, "align-size", early_create, early_docol_codeword
+  dq litstring, "dup", early_find, entry_to_execution_token, early_comma
+  dq litstring, "3unroll", early_find, entry_to_execution_token, early_comma
+  dq litstring, "dup", early_find, entry_to_execution_token, early_comma
+  dq litstring, "3unroll", early_find, entry_to_execution_token, early_comma
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 1, early_comma
+  dq litstring, "-", early_find, entry_to_execution_token, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "/%", early_find, entry_to_execution_token, early_comma
+  dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "drop", early_find, entry_to_execution_token, early_comma
+  dq litstring, "*", early_find, entry_to_execution_token, early_comma
+  dq litstring, "exit", early_find, entry_to_execution_token, early_comma
+  dq early_here, fetch, lit, 8, packalign, early_here_store
+
   ; We could in theory call this 64pack, by analogy to 32! and so on, but it's
   ; kept like this to be more similar to reg64, imm64 etc.
   ;
@@ -3078,7 +3102,8 @@ cold_start:
   dq early_here, fetch, lit, 8, packalign, early_here_store
 
   dq litstring, "unpackalign", early_create, early_docol_codeword
-  dq litstring, "packalign", early_find, entry_to_execution_token, early_comma
+  dq litstring, "align-size", early_find, entry_to_execution_token
+  dq early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
   dq early_here, fetch, lit, 8, packalign, early_here_store
 
@@ -6469,74 +6494,159 @@ cold_start:
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
   dq early_here, fetch, lit, 8, packalign, early_here_store
 
-  ;;; As a convenience for "word", we have some facilities for working with
+
+  ;;;   As a convenience for "word", we have some facilities for working with
   ;;; stack-allocated strings. Yeah, trippy concept. Also, it would be a
   ;;; buffer overrun hazard if we were worried about that, which is why this
   ;;; is no longer common practice in C.
-  dq litstring, "stack-allocate-string-accumulator", early_create
-  dq early_docol_codeword
+  ;;;
+  ;;;   The most important of these is accumulate-string, but we need some
+  ;;; smaller pieces first.
+
+  ; In:
+  ;   (multiple words) string
+  ;   (multiple words) items to be left alone
+  ;   item to be unrolled
+  ;   number of items above string that participate in the unroll
+  ; Out:
+  ;   item that was unrolled
+  ;   (multiple words) string
+  ;   (multiple words) items left alone
+  dq litstring, "unroll-past-string", early_create, early_docol_codeword
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 0, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "*", early_find, entry_to_execution_token, early_comma
+  ; (string, other items, top item, byte offset to string start)
+  dq litstring, "dup", early_find, entry_to_execution_token, early_comma
   dq litstring, "value@", early_find, entry_to_execution_token, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  ; We have two copies of the offset present, in addition to the stuff we want
+  ; to rotate. So, the actual string starts two words on... We could have
+  ; adjusted the offset instead, but we'll want the unmodified offset again
+  ; later.
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 16, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  ; (string, other items, top item, offset to start, string pointer)
+  dq litstring, "stringlen", early_find, entry_to_execution_token, early_comma
+  ; Same reasoning as in accumulate-string (see below).
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 1, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "align-size", early_find, entry_to_execution_token, early_comma
+  ; (string, other items, top item, offset to start, string length w/ padding)
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "/%", early_find, entry_to_execution_token, early_comma
+  dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "drop", early_find, entry_to_execution_token, early_comma
+  ; (string, other items, top item, number of words to unroll)
+  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
   dq early_here, fetch, lit, 8, packalign, early_here_store
 
-  dq litstring, "stack-deallocate-string-accumulator", early_create
-  dq early_docol_codeword
+  ; In:
+  ;   (multiple words) string
+  ;   item to be swapped
+  ; Out:
+  ;   item that was swapped
+  ;   (multiple words) string
+  dq litstring, "swap-past-string", 0, early_create, early_docol_codeword
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 1, early_comma
+  dq litstring, "unroll-past-string", early_find, entry_to_execution_token
+  dq early_comma
+  dq litstring, "exit", early_find, entry_to_execution_token, early_comma
+  dq early_here, fetch, lit, 8, packalign, early_here_store
+
+  ; In:
+  ;   (multiple words) string
+  dq litstring, "dropstring", early_create, early_docol_codeword
+  dq litstring, "value@", early_find, entry_to_execution_token, early_comma
+  dq litstring, "stringlen", early_find, entry_to_execution_token, early_comma
+  ; Same reasoning as in accumulate-string (see below).
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 1, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "align-size", early_find, entry_to_execution_token, early_comma
+  dq litstring, "value@", early_find, entry_to_execution_token, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  ;   At the time we fetched the stack pointer, there was an extra value atop
+  ; it, so we have to add one more word.
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
   dq litstring, "value!", early_find, entry_to_execution_token, early_comma
-  dq litstring, "drop", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
   dq early_here, fetch, lit, 8, packalign, early_here_store
 
   ; In:
   ;   (multiple words) string
-  ;   pointer to start of string
-  ;   result
+  ;   item to be kept
   ; Out:
-  ;   result
-  dq litstring, "stack-deallocate-string-accumulator-with-result"
-  dq early_create, early_docol_codeword
-  dq litstring, "swap", early_find, entry_to_execution_token, early_comma
-  dq litstring, "dup", early_find, entry_to_execution_token, early_comma
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
-  dq litstring, "swap", early_find, entry_to_execution_token, early_comma
-  dq litstring, "!", early_find, entry_to_execution_token, early_comma
-  dq litstring, "value!", early_find, entry_to_execution_token, early_comma
+  ;   item that was kept
+  dq litstring, "dropstring-with-result", early_create, early_docol_codeword
+  dq litstring, "swap-past-string", 0, early_find, entry_to_execution_token
+  dq early_comma
+  dq litstring, "dropstring", early_find, entry_to_execution_token
+  dq early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
   dq early_here, fetch, lit, 8, packalign, early_here_store
 
   ; In:
   ;   (multiple words) string-so-far
-  ;   pointer to start of string-so-far
   ;   new character byte
   ; Out:
-  ;   updated string-so-far
-  ;   pointer to start of string-so-far
-  dq litstring, "accumulate-string", early_create, early_docol_codeword
+  ;   (multiple words) updated string-so-far
+  dq litstring, "accumulate-string", early_create
+  dq early_docol_codeword
 
-  ; Examine the final word of the string, leaving other stuff undisturbed.
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
+  ; Compute the address of the final word of the string.
+  ;
+  ;   It's a little bit difficult to get the start pointer right, since all
+  ; our intermediate products affect what we get from value@, so we compute
+  ; that just once, here at the beginning.
+  dq litstring, "value@", early_find, entry_to_execution_token, early_comma
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  ; (string so far, new character byte, pointer to start of string)
   dq litstring, "dup", early_find, entry_to_execution_token, early_comma
+  dq litstring, "stringlen", early_find, entry_to_execution_token, early_comma
+  ; There are two concerns here that overlap: First, we always want at least
+  ; one word. Recall that a length of zero bytes won't receive any alignment
+  ; padding because it's already divisible by 8. Second, the result of
+  ; stringlen doesn't include the null byte, which might be in a word by
+  ; itself that needs to be counted. We can address both of them by
+  ; unconditionally adding 1 to the length before applying alignment.
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 4, early_comma
-  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
+  dq lit, 1, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  ; Pad the length for alignment.
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 0xFF00000000000000, early_comma
-  dq litstring, "and", early_find, entry_to_execution_token, early_comma
-  dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
-  dq lit, 3*8, early_comma
-
-  ; If the top byte of the final word of the string is occupied, we need to
-  ; start a new word. Our representation makes that trivial.
-  dq litstring, "swap", early_find, entry_to_execution_token, early_comma
-  dq litstring, "exit", early_find, entry_to_execution_token, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "align-size", early_find, entry_to_execution_token
+  dq early_comma
+  ; We want an offset from the first word of the string to the last word of
+  ; the string, so we subtract one word from the length.
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "-", early_find, entry_to_execution_token, early_comma
+  dq litstring, "+", early_find, entry_to_execution_token, early_comma
+  ; (string so far, new character byte, address of final word)
 
-  ; If the top byte is unoccupied, figure out where to store the new byte.
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
+  ; Examine the final word of the string, leaving other stuff undisturbed.
+  ; Work low-to-high to figure out where to store the new byte, taking the
+  ; first one that's available.
+  ; (string so far, new character byte, address of final word)
+  dq litstring, "dup", early_find, entry_to_execution_token, early_comma
+  dq litstring, "@", early_find, entry_to_execution_token, early_comma
   dq litstring, "dup", early_find, entry_to_execution_token, early_comma
-  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 4, early_comma
-  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x00000000000000FF, early_comma
   dq litstring, "and", early_find, entry_to_execution_token, early_comma
@@ -6544,144 +6654,177 @@ cold_start:
   dq lit, 0, early_comma
   dq litstring, "=", early_find, entry_to_execution_token, early_comma
   dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
-  dq lit, 5*8, early_comma
+  dq lit, 6*8, early_comma
+  ; (string so far, new character byte, address of final word, old value)
   dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "or", early_find, entry_to_execution_token, early_comma
   dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "!", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
+  ; (string so far, new character byte, address of final word, old value)
 
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
+  ; This next part is repeated several times, changing only the offsets, for
+  ; bytes 1 through 6; bytes 0 (above) and 7 (way below) are different.
+  ; (string so far, new character byte, address of final word, old value)
   dq litstring, "dup", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 4, early_comma
-  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
-  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x000000000000FF00, early_comma
   dq litstring, "and", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0, early_comma
   dq litstring, "=", early_find, entry_to_execution_token, early_comma
   dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
-  dq lit, 8*8, early_comma
+  dq lit, 9*8, early_comma
+  ; (string so far, new character byte, address of final word, old value)
+  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0000000000000100, early_comma
   dq litstring, "*", early_find, entry_to_execution_token, early_comma
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "or", early_find, entry_to_execution_token, early_comma
   dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "!", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
 
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
+  ; Highly repetitive code, see above.
+  ; (string so far, new character byte, address of final word, old value)
   dq litstring, "dup", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 4, early_comma
-  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
-  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0000000000FF0000, early_comma
   dq litstring, "and", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0, early_comma
   dq litstring, "=", early_find, entry_to_execution_token, early_comma
   dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
-  dq lit, 8*8, early_comma
+  dq lit, 9*8, early_comma
+  ; (string so far, new character byte, address of final word, old value)
+  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0000000000010000, early_comma
   dq litstring, "*", early_find, entry_to_execution_token, early_comma
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "or", early_find, entry_to_execution_token, early_comma
   dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "!", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
 
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
+  ; Highly repetitive code, see above.
+  ; (string so far, new character byte, address of final word, old value)
   dq litstring, "dup", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 4, early_comma
-  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
-  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x00000000FF000000, early_comma
   dq litstring, "and", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0, early_comma
   dq litstring, "=", early_find, entry_to_execution_token, early_comma
   dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
-  dq lit, 8*8, early_comma
+  dq lit, 9*8, early_comma
+  ; (string so far, new character byte, address of final word, old value)
+  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0000000001000000, early_comma
   dq litstring, "*", early_find, entry_to_execution_token, early_comma
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "or", early_find, entry_to_execution_token, early_comma
   dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "!", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
 
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
+  ; Highly repetitive code, see above.
+  ; (string so far, new character byte, address of final word, old value)
   dq litstring, "dup", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 4, early_comma
-  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
-  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x000000FF00000000, early_comma
   dq litstring, "and", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0, early_comma
   dq litstring, "=", early_find, entry_to_execution_token, early_comma
   dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
-  dq lit, 8*8, early_comma
+  dq lit, 9*8, early_comma
+  ; (string so far, new character byte, address of final word, old value)
+  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0000000100000000, early_comma
   dq litstring, "*", early_find, entry_to_execution_token, early_comma
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "or", early_find, entry_to_execution_token, early_comma
   dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "!", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
 
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
+  ; Highly repetitive code, see above.
+  ; (string so far, new character byte, address of final word, old value)
   dq litstring, "dup", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 4, early_comma
-  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
-  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0000FF0000000000, early_comma
   dq litstring, "and", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0, early_comma
   dq litstring, "=", early_find, entry_to_execution_token, early_comma
   dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
-  dq lit, 8*8, early_comma
+  dq lit, 9*8, early_comma
+  ; (string so far, new character byte, address of final word, old value)
+  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0000010000000000, early_comma
   dq litstring, "*", early_find, entry_to_execution_token, early_comma
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "or", early_find, entry_to_execution_token, early_comma
   dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "!", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
 
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
+  ; Highly repetitive code, see above.
+  ; (string so far, new character byte, address of final word, old value)
   dq litstring, "dup", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
-  dq lit, 4, early_comma
-  dq litstring, "unroll", early_find, entry_to_execution_token, early_comma
-  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x00FF000000000000, early_comma
   dq litstring, "and", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0, early_comma
   dq litstring, "=", early_find, entry_to_execution_token, early_comma
   dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
-  dq lit, 8*8, early_comma
+  dq lit, 9*8, early_comma
+  ; (string so far, new character byte, address of final word, old value)
+  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0001000000000000, early_comma
   dq litstring, "*", early_find, entry_to_execution_token, early_comma
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "or", early_find, entry_to_execution_token, early_comma
   dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  dq litstring, "!", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
 
+  ;   The top byte of the final word is always zero (or else stringlen
+  ; wouldn't have called it the final word), so we don't need to check it, we
+  ; can just use it.
+  ;
+  ;   We need to put the new value in the top byte, which will mean we have no
+  ; null terminator, so we also need to start a new word.
+  ;
+  ;   There is a fiddly order-dependency here: unroll-past-string relies on
+  ; being able to find the null terminator, which won't work if we've gotten
+  ; rid of it. Also, calling it will move all the earlier words, including
+  ; the one we intend to write to, which will invalidate any pointer we're
+  ; keeping at that point. There's a few ways to resolve this; what we do is
+  ; put the new terminator in place first, manually nudge the pointer, and
+  ; then write the new value.
+  ; (string so far, new character byte, address of final word, old value)
+  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0x0100000000000000, early_comma
   dq litstring, "*", early_find, entry_to_execution_token, early_comma
-  dq litstring, "3roll", early_find, entry_to_execution_token, early_comma
   dq litstring, "or", early_find, entry_to_execution_token, early_comma
   dq litstring, "swap", early_find, entry_to_execution_token, early_comma
+  ; (string so far, new value, address of final word)
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 0, early_comma
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 3, early_comma
+  dq litstring, "unroll-past-string", early_find, entry_to_execution_token
+  dq early_comma
+  ; (new null terminator, string so far, new value, invalid address)
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 8, early_comma
+  dq litstring, "-", early_find, entry_to_execution_token, early_comma
+  ; (new null terminator, string so far, new value, updated address)
+  dq litstring, "!", early_find, entry_to_execution_token, early_comma
+  ; (updated string)
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
   dq early_here, fetch, lit, 8, packalign, early_here_store
 
@@ -6690,13 +6833,12 @@ cold_start:
   ;
   ; Out:
   ;   (multiple words) string
-  ;   pointer to start of string
   dq litstring, "word", early_create, early_docol_codeword
 
-  ; We allocate this first, so that the result of "key" wil conveniently be
-  ; on the easy-to-find end of it.
-  dq litstring, "stack-allocate-string-accumulator", early_find
-  dq entry_to_execution_token, early_comma
+  ; We allocate an empty string first, so that the result of "key" will
+  ; conveniently be on the easy-to-find end of it.
+  dq litstring, "lit", early_find, entry_to_execution_token, early_comma
+  dq lit, 0, early_comma
 
   ; Skip whitespace.
   dq litstring, "key", early_find, entry_to_execution_token, early_comma
@@ -6715,8 +6857,8 @@ cold_start:
   dq litstring, "0branch", early_find, entry_to_execution_token, early_comma
   dq lit, 6*8, early_comma
   dq litstring, "drop", early_find, entry_to_execution_token, early_comma
-  dq litstring, "stack-deallocate-string-accumulator", early_find
-  dq entry_to_execution_token, early_comma
+  dq litstring, "dropstring", early_find, entry_to_execution_token
+  dq early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
   dq lit, 0, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
@@ -6940,7 +7082,10 @@ cold_start:
   dq litstring, "drop", early_find, entry_to_execution_token, early_comma
   dq litstring, "exit", early_find, entry_to_execution_token, early_comma
 
-  dq litstring, "dup", early_find, entry_to_execution_token, early_comma
+  ; The string is on the top of the stack, so to get a pointer to it we get
+  ; the stack address.
+  ; (string)
+  dq litstring, "value@", early_find, entry_to_execution_token, early_comma
   dq litstring, "find", early_find, entry_to_execution_token, early_comma
 
   ; Check whether the word was found in the dictionary.
@@ -6952,8 +7097,8 @@ cold_start:
   dq lit, 6*8, early_comma
 
   ; If the word is in the dictionary, run it.
-  dq litstring, "stack-deallocate-string-accumulator-with-result", early_find
-  dq entry_to_execution_token, early_comma
+  dq litstring, "dropstring-with-result", early_find, entry_to_execution_token
+  dq early_comma
   dq litstring, "entry-to-execution-token", 0, early_find
   dq entry_to_execution_token, early_comma
   dq litstring, "execute", early_find, entry_to_execution_token, early_comma
@@ -6963,8 +7108,9 @@ cold_start:
 
   ; If it's not in the dictionary, check whether it's a decimal number.
   dq litstring, "drop", early_find, entry_to_execution_token, early_comma
-  ; (string contents..., string pointer)
-  dq litstring, "dup", early_find, entry_to_execution_token, early_comma
+  ; As before, we get the stack address and use it as a string pointer.
+  ; (string)
+  dq litstring, "value@", early_find, entry_to_execution_token, early_comma
   dq litstring, "read-decimal", early_find, entry_to_execution_token
   dq early_comma
   dq litstring, "lit", early_find, entry_to_execution_token, early_comma
@@ -6976,15 +7122,15 @@ cold_start:
   ; It's a number; push it to the stack. Or at least, that's what the code
   ; we're interpreting will see. Really it's already on the stack, just clean
   ; everything else up and leave it there.
-  dq litstring, "stack-deallocate-string-accumulator-with-result", early_find
-  dq entry_to_execution_token, early_comma
+  dq litstring, "dropstring-with-result", early_find, entry_to_execution_token
+  dq early_comma
   dq litstring, "branch", early_find, entry_to_execution_token, early_comma
   ; o/~ Like a whirlpool and it never ends. o/~
   dq lit, -32*8, early_comma
 
   ; If it's neither in the dictionary nor a number, exit.
-  dq litstring, "stack-deallocate-string-accumulator", early_find
-  dq entry_to_execution_token, early_comma
+  dq litstring, "dropstring", early_find, entry_to_execution_token
+  dq early_comma
   dq litstring, "litstring", early_find, entry_to_execution_token, early_comma
   dq lit, "No such ", early_comma, lit, "word.", early_comma
   dq litstring, "emitstring", early_find, entry_to_execution_token
@@ -6993,6 +7139,7 @@ cold_start:
   dq early_here, fetch, lit, 8, packalign, early_here_store
 
 
+  ; The next layer is built now, so let's move on to it.
   dq litstring, "interpret", early_find, entry_to_execution_token
 
   ; Get rid of that heap pointer on the stack, we're finally done with it!
@@ -7003,42 +7150,6 @@ cold_start:
   dq lit, 1, sys_exit
 
 
-  ;;; TODO rethink this, it needs to not use space on the value stack
-  ;; Stack-allocate a buffer.
-  ;;
-  ;;   For brevity's sake, in notation below here when we give the contents of
-  ;; the stack we'll leave out the original copy of the heap pointer, and the
-  ;; four words used for the buffer.
-  ;dq lit, 0, lit, 0, lit, 0, lit, 0, fetch_value_stack
-  ;;   Make a copy of the heap pointer so we can have it a little closer. If we
-  ;; instead were to try to get to the copy on the other side of the buffer by
-  ;; rolling, it would move the buffer, invalidating the pointers.
-  ;dq lit, 6, roll, dup, lit, 7, unroll, swap
-  ;dq dup
-  ;dq lit, boot_source
-  ;; (heap pointer, buffer start pointer, buffer end pointer, input pointer)
-  ;
-  ;; Start of the loop
-  ;dq key                                                       ; 1 word
-  ;
-  ;; (heap pointer, buffer start pointer, buffer end pointer,
-  ;;  input pointer, input byte)
-  ;dq dup, lit, " ", ne, zbranch, 13*8                          ; 6 words
-  ;dq dup, lit, 0x0a, ne, zbranch, 7*8                          ; 6 words
-  ;
-  ;; If we got here, it's a body character.
-  ;dq roll3, swap, pack8, swap, branch, -18*8                   ; 6 words
-  ;
-  ;; If we got here, it's a space character.
-  ;dq drop, swap, lit, 0, pack8                                 ; 5 words
-  ;dq swap, roll3, dup, lit, 4, unroll                          ; 6 words
-  ;
-  ;; (heap pointer, buffer start pointer, buffer end pointer,
-  ;;  input pointer, word pointer)
-  ;dq lit, 5, roll, swap, early_find, swap, lit, 5, unroll      ; 8 words
-  ;dq entry_to_execution_token, execute                         ; 2 words
-  ;dq branch, -41*8
-
   ;;; For triage's sake, here's an inventory of everything else in the file.
   ;;;
   ;;; Macros:
@@ -8504,6 +8615,18 @@ defword litpack8, 0
 ;;; which it updates to point after the data item being read. Since this is
 ;;; input, the routines return data items rather than accepting them.
 
+; In:
+;   proposed size
+;   alignment
+; Out:
+;   adjusted size
+defword align_size, 0
+  dq docol
+  dq dup, unroll3, dup, unroll3
+  ; (alignment, proposed size, alignment)
+  dq lit, 1, sub, add, swap, divmod, swap, drop, mul
+  dq exit
+
 ; In: base address
 ; Out: new base address, value
 defword unpack64, 0
@@ -8523,8 +8646,8 @@ defword unpack8, 0
   dq dup, fetch8, swap, lit, 1, add, swap
   dq exit
 
-;   Since there's no data, this is identical to its output version, but we
-; allow this name as an alias for clarity.
+;   You might think this would be identical to packalign, but packalign has
+; side effects.
 ;
 ; Stack in:
 ;   base address
@@ -8532,7 +8655,7 @@ defword unpack8, 0
 ; Stack out:
 ;   new base address
 defword unpackalign, 0
-  dq docol, packalign, exit
+  dq docol, align_size, exit
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;