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