https://github.com/akkartik/mu/blob/main/baremetal/311decimal-int.subx
  1 # Helpers for decimal ints.
  2 
  3 # if slice doesn't contain a decimal number, return 0
  4 parse-decimal-int-from-slice:  # in: (addr slice) -> out/eax: int
  5     # . prologue
  6     55/push-ebp
  7     89/<- %ebp 4/r32/esp
  8     # . save registers
  9     51/push-ecx
 10     # ecx = in
 11     8b/-> *(ebp+8) 1/r32/ecx
 12     #
 13     (parse-decimal-int-helper *ecx *(ecx+4))  # => eax
 14 $parse-decimal-int-from-slice:end:
 15     # . restore registers
 16     59/pop-to-ecx
 17     # . epilogue
 18     89/<- %esp 5/r32/ebp
 19     5d/pop-to-ebp
 20     c3/return
 21 
 22 # if slice doesn't contain a decimal number, return 0
 23 parse-decimal-int:  # in: (addr array byte) -> result/eax: int
 24     # . prologue
 25     55/push-ebp
 26     89/<- %ebp 4/r32/esp
 27     # . save registers
 28     51/push-ecx
 29     52/push-edx
 30     # eax = in
 31     8b/-> *(ebp+8) 0/r32/eax
 32     # var start/ecx: (addr byte) = &in->data
 33     8d/copy-address *(eax+4) 1/r32/ecx
 34     # var end/edx: (addr byte) = &in->data[in->size]
 35     8b/-> *eax 2/r32/edx
 36     8d/copy-address *(eax+edx+4) 2/r32/edx
 37     #
 38     (parse-decimal-int-helper %ecx %edx)  # => eax
 39 $parse-decimal-int:end:
 40     # . restore registers
 41     5a/pop-to-edx
 42     59/pop-to-ecx
 43     # . epilogue
 44     89/<- %esp 5/r32/ebp
 45     5d/pop-to-ebp
 46     c3/return
 47 
 48 parse-decimal-int-from-stream:  # in: (addr stream byte) -> result/eax: int
 49     # . prologue
 50     55/push-ebp
 51     89/<- %ebp 4/r32/esp
 52     # . save registers
 53     51/push-ecx
 54     52/push-edx
 55     # eax = in
 56     8b/-> *(ebp+8) 0/r32/eax
 57     # var start/ecx: (addr byte) = &in->data[in->read]
 58     8b/-> *(eax+4) 1/r32/ecx
 59     8d/copy-address *(eax+ecx+0xc) 1/r32/ecx
 60     # var end/edx: (addr byte) = &in->data[in->write]
 61     8b/-> *eax 2/r32/edx
 62     8d/copy-address *(eax+edx+0xc) 2/r32/edx
 63     # trim a trailing newline
 64     {
 65       # speculatively trim
 66       4a/decrement-edx
 67       # if it's a newline, break
 68       8a/byte-> *edx 0/r32/eax
 69       81 4/subop/and %eax 0xff/imm32
 70       3d/compare-eax-and 0xa/imm32/newline
 71       74/jump-if-= break/disp8
 72       # not a newline, so restore it
 73       42/increment-edx
 74     }
 75     #
 76     (parse-decimal-int-helper %ecx %edx)  # => eax
 77 $parse-decimal-int-from-stream:end:
 78     # . restore registers
 79     5a/pop-to-edx
 80     59/pop-to-ecx
 81     # . epilogue
 82     89/<- %esp 5/r32/ebp
 83     5d/pop-to-ebp
 84     c3/return
 85 
 86 parse-decimal-int-helper:  # start: (addr byte), end: (addr byte) -> result/eax: int
 87     # . prologue
 88     55/push-ebp
 89     89/<- %ebp 4/r32/esp
 90     # . save registers
 91     51/push-ecx
 92     52/push-edx
 93     53/push-ebx
 94     56/push-esi
 95     57/push-edi
 96     # var curr/esi: (addr byte) = start
 97     8b/-> *(ebp+8) 6/r32/esi
 98     # edi = end
 99     8b/-> *(ebp+0xc) 7/r32/edi
