1 # Helper to print an int32 in decimal.
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
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
16 # sign-extend eax into edx
17 # eax, edx = eax/10, eax%10
24 # curr = &out->data[out->write]
25 # max = &out->data[out->size]
28 # if (eax == sentinel) break
29 # if (curr >= max) abort
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)
40 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
48 b9/copy-to-ecx 0xa/imm32
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
62 81 0/subop/add 3/mod/direct 2/rm32/edx . . . . . 0x30/imm32 # add to edx
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:
74 $write-int32-decimal:write:
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:
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:
95 88/copy-byte 0/mod/indirect 1/rm32/ecx . . . 0/r32/AL . . # copy AL to byte at *ecx
100 eb/jump $write-int32-decimal:write-loop/disp8
101 $write-int32-decimal:write-break:
103 89/copy 0/mod/indirect 7/rm32/edi . . . 2/r32/edx . . # copy edx to *edi
104 $write-int32-decimal:end:
105 # . restore registers
112 89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
116 $write-int32-decimal:abort:
117 (abort "write-int32-decimal: stream out of space")
120 test-write-int32-decimal:
121 # - check that a single-digit number converts correctly
123 # . clear-stream(_test-stream)
125 68/push _test-stream/imm32
127 e8/call clear-stream/disp32
129 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
130 # write-int32-decimal(_test-stream, 9)
133 68/push _test-stream/imm32
135 e8/call write-int32-decimal/disp32
137 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
138 # check-stream-equal(_test-stream, "9", msg)
140 68/push "F - test-write-int32-decimal"/imm32
142 68/push _test-stream/imm32
144 e8/call check-stream-equal/disp32
146 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
150 test-write-int32-decimal-zero:
151 # - check that 0 converts correctly
153 # . clear-stream(_test-stream)
155 68/push _test-stream/imm32
157 e8/call clear-stream/disp32
159 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
160 # write-int32-decimal(_test-stream, 0)
163 68/push _test-stream/imm32
165 e8/call write-int32-decimal/disp32
167 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
168 # check-stream-equal(_test-stream, "0", msg)
170 68/push "F - test-write-int32-decimal-zero"/imm32
172 68/push _test-stream/imm32
174 e8/call check-stream-equal/disp32
176 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
180 test-write-int32-decimal-multiple-digits:
181 # - check that a multi-digit number converts correctly
183 # . clear-stream(_test-stream)
185 68/push _test-stream/imm32
187 e8/call clear-stream/disp32
189 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
190 # write-int32-decimal(_test-stream, 10)
193 68/push _test-stream/imm32
195 e8/call write-int32-decimal/disp32
197 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
198 # check-stream-equal(_test-stream, "10", msg)
200 68/push "F - test-write-int32-decimal-multiple-digits"/imm32
202 68/push _test-stream/imm32
204 e8/call check-stream-equal/disp32
206 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
210 test-write-int32-decimal-negative:
211 # - check that a negative single-digit number converts correctly
213 # . clear-stream(_test-stream)
215 68/push _test-stream/imm32
217 e8/call clear-stream/disp32
219 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
220 # write-int32-decimal(_test-stream, -9)
223 68/push _test-stream/imm32
225 e8/call write-int32-decimal/disp32
227 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
228 #? # dump _test-stream {{{
229 #? # . write(2/stderr, "^")
232 #? 68/push 2/imm32/stderr
234 #? e8/call write/disp32
235 #? # . . discard args
236 #? 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
237 #? # . write-stream(2/stderr, _test-stream)
239 #? 68/push _test-stream/imm32
240 #? 68/push 2/imm32/stderr
242 #? e8/call write-stream/disp32
243 #? # . . discard args
244 #? 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
245 #? # . write(2/stderr, "$\n")
247 #? 68/push "$\n"/imm32
248 #? 68/push 2/imm32/stderr
250 #? e8/call write/disp32
251 #? # . . discard args
252 #? 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
254 # check-stream-equal(_test-stream, "-9", msg)
256 68/push "F - test-write-int32-decimal-negative"/imm32
258 68/push _test-stream/imm32
260 e8/call check-stream-equal/disp32
262 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
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:
270 # . clear-stream(_test-stream)
272 68/push _test-stream/imm32
274 e8/call clear-stream/disp32
276 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
277 # write-int32-decimal(_test-stream, 0x80000000)
279 68/push 0x80000000/imm32
280 68/push _test-stream/imm32
282 e8/call write-int32-decimal/disp32
284 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
285 # check-stream-equal(_test-stream, "-(", msg)
287 68/push "F - test-write-int32-decimal-indefinite-integer"/imm32
289 68/push _test-stream/imm32
291 e8/call check-stream-equal/disp32
293 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
297 test-write-int32-decimal-negative-multiple-digits:
298 # - check that a multi-digit number converts correctly
300 # . clear-stream(_test-stream)
302 68/push _test-stream/imm32
304 e8/call clear-stream/disp32
306 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
307 # write-int32-decimal(_test-stream, -10)
310 68/push _test-stream/imm32
312 e8/call write-int32-decimal/disp32
314 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
315 # check-stream-equal(_test-stream, "-10", msg)
317 68/push "F - test-write-int32-decimal-negative-multiple-digits"/imm32
319 68/push _test-stream/imm32
321 e8/call check-stream-equal/disp32
323 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
327 decimal-digit?: # c: code-point-utf8 -> result/eax: boolean
330 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
334 8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 8/disp8 . # copy *(ebp+8) to ecx
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
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
346 # . restore registers
349 89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
353 test-decimal-digit-below-0:
354 # eax = decimal-digit?(0x2f)
358 e8/call decimal-digit?/disp32
360 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
361 # check-ints-equal(eax, 0, msg)
363 68/push "F - test-decimal-digit-below-0"/imm32
364 68/push 0/imm32/false
367 e8/call check-ints-equal/disp32
369 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
372 test-decimal-digit-0-to-9:
373 # eax = decimal-digit?(0x30)
377 e8/call decimal-digit?/disp32
379 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
380 # check-ints-equal(eax, 1, msg)
382 68/push "F - test-decimal-digit-at-0"/imm32
386 e8/call check-ints-equal/disp32
388 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
389 # eax = decimal-digit?(0x39)
393 e8/call decimal-digit?/disp32
395 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
396 # check-ints-equal(eax, 1, msg)
398 68/push "F - test-decimal-digit-at-9"/imm32
402 e8/call check-ints-equal/disp32
404 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
407 test-decimal-digit-above-9:
408 # eax = decimal-digit?(0x3a)
412 e8/call decimal-digit?/disp32
414 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
415 # check-ints-equal(eax, 0, msg)
417 68/push "F - test-decimal-digit-above-9"/imm32
418 68/push 0/imm32/false
421 e8/call check-ints-equal/disp32
423 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
426 to-decimal-digit: # in: code-point-utf8 -> out/eax: int
429 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
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:
442 2d/subtract-from-eax 0x30/imm32/0
443 $to-decimal-digit:end:
445 89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
449 $to-decimal-digit:abort:
450 (abort "to-decimal-digit: not a digit character")
453 # . . vim:nowrap:textwidth=0