about summary refs log tree commit diff stats
path: root/subx/apps
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2019-03-30 23:41:14 -0700
committerKartik Agaram <vc@akkartik.com>2019-03-30 23:49:30 -0700
commit34e49d495059ac5c39ab482458536469654e81f7 (patch)
treeddcade9057a9224e0df2a8443f1ac11f1306f3ca /subx/apps
parent1d77b9c014b9407b282e91f03723af2c51616ccc (diff)
downloadmu-34e49d495059ac5c39ab482458536469654e81f7.tar.gz
5040 - compile immediate operands
Diffstat (limited to 'subx/apps')
-rwxr-xr-xsubx/apps/packbin28050 -> 29195 bytes
-rw-r--r--subx/apps/pack.subx572
2 files changed, 556 insertions, 16 deletions
diff --git a/subx/apps/pack b/subx/apps/pack
index 41d5afa8..c233510f 100755
--- a/subx/apps/pack
+++ b/subx/apps/pack
Binary files differdiff --git a/subx/apps/pack.subx b/subx/apps/pack.subx
index b199592f..533e1e51 100644
--- a/subx/apps/pack.subx
+++ b/subx/apps/pack.subx
@@ -2019,7 +2019,7 @@ emit-modrm:  # line : (address stream byte), out : (address buffered-file) -> <v
     #   var word-slice = {0, 0}
     #   while true
     #     word-slice = next-word(line)
-    #     if (empty(word-slice)) break
+    #     if (slice-empty?(word-slice)) break
     #     if (slice-starts-with?(word-slice, "#")) break
     #     if (has-metadata?(word-slice, "mod"))
     #       mod = parse-hex-int(next-token-from-slice(word-slice, "/"))
@@ -2055,7 +2055,7 @@ emit-sib:  # line : (address stream byte), out : (address buffered-file) -> <voi
     #   var word-slice = {0, 0}
     #   while true
     #     word-slice = next-word(line)
-    #     if (empty(word-slice)) break
+    #     if (slice-empty?(word-slice)) break
     #     if (slice-starts-with?(word-slice, "#")) break
     #     if (has-metadata?(word-slice, "base")
     #       base = parse-hex-int(next-token-from-slice(word-slice, "/"))
@@ -2092,7 +2092,7 @@ emit-disp:  # line : (address stream byte), out : (address buffered-file) -> <vo
     #   var word-slice = {0, 0}
     #   while true
     #     word-slice = next-word(line)
-    #     if (empty(word-slice)) break
+    #     if (slice-empty?(word-slice)) break
     #     if (slice-starts-with?(word-slice, "#")) break
     #     if has-metadata?(word-slice, "disp8")
     #       disp = parse-hex-int(next-token-from-slice(word-slice, "/"))
@@ -2122,30 +2122,262 @@ emit-imm:  # line : (address stream byte), out : (address buffered-file) -> <voi
     # pseudocode:
     #   rewind-stream(line)
     #   var imm = 0
-    #   var word-slice = {0, 0}
+    #   var slice = {0, 0}
     #   while true
-    #     word-slice = next-word(line)
-    #     if (slice-starts-with?(word-slice, "#")) break
-    #     if (empty(word-slice)) break
-    #     if has-metadata?(word-slice, "imm8")
-    #       imm = parse-hex-int(next-token-from-slice(word-slice, "/"))
-    #       emit-hex(out, imm, 1)
+    #     slice = next-word(line)
+    #     if (slice-empty?(slice)) break
+    #     if (slice-starts-with?(slice, "#")) break
+    #     if has-metadata?(slice, "imm32")
+    #       imm = parse-hex-int(next-token-from-slice(slice, "/"))
+    #       emit-hex(out, imm, 4)
     #       break
-    #     if has-metadata?(word-slice, "imm16")
-    #       imm = parse-hex-int(next-token-from-slice(word-slice, "/"))
+    #     if has-metadata?(slice, "imm16")
+    #       imm = parse-hex-int(next-token-from-slice(slice, "/"))
     #       emit-hex(out, imm, 2)
     #       break
