about summary refs log tree commit diff stats
path: root/apps
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2020-10-29 22:12:27 -0700
committerKartik Agaram <vc@akkartik.com>2020-10-30 06:05:52 -0700
commit264acd9ec91a7e75dc43ec57d863777b7812cc52 (patch)
treeea6548648f189160692fb4b0f9df658481ff6096 /apps
parent8fe51755bf9a0ac7270599cf096e7fc2ffd50c83 (diff)
downloadmu-264acd9ec91a7e75dc43ec57d863777b7812cc52.tar.gz
7144 - tmp: redo checks for function outputs
This isn't done, but an intermediate snapshot seems worth capturing.

Back in March (commit 6082), I made a plan to check writes to function
outputs using liveness analysis. I've been shying away from actually acting
on this plan ever since. In recent weeks I've had this gap bite me three
times.

Returning to the problem now, I think I don't actually need to compute
variable liveness. The compiler can, I think, do the same thing for output
registers whether their variables are alive or dead. The new rule is this:

Once a register gets a function output written to it, no local is popped
into it. Instead of popping outer locals to the register, we simply increment
the stack and keep going.

Since the function output will continue to live on the vars stack past
this point (see clean-up-block), any attempts to read shadowed variables
will throw an error as usual.

This rule is also now easy to explain to people, I think. "You wrote the
function output. Now the register can't be used for anything else."

It's really cool that this works (if it does). Another fruit from "Mu's
lovely property."
Diffstat (limited to 'apps')
-rw-r--r--apps/mu.subx263
1 files changed, 89 insertions, 174 deletions
diff --git a/apps/mu.subx b/apps/mu.subx
index 98f77faa..2e04009b 100644
--- a/apps/mu.subx
+++ b/apps/mu.subx
@@ -2606,58 +2606,7 @@ test-always-shadow-outermost-reg-vars-in-function:
     5d/pop-to-ebp
     c3/return
 
-_pending-test-clobber-dead-local:
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # setup
-    (clear-stream _test-input-stream)
-    (clear-stream $_test-input-buffered-file->buffer)
-    (clear-stream _test-output-stream)
-    (clear-stream $_test-output-buffered-file->buffer)
-    #
-    (write _test-input-stream "fn foo {\n")
-    (write _test-input-stream "  var x/ecx: int <- copy 3\n")
-    (write _test-input-stream "  {\n")
-    (write _test-input-stream "    var y/ecx: int <- copy 4\n")
-    (write _test-input-stream "  }\n")
-    (write _test-input-stream "}\n")
-    # convert
-    (convert-mu _test-input-buffered-file _test-output-buffered-file Stderr 0)
-    (flush _test-output-buffered-file)
-#?     # dump _test-output-stream {{{
-#?     (write 2 "^")
-#?     (write-stream 2 _test-output-stream)
-#?     (write 2 "$\n")
-#?     (rewind-stream _test-output-stream)
-#?     # }}}
-    # check output
-    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-clobber-dead-local/0")
-    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-clobber-dead-local/1")
-    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-clobber-dead-local/2")
-    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-clobber-dead-local/3")
-    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-clobber-dead-local/4")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-clobber-dead-local/5")
-    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-clobber-dead-local/6")
-    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 3/imm32"  "F - test-clobber-dead-local/7")
-    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-clobber-dead-local/8")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-clobber-dead-local/9")
-    (check-next-stream-line-equal _test-output-stream "      b9/copy-to-ecx 4/imm32"  "F - test-clobber-dead-local/10")  # no push/pop here
-    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-clobber-dead-local/11")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-clobber-dead-local/12")
-    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-clobber-dead-local/13")
-    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-clobber-dead-local/14")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-clobber-dead-local/15")
-    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-clobber-dead-local/16")
-    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-clobber-dead-local/17")
-    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-clobber-dead-local/18")
-    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-clobber-dead-local/19")
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
-test-shadow-live-local:
+test-shadow-local:
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -2684,29 +2633,29 @@ test-shadow-live-local:
 #?     (rewind-stream _test-output-stream)
 #?     # }}}
     # check output
