about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xapps/mubin466086 -> 485605 bytes
-rw-r--r--apps/mu.subx630
-rw-r--r--mu.md4
-rw-r--r--mu_instructions11
4 files changed, 644 insertions, 1 deletions
diff --git a/apps/mu b/apps/mu
index 8d77f092..dec12a22 100755
--- a/apps/mu
+++ b/apps/mu
Binary files differdiff --git a/apps/mu.subx b/apps/mu.subx
index e353c17c..28ea0201 100644
--- a/apps/mu.subx
+++ b/apps/mu.subx
@@ -10082,6 +10082,358 @@ test-length-with-too-many-outputs:
     5d/pop-to-ebp
     c3/return
 
+test-convert-function-with-return-literal:
+    # . 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 -> x/eax: int {\n")
+    (write _test-input-stream "  return 0\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-convert-function-with-return-literal/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-return-literal/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-return-literal/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-return-literal/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-return-literal/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-return-literal/5")
+    (check-next-stream-line-equal _test-output-stream "    c7 0/subop/copy %eax 0/imm32"  "F - test-convert-function-with-return-literal/6")
+    (check-next-stream-line-equal _test-output-stream "    e9/jump $foo:0x00000001:break/disp32"  "F - test-convert-function-with-return-literal/7")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-return-literal/8")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-return-literal/9")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-return-literal/10")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-return-literal/11")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-return-literal/12")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-return-literal/13")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-convert-function-with-return:
+    # . 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 -> x/eax: int {\n")
+    (write _test-input-stream "  var y: int\n")
+    (write _test-input-stream "  return y\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-convert-function-with-return/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-return/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-return/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-return/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-return/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-return/5")
+    (check-next-stream-line-equal _test-output-stream "    68/push 0/imm32"     "F - test-convert-function-with-return/6")  # y
+    (check-next-stream-line-equal _test-output-stream "    8b/-> *(ebp+0xfffffffc) 0x00000000/r32" "F - test-convert-function-with-return/7")
+    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-return/8")
+    (check-next-stream-line-equal _test-output-stream "    e9/jump $foo:0x00000001:break/disp32"  "F - test-convert-function-with-return/9")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-return/10")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-return/11")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-return/12")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-return/13")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-return/14")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-return/15")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-convert-function-with-return-register:
+    # . 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 -> x/eax: int {\n")
+    (write _test-input-stream "  var y/eax: int <- copy 3\n")
+    (write _test-input-stream "  return y\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-convert-function-with-return-register/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-return-register/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-return-register/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-return-register/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-return-register/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-return-register/5")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"  "F - test-convert-function-with-return-register/6")
+    (check-next-stream-line-equal _test-output-stream "    b8/copy-to-eax 3/imm32"  "F - test-convert-function-with-return-register/7")
+    (check-next-stream-line-equal _test-output-stream "    8b/-> %eax 0x00000000/r32" "F - test-convert-function-with-return-register/8")
+    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %esp 4/imm32"  "F - test-convert-function-with-return-register/9")
+    (check-next-stream-line-equal _test-output-stream "    e9/jump $foo:0x00000001:break/disp32"  "F - test-convert-function-with-return-register/10")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-return-register/11")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-return-register/12")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-return-register/13")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-return-register/14")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-return-register/15")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-return-register/16")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-convert-function-with-return-register-and-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 -> x/eax: int {\n")
+    (write _test-input-stream "  var y/eax: int <- copy 3\n")
+    (write _test-input-stream "  var z/ecx: int <- copy 4\n")
+    (write _test-input-stream "  return y\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-convert-function-with-return-register-and-local/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-return-register-and-local/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-return-register-and-local/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-return-register-and-local/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-return-register-and-local/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-return-register-and-local/5")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"  "F - test-convert-function-with-return-register-and-local/6")
+    (check-next-stream-line-equal _test-output-stream "    b8/copy-to-eax 3/imm32"  "F - test-convert-function-with-return-register-and-local/7")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-function-with-return-register-and-local/8")
+    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 4/imm32"  "F - test-convert-function-with-return-register-and-local/9")
+    (check-next-stream-line-equal _test-output-stream "    8b/-> %eax 0x00000000/r32" "F - test-convert-function-with-return-register-and-local/10")
+    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx"         "F - test-convert-function-with-return-register-and-local/11")
+    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %esp 4/imm32"  "F - test-convert-function-with-return-register-and-local/12")
+    (check-next-stream-line-equal _test-output-stream "    e9/jump $foo:0x00000001:break/disp32"  "F - test-convert-function-with-return-register-and-local/13")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-return-register-and-local/14")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-return-register-and-local/15")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-return-register-and-local/16")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-return-register-and-local/17")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-return-register-and-local/18")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-return-register-and-local/19")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-convert-function-with-return-register-and-local-2:
+    # . 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 -> x/eax: int {\n")
+    (write _test-input-stream "  var y/eax: int <- copy 3\n")
+    (write _test-input-stream "  var z/ecx: int <- copy 4\n")
+    (write _test-input-stream "  return z\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-convert-function-with-return-register-and-local-2/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-return-register-and-local-2/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-return-register-and-local-2/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-return-register-and-local-2/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-return-register-and-local-2/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-return-register-and-local-2/5")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"  "F - test-convert-function-with-return-register-and-local-2/6")
+    (check-next-stream-line-equal _test-output-stream "    b8/copy-to-eax 3/imm32"  "F - test-convert-function-with-return-register-and-local-2/7")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-function-with-return-register-and-local-2/8")
+    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 4/imm32"  "F - test-convert-function-with-return-register-and-local-2/9")
+    (check-next-stream-line-equal _test-output-stream "    8b/-> %ecx 0x00000000/r32" "F - test-convert-function-with-return-register-and-local-2/10")
+    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx"         "F - test-convert-function-with-return-register-and-local-2/11")
+    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %esp 4/imm32"  "F - test-convert-function-with-return-register-and-local-2/12")
+    (check-next-stream-line-equal _test-output-stream "    e9/jump $foo:0x00000001:break/disp32"  "F - test-convert-function-with-return-register-and-local-2/13")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-return-register-and-local-2/14")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-return-register-and-local-2/15")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-return-register-and-local-2/16")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-return-register-and-local-2/17")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-return-register-and-local-2/18")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-return-register-and-local-2/19")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-convert-function-with-return-float-register-and-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 -> f/xmm1: float {\n")
+    (write _test-input-stream "  var y/eax: int <- copy 3\n")
+    (write _test-input-stream "  var g/xmm0: float <- convert y\n")
+    (write _test-input-stream "  var h/xmm1: float <- convert y\n")
+    (write _test-input-stream "  return g\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-convert-function-with-return-float-register-and-local/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-return-float-register-and-local/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-return-float-register-and-local/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-return-float-register-and-local/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-return-float-register-and-local/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-return-float-register-and-local/5")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"  "F - test-convert-function-with-return-float-register-and-local/6")  # var y
+    (check-next-stream-line-equal _test-output-stream "    b8/copy-to-eax 3/imm32"  "F - test-convert-function-with-return-float-register-and-local/7")
+    (check-next-stream-line-equal _test-output-stream "    81 5/subop/subtract %esp 4/imm32"  "F - test-convert-function-with-return-float-register-and-local/8")  # var g
+    (check-next-stream-line-equal _test-output-stream "    f3 0f 11/<- *esp 0/x32"  "F - test-convert-function-with-return-float-register-and-local/9")
+    (check-next-stream-line-equal _test-output-stream "    f3 0f 2a/convert-to-float %eax 0x00000000/x32"  "F - test-convert-function-with-return-float-register-and-local/10")
+    (check-next-stream-line-equal _test-output-stream "    81 5/subop/subtract %esp 4/imm32"  "F - test-convert-function-with-return-float-register-and-local/11")  # var h
+    (check-next-stream-line-equal _test-output-stream "    f3 0f 11/<- *esp 1/x32"  "F - test-convert-function-with-return-float-register-and-local/12")
+    (check-next-stream-line-equal _test-output-stream "    f3 0f 2a/convert-to-float %eax 0x00000001/x32"  "F - test-convert-function-with-return-float-register-and-local/13")
+    (check-next-stream-line-equal _test-output-stream "    f3 0f 10/-> %xmm0 0x00000001/x32"  "F - test-convert-function-with-return-float-register-and-local/14")  # return g
+    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %esp 4/imm32"  "F - test-convert-floating-point-dereferenced/15")  # reclaim h
+    (check-next-stream-line-equal _test-output-stream "    f3 0f 10/-> *esp 0/x32"  "F - test-convert-floating-point-dereferenced/16")  # reclaim g
+    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %esp 4/imm32"  "F - test-convert-floating-point-dereferenced/17")
+    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %eax"         "F - test-convert-function-with-return-float-register-and-local/18")  # reclaim y
+    (check-next-stream-line-equal _test-output-stream "    e9/jump $foo:0x00000001:break/disp32"  "F - test-convert-function-with-return-float-register-and-local/19")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-return-float-register-and-local/20")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-return-float-register-and-local/21")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-return-float-register-and-local/22")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-return-float-register-and-local/23")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-return-float-register-and-local/24")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-return-float-register-and-local/25")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-convert-function-with-return-and-local-vars:
+    # . 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 -> a/eax: int {\n")
+    (write _test-input-stream "  {\n")
+    (write _test-input-stream "    var x: int\n")
+    (write _test-input-stream "    {\n")
+    (write _test-input-stream "      var y: int\n")
+    (write _test-input-stream "      return y\n")
+    (write _test-input-stream "      increment x\n")
+    (write _test-input-stream "    }\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-convert-function-with-return-and-local-vars/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-return-and-local-vars/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-return-and-local-vars/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-return-and-local-vars/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-return-and-local-vars/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-return-and-local-vars/5")
+    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-return-and-local-vars/6")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-return-and-local-vars/7")
+    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-return-and-local-vars/8")  # var x
+    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-return-and-local-vars/9")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:"   "F - test-convert-function-with-return-and-local-vars/10")
+    (check-next-stream-line-equal _test-output-stream "        68/push 0/imm32" "F - test-convert-function-with-return-and-local-vars/11")  # var y
+    (check-next-stream-line-equal _test-output-stream "        8b/-> *(ebp+0xfffffff8) 0x00000000/r32" "F - test-convert-function-with-return-and-local-vars/12")
+    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-return-and-local-vars/13")
+    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-return-and-local-vars/14")
+    (check-next-stream-line-equal _test-output-stream "        e9/jump $foo:0x00000001:break/disp32"  "F - test-convert-function-with-return-and-local-vars/15")
+    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-return-and-local-vars/16")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:"  "F - test-convert-function-with-return-and-local-vars/17")
+    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-return-and-local-vars/18")
+    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-return-and-local-vars/19")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-return-and-local-vars/20")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-return-and-local-vars/21")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-return-and-local-vars/22")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-return-and-local-vars/23")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-return-and-local-vars/24")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-return-and-local-vars/25")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-return-and-local-vars/26")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 #######################################################
 # Parsing
 #######################################################
