about summary refs log tree commit diff stats
path: root/apps/mu.subx
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2020-10-30 20:23:56 -0700
committerKartik Agaram <vc@akkartik.com>2020-10-30 20:23:56 -0700
commit8c44afcccc12ce0715f780ee70c312bd546c57c7 (patch)
tree093756f9609346a2a0d2647281384109ae3a5e86 /apps/mu.subx
parent264acd9ec91a7e75dc43ec57d863777b7812cc52 (diff)
downloadmu-8c44afcccc12ce0715f780ee70c312bd546c57c7.tar.gz
7145 - roll back to 7143
Diffstat (limited to 'apps/mu.subx')
-rw-r--r--apps/mu.subx263
1 files changed, 174 insertions, 89 deletions
diff --git a/apps/mu.subx b/apps/mu.subx
index 2e04009b..98f77faa 100644
--- a/apps/mu.subx
+++ b/apps/mu.subx
@@ -2606,7 +2606,58 @@ test-always-shadow-outermost-reg-vars-in-function:
     5d/pop-to-ebp
     c3/return
 
-test-shadow-local:
+_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:
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -2633,29 +2684,29 @@ test-shadow-local:
 #?     (rewind-stream _test-output-stream)
 #?     # }}}
     # check output
-    (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")
+    (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")
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -2871,7 +2922,7 @@ test-spill-different-register-in-block:
     5d/pop-to-ebp
     c3/return
 
-test-shadow-output:
+test-shadow-live-output:
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -2898,27 +2949,27 @@ test-shadow-output:
 #?     (rewind-stream _test-output-stream)
 #?     # }}}
     # check output
-    (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")
+    (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")
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -2997,16 +3048,14 @@ 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 "    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")
+    (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")
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -18536,7 +18585,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) *(ebp+0x14))
+              (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
               e9/jump $emit-subx-stmt-list:clean-up/disp32
             }
           }
@@ -18553,7 +18602,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) *(ebp+0x14))
+            (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
             e9/jump $emit-subx-stmt-list:clean-up/disp32
           }
           # }}}
@@ -18605,7 +18654,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) *(ebp+0x14))
+            (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
             # cleanup epilogue
             ff 1/subop/decrement *Curr-block-depth
             (emit-indent *(ebp+8) *Curr-block-depth)
@@ -18637,7 +18686,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+0x18) *(ebp+0x1c))
+        (push-output-and-maybe-emit-spill *(ebp+8) %ecx *(ebp+0x10) %esi *(ebp+0x14) *(ebp+0x18) *(ebp+0x1c))
         # emit the instruction as usual
         (emit-subx-stmt *(ebp+8) %ecx Primitives *(ebp+0x18) *(ebp+0x1c))
         #
@@ -18650,7 +18699,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 *(ebp+0x14))
+    (emit-cleanup-code-until-depth *(ebp+8) *(ebp+0x10) *Curr-block-depth)
 $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))
@@ -18666,7 +18715,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), 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), fn: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -18695,11 +18744,16 @@ 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?(v, vars)
+    # var emit-spill?/edx: boolean = not-yet-spilled-this-block? && will-not-write-some-register?(fn)
     (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
@@ -18725,14 +18779,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+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)
+    (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)
     # never gets here
 
-emit-subx-cleanup-and-unconditional-nonlocal-branch:  # out: (addr buffered-file), stmt: (addr stmt1), vars: (addr stack live-var), fn: (addr function)
+emit-subx-cleanup-and-unconditional-nonlocal-branch:  # out: (addr buffered-file), stmt: (addr stmt1), vars: (addr stack live-var)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -18746,7 +18800,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 *(ebp+0x14))
+    (emit-cleanup-code-until-target *(ebp+8) *(ebp+0x10) %eax)
     # emit jump to target block
     (emit-indent *(ebp+8) *Curr-block-depth)
     (write-buffered *(ebp+8) "e9/jump ")
@@ -18930,7 +18984,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, fn: (addr function)
+emit-cleanup-code-until-depth:  # out: (addr buffered-file), vars: (addr stack live-var), until-block-depth: int
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -18978,7 +19032,8 @@ $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:
-          (emit-pop-register *(ebp+8) %ebx *(ebp+0xc) *(ebp+0x14))
+          (lookup *(ebx+0x18) *(ebx+0x1c))  # Var-register Var-register => eax
+          (emit-pop-register *(ebp+8) %eax)
         }
         eb/jump $emit-cleanup-code-until-depth:continue/disp8
       }
@@ -19050,21 +19105,16 @@ $emit-push-register:end:
     5d/pop-to-ebp
     c3/return
 
-emit-pop-register:  # out: (addr buffered-file), regvar: (addr var), vars: (addr stack live-var), fn: (addr function)
+emit-pop-register:  # out: (addr buffered-file), reg: (addr array byte)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
     # . save registers
     50/push-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
+    # eax = reg
+    8b/-> *(ebp+0xc) 0/r32/eax
     # var prefix/eax: byte = reg->data[0]
-    8a/copy-byte *(ecx+4) 0/r32/AL
+    8a/copy-byte *(eax+4) 0/r32/AL
     81 4/subop/and %eax 0xff/imm32
     # if (prefix == 'x') pop to xmm register
     {
@@ -19074,7 +19124,8 @@ emit-pop-register:  # out: (addr buffered-file), regvar: (addr var), vars: (addr
       (emit-indent *(ebp+8) *Curr-block-depth)
       (write-buffered *(ebp+8) "f3 0f 10/-> *esp ")
       # var prefix/eax: byte = reg->data[3]
-      8a/copy-byte *(ecx+7) 0/r32/AL
+      8b/-> *(ebp+0xc) 0/r32/eax
+      8a/copy-byte *(eax+7) 0/r32/AL
       81 4/subop/and %eax 0xff/imm32
       (write-byte-buffered *(ebp+8) %eax)
       (write-buffered *(ebp+8) "/x32\n")
@@ -19085,12 +19136,10 @@ emit-pop-register:  # out: (addr buffered-file), regvar: (addr var), vars: (addr
     # otherwise pop to gp register
     (emit-indent *(ebp+8) *Curr-block-depth)
     (write-buffered *(ebp+8) "8f 0/subop/pop %")
-    (write-buffered *(ebp+8) %ecx)
+    (write-buffered *(ebp+8) *(ebp+0xc))
     (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
@@ -19099,7 +19148,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), fn: (addr function)
+emit-cleanup-code-until-target:  # out: (addr buffered-file), vars: (addr stack live-var), until-block-label: (addr array byte)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -19139,7 +19188,8 @@ $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:
-          (emit-pop-register *(ebp+8) %ebx *(ebp+0xc) *(ebp+0x14))
+          (lookup *(ebx+0x18) *(ebx+0x1c))  # Var-register Var-register => eax
+          (emit-pop-register *(ebp+8) %eax)
         }
         eb/jump $emit-cleanup-code-until-target:continue/disp8
       }
@@ -19319,6 +19369,35 @@ $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)
@@ -19538,8 +19617,14 @@ $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.
-# If a register already contains a function output, we drop the var to avoid
-# clobbering it.
+#
+# 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.
 clean-up-blocks:  # vars: (addr stack live-var), until-block-depth: int, fn: (addr function)
     # pseudocode:
     #   to = vars->top  (which points outside the stack)