https://github.com/akkartik/mu/blob/master/subx/074print-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 #? Entry:  # run a single test, while debugging
  9 #?     e8/call test-print-int32-decimal/disp32
 10 #?     # syscall(exit, Num-test-failures)
 11 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 12 #?     b8/copy-to-EAX  1/imm32/exit
 13 #?     cd/syscall  0x80/imm8
 14 
 15 print-int32-decimal:  # out : (address stream), n : int32
 16     # works by generating characters from lowest to highest and pushing them
 17     # to the stack, before popping them one by one into the stream
 18     #
 19     # pseudocode:
 20     #   copy ESP to EBX
 21     #   EAX = n
 22     #   while true
 23     #     if (EAX == 0) break
 24     #     sign-extend EAX into EDX
 25     #     EAX, EDX = EAX/10, EAX%10
 26     #     push EDX
 27     #   if n < 0
 28     #     push '-' - 0x30 = -3
 29     #   w/EAX = out->write
 30     #   max/ECX = out->length
 31     #   curr/EDI = out->data[out->write]
 32     #   while true
 33     #     if (ESP == EBX) break
 34     #     if (curr >= max) abort
 35     #     pop into EDX
 36     #     EDX += '0'  # convert decimal digit to ascii
 37     #     *curr = DL
 38     #     ++curr
 39     #     ++w
 40     #   out->write = w
 41     #
 42     # . prolog
 43     55/push-EBP
 44     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 45     # . save registers
 46     50/push-EAX
 47     51/push-ECX
 48     52/push-EDX
 49     53/push-EBX
 50     57/push-EDI
 51     # copy ESP to EBX
 52     89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
 53     # ten/ECX = 10
 54     b9/copy-to-ECX  0xa/imm32
 55     # EAX = |n|
 56     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
 57     3d/compare-EAX-with  0/imm32
 58     7d/jump-if-greater-or-equal $print-int32-decimal:read-loop/disp8
 59 $print-int32-decimal:negative:
 60     f7          3/subop/negate      3/mod/direct    0/rm32/EAX    .           .             .           .           .               .                 # negate EAX
 61 $print-int32-decimal:read-loop:
 62     # if (EAX == 0) break
 63     3d/compare-EAX-and  0/imm32
 64     74/jump-if-equal $print-int32-decimal:read-break/disp8
 65     # sign-extend
 66     99/sign-extend-EAX-into-EDX
 67     # EAX, EDX = divide-with-remainder EAX/ten, EAX%ten
 68     f7          7/subop/divide-by   3/mod/direct    1/rm32/ECX    .           .             .           .           .               .                 # divide EDX:EAX by ECX, storing quotient in EAX and remainder in EDX
 69     # push EDX
 70     52/push-EDX
 71     eb/jump  $print-int32-decimal:read-loop/disp8
 72 $print-int32-decimal:read-break:
 73     # if (n < 0) push('-')
 74     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       0/imm32           # compare *(EBP+12)
 75     7d/jump-if-greater-or-equal $print-int32-decimal:write/disp8
 76 $print-int32-decimal:push-negative:
 77     68/push  -3/imm32/dash-minus-zero
 78     # fall through
 79 $print-int32-decimal:write:
 80     # EDI = out
 81     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
 82     # max/ECX = &out->data[out->length]
 83     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EDI+8) to ECX
 84     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           1/r32/ECX   0xc/disp8       .                 # copy EDI+ECX+12 to ECX
 85     # w/EAX = out->write
 86     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy *EDI to EAX
 87     # curr/EDI = &out->data[out->write]
 88     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  0/index/EAX   .           7/r32/EDI   0xc/disp8       .                 # copy EDI+EAX+12 to EDI
 89 $print-int32-decimal:write-loop:
 90     # if (ESP == EBX) break
 91     39/compare                      3/mod/direct    4/rm32/ESP    .           .             .           3/r32/EBX   .               .                 # compare ESP and EBX
 92     74/jump-if-equal  $print-int32-decimal:write-break/disp8
 93     # if (curr >= max) abort
 94     39/compare                      3/mod/direct    7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # compare EDI and ECX
 95     7d/jump-if-greater-or-equal  $print-int32-decimal:abort/disp8
 96     # pop into EDX
 97     5a/pop-into-EDX
 98     # EDX += '0'
 99     81          0/subop/add         3/mod/direct    2/rm32/EDX    .           .             .           .           .               0x30/imm32/zero   # add to EDX
