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