100     # var negate?/edx: boolean = false
101     ba/copy-to-edx 0/imm32/false
102     # if (*curr == '-') ++curr, negate = true
103     {
104 $parse-decimal-int-helper:negative:
105       b8/copy-to-eax 0/imm32
106       8a/copy-byte *esi 0/r32/AL
107       3d/compare-eax-and 0x2d/imm32/-
108       75/jump-if-!= break/disp8
109       # . ++curr
110       46/increment-esi
111       # . negate = true
112       ba/copy-to-edx  1/imm32/true
113     }
114     # spill negate?
115     52/push-edx
116     # var result/eax: int = 0
117     b8/copy-to-eax 0/imm32
118     # var digit/ecx: int = 0
119     b9/copy-to-ecx 0/imm32
120     # const TEN/ebx: int = 10
121     bb/copy-to-ebx 0xa/imm32
122     {
123 $parse-decimal-int-helper:loop:
124       # if (curr >= in->end) break
125       39/compare %esi 7/r32/edi
126       73/jump-if-addr>= break/disp8
127       # if !is-decimal-digit?(*curr) return 0
128       8a/copy-byte *esi 1/r32/CL
129       50/push-eax
130       (is-decimal-digit? %ecx)  # => eax
131       {
132         3d/compare-eax-and 0/imm32/false
133         75/jump-if-!= break/disp8
134         58/pop-to-eax
135         b8/copy-to-eax 0/imm32
136         eb/jump $parse-decimal-int-helper:negate/disp8
137       }
138       58/pop-to-eax
139       # digit = from-decimal-char(*curr)
140       81 5/subop/subtract %ecx 0x30/imm32/zero
141       # TODO: error checking
142       # result = result * 10 + digit
143       ba/copy-to-edx 0/imm32
144       f7 4/subop/multiply-into-edx-eax %ebx
145       # TODO: check edx for overflow
146       01/add %eax 1/r32/ecx
147       # ++curr
148       46/increment-esi
149       #
150       eb/jump loop/disp8
151     }
152 $parse-decimal-int-helper:negate:
153     # if (negate?) result = -result
154     5a/pop-to-edx
155     {
156       81 7/subop/compare %edx 0/imm32/false
157       74/jump-if-= break/disp8
158       f7 3/subop/negate %eax
159     }
160 $parse-decimal-int-helper:end:
161     # . restore registers
162     5f/pop-to-edi
163     5e/pop-to-esi
164     5b/pop-to-ebx
165     5a/pop-to-edx
166     59/pop-to-ecx
167     # . epilogue
168     89/<- %esp 5/r32/ebp
169     5d/pop-to-ebp
170     c3/return
171 
172 test-parse-decimal-int-from-slice-single-digit:
173     # . prologue
174     55/push-ebp
175     89/<- %ebp 4/r32/esp
176     # . save registers
177     50/push-eax
178     51/push-ecx
179     # (eax..ecx) = "3"
180     b8/copy-to-eax "3"/imm32
181     8b/-> *eax 1/r32/ecx
182     8d/copy-address *(eax+ecx+4) 1/r32/ecx
183     05/add-to-eax 4/imm32
184     # var slice/ecx: slice = {eax, ecx}
185     51/push-ecx
186     50/push-eax
187     89/<- %ecx 4/r32/esp
188     #
189     (parse-decimal-int-from-slice %ecx)  # => eax
190     (check-ints-equal %eax 3 "F - test-parse-decimal-int-from-slice-single-digit")
191 $test-parse-decimal-int-helper-single-digit:end:
192     # . restore registers
193     59/pop-to-ecx
194     58/pop-to-eax
195     # . epilogue
196     89/<- %esp 5/r32/ebp
197     5d/pop-to-ebp
198     c3/return
199 
200 test-parse-decimal-int-from-slice-multi-digit:
201     # . prologue
202     55/push-ebp
203     89/<- %ebp 4/r32/esp
204     # . save registers
205     50/push-eax
206     51/push-ecx
207     # (eax..ecx) = "34"
208     b8/copy-to-eax "34"/imm32
209     8b/-> *eax 1/r32/ecx
210     8d/copy-address *(eax+ecx+4) 1/r32/ecx
211     05/add-to-eax 4/imm32
212     # var slice/ecx: slice = {eax, ecx}
213     51/push-ecx
214     50/push-eax
215     89/<- %ecx 4/r32/esp
216     #
217     (parse-decimal-int-from-slice %ecx)  # => eax
218     (check-ints-equal %eax 0x22 "F - test-parse-decimal-int-from-slice-multi-digit")  # 34 in hex
219 $test-parse-decimal-int-helper-multi-digit:end:
220     # . restore registers
221     59/pop-to-ecx
222     58/pop-to-eax
223     # . epilogue
224     89/<- %esp 5/r32/ebp
225     5d/pop-to-ebp
226     c3/return
227 
228 test-parse-decimal-int-from-slice-zero:
229     # . prologue
230     55/push-ebp
231     89/<- %ebp 4/r32/esp
232     # . save registers
233     50/push-eax
234     51/push-ecx
235     # (eax..ecx) = "00"
236     b8/copy-to-eax "00"/imm32
237     8b/-> *eax 1/r32/ecx
238     8d/copy-address *(eax+ecx+4) 1/r32/ecx
239     05/add-to-eax 4/imm32
240     # var slice/ecx: slice = {eax, ecx}
241     51/push-ecx
242     50/push-eax
243     89/<- %ecx 4/r32/esp
244     #
245     (parse-decimal-int-from-slice %ecx)  # => eax
246     (check-ints-equal %eax 0 "F - test-parse-decimal-int-from-slice-zero")
247 $test-parse-decimal-int-helper-zero:end:
248     # . restore registers
249     59/pop-to-ecx
250     58/pop-to-eax
251     # . epilogue
252     89/<- %esp 5/r32/ebp
253     5d/pop-to-ebp
254     c3/return
255 
256 test-parse-decimal-int-from-slice-negative:
257     # . prologue
258     55/push-ebp
259     89/<- %ebp 4/r32/esp
260     # . save registers
261     50/push-eax
262     51/push-ecx
263     # (eax..ecx) = "-3"
264     b8/copy-to-eax "-3"/imm32
265     8b/-> *eax 1/r32/ecx
266     8d/copy-address *(eax+ecx+4) 1/r32/ecx
267     05/add-to-eax 4/imm32
268     # var slice/ecx: slice = {eax, ecx}
269     51/push-ecx
270     50/push-eax
271     89/<- %ecx 4/r32/esp
272     #
273     (parse-decimal-int-from-slice %ecx)  # => eax
274     (check-ints-equal %eax -3 "F - test-parse-decimal-int-from-slice-negative")
275 $test-parse-decimal-int-helper-negative:end:
276     # . restore registers
277     59/pop-to-ecx
278     58/pop-to-eax
279     # . epilogue
280     89/<- %esp 5/r32/ebp
281     5d/pop-to-ebp
282     c3/return
283 
284 test-parse-decimal-int-from-slice-multi-digit-negative:
285     # . prologue
286     55/push-ebp
287     89/<- %ebp 4/r32/esp
288     # . save registers
289     50/push-eax
290     51/push-ecx
291     # (eax..ecx) = "-32"
292     b8/copy-to-eax "-32"/imm32
293     8b/-> *eax 1/r32/ecx
294     8d/copy-address *(eax+ecx+4) 1/r32/ecx
295     05/add-to-eax 4/imm32
296     # var slice/ecx: slice = {eax, ecx}
297     51/push-ecx
298     50/push-eax
299     89/<- %ecx 4/r32/esp
300     #
301     (parse-decimal-int-from-slice %ecx)  # => eax
302     (check-ints-equal %eax -0x20 "F - test-parse-decimal-int-from-slice-multi-digit-negative")  # -32 in hex
303 $test-parse-decimal-int-helper-multi-digit-negative:end:
304     # . restore registers
305     59/pop-to-ecx
306     58/pop-to-eax
307     # . epilogue
308     89/<- %esp 5/r32/ebp
309     5d/pop-to-ebp
310     c3/return
311 
312 decimal-size:  # n: int -> result/eax: int
313     # pseudocode:
314     #   edi = 0
315     #   eax = n
316     #   if eax < 0
317     #     ++edi  # for '-'
318     #     negate eax
319     #   while true
320     #     edx = 0
321     #     eax, edx = eax/10, eax%10
322     #     ++edi
323     #     if (eax == 0) break
324     #   eax = edi
325     #
326     # . prologue
327     55/push-ebp
328     89/<- %ebp 4/r32/esp
329     # . save registers
330     51/push-ecx
331     52/push-edx
332     57/push-edi
333     # edi = 0
334     bf/copy-to-edi 0/imm32
335     # eax = n
336     8b/-> *(ebp+8) 0/r32/eax
337     # if (n < 0) negate n, increment edi
338     {
339       3d/compare-eax-with 0/imm32
340       7d/jump-if->= break/disp8
341       f7 3/subop/negate %eax
342       47/increment-edi
343     }
344     # const ten/ecx = 10
345     b9/copy-to-ecx  0xa/imm32
346     {
347       ba/copy-to-edx 0/imm32
348       f7 7/subop/idiv-edx-eax-by %ecx  # eax = edx:eax/10; edx = edx:eax%10
349       47/increment-edi
350       3d/compare-eax-and 0/imm32
351       75/jump-if-!= loop/disp8
352     }
353 $decimal-size:end:
354     89/<- %eax 7/r32/edi
355     # . restore registers
356     5f/pop-to-edi
357     5a/pop-to-edx
358     59/pop-to-ecx
359     # . epilogue
360     89/<- %esp 5/r32/ebp
361     5d/pop-to-ebp
362     c3/return
363 
364 test-decimal-size-of-zero:
365     # . prologue
366     55/push-ebp
367     89/<- %ebp 4/r32/esp
368     #
369     (decimal-size 0)  # => eax
370     (check-ints-equal %eax 1 "F - test-decimal-size-of-zero")
371 $test-decimal-size-of-zero:end:
372     # . epilogue
373     89/<- %esp 5/r32/ebp
374     5d/pop-to-ebp
375     c3/return
376 
377 test-decimal-size-single-digit:
378     # . prologue
379     55/push-ebp
380     89/<- %ebp 4/r32/esp
381     #
382     (decimal-size 4)  # => eax
383     (check-ints-equal %eax 1 "F - test-decimal-size-single-digit")
384 $test-decimal-size-single-digit:end:
385     # . epilogue
386     89/<- %esp 5/r32/ebp
387     5d/pop-to-ebp
388     c3/return
389 
390 test-decimal-size-multi-digit:
391     # . prologue
392     55/push-ebp
393     89/<- %ebp 4/r32/esp
394     #
395     (decimal-size 0xa)  # => eax
396     (check-ints-equal %eax 2 "F - test-decimal-size-multi-digit")
397 $test-decimal-size-multi-digit:end:
398     # . epilogue
399     89/<- %esp 5/r32/ebp
400     5d/pop-to-ebp
401     c3/return
402 
403 test-decimal-size-single-digit-negative:
404     # . prologue
405     55/push-ebp
406     89/<- %ebp 4/r32/esp
407     #
408     (decimal-size -4)  # => eax
409     (check-ints-equal %eax 2 "F - test-decimal-size-single-digit-negative")
410 $test-decimal-size-single-digit-negative:end:
411     # . epilogue
412     89/<- %esp 5/r32/ebp
413     5d/pop-to-ebp
414     c3/return
415 
416 test-decimal-size-multi-digit-negative:
417     # . prologue
418     55/push-ebp
419     89/<- %ebp 4/r32/esp
420     #
421     (decimal-size -0xa)  # => eax
422     (check-ints-equal %eax 3 "F - test-decimal-size-multi-digit-negative")
423 $test-decimal-size-multi-digit-negative:end:
424     # . epilogue
425     89/<- %esp 5/r32/ebp
426     5d/pop-to-ebp
427     c3/return