-    #     else if has-metadata?(word-slice, "imm32")
-    #       imm = parse-hex-int(next-token-from-slice(word-slice, "/"))
-    #       emit-hex(out, imm, 4)
+    #     if has-metadata?(slice, "imm8")
+    #       imm = parse-hex-int(next-token-from-slice(slice, "/"))
+    #       emit-hex(out, imm, 1)
     #       break
     #
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # . save registers
-$emit-imm:end:
+    50/push-EAX
+    51/push-ECX
+    52/push-EDX
+    53/push-EBX
+    # var slice/ECX = {0, 0}
+    68/push  0/imm32/end
+    68/push  0/imm32/start
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # var imm/EBX = 0
+    68/push  0/imm32
+    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
+#?     # dump line {{{
+#?     # write-buffered(Stderr, "LL: ")
+#?     # . . push args
+#?     68/push  "LL: "/imm32
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # write-stream-buffered(Stderr, line)
+#?     # . . push args
+#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-stream-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # write-buffered(Stderr, "\n")
+#?     # . . push args
+#?     68/push  Newline/imm32
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # }}}
+$emit-imm:loop:
+    # next-word(line, slice)
+    # . . push args
+    51/push-ECX
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # dump slice {{{
+#?     # write-buffered(Stderr, "AA: ")
+#?     # . . push args
+#?     68/push  "AA: "/imm32
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # write-slice(Stderr, slice)
+#?     # . . push args
+#?     51/push-ECX
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-slice/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # write-buffered(Stderr, "\n")
+#?     # . . push args
+#?     68/push  Newline/imm32
+#?     68/push  Stderr/imm32
+#?     # . . call
+#?     e8/call  write-buffered/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # }}}
+$emit-imm:check0:
+    # if (slice-empty?(slice)) break
+    # . EAX = slice-empty?(slice)
+    # . . push args
+    51/push-ECX
+    # . . call
+    e8/call  slice-empty?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . if (EAX != 0) pass through
+    3d/compare-EAX-and  0/imm32
+    0f 85/jump-if-not-equal  $emit-imm:break/disp32
+$emit-imm:check1:
+    # if (slice-starts-with?(slice, "#")) break
+    # . start/EDX = slice->start
+    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
+    # . c/EAX = *start
+    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
+    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
+    # . if (EAX == '#') break
+    3d/compare-EAX-and  0x23/imm32/hash
+    0f 84/jump-if-equal  $emit-imm:break/disp32
+$emit-imm:check-for-imm32:
+    # if (has-metadata?(slice, "imm32")) emit(imm) and break
+    # . EAX = has-metadata?(ECX, "c")
+    # . . push args
+    68/push  "imm32"/imm32
+    51/push-ECX
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
+    # . if (EAX == 0) goto next check
+    81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
+    74/jump-if-equal  $emit-imm:check-for-imm16/disp8
+$emit-imm:imm32:
+    # slice = parse-hex-int(next-token-from-slice(slice->start, slice->end, '/'))
+    # . slice = next-token-from-slice(slice->start, slice->end, '/')
+    # . . push args
+    51/push-ECX
+    68/push  0x2f/imm32/slash
+    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
+    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
+    # . . call
+    e8/call  next-token-from-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
+    # . EAX = parse-hex-int(slice)
+    # . . push args
+    51/push-ECX
+    # . . call
+    e8/call  parse-hex-int/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . imm = EAX
+    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
+    # . emit-hex(out, imm, 4)
+    # . . push args
+    68/push  4/imm32
+    53/push-EBX
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . break
+    e9/jump  $emit-imm:break/disp32
+$emit-imm:check-for-imm16:
+    # if (has-metadata?(slice, "imm16")) emit-hex(out, imm, 2) and break
+    # . EAX = has-metadata?(ECX, "imm16")
+    # . . push args
+    68/push  "imm16"/imm32
+    51/push-ECX
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
+    # . if (EAX == 0) goto next check
+    81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
+    74/jump-if-equal  $emit-imm:check-for-imm8/disp8
+$emit-imm:imm16:
+    # slice = parse-hex-int(next-token-from-slice(slice->start, slice->end, '/'))
+    # . slice = next-token-from-slice(slice->start, slice->end, '/')
+    # . . push args
+    51/push-ECX
+    68/push  0x2f/imm32/slash
+    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
+    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
+    # . . call
+    e8/call  next-token-from-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
+    # . EAX = parse-hex-int(slice)
+    # . . push args
+    51/push-ECX
+    # . . call
+    e8/call  parse-hex-int/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . imm = EAX
+    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
+    # . emit-hex(out, imm, 2)
+    # . . push args
+    68/push  2/imm32
+    53/push-EBX
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . break
+    e9/jump  $emit-imm:break/disp32
+$emit-imm:check-for-imm8:
+    # if (has-metadata?(slice, "imm8")) emit-hex(out, imm, 2) and break
+    # . EAX = has-metadata?(ECX, "imm8")
+    # . . push args
+    68/push  "imm8"/imm32
+    51/push-ECX
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
+    # . if (EAX == 0) loop
+    81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
+    0f 84/jump-if-equal  $emit-imm:loop/disp32
+$emit-imm:imm8:
+    # slice = parse-hex-int(next-token-from-slice(slice->start, slice->end, '/'))
+    # . slice = next-token-from-slice(slice->start, slice->end, '/')
+    # . . push args
+    51/push-ECX
+    68/push  0x2f/imm32/slash
+    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
+    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
+    # . . call
+    e8/call  next-token-from-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
+    # . EAX = parse-hex-int(slice)
+    # . . push args
+    51/push-ECX
+    # . . call
+    e8/call  parse-hex-int/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . imm = EAX
+    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
+    # . emit-hex(out, imm, 1)
+    # . . push args
+    68/push  1/imm32
+    53/push-EBX
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . break
+$emit-imm:break:
     # . restore registers
