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-negative/disp32
 10 #?
 11 #?     # syscall(exit, Num-test-failures)
 12 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 13 #?     b8/copy-to-EAX  1/imm32/exit
 14 #?     cd/syscall  0x80/imm8
 15 
 16 print-int32-decimal:  # out : (address stream), n : int32
 17     # works by generating characters from lowest to highest and pushing them
 18     # to the stack, before popping them one by one into the stream
 19     #
 20     # pseudocode:
 21     #   push sentinel
 22     #   EAX = abs(n)
 23     #   while true
 24     #     sign-extend EAX into EDX
 25     #     EAX, EDX = EAX/10, EAX%10
 26     #     EDX += '0'
 27     #     push EDX
 28     #     if (EAX == 0) break
 29     #   if n < 0
 30     #     push '-'
 31     #   w = out->write
 32     #   curr = &out->data[out->write]
 33     #   max = &out->data[out->length]
 34     #   while true
 35     #     pop into EAX
 36     #     if (EAX == sentinel) break
 37     #     if (curr >= max) abort
 38     #     *curr = AL
 39     #     ++curr
 40     #     ++w
 41     #   out->write = w
 42     # (based on K&R itoa: https://en.wikibooks.org/wiki/C_Programming/stdlib.h/itoa)
 43     # (this pseudocode contains registers because operations like division
 44     # require specific registers in x86)
 45     #
 46     # . prolog
 47     55/push-EBP
 48     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 49     # . save registers
 50     50/push-EAX
 51     51/push-ECX
 52     52/push-EDX
 53     53/push-EBX
 54     57/push-EDI
 55     # ten/ECX = 10
 56     b9/copy-to-ECX  0xa/imm32
 57     # push sentinel
 58     68/push  0/imm32/sentinel
 59     # EAX = abs(n)
 60     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
 61     3d/compare-EAX-with  0/imm32
 62     7d/jump-if-greater-or-equal  $print-int32-decimal:read-loop/disp8
 63 $print-int32-decimal:negative:
 64     f7          3/subop/negate      3/mod/direct    0/rm32/EAX    .           .             .           .           .               .                 # negate EAX
 65 $print-int32-decimal:read-loop:
 66     # EAX, EDX = EAX / 10, EAX % 10
 67     99/sign-extend-EAX-into-EDX
 68     f7          7/subop/idiv        3/mod/direct    1/rm32/ECX    .           .             .           .           .               .                 # divide EDX:EAX by ECX, storing quotient in EAX and remainder in EDX
 69     # EDX += '0'
 70     81          0/subop/add         3/mod/direct    2/rm32/EDX    .           .             .           .           .               0x30/imm32        # add to EDX
 71     # push EDX
 72     52/push-EDX
 73     # if (EAX == 0) break
 74     3d/compare-EAX-and  0/imm32
 75     7f/jump-if-greater  $print-int32-decimal:read-loop/disp8
 76 $print-int32-decimal:read-break:
 77     # if (n < 0) push('-')
 78     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       0/imm32           # compare *(EBP+12)
 79     7d/jump-if-greater-or-equal  $print-int32-decimal:write/disp8
 80 $print-int32-decimal:push-negative:
 81     68/push  0x2d/imm32/-
 82 $print-int32-decimal:write:
 83     # EDI = out
 84     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
 85     # w/EDX = out->write
 86     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy *EDI to EDX
 87     # curr/ECX = &out->data[out->write]
 88     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
 89     # max/EBX = &out->data[out->length]
 90     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   8/disp8         .                 # copy *(EDI+8) to EBX
 91     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
 92 $print-int32-decimal:write-loop:
 93     # pop into EAX
 94     58/pop-to-EAX
 95     # if (EAX == sentinel) break
 96     3d/compare-EAX-and  0/imm32/sentinel
 97     74/jump-if-equal  $print-int32-decimal:write-break/disp8
 98     # if (curr >= max) abort
 99     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           3/r32/EBX   .               .                 # compare ECX with EBX
100     7d/jump-if-greater-or-equal  $print-int32-decimal:abort/disp8
101 $print-int32-decimal:write-char:
102     # *curr = AL
103     88/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy AL to byte at *ECX
104     # ++curr
105     41/increment-ECX
106     # ++w
107     42/increment-EDX
108     eb/jump  $print-int32-decimal:write-loop/disp8
109 $print-int32-decimal:write-break:
110     # out->write = w
111     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy EDX 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\n"/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-zero:
170     # - check that 0 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, 0)
180     # . . push args
181     68/push  0/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, "0", msg)
188     # . . push args
189     68/push  "F - test-print-int32-decimal-zero"/imm32
190     68/push  "0"/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-multiple-digits:
200     # - check that a multi-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, 10)
210     # . . push args
211     68/push  0xa/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     # check-stream-equal(_test-stream, "10", msg)
218     # . . push args
219     68/push  "F - test-print-int32-decimal-multiple-digits"/imm32
220     68/push  "10"/imm32
221     68/push  _test-stream/imm32
222     # . . call
223     e8/call  check-stream-equal/disp32
224     # . . discard args
225     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
226     # . end
227     c3/return
228 
229 test-print-int32-decimal-negative:
230     # - check that a negative single-digit number converts correctly
231     # setup
232     # . clear-stream(_test-stream)
233     # . . push args
234     68/push  _test-stream/imm32
235     # . . call
236     e8/call  clear-stream/disp32
237     # . . discard args
238     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
239     # print-int32-decimal(_test-stream, -9)
240     # . . push args
241     68/push  -9/imm32
242     68/push  _test-stream/imm32
243     # . . call
244     e8/call  print-int32-decimal/disp32
245     # . . discard args
246     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
247 +-- 26 lines: #?     # dump _test-stream ---------------------------------------------------------------------------------------------------------------------
273     # check-stream-equal(_test-stream, "-9", msg)
274     # . . push args
275     68/push  "F - test-print-int32-decimal-negative"/imm32
276     68/push  "-9"/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 test-print-int32-decimal-negative-multiple-digits:
286     # - check that a multi-digit number converts correctly
287     # setup
288     # . clear-stream(_test-stream)
289     # . . push args
290     68/push  _test-stream/imm32
291     # . . call
292     e8/call  clear-stream/disp32
293     # . . discard args
294     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
295     # print-int32-decimal(_test-stream, -10)
296     # . . push args
297     68/push  -0xa/imm32
298     68/push  _test-stream/imm32
299     # . . call
300     e8/call  print-int32-decimal/disp32
301     # . . discard args
302     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
303     # check-stream-equal(_test-stream, "-10", msg)
304     # . . push args
305     68/push  "F - test-print-int32-decimal-negative-multiple-digits"/imm32
306     68/push  "-10"/imm32
307     68/push  _test-stream/imm32
308     # . . call
309     e8/call  check-stream-equal/disp32
310     # . . discard args
311     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
312     # . end
313     c3/return
314 
315 # . . vim:nowrap:textwidth=0