100 $print-int32-decimal:write-char:
101     # *curr = DL
102     88/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/DL    .               .                 # copy DL to byte at *ECX
103     # ++curr
104     47/increment-EDI
105     # ++w
106     40/increment-EAX
107     eb/jump  $print-int32-decimal:write-loop/disp8
108 $print-int32-decimal:write-break:
109     # out->write = w
110     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
111     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDI
112 $print-int32-decimal:end:
113     # . restore registers
114     5f/pop-to-EDI
115     5b/pop-to-EBX
116     5a/pop-to-EDX
117     59/pop-to-ECX
118     58/pop-to-EAX
119     # . epilog
120     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
121     5d/pop-to-EBP
122     c3/return
123 
124 $print-int32-decimal:abort:
125     # . _write(2/stderr, error)
126     # . . push args
127     68/push  "print-int32-decimal: out of space"/imm32
128     68/push  2/imm32/stderr
129     # . . call
130     e8/call  _write/disp32
131     # . . discard args
132     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
133     # . syscall(exit, 1)
134     bb/copy-to-EBX  1/imm32
135     b8/copy-to-EAX  1/imm32/exit
136     cd/syscall  0x80/imm8
137     # never gets here
138 
139 test-print-int32-decimal:
140     # check that a single-digit number converts correctly
141     # setup
142     # . clear-stream(_test-stream)
143     # . . push args
144     68/push  _test-stream/imm32
145     # . . call
146     e8/call  clear-stream/disp32
147     # . . discard args
148     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
149     # print-int32-decimal(_test-stream, 9)
150     # . . push args
151     68/push  9/imm32
152     68/push  _test-stream/imm32
153     # . . call
154     e8/call  print-int32-decimal/disp32
155     # . . discard args
156     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
157     # check-stream-equal(_test-stream, "9", msg)
158     # . . push args
159     68/push  "F - test-print-int32-decimal"/imm32
160     68/push  "9"/imm32
161     68/push  _test-stream/imm32
162     # . . call
163     e8/call  check-stream-equal/disp32
164     # . . discard args
165     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
166     # . end
167     c3/return
168 
169 test-print-int32-decimal-multiple-digits:
170     # check that a multi-digit number converts correctly
171     # setup
172     # . clear-stream(_test-stream)
173     # . . push args
174     68/push  _test-stream/imm32
175     # . . call
176     e8/call  clear-stream/disp32
177     # . . discard args
178     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
179     # print-int32-decimal(_test-stream, 10)
180     # . . push args
181     68/push  0xa/imm32
182     68/push  _test-stream/imm32
183     # . . call
184     e8/call  print-int32-decimal/disp32
185     # . . discard args
186     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
187     # check-stream-equal(_test-stream, "10", msg)
188     # . . push args
189     68/push  "F - test-print-int32-decimal-multiple-digits"/imm32
190     68/push  "10"/imm32
191     68/push  _test-stream/imm32
192     # . . call
193     e8/call  check-stream-equal/disp32
194     # . . discard args
195     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
196     # . end
197     c3/return
198 
199 test-print-int32-decimal-negative:
200     # check that a negative single-digit number converts correctly
201     # setup
202     # . clear-stream(_test-stream)
203     # . . push args
204     68/push  _test-stream/imm32
205     # . . call
206     e8/call  clear-stream/disp32
207     # . . discard args
208     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
209     # print-int32-decimal(_test-stream, -9)
210     # . . push args
211     68/push  -9/imm32
212     68/push  _test-stream/imm32
213     # . . call
214     e8/call  print-int32-decimal/disp32
215     # . . discard args
216     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
217 +-- 26 lines: #?     # dump _test-stream ---------------------------------------------------------------------------------------------------------------------
243     # check-stream-equal(_test-stream, "-9", msg)
244     # . . push args
245     68/push  "F - test-print-int32-decimal-negative"/imm32
246     68/push  "-9"/imm32
247     68/push  _test-stream/imm32
248     # . . call
249     e8/call  check-stream-equal/disp32
250     # . . discard args
251     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
252     # . end
253     c3/return
254 
255 test-print-int32-decimal-negative-multiple-digits:
256     # check that a multi-digit number converts correctly
257     # setup
258     # . clear-stream(_test-stream)
259     # . . push args
260     68/push  _test-stream/imm32
261     # . . call
262     e8/call  clear-stream/disp32
263     # . . discard args
264     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
265     # print-int32-decimal(_test-stream, -10)
266     # . . push args
267     68/push  -0xa/imm32
268     68/push  _test-stream/imm32
269     # . . call
270     e8/call  print-int32-decimal/disp32
271     # . . discard args
272     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
273     # check-stream-equal(_test-stream, "-10", msg)
274     # . . push args
275     68/push  "F - test-print-int32-decimal-negative-multiple-digits"/imm32
276     68/push  "-10"/imm32
277     68/push  _test-stream/imm32
278     # . . call
279     e8/call  check-stream-equal/disp32
280     # . . discard args
281     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
282     # . end
283     c3/return
284 
285 # . . vim:nowrap:textwidth=0