about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--054string-equal.subx80
-rwxr-xr-xapps/assortbin40163 -> 40210 bytes
-rwxr-xr-xapps/bracesbin41857 -> 41904 bytes
-rwxr-xr-xapps/callsbin46572 -> 46619 bytes
-rwxr-xr-xapps/crenshaw2-1bin39571 -> 39618 bytes
-rwxr-xr-xapps/crenshaw2-1bbin40118 -> 40165 bytes
-rwxr-xr-xapps/dquotesbin43813 -> 43860 bytes
-rwxr-xr-xapps/factorialbin38590 -> 38637 bytes
-rwxr-xr-xapps/handlebin39488 -> 39535 bytes
-rwxr-xr-xapps/hexbin42410 -> 42457 bytes
-rwxr-xr-xapps/mubin95695 -> 100192 bytes
-rw-r--r--apps/mu.subx428
-rwxr-xr-xapps/packbin52555 -> 52602 bytes
-rwxr-xr-xapps/sigilsbin54242 -> 54289 bytes
-rwxr-xr-xapps/surveybin49404 -> 49451 bytes
-rwxr-xr-xapps/testsbin38961 -> 39008 bytes
16 files changed, 476 insertions, 32 deletions
diff --git a/054string-equal.subx b/054string-equal.subx
index 10706302..dff7a485 100644
--- a/054string-equal.subx
+++ b/054string-equal.subx
@@ -16,10 +16,52 @@ Entry:  # run all tests
 string-equal?:  # s: (addr array byte), benchmark: (addr array byte) -> eax: boolean
     # pseudocode:
     #   if (s->length != benchmark->length) return false
+    #   return string-starts-with?(s, benchmark)
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    51/push-ecx
+    56/push-esi
+    57/push-edi
+    # esi = s
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
+    # edi = benchmark
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
+    # ecx = s->length
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
+$string-equal?:lengths:
+    # if (ecx != benchmark->length) return false
+    39/compare                      0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # compare *edi and ecx
+    b8/copy-to-eax  0/imm32/false
+    75/jump-if-!=  $string-equal?:end/disp8
+$string-equal?:contents:
+    # string-starts-with?(s, benchmark)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+0xc)
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  string-starts-with?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+$string-equal?:end:
+    # . restore registers
+    5f/pop-to-edi
+    5e/pop-to-esi
+    59/pop-to-ecx
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+string-starts-with?:  # s: (addr array byte), benchmark: (addr array byte) -> eax: boolean
+    # pseudocode:
+    #   if (s->length < benchmark->length) return false
     #   currs = s->data
     #   currb = benchmark->data
-    #   maxs = &s->data[s->length]
-    #   while currs < maxs
+    #   maxb = &benchmark->data[benchmark->length]
+    #   while currb < maxb
     #     c1 = *currs
     #     c2 = *currb
     #     if (c1 != c2) return false
@@ -45,44 +87,44 @@ string-equal?:  # s: (addr array byte), benchmark: (addr array byte) -> eax: boo
     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
     # edi = benchmark
     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # ecx = s->length
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
-$string-equal?:lengths:
-    # if (ecx != benchmark->length) return false
-    39/compare                      0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # compare *edi and ecx
-    75/jump-if-!=  $string-equal?:false/disp8
+    # var blen/ecx: int = benchmark->length
+    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
+$string-starts-with?:lengths:
+    # if (s->length < blen) return false
+    39/compare                      0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare *esi with ecx
+    7c/jump-if-<  $string-starts-with?:false/disp8
     # var currs/esi: (addr byte) = s->data
     81          0/subop/add         3/mod/direct    6/rm32/esi    .           .             .           .           .               4/imm32           # add to esi
-    # var maxs/ecx: (addr byte) = &s->data[s->length]
-    01/add                          3/mod/direct    1/rm32/ecx    .           .             .           6/r32/esi   .               .                 # add esi to ecx
     # var currb/edi: (addr byte) = benchmark->data
     81          0/subop/add         3/mod/direct    7/rm32/edi    .           .             .           .           .               4/imm32           # add to edi
