https://github.com/akkartik/mu/blob/main/105string-equal.subx
  1 # Comparing 'regular' size-prefixed strings.
  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 string-equal?:  # s: (addr array byte), benchmark: (addr array byte) -> result/eax: boolean
  9     # pseudocode:
 10     #   if (s->size != benchmark->size) return false
 11     #   return string-starts-with?(s, benchmark)
 12     #
 13     # . prologue
 14     55/push-ebp
 15     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 16     # . save registers
 17     51/push-ecx
 18     56/push-esi
 19     57/push-edi
 20     # esi = s
 21     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 22     # edi = benchmark
 23     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
 24     # ecx = s->size
 25     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
 26 $string-equal?:sizes:
 27     # if (ecx != benchmark->size) return false
 28     39/compare                      0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # compare *edi and ecx
 29     b8/copy-to-eax  0/imm32/false
 30     75/jump-if-!=  $string-equal?:end/disp8
 31 $string-equal?:contents:
 32     # string-starts-with?(s, benchmark)
 33     # . . push args
 34     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 35     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 36     # . . call
 37     e8/call  string-starts-with?/disp32
 38     # . . discard args
 39     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 40 $string-equal?:end:
 41     # . restore registers
 42     5f/pop-to-edi
 43     5e/pop-to-esi
 44     59/pop-to-ecx
 45     # . epilogue
 46     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 47     5d/pop-to-ebp
 48     c3/return
 49 
 50 string-starts-with?:  # s: (addr array byte), benchmark: (addr array byte) -> result/eax: boolean
 51     # pseudocode:
 52     #   if (s->size < benchmark->size) return false
 53     #   currs = s->data
 54     #   currb = benchmark->data
 55     #   maxb = &benchmark->data[benchmark->size]
 56     #   while currb < maxb
 57     #     c1 = *currs
 58     #     c2 = *currb
 59     #     if (c1 != c2) return false
 60     #     ++currs, ++currb
 61     #   return true
 62     #
 63     # registers:
 64     #   currs: esi
 65     #   maxs: ecx
 66     #   currb: edi
 67     #   c1: eax
 68     #   c2: ebx
 69     #
 70     # . prologue
 71     55/push-ebp
 72     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 73     # . save registers
 74     51/push-ecx
 75     52/push-edx
 76     56/push-esi
 77     57/push-edi
 78     # esi = s
 79     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 80     # edi = benchmark
 81     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
 82     # var bsize/ecx: int = benchmark->size
 83     8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
 84 $string-starts-with?:sizes:
 85     # if (s->size < bsize) return false
 86     39/compare                      0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare *esi with ecx
 87     7c/jump-if-<  $string-starts-with?:false/disp8
 88     # var currs/esi: (addr byte) = s->data
 89     81          0/subop/add         3/mod/direct    6/rm32/esi    .           .             .           .           .               4/imm32           # add to esi
 90     # var currb/edi: (addr byte) = benchmark->data
 91     81          0/subop/add         3/mod/direct    7/rm32/edi    .           .             .           .           .               4/imm32           # add to edi
 92     # var maxb/ecx: (addr byte) = &benchmark->data[benchmark->size]
 93     01/add                          3/mod/direct    1/rm32/ecx    .           .             .           7/r32/edi   .               .                 # add edi to ecx
 94     # var c1/eax: byte = 0
 95     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 96     # var c2/edx: byte = 0
 97     31/xor                          3/mod/direct    2/rm32/edx    .           .             .           2/r32/edx   .               .                 # clear edx
 98 $string-starts-with?:loop:
 99     # if (currs >= maxs) return true