@@ -15418,6 +15770,10 @@ has-primitive-name?:  # stmt: (addr stmt) -> result/eax: boolean
     8b/-> *(ebp+8) 6/r32/esi
     (lookup *(esi+4) *(esi+8))  # Stmt1-operation Stmt1-operation => eax
     89/<- %esi 0/r32/eax
+    # if (name == "return") return true
+    (string-equal? %esi "return")  # => eax
+    3d/compare-eax-and 0/imm32/false
+    0f 85/jump-if-!= $has-primitive-name?:end/disp32
     # if (name == "get") return true
     (string-equal? %esi "get")  # => eax
     3d/compare-eax-and 0/imm32/false
@@ -15534,6 +15890,14 @@ check-mu-primitive:  # stmt: (addr stmt), fn: (addr function), err: (addr buffer
       (check-mu-address-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
       e9/jump $check-mu-primitive:end/disp32
     }
+    # if (op == "return") check-mu-return-stmt
+    {
+      (string-equal? %ecx "return")  # => eax
+      3d/compare-eax-and 0/imm32/false
+      74/jump-if-= break/disp8
+      (check-mu-return-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
+      e9/jump $check-mu-primitive:end/disp32
+    }
     # if (op == "get") check-mu-get-stmt
     {
       (string-equal? %ecx "get")  # => eax
@@ -15877,6 +16241,18 @@ $check-mu-address-stmt:end:
     5d/pop-to-ebp
     c3/return
 
+check-mu-return-stmt:  # stmt: (addr stmt), fn: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+$check-mu-return-stmt:end:
+    # . restore registers
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 check-mu-get-stmt:  # stmt: (addr stmt), fn: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
     # . prologue
     55/push-ebp
@@ -18512,6 +18888,27 @@ $emit-subx-stmt-list:stmt1:
           3d/compare-eax-and 0/imm32/false
           0f 84/jump-if-= break/disp32
 $emit-subx-stmt-list:branch-stmt:
+          # unconditional return {{{
+          {
+$emit-subx-stmt-list:return:
+            # if (!string-equal?(curr-stmt->operation, "return")) break
+            (lookup *(ecx+4) *(ecx+8))  # Stmt1-operation Stmt1-operation => eax
+            (string-equal? %eax "return")  # => eax
+            3d/compare-eax-and 0/imm32/false
+            0f 84/jump-if-= break/disp32
+            #
+            (emit-outputs *(ebp+8) %ecx *(ebp+0x14))
+            (emit-cleanup-code-for-non-outputs *(ebp+8) *(ebp+0x10) *(ebp+0x14))
+            # emit jump to end of function
+            (emit-indent *(ebp+8) *Curr-block-depth)
+            (write-buffered *(ebp+8) "e9/jump $")
+            8b/-> *(ebp+0x14) 0/r32/eax
+            (lookup *eax *(eax+4))  # Function-name Function-name => eax
+            (write-buffered *(ebp+8) %eax)
+            (write-buffered *(ebp+8) ":0x00000001:break/disp32\n")
+            e9/jump $emit-subx-stmt-list:clean-up/disp32
+          }
+          # }}}
           # unconditional loops {{{
           {
             # if (!string-equal?(var->operation, "loop")) break
@@ -18772,6 +19169,106 @@ $emit-subx-cleanup-and-unconditional-nonlocal-branch:end:
     5d/pop-to-ebp
     c3/return
 
+emit-outputs:  # out: (addr buffered-file), return-stmt: (addr stmt1), fn: (addr function)
+    # pseudocode:
+    #   for every inout, output in return-stmt, fn->outputs
+    #     if inout is a literal
+    #       c7 0/subop/copy %output inout/imm32
+    #     otherwise
+    #       8b/-> inout %output
+    #
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    56/push-esi
+    57/push-edi
+    # var curr-inout/esi: (addr stmt-var) = return-stmt->inouts
+    8b/-> *(ebp+0xc) 0/r32/eax
+    (lookup *(eax+0xc) *(eax+0x10))  # Stmt1-inouts Stmt1-inouts => eax
+    89/<- %esi 0/r32/eax
+    # var curr-output/edi: (addr list var) = fn->outputs
+    8b/-> *(ebp+0x10) 0/r32/eax
+    (lookup *(eax+0x10) *(eax+0x14))  # Function-outputs Function-outputs => eax
+    89/<- %edi 0/r32/eax
+    {
+$emit-outputs:loop:
+      81 7/subop/compare %esi 0/imm32
+      0f 84/jump-if-= break/disp32
+      # emit copy to output register
+      # var curr-var/ecx = lookup(curr-inout->value)
+      (lookup *esi *(esi+4))  # Stmt-var-value Stmt-var-value => eax
+      89/<- %ecx 0/r32/eax
+      # if curr-var is a literal, emit copy of a literal to the output
+      (lookup *(ecx+8) *(ecx+0xc))  # Var-type Var-type => eax
+      (is-simple-mu-type? %eax 0)  # literal => eax
+      {
+        3d/compare-eax-and 0/imm32/false
+        0f 84/jump-if-= break/disp32
+        (emit-indent *(ebp+8) *Curr-block-depth)
+        (write-buffered *(ebp+8) "c7 0/subop/copy %")
+        (lookup *edi *(edi+4))  # List-value List-value => eax
+        (lookup *(eax+0x18) *(eax+0x1c))  # Var-register Var-register => eax
+        (write-buffered *(ebp+8) %eax)
+        (write-buffered *(ebp+8) " ")
+        (lookup *ecx *(ecx+4))  # Var-name Var-name => eax
+        (write-buffered *(ebp+8) %eax)
+        (write-buffered *(ebp+8) "/imm32\n")
+        e9/jump $emit-outputs:continue/disp32
+      }
+      # if the non-literal is a register starting with "x", emit a floating-point copy
+      (lookup *(ecx+0x18) *(ecx+0x1c))  # Var-register Var-register => eax
+      {
+        3d/compare-eax-and 0/imm32
+        0f 84/jump-if-= break/disp32
+        8a/copy-byte *(eax+4) 0/r32/AL
+        81 4/subop/and %eax 0xff/imm32
+        3d/compare-eax-and 0x78/imm32/x
+        0f 85/jump-if-!= break/disp32
+        (emit-indent *(ebp+8) *Curr-block-depth)
+        (write-buffered *(ebp+8) "f3 0f 10/->")
+        (emit-subx-var-as-rm32 *(ebp+8) %esi)
+        (write-buffered *(ebp+8) " ")
+        (lookup *edi *(edi+4))  # List-value List-value => eax
+        (lookup *(eax+0x18) *(eax+0x1c))  # Var-register Var-register => eax
+        (get Mu-registers %eax 0xc "Mu-registers")  # => eax
+        (write-int32-hex-buffered *(ebp+8) *eax)
+        (write-buffered *(ebp+8) "/x32\n")
+        e9/jump $emit-outputs:continue/disp32
+      }
+      # otherwise emit an integer copy
+      (emit-indent *(ebp+8) *Curr-block-depth)
+      (write-buffered *(ebp+8) "8b/->")
+      (emit-subx-var-as-rm32 *(ebp+8) %esi)
+      (write-buffered *(ebp+8) " ")
+      (lookup *edi *(edi+4))  # List-value List-value => eax
+      (lookup *(eax+0x18) *(eax+0x1c))  # Var-register Var-register => eax
+      (get Mu-registers %eax 0xc "Mu-registers")  # => eax
+      (write-int32-hex-buffered *(ebp+8) *eax)
+      (write-buffered *(ebp+8) "/r32\n")
+$emit-outputs:continue:
+      # curr-inout = curr-inout->next
+      (lookup *(esi+8) *(esi+0xc))  # Stmt-var-next Stmt-var-next => eax
+      89/<- %esi 0/r32/eax
+      # curr-output = curr-output->next
+      (lookup *(edi+8) *(edi+0xc))  # List-next List-next => eax
+      89/<- %edi 0/r32/eax
+      #
+      e9/jump loop/disp32
+    }
+$emit-outputs:end:
+    # . restore registers
+    5f/pop-to-edi
+    5e/pop-to-esi
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 is-mu-branch?:  # stmt: (addr stmt1) -> result/eax: boolean
     # . prologue
     55/push-ebp
@@ -18786,8 +19283,12 @@ is-mu-branch?:  # stmt: (addr stmt1) -> result/eax: boolean
     (string-starts-with? %ecx "loop")  # => eax
     3d/compare-eax-and 0/imm32/false
     75/jump-if-not-equal $is-mu-branch?:end/disp8
-    # otherwise return (stmt->operation starts with "break")
+    # if (stmt->operation starts with "break") return true
     (string-starts-with? %ecx "break")  # => eax
+    3d/compare-eax-and 0/imm32/false
+    75/jump-if-not-equal $is-mu-branch?:end/disp8
+    # otherwise return (stmt->operation starts with "return")
+    (string-starts-with? %ecx "return")  # => eax
 $is-mu-branch?:end:
     # . restore registers
     59/pop-to-ecx
@@ -19014,6 +19515,95 @@ $emit-cleanup-code-until-depth:end:
     5d/pop-to-ebp
     c3/return
 
+# emit clean-up code for 'vars' that don't conflict with output registers
+# doesn't actually modify 'vars' so we need traverse manually inside the stack
+emit-cleanup-code-for-non-outputs:  # out: (addr buffered-file), vars: (addr stack live-var), fn: (addr function)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    52/push-edx
+    53/push-ebx
+    56/push-esi
+    57/push-edi
+    # ecx = vars
+    8b/-> *(ebp+0xc) 1/r32/ecx
+    # var esi: int = vars->top
+    8b/-> *ecx 6/r32/esi
+    # var curr/esi: (addr handle var) = &vars->data[vars->top - 12]
+    8d/copy-address *(ecx+esi-4) 6/r32/esi  # vars + 8 + vars->top - 12/Live-var-size
+    # var min/ecx: (addr handle var) = vars->data
+    81 0/subop/add %ecx 8/imm32
+    {
+$emit-cleanup-code-for-non-outputs:loop:
+      # if (curr < min) break
+      39/compare %esi 1/r32/ecx
+      0f 82/jump-if-addr< break/disp32
+      # var v/ebx: (addr var) = lookup(*curr)
+      (lookup *esi *(esi+4))  # => eax
+      89/<- %ebx 0/r32/eax
+      # if v is in a register
+      81 7/subop/compare *(ebx+0x18) 0/imm32  # Var-register
+      {
+        0f 84/jump-if-= break/disp32
+        {
+$emit-cleanup-code-for-non-outputs:check-for-previous-spill:
+          8b/-> *(esi+8) 0/r32/eax  # Live-var-register-spilled
+          3d/compare-eax-and 0/imm32/false
+          0f 84/jump-if-= break/disp32
+$emit-cleanup-code-for-non-outputs:reclaim-var-in-register:
+          # var reg/edi: (addr array name) = v->register
+          (lookup *(ebx+0x18) *(ebx+0x1c))  # Var-register Var-register => eax
+          89/<- %edi 0/r32/eax
+          # if reg is not in function outputs, emit a pop
+          (reg-in-function-outputs? *(ebp+0x10) %edi)  # => eax
+          3d/compare-eax-and 0/imm32/false
+          {
+            75/jump-if-!= break/disp8
+            (emit-pop-register *(ebp+8) %edi)
+            eb/jump $emit-cleanup-code-for-non-outputs:reclaim-var-in-register-done/disp8
+          }
+          # otherwise just drop it from the stack
+          (emit-indent *(ebp+8) *Curr-block-depth)
+          (write-buffered *(ebp+8) "81 0/subop/add %esp 4/imm32\n")
+        }
+$emit-cleanup-code-for-non-outputs:reclaim-var-in-register-done:
+        eb/jump $emit-cleanup-code-for-non-outputs:continue/disp8
+      }
+      # otherwise v is on the stack
+      {
+        75/jump-if-!= break/disp8
+$emit-cleanup-code-for-non-outputs:var-on-stack:
+        (size-of %ebx)  # => eax
+        # don't emit code for labels
+        3d/compare-eax-and 0/imm32
+        74/jump-if-= break/disp8
+$emit-cleanup-code-for-non-outputs:reclaim-var-on-stack:
+        (emit-indent *(ebp+8) *Curr-block-depth)
+        (write-buffered *(ebp+8) "81 0/subop/add %esp ")
+        (write-int32-hex-buffered *(ebp+8) %eax)
+        (write-buffered *(ebp+8) "/imm32\n")
+      }
+$emit-cleanup-code-for-non-outputs:continue:
+      # curr -= 12
+      81 5/subop/subtract %esi 0xc/imm32
+      e9/jump loop/disp32
+    }
+$emit-cleanup-code-for-non-outputs:end:
+    # . restore registers
+    5f/pop-to-edi
+    5e/pop-to-esi
+    5b/pop-to-ebx
+    5a/pop-to-edx
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 emit-push-register:  # out: (addr buffered-file), reg: (addr array byte)
     # . prologue
     55/push-ebp
@@ -19725,6 +20315,44 @@ $in-function-outputs?:end:
     5d/pop-to-ebp
     c3/return
 
+reg-in-function-outputs?:  # fn: (addr function), target: (addr array byte) -> result/eax: boolean
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    51/push-ecx
+    # var curr/ecx: (addr list var) = lookup(fn->outputs)
+    8b/-> *(ebp+8) 1/r32/ecx
+    (lookup *(ecx+0x10) *(ecx+0x14))  # Function-outputs Function-outputs => eax
+    89/<- %ecx 0/r32/eax
+    # while curr != null
+    {
+      81 7/subop/compare %ecx 0/imm32
+      74/jump-if-= break/disp8
+      # var v/eax: (addr var) = lookup(curr->value)
+      (lookup *ecx *(ecx+4))  # List-value List-value => eax
+      # var reg/eax: (addr array byte) = lookup(v->register)
+      (lookup *(eax+0x18) *(eax+0x1c))  # Var-register Var-register => eax
+      # if (reg == target) return true
+      (string-equal? %eax *(ebp+0xc))  # => eax
+      3d/compare-eax-and 0/imm32/false
+      75/jump-if-!= $reg-in-function-outputs?:end/disp8
+      # curr = curr->next
+      (lookup *(ecx+8) *(ecx+0xc))  # List-next List-next => eax
+      89/<- %ecx 0/r32/eax
+      #
+      eb/jump loop/disp8
+    }
+    # return false
+    b8/copy-to-eax 0/imm32
+$reg-in-function-outputs?:end:
+    # . restore registers
+    59/pop-to-ecx
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 emit-subx-var-def:  # out: (addr buffered-file), stmt: (addr stmt)
     # . prologue
     55/push-ebp
diff --git a/mu.md b/mu.md
index 81c057df..bb8b68c9 100644
--- a/mu.md
+++ b/mu.md
@@ -85,6 +85,10 @@ fn g {
 }
 ```
 
+You can exit a function at any time with the `return` instruction. Give it the
+right number of arguments, and it'll assign them respectively to the function's
+outputs before jumping back to the caller.
+
 The function `main` is special; it is where the program starts running. It
 must always return a single int in register `ebx` (as the exit status of the
 process). It can also optionally accept an array of strings as input (from the
diff --git a/mu_instructions b/mu_instructions
index bece4fc1..253ede30 100644
--- a/mu_instructions
+++ b/mu_instructions
@@ -290,6 +290,17 @@ Similar float variants like `break-if-float<` are aliases for the corresponding
 `addr` equivalents. The x86 instruction set stupidly has floating-point
 operations only update a subset of flags.
 
+## Returns
+
+The `return` instruction cleans up variable declarations just like an unconditional
+`jump` to end of function, but also emits a series of copies before the final
+`jump`, copying each argument of `return` to the register appropriate to the
+respective function output. This doesn't work if a function output register
+contains a later `return` argument (e.g. if the registers for two outputs are
+swapped in `return`), so you can't do that.
+
+return                            => "c3/return"
+
 ---
 
 In the following instructions types are provided for clarity even if they must