https://github.com/akkartik/mu/blob/main/126write-int-decimal.subx

      1 # Helper to print an int32 in decimal.
      2
      3 == code
      4 #   instruction                     effective address                                                   register    displacement    immediate
      5 # . op          subop               mod             rm32          base        index         scale       r32
      6 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
      7
      8 write-int32-decimal:  # out: (addr stream byte), n: int
      9     # works by generating characters from lowest to highest and pushing them
     10     # to the stack, before popping them one by one into the stream
     11     #
     12     # pseudocode:
     13     #   push sentinel
     14     #   eax = abs(n)
     15     #   while true
     16     #     sign-extend eax into edx
     17     #     eax, edx = eax/10, eax%10
     18     #     edx += '0'
     19     #     push edx
     20     #     if (eax == 0) break
     21     #   if n < 0
     22     #     push '-'
     23     #   w = out->write
     24     #   curr = &out->data[out->write]
     25     #   max = &out->data[out->size]
     26     #   while true
     27     #     pop into eax
     28     #     if (eax == sentinel) break
     29     #     if (curr >= max) abort
     30     #     *curr = AL
     31     #     ++curr
     32     #     ++w
     33     #   out->write = w
     34     # (based on K&R itoa: https://en.wikibooks.org/wiki/C_Programming/stdlib.h/itoa)
     35     # (this pseudocode contains registers because operations like division
     36     # require specific registers in x86)
     37     #
     38     # . prologue
     39     55/push-ebp
     40     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
     41     # . save registers
     42     50/push-eax
     43     51/push-ecx
     44     52/push-edx
     45     53/push-ebx
     46     57/push-edi
     47     # const ten/ecx = 10
     48     b9/copy-to-ecx  0xa/imm32
     49     # push sentinel
     50     68/push  0/imm32/sentinel
     51     # var eax: int = abs(n)
     52     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
     53     3d/compare-eax-with  0/imm32
     54     7d/jump-if->=  $write-int32-decimal:read-loop/disp8
     55 $write-int32-decimal:negative:
     56     f7          3/subop/negate      3/mod/direct    0/rm32/eax    .           .             .           .           .               .                 # negate eax
     57 $write-int32-decimal:read-loop:
     58     # eax, edx = eax / 10, eax % 10
     59     99/sign-extend-eax-into-edx
     60     f7          7/subop/idiv        3/mod/direct    1/rm32/ecx    .           .             .           .           .               .                 # divide edx:eax by ecx, storing quotient in eax and remainder in edx
     61     # edx += '0'
     62     81          0/subop/add         3/mod/direct    2/rm32/edx    .           .             .           .           .               0x30/imm32        # add to edx
     63     # push edx
     64     52/push-edx
     65     # if (eax == 0) break
     66     3d/compare-eax-and  0/imm32
     67     7f/jump-if->  $write-int32-decimal:read-loop/disp8
     68 $write-int32-decimal:read-break:
     69     # if (n < 0) push('-')
     70     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       0/imm32           # compare *(ebp+12)
     71     7d/jump-if->=  $write-int32-decimal:write/disp8
     72 $write-int32-decimal:push-negative:
     73     68/push  0x2d/imm32/-
     74 $write-int32-decimal:write:
     75     # edi = out
     76     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
     77     # var w/edx: int = out->write
     78     8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # copy *edi to edx
     79     # var curr/ecx: (addr byte) = &out->data[out->write]
     80     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  2/index/edx   .           1/r32/ecx   0xc/disp8       .                 # copy ebx+edx+12 to ecx
     81     # var max/ebx: (addr byte) = &out->data[out->size]
     82     8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           3/r32/ebx   8/disp8         .                 # copy *(edi+8) to ebx
     83     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  3/index/ebx   .           3/r32/ebx   0xc/disp8       .                 # copy edi+ebx+12 to ebx
     84 $write-int32-decimal:write-loop:
     85     # pop into eax
     86     58/pop-to-eax
     87     # if (eax == sentinel) break
     88     3d/compare-eax-and  0/imm32/sentinel
     89     74/jump-if-=  $write-int32-decimal:write-break/disp8
     90     # if (curr >= max) abort
     91     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
     92     73/jump-if-addr>=  $write-int32-decimal:abort/disp8
     93 $write-int32-decimal:write-char:
     94     # *curr = AL
     95     88/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy AL to byte at *ecx
     96     # ++curr
     97     41/increment-ecx
     98     # ++w
     99     42/increment-edx
    100     eb/jump  $write-int32-decimal:write-loop/disp8
    101 $write-int32-decimal:write-break:
    102     # out->write = w
    103     89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # copy edx to *edi
    104 $write-int32-decimal:end:
    105     # . restore registers
    106     5f/pop-to-edi
    107     5b/pop-to-ebx
    108     5a/pop-to-edx
    109     59/pop-to-ecx
    110     58/pop-to-eax
    111     # . epilogue
    112     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    113     5d/pop-to-ebp
    114     c3/return
    115
    116 $write-int32-decimal:abort:
    117     (abort "write-int32-decimal: stream out of space")
    118     # never gets here
    119
    120 test-write-int32-decimal:
    121     # - check that a single-digit number converts correctly
    122     # setup
    123     # . clear-stream(_test-stream)
    124     # . . push args
    125     68/push  _test-stream/imm32
    126     # . . call
    127     e8/call  clear-stream/disp32
    128     # . . discard args
    129     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    130     # write-int32-decimal(_test-stream, 9)
    131     # . . push args
    132     68/push  9/imm32
    133     68/push  _test-stream/imm32
    134     # . . call
    135     e8/call  write-int32-decimal/disp32
    136     # . . discard args
    137     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    138     # check-stream-equal(_test-stream, "9", msg)
    139     # . . push args
    140     68/push  "F - test-write-int32-decimal"/imm32
    141     68/push  "9"/imm32
    142     68/push  _test-stream/imm32
    143     # . . call
    144     e8/call  check-stream-equal/disp32
    145     # . . discard args
    146     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    147     # . end
    148     c3/return
    149
    150 test-write-int32-decimal-zero:
    151     # - check that 0 converts correctly
    152     # setup
    153     # . clear-stream(_test-stream)
    154     # . . push args
    155     68/push  _test-stream/imm32
    156     # . . call
    157     e8/call  clear-stream/disp32
    158     # . . discard args
    159     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    160     # write-int32-decimal(_test-stream, 0)
    161     # . . push args
    162     68/push  0/imm32
    163     68/push  _test-stream/imm32
    164     # . . call
    165     e8/call  write-int32-decimal/disp32
    166     # . . discard args
    167     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    168     # check-stream-equal(_test-stream, "0", msg)
    169     # . . push args
    170     68/push  "F - test-write-int32-decimal-zero"/imm32
    171     68/push  "0"/imm32
    172     68/push  _test-stream/imm32
    173     # . . call
    174     e8/call  check-stream-equal/disp32
    175     # . . discard args
    176     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    177     # . end
    178     c3/return
    179
    180 test-write-int32-decimal-multiple-digits:
    181     # - check that a multi-digit number converts correctly
    182     # setup
    183     # . clear-stream(_test-stream)
    184     # . . push args
    185     68/push  _test-stream/imm32
    186     # . . call
    187     e8/call  clear-stream/disp32
    188     # . . discard args
    189     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    190     # write-int32-decimal(_test-stream, 10)
    191     # . . push args
    192     68/push  0xa/imm32
    193     68/push  _test-stream/imm32
    194     # . . call
    195     e8/call  write-int32-decimal/disp32
    196     # . . discard args
    197     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    198     # check-stream-equal(_test-stream, "10", msg)
    199     # . . push args
    200     68/push  "F - test-write-int32-decimal-multiple-digits"/imm32
    201     68/push  "10"/imm32
    202     68/push  _test-stream/imm32
    203     # . . call
    204     e8/call  check-stream-equal/disp32
    205     # . . discard args
    206     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    207     # . end
    208     c3/return
    209
    210 test-write-int32-decimal-negative:
    211     # - check that a negative single-digit number converts correctly
    212     # setup
    213     # . clear-stream(_test-stream)
    214     # . . push args
    215     68/push  _test-stream/imm32
    216     # . . call
    217     e8/call  clear-stream/disp32
    218     # . . discard args
    219     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    220     # write-int32-decimal(_test-stream, -9)
    221     # . . push args
    222     68/push  -9/imm32
    223     68/push  _test-stream/imm32
    224     # . . call
    225     e8/call  write-int32-decimal/disp32
    226     # . . discard args
    227     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    228 +-- 26 lines: #?     # dump _test-stream -----------------------------------------------------------------------------------------------------------------------------------
    254     # check-stream-equal(_test-stream, "-9", msg)
    255     # . . push args
    256     68/push  "F - test-write-int32-decimal-negative"/imm32
    257     68/push  "-9"/imm32
    258     68/push  _test-stream/imm32
    259     # . . call
    260     e8/call  check-stream-equal/disp32
    261     # . . discard args
    262     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    263     # . end
    264     c3/return
    265
    266 # There's a special bit pattern that corresponds to no 2's complement integer.
    267 # There doesn't seem to be a widespread convention for representing it.
    268 test-write-int32-decimal-indefinite-integer:
    269     # setup
    270     # . clear-stream(_test-stream)
    271     # . . push args
    272     68/push  _test-stream/imm32
    273     # . . call
    274     e8/call  clear-stream/disp32
    275     # . . discard args
    276     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    277     # write-int32-decimal(_test-stream, 0x80000000)
    278     # . . push args
    279     68/push  0x80000000/imm32
    280     68/push  _test-stream/imm32
    281     # . . call
    282     e8/call  write-int32-decimal/disp32
    283     # . . discard args
    284     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    285     # check-stream-equal(_test-stream, "-(", msg)
    286     # . . push args
    287     68/push  "F - test-write-int32-decimal-indefinite-integer"/imm32
    288     68/push  "-("/imm32
    289     68/push  _test-stream/imm32
    290     # . . call
    291     e8/call  check-stream-equal/disp32
    292     # . . discard args
    293     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    294     # . end
    295     c3/return
    296
    297 test-write-int32-decimal-negative-multiple-digits:
    298     # - check that a multi-digit number converts correctly
    299     # setup
    300     # . clear-stream(_test-stream)
    301     # . . push args
    302     68/push  _test-stream/imm32
    303     # . . call
    304     e8/call  clear-stream/disp32
    305     # . . discard args
    306     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    307     # write-int32-decimal(_test-stream, -10)
    308     # . . push args
    309     68/push  -0xa/imm32
    310     68/push  _test-stream/imm32
    311     # . . call
    312     e8/call  write-int32-decimal/disp32
    313     # . . discard args
    314     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    315     # check-stream-equal(_test-stream, "-10", msg)
    316     # . . push args
    317     68/push  "F - test-write-int32-decimal-negative-multiple-digits"/imm32
    318     68/push  "-10"/imm32
    319     68/push  _test-stream/imm32
    320     # . . call
    321     e8/call  check-stream-equal/disp32
    322     # . . discard args
    323     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    324     # . end
    325     c3/return
    326
    327 decimal-digit?:  # c: grapheme -> result/eax: boolean
    328     # . prologue
    329     55/push-ebp
    330     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    331     # . save registers
    332     51/push-ecx
    333     # ecx = c
    334     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
    335     # result = false
    336     b8/copy-to-eax  0/imm32/false
    337     # return false if c < '0'
    338     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x30/imm32        # compare ecx
    339     7c/jump-if-<  $decimal-digit?:end/disp8
    340     # return (c <= '9')
    341     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x39/imm32        # compare ecx
    342     7f/jump-if->  $decimal-digit?:end/disp8
    343 $decimal-digit?:true:
    344     b8/copy-to-eax  1/imm32/true
    345 $decimal-digit?:end:
    346     # . restore registers
    347     59/pop-to-ecx
    348     # . epilogue
    349     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    350     5d/pop-to-ebp
    351     c3/return
    352
    353 test-decimal-digit-below-0:
    354     # eax = decimal-digit?(0x2f)
    355     # . . push args
    356     68/push  0x2f/imm32
    357     # . . call
    358     e8/call  decimal-digit?/disp32
    359     # . . discard args
    360     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    361     # check-ints-equal(eax, 0, msg)
    362     # . . push args
    363     68/push  "F - test-decimal-digit-below-0"/imm32
    364     68/push  0/imm32/false
    365     50/push-eax
    366     # . . call
    367     e8/call  check-ints-equal/disp32
    368     # . . discard args
    369     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    370     c3/return
    371
    372 test-decimal-digit-0-to-9:
    373     # eax = decimal-digit?(0x30)
    374     # . . push args
    375     68/push  0x30/imm32
    376     # . . call
    377     e8/call  decimal-digit?/disp32
    378     # . . discard args
    379     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    380     # check-ints-equal(eax, 1, msg)
    381     # . . push args
    382     68/push  "F - test-decimal-digit-at-0"/imm32
    383     68/push  1/imm32/true
    384     50/push-eax
    385     # . . call
    386     e8/call  check-ints-equal/disp32
    387     # . . discard args
    388     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    389     # eax = decimal-digit?(0x39)
    390     # . . push args
    391     68/push  0x39/imm32
    392     # . . call
    393     e8/call  decimal-digit?/disp32
    394     # . . discard args
    395     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    396     # check-ints-equal(eax, 1, msg)
    397     # . . push args
    398     68/push  "F - test-decimal-digit-at-9"/imm32
    399     68/push  1/imm32/true
    400     50/push-eax
    401     # . . call
    402     e8/call  check-ints-equal/disp32
    403     # . . discard args
    404     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    405     c3/return
    406
    407 test-decimal-digit-above-9:
    408     # eax = decimal-digit?(0x3a)
    409     # . . push args
    410     68/push  0x3a/imm32
    411     # . . call
    412     e8/call  decimal-digit?/disp32
    413     # . . discard args
    414     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    415     # check-ints-equal(eax, 0, msg)
    416     # . . push args
    417     68/push  "F - test-decimal-digit-above-9"/imm32
    418     68/push  0/imm32/false
    419     50/push-eax
    420     # . . call
    421     e8/call  check-ints-equal/disp32
    422     # . . discard args
    423     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    424     c3/return
    425
    426 to-decimal-digit:  # in: grapheme -> out/eax: int
    427     # . prologue
    428     55/push-ebp
    429     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    430     # eax = in
    431     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
    432 $to-decimal-digit:check0:
    433     # if (eax < '0') goto abort
    434     3d/compare-eax-with  0x30/imm32/0
    435     7c/jump-if-<  $to-decimal-digit:abort/disp8
    436 $to-decimal-digit:check1:
    437     # if (eax > '9') goto abort
    438     3d/compare-eax-with  0x39/imm32/f
    439     7f/jump-if->  $to-decimal-digit:abort/disp8
    440 $to-decimal-digit:digit:
    441     # return eax - '0'
    442     2d/subtract-from-eax  0x30/imm32/0
    443 $to-decimal-digit:end:
    444     # . epilogue
    445     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    446     5d/pop-to-ebp
    447     c3/return
    448
    449 $to-decimal-digit:abort:
    450     (abort "to-decimal-digit: not a digit character")
    451     # never gets here
    452
    453 # . . vim:nowrap:textwidth=0