100     39/compare                      3/mod/direct    7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # compare edi with ecx
101     73/jump-if-addr>=  $string-starts-with?:true/disp8
102     # c1 = *currs
103     8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
104     # c2 = *currb
105     8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           2/r32/DL    .               .                 # copy byte at *edi to DL
106     # if (c1 != c2) return false
107     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax and edx
108     75/jump-if-!=  $string-starts-with?:false/disp8
109     # ++currs
110     46/increment-esi
111     # ++currb
112     47/increment-edi
113     eb/jump  $string-starts-with?:loop/disp8
114 $string-starts-with?:true:
115     b8/copy-to-eax  1/imm32
116     eb/jump  $string-starts-with?:end/disp8
117 $string-starts-with?:false:
118     b8/copy-to-eax  0/imm32
119 $string-starts-with?:end:
120     # . restore registers
121     5f/pop-to-edi
122     5e/pop-to-esi
123     5a/pop-to-edx
124     59/pop-to-ecx
125     # . epilogue
126     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
127     5d/pop-to-ebp
128     c3/return
129 
130 # - tests
131 
132 test-compare-empty-with-empty-string:
133     # eax = string-equal?("", "")
134     # . . push args
135     68/push  ""/imm32
136     68/push  ""/imm32
137     # . . call
138     e8/call  string-equal?/disp32
139     # . . discard args
140     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
141     # check-ints-equal(eax, 1, msg)
142     # . . push args
143     68/push  "F - test-compare-empty-with-empty-string"/imm32
144     68/push  1/imm32/true
145     50/push-eax
146     # . . call
147     e8/call  check-ints-equal/disp32
148     # . . discard args
149     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
150     c3/return
151 
152 test-compare-empty-with-non-empty-string:  # also checks size-mismatch code path
153     # eax = string-equal?("", "Abc")
154     # . . push args
155     68/push  "Abc"/imm32
156     68/push  ""/imm32
157     # . . call
158     e8/call  string-equal?/disp32
159     # . . discard args
160     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
161     # check-ints-equal(eax, 0, msg)
162     # . . push args
163     68/push  "F - test-compare-empty-with-non-empty-string"/imm32
164     68/push  0/imm32/false
165     50/push-eax
166     # . . call
167     e8/call  check-ints-equal/disp32
168     # . . discard args
169     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
170     c3/return
171 
172 test-compare-equal-strings:
173     # eax = string-equal?("Abc", "Abc")
174     # . . push args
175     68/push  "Abc"/imm32
176     68/push  "Abc"/imm32
177     # . . call
178     e8/call  string-equal?/disp32
179     # . . discard args
180     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
181     # check-ints-equal(eax, 1, msg)
182     # . . push args
183     68/push  "F - test-compare-equal-strings"/imm32
184     68/push  1/imm32/true
185     50/push-eax
186     # . . call
187     e8/call  check-ints-equal/disp32
188     # . . discard args
189     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
190     c3/return
191 
192 test-compare-inequal-strings-equal-sizes:
193     # eax = string-equal?("Abc", "Adc")
194     # . . push args
195     68/push  "Adc"/imm32
196     68/push  "Abc"/imm32
197     # . . call
198     e8/call  string-equal?/disp32
199     # . . discard args
200     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
201     # check-ints-equal(eax, 0, msg)
202     # . . push args
203     68/push  "F - test-compare-inequal-strings-equal-sizes"/imm32
204     68/push  0/imm32/false
205     50/push-eax
206     # . . call
207     e8/call  check-ints-equal/disp32
208     # . . discard args
209     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
210     c3/return
211 
212 # helper for later tests
213 check-strings-equal:  # s: (addr array byte), expected: (addr array byte), msg: (addr array byte)
214     # . prologue
215     55/push-ebp
216     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
217     # . save registers
218     50/push-eax
219     # var eax: boolean = string-equal?(s, expected)
220     # . . push args
221     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
222     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
223     # . . call
224     e8/call  string-equal?/disp32
225     # . . discard args
226     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
227     # check-ints-equal(eax, 1, msg)
228     # . . push args
229     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
230     68/push  1/imm32
231     50/push-eax
232     # . . call
233     e8/call  check-ints-equal/disp32
234     # . . discard args
235     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
236 $check-strings-equal:end:
237     # . restore registers
238     58/pop-to-eax
239     # . epilogue
240     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
241     5d/pop-to-ebp
242     c3/return
243 
244 # test the helper
245 test-check-strings-equal:
246     # check-strings-equal("Abc", "Abc")
247     # . . push args
248     68/push  "Abc"/imm32
249     68/push  "Abc"/imm32
250     # . . call
251     e8/call  check-strings-equal/disp32
252     # . . discard args
253     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
254     c3/return
255 
256 # . . vim:nowrap:textwidth=0