https://github.com/akkartik/mu/blob/main/linux/ex11.subx
  1 # Null-terminated vs size-prefixed ascii strings.
  2 #
  3 # By default we create strings with a 4-byte size prefix rather than a null suffix.
  4 # However we still need null-prefixed strings when interacting with the Linux
  5 # kernel in a few places. This layer implements a function for comparing
  6 # a null-terminated 'kernel string' with a size-prefixed 'SubX string'.
  7 #
  8 # To run:
  9 #   $ bootstrap/bootstrap translate apps/ex11.subx -o apps/ex11
 10 #   $ bootstrap/bootstrap run ex11  # runs a series of tests
 11 #   ......  # all tests pass
 12 #
 13 # (We can't yet run the tests when given a "test" commandline argument,
 14 # because checking for it would require the function being tested! Breakage
 15 # would cause tests to not run, rather than to fail as we'd like.)
 16 
 17 == code
 18 #   instruction                     effective address                                                   register    displacement    immediate
 19 # . op          subop               mod             rm32          base        index         scale       r32
 20 # . 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
 21 
 22 Entry:  # run all tests
 23     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
 24     # exit(eax)
 25     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # copy eax to ebx
 26     e8/call  syscall_exit/disp32
 27 
 28 # compare a null-terminated ascii string with a more idiomatic size-prefixed byte array
 29 # reason for the name: the only place we should have null-terminated ascii strings is from commandline args
 30 kernel-string-equal?:  # s: null-terminated ascii string, benchmark: size-prefixed ascii string -> eax: boolean
 31     # pseudocode:
 32     #   n = benchmark->size
 33     #   s1 = s
 34     #   s2 = benchmark->data
 35     #   i = 0
 36     #   while i < n
 37     #     c1 = *s1
 38     #     c2 = *s2
 39     #     if (c1 == 0) return false
 40     #     if (c1 != c2) return false
 41     #     ++s1, ++s2, ++i
 42     #   return *s1 == 0
 43     #
 44     # registers:
 45     #   i: ecx
 46     #   n: edx
 47     #   s1: edi
 48     #   s2: esi
 49     #   c1: eax
 50     #   c2: ebx
 51     #
 52     # . prologue
 53     55/push-ebp
 54     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 55     # . save registers
 56     51/push-ecx
 57     52/push-edx
 58     53/push-ebx
 59     56/push-esi
 60     57/push-edi
 61     # s1/edi = s
 62     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
 63     # n/edx = benchmark->size
 64     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
 65     8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           2/r32/edx   .               .                 # copy *edx to edx
 66     # s2/esi = benchmark->data
 67     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
 68     81          0/subop/add         3/mod/direct    6/rm32/esi    .           .             .           .           .               4/imm32           # add to esi
 69     # i/ecx = c1/eax = c2/ebx = 0
 70     b9/copy-to-ecx  0/imm32/exit
 71     b8/copy-to-eax  0/imm32
 72     bb/copy-to-ebx  0/imm32
 73 $kernel-string-equal?:loop:
 74     # if (i >= n) break
 75     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 76     7d/jump-if->=  $kernel-string-equal?:break/disp8
 77     # c1 = *s1
 78     8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           0/r32/AL    .               .                 # copy byte at *edi to AL
 79     # c2 = *s2
 80     8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           3/r32/BL    .               .                 # copy byte at *esi to BL
 81     # if (c1 == 0) return false
 82     3d/compare-eax-and  0/imm32/null
 83     74/jump-if-=  $kernel-string-equal?:false/disp8
 84     # if (c1 != c2) return false
 85     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # compare eax and ebx
 86     75/jump-if-!=  $kernel-string-equal?:false/disp8
 87     # ++i
 88     41/increment-ecx
 89     # ++s1
 90     47/increment-edi
 91     # ++s2
 92     46/increment-esi
 93     eb/jump  $kernel-string-equal?:loop/disp8
 94 $kernel-string-equal?:break:
 95     # return *s1 == 0
 96     8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           0/r32/AL    .               .                 # copy byte at *edi to AL
 97     3d/compare-eax-and  0/imm32/null
 98     75/jump-if-!=  $kernel-string-equal?:false/disp8
 99 $kernel-string-equal?:true:
100     b8/copy-to-eax  1/imm32
101     eb/jump  $kernel-string-equal?:end/disp8
102 $kernel-string-equal?:false:
103     b8/copy-to-eax  0/imm32
104 $kernel-string-equal?:end:
105     # . restore registers
106     5f/pop-to-edi
107     5e/pop-to-esi
108     5b/pop-to-ebx
109     5a/pop-to-edx
110     59/pop-to-ecx
111     # . epilogue
112     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
113     5d/pop-to-ebp
114     c3/return
115 
116 # - tests
117 
118 test-compare-null-kernel-string-with-empty-array:
119     # eax = kernel-string-equal?(Null-kernel-string, "")
120     # . . push args
121     68/push  ""/imm32
122     68/push  Null-kernel-string/imm32
123     # . . call
124     e8/call  kernel-string-equal?/disp32
125     # . . discard args
126     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
127     # check-ints-equal(eax, 1, msg)
128     # . . push args
129     68/push  "F - test-compare-null-kernel-string-with-empty-array"/imm32
130     68/push  1/imm32/true
131     50/push-eax
132     # . . call
133     e8/call  check-ints-equal/disp32
134     # . . discard args
135     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
136     c3/return
137 
138 test-compare-null-kernel-string-with-non-empty-array:
139     # eax = kernel-string-equal?(Null-kernel-string, "Abc")
140     # . . push args
141     68/push  "Abc"/imm32
142     68/push  Null-kernel-string/imm32
143     # . . call
144     e8/call  kernel-string-equal?/disp32
145     # . . discard args
146     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
147     # check-ints-equal(eax, 0, msg)
148     # . . push args
149     68/push  "F - test-compare-null-kernel-string-with-non-empty-array"/imm32
150     68/push  0/imm32/false
151     50/push-eax
152     # . . call
153     e8/call  check-ints-equal/disp32
154     # . . discard args
155     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
156     c3/return
157 
158 test-compare-kernel-string-with-equal-array:
159     # eax = kernel-string-equal?(_test-Abc-kernel-string, "Abc")
160     # . . push args
161     68/push  "Abc"/imm32
162     68/push  _test-Abc-kernel-string/imm32
163     # . . call
164     e8/call  kernel-string-equal?/disp32
165     # . . discard args
166     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
167     # check-ints-equal(eax, 1, msg)
168     # . . push args
169     68/push  "F - test-compare-kernel-string-with-equal-array"/imm32
170     68/push  1/imm32/true
171     50/push-eax
172     # . . call
173     e8/call  check-ints-equal/disp32
174     # . . discard args
175     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
176     c3/return
177 
178 test-compare-kernel-string-with-inequal-array:
179     # eax = kernel-string-equal?(_test-Abc-kernel-string, "Adc")
180     # . . push args
181     68/push  "Adc"/imm32
182     68/push  _test-Abc-kernel-string/imm32
183     # . . call
184     e8/call  kernel-string-equal?/disp32
185     # . . discard args
186     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
187     # check-ints-equal(eax, 0, msg)
188     # . . push args
189     68/push  "F - test-compare-kernel-string-with-equal-array"/imm32
190     68/push  0/imm32/false
191     50/push-eax
192     # . . call
193     e8/call  check-ints-equal/disp32
194     # . . discard args
195     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
196     c3/return
197 
198 test-compare-kernel-string-with-empty-array:
199     # eax = kernel-string-equal?(_test-Abc-kernel-string, "")
200     # . . push args
201     68/push  ""/imm32
202     68/push  _test-Abc-kernel-string/imm32
203     # . . call
204     e8/call  kernel-string-equal?/disp32
205     # . . discard args
206     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
207     # check-ints-equal(eax, 0, msg)
208     # . . push args
209     68/push  "F - test-compare-kernel-string-with-equal-array"/imm32
210     68/push  0/imm32/false
211     50/push-eax
212     # . . call
213     e8/call  check-ints-equal/disp32
214     # . . discard args
215     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
216     c3/return
217 
218 test-compare-kernel-string-with-shorter-array:
219     # eax = kernel-string-equal?(_test-Abc-kernel-string, "Ab")
220     # . . push args
221     68/push  "Ab"/imm32
222     68/push  _test-Abc-kernel-string/imm32
223     # . . call
224     e8/call  kernel-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, 0, msg)
228     # . . push args
229     68/push  "F - test-compare-kernel-string-with-shorter-array"/imm32
230     68/push  0/imm32/false
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     c3/return
237 
238 test-compare-kernel-string-with-longer-array:
239     # eax = kernel-string-equal?(_test-Abc-kernel-string, "Abcd")
240     # . . push args
241     68/push  "Abcd"/imm32
242     68/push  _test-Abc-kernel-string/imm32
243     # . . call
244     e8/call  kernel-string-equal?/disp32
245     # . . discard args
246     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
247     # check-ints-equal(eax, 0, msg)
248     # . . push args
249     68/push  "F - test-compare-kernel-string-with-longer-array"/imm32
250     68/push  0/imm32/false
251     50/push-eax
252     # . . call
253     e8/call  check-ints-equal/disp32
254     # . . discard args
255     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
256     c3/return
257 
258 # - helpers
259 
260 # print msg to stderr if a != b, otherwise print "."
261 check-ints-equal:  # (a: int, b: int, msg: (addr array byte)) -> boolean
262     # . prologue
263     55/push-ebp
264     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
265     # . save registers
266     51/push-ecx
267     53/push-ebx
268     # load args into eax, ebx and ecx
269     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
270     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           3/r32/ebx   0xc/disp8       .                 # copy *(ebp+12) to ebx
271     # if (eax == b/ebx) print('.') and return
272     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # compare eax and ebx
273     75/jump-if-unequal  $check-ints-equal:else/disp8
274     # . write-stderr('.')
275     # . . push args
276     68/push  "."/imm32
277     # . . call
278     e8/call  write-stderr/disp32
279     # . . discard args
280     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
281     # . return
282     eb/jump  $check-ints-equal:end/disp8
283     # otherwise print(msg)
284 $check-ints-equal:else:
285     # copy msg into ecx
286     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8       .                # copy *(ebp+16) to ecx
287     # print(ecx)
288     # . . push args
289     51/push-ecx
290     # . . call
291     e8/call  write-stderr/disp32
292     # . . discard args
293     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
294     # print newline
295     # . . push args
296     68/push  Newline/imm32
297     # . . call
298     e8/call  write-stderr/disp32
299     # . . discard args
300     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
301 $check-ints-equal:end:
302     # . restore registers
303     5b/pop-to-ebx
304     59/pop-to-ecx
305     # end
306     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
307     5d/pop-to-ebp
308     c3/return
309 
310 write-stderr:  # s: (addr array byte) -> <void>
311     # . prologue
312     55/push-ebp
313     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
314     # . save registers
315     50/push-eax
316     51/push-ecx
317     52/push-edx
318     53/push-ebx
319     # syscall(write, 2/stderr, (data) s+4, (size) *s)
320     # . . fd = 2 (stderr)
321     bb/copy-to-ebx  2/imm32
322     # . . x = s+4
323     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
324     81          0/subop/add         3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # add to ecx
325     # . . size = *s
326     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   8/disp8         .                 # copy *(ebp+8) to edx
327     8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           2/r32/edx   .               .                 # copy *edx to edx
328     # . . syscall
329     e8/call  syscall_write/disp32
330     # . restore registers
331     5b/pop-to-ebx
332     5a/pop-to-edx
333     59/pop-to-ecx
334     58/pop-to-eax
335     # . end
336     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
337     5d/pop-to-ebp
338     c3/return
339 
340 == data
341 
342 Newline:
343     # size
344     1/imm32
345     # data
346     0a/newline
347 
348 # for kernel-string-equal tests
349 Null-kernel-string:
350     00/null
351 
352 _test-Abc-kernel-string:
353     41/A 62/b 63/c 00/null
354 
355 # . . vim:nowrap:textwidth=0