-    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-shadow-live-local/0")
-    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-shadow-live-local/1")
-    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-shadow-live-local/2")
-    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-shadow-live-local/3")
-    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-shadow-live-local/4")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-shadow-live-local/5")
-    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-shadow-live-local/6")
-    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 3/imm32"  "F - test-shadow-live-local/7")
-    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-shadow-live-local/8")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-shadow-live-local/9")
-    (check-next-stream-line-equal _test-output-stream "      ff 6/subop/push %ecx"  "F - test-shadow-live-local/10")
-    (check-next-stream-line-equal _test-output-stream "      b9/copy-to-ecx 4/imm32"  "F - test-shadow-live-local/11")
-    (check-next-stream-line-equal _test-output-stream "      8f 0/subop/pop %ecx" "F - test-shadow-live-local/12")
-    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-shadow-live-local/13")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-shadow-live-local/14")
-    (check-next-stream-line-equal _test-output-stream "    41/increment-ecx"    "F - test-shadow-live-local/15")
-    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-shadow-live-local/16")
-    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-shadow-live-local/17")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-shadow-live-local/18")
-    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-shadow-live-local/19")
-    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-shadow-live-local/20")
-    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-shadow-live-local/21")
-    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-shadow-live-local/22")
+    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-shadow-local/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-shadow-local/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-shadow-local/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-shadow-local/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-shadow-local/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-shadow-local/5")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-shadow-local/6")
+    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 3/imm32"  "F - test-shadow-local/7")
+    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-shadow-local/8")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-shadow-local/9")
+    (check-next-stream-line-equal _test-output-stream "      ff 6/subop/push %ecx"  "F - test-shadow-local/10")
+    (check-next-stream-line-equal _test-output-stream "      b9/copy-to-ecx 4/imm32"  "F - test-shadow-local/11")
+    (check-next-stream-line-equal _test-output-stream "      8f 0/subop/pop %ecx" "F - test-shadow-local/12")
+    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-shadow-local/13")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-shadow-local/14")
+    (check-next-stream-line-equal _test-output-stream "    41/increment-ecx"    "F - test-shadow-local/15")
+    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-shadow-local/16")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-shadow-local/17")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-shadow-local/18")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-shadow-local/19")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-shadow-local/20")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-shadow-local/21")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-shadow-local/22")
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -2922,7 +2871,7 @@ test-spill-different-register-in-block:
     5d/pop-to-ebp
     c3/return
 
-test-shadow-live-output:
+test-shadow-output:
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -2949,27 +2898,27 @@ test-shadow-live-output:
 #?     (rewind-stream _test-output-stream)
 #?     # }}}
     # check output