+    # var maxb/ecx: (addr byte) = &benchmark->data[benchmark->length]
+    01/add                          3/mod/direct    1/rm32/ecx    .           .             .           7/r32/edi   .               .                 # add edi to ecx
     # var c1/eax: byte = 0
     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
     # var c2/edx: byte = 0
     31/xor                          3/mod/direct    2/rm32/edx    .           .             .           2/r32/edx   .               .                 # clear edx
-$string-equal?:loop:
+$string-starts-with?:loop:
     # if (currs >= maxs) return true
-    39/compare                      3/mod/direct    6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare esi with ecx
-    73/jump-if-addr>=  $string-equal?:true/disp8
+    39/compare                      3/mod/direct    7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # compare edi with ecx
+    73/jump-if-addr>=  $string-starts-with?:true/disp8
     # c1 = *currs
     8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
     # c2 = *currb
     8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           2/r32/DL    .               .                 # copy byte at *edi to DL
     # if (c1 != c2) return false
     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax and edx
-    75/jump-if-!=  $string-equal?:false/disp8
+    75/jump-if-!=  $string-starts-with?:false/disp8
     # ++currs
     46/increment-esi
     # ++currb
     47/increment-edi
-    eb/jump  $string-equal?:loop/disp8
-$string-equal?:true:
+    eb/jump  $string-starts-with?:loop/disp8
+$string-starts-with?:true:
     b8/copy-to-eax  1/imm32
-    eb/jump  $string-equal?:end/disp8
-$string-equal?:false:
+    eb/jump  $string-starts-with?:end/disp8
+$string-starts-with?:false:
     b8/copy-to-eax  0/imm32
-$string-equal?:end:
+$string-starts-with?:end:
     # . restore registers
     5f/pop-to-edi
     5e/pop-to-esi
diff --git a/apps/assort b/apps/assort
index 1711e7fa..ae78eeb6 100755
--- a/apps/assort
+++ b/apps/assort
Binary files differdiff --git a/apps/braces b/apps/braces
index 2513add4..4d89b6b6 100755
--- a/apps/braces
+++ b/apps/braces
Binary files differdiff --git a/apps/calls b/apps/calls
index 06962742..0f6c5a28 100755
--- a/apps/calls
+++ b/apps/calls
Binary files differdiff --git a/apps/crenshaw2-1 b/apps/crenshaw2-1
index feef8d4b..33cfddf4 100755
--- a/apps/crenshaw2-1
+++ b/apps/crenshaw2-1
Binary files differdiff --git a/apps/crenshaw2-1b b/apps/crenshaw2-1b
index 16591ef0..f2d625b9 100755
--- a/apps/crenshaw2-1b
+++ b/apps/crenshaw2-1b
Binary files differdiff --git a/apps/dquotes b/apps/dquotes
index 98a1bca2..271c3895 100755
--- a/apps/dquotes
+++ b/apps/dquotes
Binary files differdiff --git a/apps/factorial b/apps/factorial
index b9e7d7be..d3e0352d 100755
--- a/apps/factorial
+++ b/apps/factorial
Binary files differdiff --git a/apps/handle b/apps/handle
index 30310ff3..9e936219 100755
--- a/apps/handle
+++ b/apps/handle
Binary files differdiff --git a/apps/hex b/apps/hex
index f31f312d..dbfe5536 100755
--- a/apps/hex
+++ b/apps/hex
Binary files differdiff --git a/apps/mu b/apps/mu
index 0bb5172a..ac41fc15 100755
--- a/apps/mu
+++ b/apps/mu
Binary files differdiff --git a/apps/mu.subx b/apps/mu.subx
index d06470c7..8344d6a7 100644
--- a/apps/mu.subx
+++ b/apps/mu.subx
@@ -132,7 +132,7 @@
 #
 #   A named block contains:
 #     tag: 4
-#     name: (handle array byte)
+#     name: (handle array byte) -- starting with '$'
 #     statements: (handle list statement)
 
 # == Translation: managing the stack
@@ -1157,7 +1157,7 @@ test-convert-function-with-local-var-in-block:
 test-convert-function-with-local-var-in-named-block:
     # empty function decl => function prologue and epilogue
     #   fn foo {
-    #     bar: {
+    #     $bar: {
     #       var x: int
     #       increment x
     #     }
