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 : (address string), benchmark : (address string) -> EAX : boolean
 17     # pseudocode:
 18     #   if (s->length != benchmark->length) return false
 19     #   currs = s->data
 20     #   currb = benchmark->data
 21     #   maxs = s->data + s->length
 22     #   while currs < maxs
 23     #     c1 = *currs
 24     #     c2 = *currb
 25     #     if (c1 != c2) return false
 26     #     ++currs, ++currb
 27     #   return true
 28     #
 29     # registers:
 30     #   currs: ESI
 31     #   maxs: ECX
 32     #   currb: EDI
 33     #   c1: EAX
 34     #   c2: EBX
 35     #
 36     # . prolog
 37     55/push-EBP
 38     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 39     # . save registers
 40     51/push-ECX
 41     52/push-EDX
 42     56/push-ESI
 43     57/push-EDI
 44     # ESI = s
 45     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 46     # EDI = benchmark
 47     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
 48     # ECX = s->length
 49     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
 50 $string-equal?:lengths:
 51     # if (ECX != benchmark->length) return false
 52     39/compare                      0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # compare *EDI and ECX
 53     75/jump-if-not-equal  $string-equal?:false/disp8
 54     # currs/ESI = s->data
 55     81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               4/imm32           # add to ESI
 56     # maxs/ECX = s->data + s->length
 57     01/add                          3/mod/direct    1/rm32/ECX    .           .             .           6/r32/ESI   .               .                 # add ESI to ECX
 58     # currb/EDI = benchmark->data
 59     81          0/subop/add         3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm32           # add to EDI
 60     # c1/EAX = c2/EDX = 0
 61     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 62     31/xor                          3/mod/direct    2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # clear EDX
 63 $string-equal?:loop:
 64     # if (currs >= maxs) return true
 65     39/compare                      3/mod/direct    6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # compare ESI with ECX
 66     73/jump-if-greater-or-equal-unsigned  $string-equal?:true/disp8
 67     # c1 = *currs
 68     8a/copy-byte                    0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/AL    .               .                 # copy byte at *ESI to AL
 69     # c2 = *currb
 70     8a/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/DL    .               .                 # copy byte at *EDI to DL
 71     # if (c1 != c2) return false
 72     39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # compare EAX and EDX
 73     75/jump-if-not-equal  $string-equal?:false/disp8
 74     # ++currs
 75     46/increment-ESI
 76     # ++currb
 77     47/increment-EDI
 78     eb/jump  $string-equal?:loop/disp8
 79 $string-equal?:true:
 80     b8/copy-to-EAX  1/imm32
 81     eb/jump  $string-equal?:end/disp8
 82 $string-equal?:false:
 83     b8/copy-to-EAX  0/imm32
 84 $string-equal?:end:
 85     # . restore registers
 86     5f/pop-to-EDI
 87     5e/pop-to-ESI
 88     5a/pop-to-EDX
 89     59/pop-to-ECX
 90     # . epilog
 91     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 92     5d/pop-to-EBP
 93     c3/return
 94 
 95 # - tests
 96 
 97 test-compare-empty-with-empty-string:
 98     # EAX = string-equal?("", "")
 99     # . . push args
100     68/push  ""/imm32
101     68/push  ""/imm32
102     # . . call
103     e8/call  string-equal?/disp32
104     # . . discard args
105     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
106     # check-ints-equal(EAX, 1, msg)
107     # . . push args
108     68/push  "F - test-compare-empty-with-empty-string"/imm32
109     68/push  1/imm32/true
110     50/push-EAX
111     # . . call
112     e8/call  check-ints-equal/disp32
113     # . . discard args
114     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
115     c3/return
116 
117 test-compare-empty-with-non-empty-string:  # also checks length-mismatch code path
118     # EAX = string-equal?("", "Abc")
119     # . . push args
120     68/push  "Abc"/imm32
121     68/push  ""/imm32
122     # . . call
123     e8/call  string-equal?/disp32
124     # . . discard args
125     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
126     # check-ints-equal(EAX, 0, msg)
127     # . . push args
128     68/push  "F - test-compare-empty-with-non-empty-string"/imm32
129     68/push  0/imm32/false
130     50/push-EAX
131     # . . call
132     e8/call  check-ints-equal/disp32
133     # . . discard args
134     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
135     c3/return
136 
137 test-compare-equal-strings:
138     # EAX = string-equal?("Abc", "Abc")
139     # . . push args
140     68/push  "Abc"/imm32
141     68/push  "Abc"/imm32
142     # . . call
143     e8/call  string-equal?/disp32
144     # . . discard args
145     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
146     # check-ints-equal(EAX, 1, msg)
147     # . . push args
148     68/push  "F - test-compare-equal-strings"/imm32
149     68/push  1/imm32/true
150     50/push-EAX
151     # . . call
152     e8/call  check-ints-equal/disp32
153     # . . discard args
154     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
155     c3/return
156 
157 test-compare-inequal-strings-equal-lengths:
158     # EAX = string-equal?("Abc", "Adc")
159     # . . push args
160     68/push  "Adc"/imm32
161     68/push  "Abc"/imm32
162     # . . call
163     e8/call  string-equal?/disp32
164     # . . discard args
165     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
166     # check-ints-equal(EAX, 0, msg)
167     # . . push args
168     68/push  "F - test-compare-inequal-strings-equal-lengths"/imm32
169     68/push  0/imm32/false
170     50/push-EAX
171     # . . call
172     e8/call  check-ints-equal/disp32
173     # . . discard args
174     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
175     c3/return
176 
177 # helper for later tests
178 check-string-equal:  # s : (address string), expected : (address string), msg : (address string)
179     # . prolog
180     55/push-EBP
181     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
182     # . save registers
183     50/push-EAX
184     # EAX = string-equal?(s, expected)
185     # . . push args
186     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
187     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
188     # . . call
189     e8/call  string-equal?/disp32
190     # . . discard args
191     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
192     # check-ints-equal(EAX, 1, msg)
193     # . . push args
194     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
195     68/push  1/imm32
196     50/push-EAX
197     # . . call
198     e8/call  check-ints-equal/disp32
199     # . . discard args
200     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
201 $check-string-equal:end:
202     # . restore registers
203     58/pop-to-EAX
204     # . epilog
205     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
206     5d/pop-to-EBP
207     c3/return
208 
209 # test the helper
210 test-check-string-equal:
211     # check-string-equal?("Abc", "Abc")
212     # . . push args
213     68/push  "Abc"/imm32
214     68/push  "Abc"/imm32
215     # . . call
216     e8/call  check-string-equal/disp32
217     # . . discard args
218     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
219     # check-ints-equal(EAX, 1, msg)
220     # . . push args
221     68/push  "F - test-check-string-equal"/imm32
222     68/push  0/imm32/false
223     50/push-EAX
224     # . . call
225     e8/call  check-ints-equal/disp32
226     # . . discard args
227     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
228     c3/return
229 
230 # . . vim:nowrap:textwidth=0