-    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-shadow-live-output/0")
-    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-shadow-live-output/1")
-    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-shadow-live-output/2")
-    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-shadow-live-output/3")
-    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-shadow-live-output/4")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-shadow-live-output/5")
-    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 3/imm32"  "F - test-shadow-live-output/7")  # no push because it's an output reg
-    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-shadow-live-output/8")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-shadow-live-output/9")
-    (check-next-stream-line-equal _test-output-stream "      ff 6/subop/push %ecx"  "F - test-shadow-live-output/10")
-    (check-next-stream-line-equal _test-output-stream "      b9/copy-to-ecx 4/imm32"  "F - test-shadow-live-output/11")
-    (check-next-stream-line-equal _test-output-stream "      8f 0/subop/pop %ecx" "F - test-shadow-live-output/12")
-    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-shadow-live-output/13")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-shadow-live-output/14")
-    (check-next-stream-line-equal _test-output-stream "    41/increment-ecx"    "F - test-shadow-live-output/15")
-    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-shadow-live-output/17")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-shadow-live-output/18")
-    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-shadow-live-output/19")
-    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-shadow-live-output/20")
-    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-shadow-live-output/21")
-    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-shadow-live-output/21")
+    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-shadow-output/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-shadow-output/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-shadow-output/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-shadow-output/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-shadow-output/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-shadow-output/5")
+    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 3/imm32"  "F - test-shadow-output/7")  # no push because it's an output reg
+    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-shadow-output/8")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-shadow-output/9")
+    (check-next-stream-line-equal _test-output-stream "      ff 6/subop/push %ecx"  "F - test-shadow-output/10")
+    (check-next-stream-line-equal _test-output-stream "      b9/copy-to-ecx 4/imm32"  "F - test-shadow-output/11")
+    (check-next-stream-line-equal _test-output-stream "      8f 0/subop/pop %ecx" "F - test-shadow-output/12")
+    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-shadow-output/13")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-shadow-output/14")
+    (check-next-stream-line-equal _test-output-stream "    41/increment-ecx"    "F - test-shadow-output/15")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-shadow-output/17")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-shadow-output/18")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-shadow-output/19")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-shadow-output/20")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-shadow-output/21")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-shadow-output/21")
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -3048,14 +2997,16 @@ test-local-clobbered-by-fn-output:
     (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-local-clobbered-by-fn-output/3")
     (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-local-clobbered-by-fn-output/4")
     (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-local-clobbered-by-fn-output/5")
-    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 4/imm32"  "F - test-local-clobbered-by-fn-output/6")  # no push because it's an output reg
-    (check-next-stream-line-equal _test-output-stream "    89/<- %ecx 0x00000001/r32"  "F - test-local-clobbered-by-fn-output/7")
-    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-local-clobbered-by-fn-output/8")
-    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-local-clobbered-by-fn-output/9")
-    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-local-clobbered-by-fn-output/10")
-    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-local-clobbered-by-fn-output/11")
-    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-local-clobbered-by-fn-output/12")
-    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-local-clobbered-by-fn-output/13")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-local-clobbered-by-fn-output/6")
+    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 4/imm32"  "F - test-local-clobbered-by-fn-output/7")
+    (check-next-stream-line-equal _test-output-stream "    89/<- %ecx 0x00000001/r32"  "F - test-local-clobbered-by-fn-output/8")
+    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %esp 0x00000004/imm32"  "F - test-local-clobbered-by-fn-output/9")  # not a pop because there's already a function output in the reg
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-local-clobbered-by-fn-output/10")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-local-clobbered-by-fn-output/11")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-local-clobbered-by-fn-output/12")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-local-clobbered-by-fn-output/13")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-local-clobbered-by-fn-output/14")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-local-clobbered-by-fn-output/15")
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -18585,7 +18536,7 @@ $emit-subx-stmt-list:zero-arg-unconditional-loop:
             # unconditional loops with a target
             {
               0f 84/jump-if-= break/disp32
-              (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
+              (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10) *(ebp+0x14))
               e9/jump $emit-subx-stmt-list:clean-up/disp32
             }
           }
@@ -18602,7 +18553,7 @@ $emit-subx-stmt-list:unconditional-break:
             # simple unconditional breaks without a target
             0f 84/jump-if-= $emit-subx-stmt-list:emit-cleanup/disp32  # easy: just skip remaining statements
             # unconditional breaks with a target
-            (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
+            (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10) *(ebp+0x14))
             e9/jump $emit-subx-stmt-list:clean-up/disp32
           }
           # }}}
@@ -18654,7 +18605,7 @@ $emit-subx-stmt-list:conditional-branch-with-target:
             ff 0/subop/increment *Curr-block-depth
             #
             (emit-reverse-break *(ebp+8) %ecx)
-            (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
+            (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10) *(ebp+0x14))
             # cleanup epilogue
             ff 1/subop/decrement *Curr-block-depth
             (emit-indent *(ebp+8) *Curr-block-depth)
@@ -18686,7 +18637,7 @@ $emit-subx-stmt-list:check-for-reg-var-def:
         0f 85/jump-if-!= break/disp32
 $emit-subx-stmt-list:reg-var-def:
         # TODO: ensure that there's exactly one output