+    5b/pop-to-EBX
+    5a/pop-to-EDX
+    59/pop-to-ECX
+    58/pop-to-EAX
     # . epilog
     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
     5d/pop-to-EBP
@@ -3231,6 +3463,314 @@ test-convert-instruction-handles-unused-second-opcodes-2:
     5d/pop-to-EBP
     c3/return
 
+test-convert-instruction-handles-imm32-operand:
+    # expand /imm32 operand into 4 bytes
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-buffered-file+4)
+    # . . push args
+    b8/copy-to-EAX  _test-output-buffered-file/imm32
+    05/add-to-EAX  4/imm32
+    50/push-EAX
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # initialize input
+    # . write(_test-input-stream, "68/push 0x20/imm32")
+    # . . push args
+    68/push  "68/push 0x20/imm32"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # convert-instruction(_test-input-stream, _test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  convert-instruction/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check output
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+#?     # dump output {{{
+#?     # . write(2/stderr, "^")
+#?     # . . push args
+#?     68/push  "^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "$")
+#?     # . . push args
+#?     68/push  "$"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "\n")
+#?     # . . push args
+#?     68/push  Newline/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # }}}
+    # . check-stream-equal(_test-output-stream, "68 20 00 00 00  # 68/push 0x20/imm32", msg)
+    # . . push args
+    68/push  "F - test-convert-instruction-handles-imm32-operand"/imm32
+    68/push  "68 20 00 00 00  # 68/push 0x20/imm32"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
+test-convert-instruction-handles-imm16-operand:
+    # expand /imm16 operand into 2 bytes
+    # we don't have one of these at the moment, so this expands to an invalid instruction
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-buffered-file+4)
+    # . . push args
+    b8/copy-to-EAX  _test-output-buffered-file/imm32
+    05/add-to-EAX  4/imm32
+    50/push-EAX
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # initialize input
+    # . write(_test-input-stream, "68/push 0x20/imm16")
+    # . . push args
+    68/push  "68/push 0x20/imm16"/imm32  # not a valid instruction
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # convert-instruction(_test-input-stream, _test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  convert-instruction/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check output
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+#?     # dump output {{{
+#?     # . write(2/stderr, "^")
+#?     # . . push args
+#?     68/push  "^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "$")
+#?     # . . push args
+#?     68/push  "$"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "\n")
+#?     # . . push args
+#?     68/push  Newline/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # }}}
+    # . check-stream-equal(_test-output-stream, "68 20 00  # 68/push 0x20/imm16", msg)
+    # . . push args
+    68/push  "F - test-convert-instruction-handles-imm16-operand"/imm32
+    68/push  "68 20 00  # 68/push 0x20/imm16"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
+test-convert-instruction-handles-imm8-operand:
+    # expand /imm8 operand into 1 byte
+    # we don't have one of these at the moment, so this expands to an invalid instruction
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . clear-stream(_test-output-buffered-file+4)
+    # . . push args
+    b8/copy-to-EAX  _test-output-buffered-file/imm32
+    05/add-to-EAX  4/imm32
+    50/push-EAX
+    # . . call
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # initialize input
+    # . write(_test-input-stream, "68/push 0x20/imm8")
+    # . . push args
+    68/push  "68/push 0x20/imm8"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # convert-instruction(_test-input-stream, _test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  convert-instruction/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check output
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+#?     # dump output {{{
+#?     # . write(2/stderr, "^")
+#?     # . . push args
+#?     68/push  "^"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "$")
+#?     # . . push args
+#?     68/push  "$"/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . write(2/stderr, "\n")
+#?     # . . push args
+#?     68/push  Newline/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # }}}
+    # . check-stream-equal(_test-output-stream, "68 20  # 68/push 0x20/imm8", msg)
+    # . . push args
+    68/push  "F - test-convert-instruction-handles-imm8-operand"/imm32
+    68/push  "68 20  # 68/push 0x20/imm8"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
 # (re)compute the bounds of the next word in the line
 # return empty string on reaching end of file
 next-word:  # line : (address stream byte), out : (address slice)