@@ -1169,12 +1169,12 @@ test-convert-function-with-local-var-in-named-block:
     #     89/<- %ebp 4/r32/esp
     #     {
     #       {
-    #     bar:loop:
+    #     $bar:loop:
     #         68/push 0/imm32
     #         ff 0/subop/increment *(ebp-4)
     #         81 0/subop/add %esp 4/imm32
     #       }
-    #     bar:break:
+    #     $bar:break:
     #     }
     #     # . epilogue
     #     89/<- %esp 5/r32/ebp
@@ -1190,7 +1190,7 @@ test-convert-function-with-local-var-in-named-block:
     (clear-stream $_test-output-buffered-file->buffer)
     #
     (write _test-input-stream "fn foo {\n")
-    (write _test-input-stream "  bar: {\n")
+    (write _test-input-stream "  $bar: {\n")
     (write _test-input-stream "    var x: int\n")
     (write _test-input-stream "    increment x\n")
     (write _test-input-stream "  }\n")
@@ -1211,12 +1211,12 @@ test-convert-function-with-local-var-in-named-block:
     (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-local-var-in-named-block/3")
     (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-with-local-var-in-named-block/4")
     (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-with-local-var-in-named-block/5")
-    (check-next-stream-line-equal _test-output-stream "bar:loop:"             "F - test-convert-function-with-local-var-in-named-block/6")
+    (check-next-stream-line-equal _test-output-stream "$bar:loop:"            "F - test-convert-function-with-local-var-in-named-block/6")
     (check-next-stream-line-equal _test-output-stream "68/push 0/imm32"       "F - test-convert-function-with-local-var-in-named-block/7")
     (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-local-var-in-named-block/8")
     (check-next-stream-line-equal _test-output-stream "81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-local-var-in-named-block/9")
     (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-with-local-var-in-named-block/10")
-    (check-next-stream-line-equal _test-output-stream "bar:break:"            "F - test-convert-function-with-local-var-in-named-block/11")
+    (check-next-stream-line-equal _test-output-stream "$bar:break:"           "F - test-convert-function-with-local-var-in-named-block/11")
     (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-with-local-var-in-named-block/12")
     (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-with-local-var-in-named-block/13")
     (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-local-var-in-named-block/14")
@@ -1302,6 +1302,85 @@ test-convert-function-with-branches-in-block:
     5d/pop-to-ebp
     c3/return
 
+test-convert-function-with-branches-in-named-block:
+    # empty function decl => function prologue and epilogue
+    #   fn foo x: int {
+    #     $bar: {
+    #       break-if->= $bar
+    #       loop-if-addr< $bar
+    #       increment x
+    #       loop
+    #     }
+    #   }
+    # =>
+    #   foo:
+    #     # . prologue
+    #     55/push-ebp
+    #     89/<- %ebp 4/r32/esp
+    #     {
+    #       {
+    #   $bar:loop:
+    #         0f 8d/jump-if->= $bar:break/disp32
+    #         0f 82/jump-if-addr< $bar:loop/disp32
+    #         ff 0/subop/increment *(ebp+8)
+    #         e9/jump loop/disp32
+    #       }
+    #   $bar:break:
+    #     }
+    #     # . epilogue
+    #     89/<- %esp 5/r32/ebp
+    #     5d/pop-to-ebp
+    #     c3/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: int {\n")
+    (write _test-input-stream "  $bar: {\n")
+    (write _test-input-stream "    break-if->= $bar\n")
+    (write _test-input-stream "    loop-if-addr< $bar\n")
+    (write _test-input-stream "    increment x\n")
+    (write _test-input-stream "    loop\n")
+    (write _test-input-stream "  }\n")
+    (write _test-input-stream "}\n")
+    # convert
+    (convert-mu _test-input-buffered-file _test-output-buffered-file)
+    (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-branches-in-named-block/0")
+    (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-with-branches-in-named-block/1")
+    (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-with-branches-in-named-block/2")
+    (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-branches-in-named-block/3")
+    (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-with-branches-in-named-block/4")
+    (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-with-branches-in-named-block/5")
+    (check-next-stream-line-equal _test-output-stream "$bar:loop:"            "F - test-convert-function-with-branches-in-named-block/6")
+    (check-next-stream-line-equal _test-output-stream "0f 8d/jump-if->= $bar:break/disp32"  "F - test-convert-function-with-branches-in-named-block/7")
+    (check-next-stream-line-equal _test-output-stream "0f 82/jump-if-addr< $bar:loop/disp32"  "F - test-convert-function-with-branches-in-named-block/8")
+    (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0x00000008)"  "F - test-convert-function-with-branches-in-named-block/9")
+    (check-next-stream-line-equal _test-output-stream "e9/jump loop/disp32"   "F - test-convert-function-with-branches-in-named-block/10")
+    (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-with-branches-in-named-block/11")
+    (check-next-stream-line-equal _test-output-stream "$bar:break:"           "F - test-convert-function-with-branches-in-named-block/12")
+    (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-with-branches-in-named-block/13")
+    (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-with-branches-in-named-block/14")
+    (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-branches-in-named-block/15")
+    (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-with-branches-in-named-block/16")
+    (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-with-branches-in-named-block/17")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 #######################################################
 # Parsing
 #######################################################
@@ -3401,7 +3480,25 @@ $add-operation-and-inputs-to-stmt:read-inouts:
       (slice-equal? %ecx "<-")
       3d/compare-eax-and 0/imm32
       0f 85/jump-if-!= $add-operation-and-inputs-to-stmt:abort/disp32
-      #
+      # if (name starts with "$") treat it as a literal
+      {
+        # var eax: byte = name[0]
+        8b/-> *ecx 0/r32/eax
+        8a/copy-byte *eax 0/r32/AL
+        81 4/subop/and %eax 0xff/imm32
+        # if (eax != '$') goto next condition
+        3d/compare-eax-and 0x24/imm32/dollar
+        75/jump-if-!= break/disp8
+        # var eax: (handle var)
+        (new-label Heap %ecx)  # => eax
+        # stmt->inouts = append(eax, stmt->inouts)
+        (append-list Heap %eax *(edi+8))  # Stmt1-inouts => eax
+        89/<- *(edi+8) 0/r32/eax  # Stmt1-inouts
+        # continue
+        e9/jump $add-operation-and-inputs-to-stmt:read-inouts/disp32
+      }
+$add-operation-and-inputs-to-stmt:regular-inout:
+      # otherwise
       (lookup-var-or-literal %ecx *(ebp+0x10))  # => eax
       (append-list Heap %eax *(edi+8))  # Stmt1-inouts or Regvardef-inouts => eax
       89/<- *(edi+8) 0/r32/eax  # Stmt1-inouts or Regvardef-inouts
@@ -3860,6 +3957,33 @@ $new-literal-integer:abort:
     cd/syscall  0x80/imm8
     # never gets here
 
+new-label:  # ad: (addr allocation-descriptor), name: (addr slice) -> result/eax: (handle var)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    51/push-ecx
+    # var s/ecx: (addr array byte)
+    (slice-to-string Heap *(ebp+0xc))  # => eax
+    89/<- %ecx 0/r32/eax
+    #
+    (allocate *(ebp+8) *Var-size)  # => eax
+    89/<- *eax 1/r32/ecx  # Var-name
+    89/<- %ecx 0/r32/eax
+    (allocate *(ebp+8) *Tree-size)  # => eax
+    89/<- *(ecx+4) 0/r32/eax  # Var-type
+    89/<- %eax 1/r32/ecx
+    c7 0/subop/copy *(eax+8) 0/imm32  # Var-block
+    c7 0/subop/copy *(eax+0xc) 0/imm32  # Var-stack-offset
+    c7 0/subop/copy *(eax+0x10) 0/imm32  # Var-register
+$new-label:end:
+    # . restore registers
+    59/pop-to-ecx
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 new-block:  # ad: (addr allocation-descriptor), data: (handle list statement) -> result/eax: (handle statement)
     # . prologue
     55/push-ebp
@@ -4227,7 +4351,7 @@ $emit-subx-stmt-list:regvardef:
         8b/-> *eax 0/r32/eax
         # ensure that output is in a register
         81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
-        0f 84/jump-if-equal $emit-subx-stmt-list:abort-regvardef-without-register/disp32
+        0f 84/jump-if-= $emit-subx-stmt-list:abort-regvardef-without-register/disp32
         # emit spill
         (write-buffered *(ebp+8) "ff 6/subop/push %")
         (write-buffered *(ebp+8) *(eax+0x10))
@@ -4341,19 +4465,21 @@ emit-subx-statement:  # out: (addr buffered-file), stmt: (handle statement), pri
     51/push-ecx
     # if stmt matches a primitive, emit it
     {
-$emit-subx-statement:primitive:
+$emit-subx-statement:check-for-primitive:
       (find-matching-primitive *(ebp+0x10) *(ebp+0xc))  # primitives, stmt => curr/eax
       3d/compare-eax-and 0/imm32
       74/jump-if-= break/disp8
+$emit-subx-statement:primitive:
       (emit-subx-primitive *(ebp+8) *(ebp+0xc) %eax)  # out, stmt, curr
       e9/jump $emit-subx-statement:end/disp32
     }
     # else if stmt matches a function, emit a call to it
     {
-$emit-subx-statement:call:
+$emit-subx-statement:check-for-call:
       (find-matching-function *(ebp+0x14) *(ebp+0xc))  # functions, stmt => curr/eax
       3d/compare-eax-and 0/imm32
       74/jump-if-= break/disp8
+$emit-subx-statement:call:
       (emit-subx-call *(ebp+8) *(ebp+0xc) %eax)  # out, stmt, curr
       e9/jump $emit-subx-statement:end/disp32
     }
@@ -4370,8 +4496,9 @@ $emit-subx-statement:end:
 
 $emit-subx-statement:abort:
     # error("couldn't translate '" stmt "'\n")
-    (write-buffered Stderr "couldn't translate '")
-#?     (emit-string Stderr *(ebp+0xc))  # TODO
+    (write-buffered Stderr "couldn't translate an instruction with operation '")
+    8b/-> *(ebp+0xc) 0/r32/eax
+    (write-buffered Stderr *(eax+4))  # Stmt1-operation
     (write-buffered Stderr "'\n")
     (flush Stderr)
     # . syscall(exit, 1)
@@ -5341,6 +5468,238 @@ _Primitive-loop:
     0/imm32/no-imm32
     0/imm32/no-disp32
     0/imm32/no-output
+    _Primitive-break-if-addr<-named/imm32/next
+# - branches to named blocks
+_Primitive-break-if-addr<-named:
+    "break-if-addr<"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 82/jump-if-addr<"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if-addr>=-named/imm32/next
+_Primitive-break-if-addr>=-named:
+    "break-if-addr>="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 83/jump-if-addr>="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if-=-named/imm32/next
+_Primitive-break-if-=-named:
+    "break-if-="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 84/jump-if-="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if-!=-named/imm32/next
+_Primitive-break-if-!=-named:
+    "break-if-!="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 85/jump-if-!="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if-addr<=-named/imm32/next
+_Primitive-break-if-addr<=-named:
+    "break-if-addr<="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 86/jump-if-addr<="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if-addr>-named/imm32/next
+_Primitive-break-if-addr>-named:
+    "break-if-addr>"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 87/jump-if-addr>"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if-<-named/imm32/next
+_Primitive-break-if-<-named:
+    "break-if-<"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 8c/jump-if-<"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if->=-named/imm32/next
+_Primitive-break-if->=-named:
+    "break-if->="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 8d/jump-if->="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if-<=-named/imm32/next
+_Primitive-break-if-<=-named:
+    "break-if-<="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 8e/jump-if-<="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-break-if->-named/imm32/next
+_Primitive-break-if->-named:
+    "break-if->"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 8f/jump-if->"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if-addr<-named/imm32/next
+_Primitive-loop-if-addr<-named:
+    "loop-if-addr<"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 82/jump-if-addr<"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if-addr>=-named/imm32/next
+_Primitive-loop-if-addr>=-named:
+    "loop-if-addr>="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 83/jump-if-addr>="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if-=-named/imm32/next
+_Primitive-loop-if-=-named:
+    "loop-if-="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 84/jump-if-="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if-!=-named/imm32/next
+_Primitive-loop-if-!=-named:
+    "loop-if-!="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 85/jump-if-!="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if-addr<=-named/imm32/next
+_Primitive-loop-if-addr<=-named:
+    "loop-if-addr<="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 86/jump-if-addr<="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if-addr>-named/imm32/next
+_Primitive-loop-if-addr>-named:
+    "loop-if-addr>"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 87/jump-if-addr>"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if-<-named/imm32/next
+_Primitive-loop-if-<-named:
+    "loop-if-<"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 8c/jump-if-<"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if->=-named/imm32/next
+_Primitive-loop-if->=-named:
+    "loop-if->="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 8d/jump-if->="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if-<=-named/imm32/next
+_Primitive-loop-if-<=-named:
+    "loop-if-<="/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 8e/jump-if-<="/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-if->-named/imm32/next
+_Primitive-loop-if->-named:
+    "loop-if->"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "0f 8f/jump-if->"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
+    _Primitive-loop-named/imm32/next  # we probably don't need an unconditional break
+_Primitive-loop-named:
+    "loop"/imm32/name
+    Single-lit-var/imm32/inouts
+    0/imm32/outputs
+    "e9/jump"/imm32/subx-name
+    0/imm32/no-rm32
+    0/imm32/no-r32
+    0/imm32/no-imm32
+    1/imm32/disp32-is-first-inout
+    0/imm32/no-output
     0/imm32/next
 
 Single-int-var-on-stack:
@@ -5476,6 +5835,8 @@ emit-subx-primitive:  # out: (addr buffered-file), stmt: (handle statement), pri
     (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc))  # out, Primitive-subx-r32, stmt
     # emit imm32 if necessary
     (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc))  # out, Primitive-subx-imm32, stmt
+    # emit disp32 if necessary
+    (emit-subx-disp32 *(ebp+8) *(ecx+0x1c) *(ebp+0xc))  # out, Primitive-subx-disp32, stmt
     (write-buffered *(ebp+8) Newline)
 $emit-subx-primitive:end:
     # . restore registers
@@ -5615,6 +5976,47 @@ $emit-subx-imm32:end:
     5d/pop-to-ebp
     c3/return
 
+emit-subx-disp32:  # out: (addr buffered-file), l: arg-location, stmt: (handle statement)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    # if (location == 0) return
+    81 7/subop/compare *(ebp+0xc) 0/imm32
+    0f 84/jump-if-= $emit-subx-disp32:end/disp32
+    #
+    (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # stmt, l => var/eax
+    (write-buffered *(ebp+8) Space)
+    (write-buffered *(ebp+8) *eax)  # Var-name
+    # hack: if instruction operation starts with "break", emit ":break"
+    # var name/ecx: (addr array byte) = stmt->operation
+    8b/-> *(ebp+0x10) 0/r32/eax
+    8b/-> *(eax+4) 1/r32/ecx
+    {
+      (string-starts-with? %ecx "break")  # => eax
+      3d/compare-eax-and 0/imm32
+      74/jump-if-= break/disp8
+      (write-buffered *(ebp+8) ":break")
+    }
+    # hack: if instruction operation starts with "loop", emit ":loop"
+    {
+      (string-starts-with? %ecx "loop")  # => eax
+      3d/compare-eax-and 0/imm32
+      74/jump-if-= break/disp8
+      (write-buffered *(ebp+8) ":loop")
+    }
+    (write-buffered *(ebp+8) "/disp32")
+$emit-subx-disp32:end:
+    # . restore registers
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 emit-subx-call:  # out: (addr buffered-file), stmt: (handle statement), callee: (handle function)
     # . prologue
     55/push-ebp
diff --git a/apps/pack b/apps/pack
index 49d9000c..e3e732cd 100755
--- a/apps/pack
+++ b/apps/pack
Binary files differdiff --git a/apps/sigils b/apps/sigils
index ef8b9b69..ae5b1bf4 100755
--- a/apps/sigils
+++ b/apps/sigils
Binary files differdiff --git a/apps/survey b/apps/survey
index 261c5623..d56e50d3 100755
--- a/apps/survey
+++ b/apps/survey
Binary files differdiff --git a/apps/tests b/apps/tests
index 7561184f..e6268681 100755
--- a/apps/tests
+++ b/apps/tests
Binary files differ