-        (push-output-and-maybe-emit-spill *(ebp+8) %ecx *(ebp+0x10) %esi *(ebp+0x14) *(ebp+0x18) *(ebp+0x1c))
+        (push-output-and-maybe-emit-spill *(ebp+8) %ecx *(ebp+0x10) %esi *(ebp+0x18) *(ebp+0x1c))
         # emit the instruction as usual
         (emit-subx-stmt *(ebp+8) %ecx Primitives *(ebp+0x18) *(ebp+0x1c))
         #
@@ -18699,7 +18650,7 @@ $emit-subx-stmt-list:continue:
       e9/jump loop/disp32
     }
 $emit-subx-stmt-list:emit-cleanup:
-    (emit-cleanup-code-until-depth *(ebp+8) *(ebp+0x10) *Curr-block-depth)
+    (emit-cleanup-code-until-depth *(ebp+8) *(ebp+0x10) *Curr-block-depth *(ebp+0x14))
 $emit-subx-stmt-list:clean-up:
     (clean-up-stack-offset-state *(ebp+0x10) *Curr-block-depth)
     (clean-up-blocks *(ebp+0x10) *Curr-block-depth *(ebp+0x14))
@@ -18715,7 +18666,7 @@ $emit-subx-stmt-list:end:
     c3/return
 
 # 'later-stmts' includes 'stmt', but will behave the same even without it; reg-var-def stmts are guaranteed not to write to function outputs.
-push-output-and-maybe-emit-spill:  # out: (addr buffered-file), stmt: (addr reg-var-def), vars: (addr stack (handle var)), later-stmts: (addr list stmt), fn: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
+push-output-and-maybe-emit-spill:  # out: (addr buffered-file), stmt: (addr reg-var-def), vars: (addr stack (handle var)), later-stmts: (addr list stmt), err: (addr buffered-file), ed: (addr exit-descriptor)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -18744,16 +18695,11 @@ push-output-and-maybe-emit-spill:  # out: (addr buffered-file), stmt: (addr reg-
     # ensure that v is in a register
     81 7/subop/compare *(ecx+0x18) 0/imm32  # Var-register
     0f 84/jump-if-= $push-output-and-maybe-emit-spill:abort/disp32
-    # var emit-spill?/edx: boolean = not-yet-spilled-this-block? && will-not-write-some-register?(fn)
+    # var emit-spill?/edx: boolean = not-yet-spilled-this-block?(v, vars)
     (not-yet-spilled-this-block? %ecx *(ebp+0x10))  # => eax
     89/<- %edx 0/r32/eax
     3d/compare-eax-and 0/imm32/false
     0f 84/jump-if-= $push-output-and-maybe-emit-spill:push/disp32
-    (will-not-write-some-register? %ecx *(ebp+0x14) *(ebp+0x18))  # => eax
-    89/<- %edx 0/r32/eax
-    # check emit-spill?
-    3d/compare-eax-and 0/imm32/false
-    0f 84/jump-if-= $push-output-and-maybe-emit-spill:push/disp32
     # TODO: assert(size-of(output) == 4)
     # *Curr-local-stack-offset -= 4
     81 5/subop/subtract *Curr-local-stack-offset 4/imm32
@@ -18779,14 +18725,14 @@ $push-output-and-maybe-emit-spill:end:
 
 $push-output-and-maybe-emit-spill:abort:
     # error("var '" var->name "' initialized from an instruction must live in a register\n")
-    (write-buffered *(ebp+0x1c) "var '")
-    (write-buffered *(ebp+0x1c) *eax)  # Var-name
-    (write-buffered *(ebp+0x1c) "' initialized from an instruction must live in a register\n")
-    (flush *(ebp+0x1c))
-    (stop *(ebp+0x20) 1)
+    (write-buffered *(ebp+0x18) "var '")
+    (write-buffered *(ebp+0x18) *eax)  # Var-name
+    (write-buffered *(ebp+0x18) "' initialized from an instruction must live in a register\n")
+    (flush *(ebp+0x18))
+    (stop *(ebp+0x1c) 1)
     # never gets here
 
-emit-subx-cleanup-and-unconditional-nonlocal-branch:  # out: (addr buffered-file), stmt: (addr stmt1), vars: (addr stack live-var)
+emit-subx-cleanup-and-unconditional-nonlocal-branch:  # out: (addr buffered-file), stmt: (addr stmt1), vars: (addr stack live-var), fn: (addr function)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -18800,7 +18746,7 @@ emit-subx-cleanup-and-unconditional-nonlocal-branch:  # out: (addr buffered-file
     (lookup *eax *(eax+4))  # Stmt-var-value Stmt-var-value => eax
     (lookup *eax *(eax+4))  # Var-name Var-name => eax
     # clean up until target block
-    (emit-cleanup-code-until-target *(ebp+8) *(ebp+0x10) %eax)
+    (emit-cleanup-code-until-target *(ebp+8) *(ebp+0x10) %eax *(ebp+0x14))
     # emit jump to target block
     (emit-indent *(ebp+8) *Curr-block-depth)
     (write-buffered *(ebp+8) "e9/jump ")
@@ -18984,7 +18930,7 @@ $emit-unconditional-jump-to-depth:end:
 
 # emit clean-up code for 'vars' until some block depth
 # doesn't actually modify 'vars' so we need traverse manually inside the stack
-emit-cleanup-code-until-depth:  # out: (addr buffered-file), vars: (addr stack live-var), until-block-depth: int
+emit-cleanup-code-until-depth:  # out: (addr buffered-file), vars: (addr stack live-var), until-block-depth: int, fn: (addr function)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -19032,8 +18978,7 @@ $emit-cleanup-code-until-depth:check-for-previous-spill:
           3d/compare-eax-and 0/imm32/false
           74/jump-if-= break/disp8
 $emit-cleanup-code-until-depth:reclaim-var-in-register:
-          (lookup *(ebx+0x18) *(ebx+0x1c))  # Var-register Var-register => eax
-          (emit-pop-register *(ebp+8) %eax)
+          (emit-pop-register *(ebp+8) %ebx *(ebp+0xc) *(ebp+0x14))
         }
         eb/jump $emit-cleanup-code-until-depth:continue/disp8
       }
@@ -19105,16 +19050,21 @@ $emit-push-register:end:
     5d/pop-to-ebp
     c3/return
 
-emit-pop-register:  # out: (addr buffered-file), reg: (addr array byte)
+emit-pop-register:  # out: (addr buffered-file), regvar: (addr var), vars: (addr stack live-var), fn: (addr function)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
     # . save registers
     50/push-eax
-    # eax = reg
-    8b/-> *(ebp+0xc) 0/r32/eax
+    51/push-ecx
+    56/push-esi
+    # esi = regvar
+    8b/-> *(ebp+0xc) 6/r32/esi
+    # var reg/ecx: (addr array byte) = lookup(regvar->register)
+    (lookup *(esi+0x18) *(esi+0x1c))  # Var-register Var-register => eax
+    89/<- %ecx 0/r32/eax
     # var prefix/eax: byte = reg->data[0]
-    8a/copy-byte *(eax+4) 0/r32/AL
+    8a/copy-byte *(ecx+4) 0/r32/AL
     81 4/subop/and %eax 0xff/imm32
     # if (prefix == 'x') pop to xmm register
     {
@@ -19124,8 +19074,7 @@ emit-pop-register:  # out: (addr buffered-file), reg: (addr array byte)
       (emit-indent *(ebp+8) *Curr-block-depth)
       (write-buffered *(ebp+8) "f3 0f 10/-> *esp ")
       # var prefix/eax: byte = reg->data[3]
-      8b/-> *(ebp+0xc) 0/r32/eax
-      8a/copy-byte *(eax+7) 0/r32/AL
+      8a/copy-byte *(ecx+7) 0/r32/AL
       81 4/subop/and %eax 0xff/imm32
       (write-byte-buffered *(ebp+8) %eax)
       (write-buffered *(ebp+8) "/x32\n")
@@ -19136,10 +19085,12 @@ emit-pop-register:  # out: (addr buffered-file), reg: (addr array byte)
     # otherwise pop to gp register
     (emit-indent *(ebp+8) *Curr-block-depth)
     (write-buffered *(ebp+8) "8f 0/subop/pop %")
-    (write-buffered *(ebp+8) *(ebp+0xc))
+    (write-buffered *(ebp+8) %ecx)
     (write-buffered *(ebp+8) Newline)
 $emit-pop-register:end:
     # . restore registers
+    5e/pop-to-esi
+    59/pop-to-ecx
     58/pop-to-eax
     # . epilogue
     89/<- %esp 5/r32/ebp
@@ -19148,7 +19099,7 @@ $emit-pop-register:end:
 
 # emit clean-up code for 'vars' until a given label is encountered
 # doesn't actually modify 'vars' so we need traverse manually inside the stack
-emit-cleanup-code-until-target:  # out: (addr buffered-file), vars: (addr stack live-var), until-block-label: (addr array byte)
+emit-cleanup-code-until-target:  # out: (addr buffered-file), vars: (addr stack live-var), until-block-label: (addr array byte), fn: (addr function)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -19188,8 +19139,7 @@ $emit-cleanup-code-until-target:check-for-previous-spill:
           3d/compare-eax-and 0/imm32/false
           74/jump-if-= break/disp8
 $emit-cleanup-code-until-target:reclaim-var-in-register:
-          (lookup *(ebx+0x18) *(ebx+0x1c))  # Var-register Var-register => eax
-          (emit-pop-register *(ebp+8) %eax)
+          (emit-pop-register *(ebp+8) %ebx *(ebp+0xc) *(ebp+0x14))
         }
         eb/jump $emit-cleanup-code-until-target:continue/disp8
       }
@@ -19369,35 +19319,6 @@ $not-yet-spilled-this-block?:end:
     5d/pop-to-ebp
     c3/return
 
-# could the register of 'v' ever be written to by one of the vars in fn-outputs?
-will-not-write-some-register?:  # v: (addr var), stmts: (addr list stmt), fn: (addr function) -> result/eax: boolean
-    # . prologue
-    55/push-ebp
-    89/<- %ebp 4/r32/esp
-    # eax = v
-    8b/-> *(ebp+8) 0/r32/eax
-    # var reg/eax: (addr array byte) = lookup(v->register)
-    (lookup *(eax+0x18) *(eax+0x1c))  # Var-register Var-register => eax
-    # var target/eax: (addr var) = find-register(fn-outputs, reg)
-    (find-register *(ebp+0x10) %eax)  # => eax
-    # if (target == 0) return true
-    {
-      3d/compare-eax-and 0/imm32
-      75/jump-if-!= break/disp8
-      b8/copy-to-eax 1/imm32/true
-      eb/jump $will-not-write-some-register?:end/disp8
-    }
-    # return !assigns-in-stmts?(stmts, target)
-    (assigns-in-stmts? *(ebp+0xc) %eax)  # => eax
-    3d/compare-eax-and 0/imm32/false
-    # assume: true = 1, so no need to mask with 0x000000ff
-    0f 94/set-if-= %al
-$will-not-write-some-register?:end:
-    # . epilogue
-    89/<- %esp 5/r32/ebp
-    5d/pop-to-ebp
-    c3/return
-
 # return fn output with matching register
 # always returns false if 'reg' is null
 find-register:  # fn: (addr function), reg: (addr array byte) -> result/eax: (addr var)
@@ -19617,14 +19538,8 @@ $same-register-spilled-before?:end:
 # This would be a simple series of pops, if it wasn't for fn outputs, which
 # can occur anywhere in the stack.
 # So we have to _compact_ the entire array underlying the stack.
-#
-# We want to allow a fn output register to be written to by locals before the
-# output is set.
-# So fn outputs can't just be pushed at the start of the function.
-#
-# We want to allow other locals to shadow a fn output register after the
-# output is set.
-# So the output can't just always override anything in the stack. Sequence matters.
+# If a register already contains a function output, we drop the var to avoid
+# clobbering it.
 clean-up-blocks:  # vars: (addr stack live-var), until-block-depth: int, fn: (addr function)
     # pseudocode:
     #   to = vars->top  (which points outside the stack)