https://github.com/akkartik/mu/blob/main/linux/203stack.subx
  1 # A stack looks like this:
  2 #   top: int
  3 #   data: (array byte)  # prefixed by size as usual
  4 #
  5 # TODO: is it confusing that the push opcodes grow memory downward, but the
  6 # push function grows stacks upward?
  7 
  8 == code
  9 #   instruction                     effective address                                                   register    displacement    immediate
 10 # . op          subop               mod             rm32          base        index         scale       r32
 11 # . 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
 12 
 13 clear-stack:  # s: (addr stack)
 14     # . prologue
 15     55/push-ebp
 16     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 17     # . save registers
 18     50/push-eax
 19     51/push-ecx
 20     # eax = s
 21     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
 22     # var max/ecx: (addr byte) = &s->data[s->size]
 23     8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(eax+4) to eax
 24     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   8/disp8         .                 # copy eax+ecx+8 to ecx
 25     # s->top = 0
 26     c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # copy to *eax
 27     # var curr/eax: (addr byte) = s->data
 28     81          0/subop/add         3/mod/direct    0/rm32/eax    .           .             .           .           .               8/imm32           # add to eax
 29 $clear-stack:loop:
 30     # if (curr >= max) break
 31     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
 32     73/jump-if-addr>=  $clear-stack:end/disp8
 33     # *curr = 0
 34     c6          0/subop/copy-byte   0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm8            # copy byte to *eax
 35     # ++curr
 36     40/increment-eax
 37     eb/jump $clear-stack:loop/disp8
 38 $clear-stack:end:
 39     # . restore registers
 40     59/pop-to-ecx
 41     58/pop-to-eax
 42     # . epilogue
 43     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 44     5d/pop-to-ebp
 45     c3/return
 46 
 47 test-clear-stack:
 48     # . prologue
 49     55/push-ebp
 50     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 51     # var stack/ecx = stack of size 8 with random data in it
 52     68/push 34/imm32
 53     68/push 35/imm32
 54     68/push 8/imm32/size
 55     68/push 14/imm32/top
 56     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 57     # clear(stack)
 58     # . . push args
 59     51/push-ecx
 60     # . . call
 61     e8/call  clear-stack/disp32
 62     # . . discard args
 63     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 64     # top should be 0
 65     58/pop-to-eax
 66     # . check-ints-equal(eax, 0, msg)
 67     # . . push args
 68     68/push  "F - test-clear-stack: top"/imm32
 69     68/push  0/imm32
 70     50/push-eax
 71     # . . call
 72     e8/call  check-ints-equal/disp32
 73     # . . discard args
 74     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 75     # size should remain 8
 76     58/pop-to-eax
 77     # . check-ints-equal(eax, 8, msg)
 78     # . . push args
 79     68/push  "F - test-clear-stack: size"/imm32
 80     68/push  8/imm32
 81     50/push-eax
 82     # . . call
 83     e8/call  check-ints-equal/disp32
 84     # . . discard args
 85     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 86     # first word is 0
 87     58/pop-to-eax
 88     # . check-ints-equal(eax, 0, msg)
 89     # . . push args
 90     68/push  "F - test-clear-stack: data[0..3]"/imm32
 91     68/push  0/imm32
 92     50/push-eax
 93     # . . call
 94     e8/call  check-ints-equal/disp32
 95     # . . discard args
 96     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 97     # second word is 0
 98     58/pop-to-eax
 99     # . check-ints-equal(eax, 0, msg)
100     # . . push args
101     68/push  "F - test-clear-stack: data[4..7]"/imm32
102     68/push  0/imm32
103     50/push-eax
104     # . . call
105     e8/call  check-ints-equal/disp32
106     # . . discard args
107     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
108     # . epilogue
109     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
110     5d/pop-to-ebp
111     c3/return
112 
113 # TODO: make this generic
114 push:  # s: (addr stack), n: int
115     # . prologue
116     55/push-ebp
117     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
118     # . save registers
119     50/push-eax
120     51/push-ecx
121     56/push-esi
122     # esi = s
123     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
124     # ecx = s->top
125     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
126     # if (s->top >= s->size) abort
127     39/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # compare *(esi+4) and ecx
128     7e/jump-if-<=  $push:abort/disp8
129     # s->data[s->top] = n
130     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
131     89/copy                         1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   8/disp8         .                 # copy eax to *(esi+ecx+8)
132     # s->top += 4
133     81          0/subop/add         0/mod/direct    6/rm32/esi    .           .             .           .           .               4/imm32           # subtract from *esi
134 $push:end:
135     # . restore registers
136     5e/pop-to-esi
137     59/pop-to-ecx
138     58/pop-to-eax
139     # . epilogue
140     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
141     5d/pop-to-ebp
142     c3/return
143 
144 $push:abort:
145     # print(stderr, "error: push: no space left")
146     # . write-buffered(Stderr, "error: push: no space left")
147     # . . push args
148     68/push  "error: push: no space left"/imm32
149     68/push  Stderr/imm32
150     # . . call
151     e8/call  write-buffered/disp32
152     # . . discard args
153     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
154     # . flush(Stderr)
155     # . . push args
156     68/push  Stderr/imm32
157     # . . call
158     e8/call  flush/disp32
159     # . . discard args
160     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
161     # . syscall_exit(1)
162     bb/copy-to-ebx  1/imm32
163     e8/call  syscall_exit/disp32
164     # never gets here
165 
166 test-push:
167     # . prologue
168     55/push-ebp
169     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
170     # var stack/ecx = empty stack of size 8
171     68/push 0/imm32
172     68/push 0/imm32
173     68/push 8/imm32/size
174     68/push 0/imm32/top
175     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
176     # push(stack, 0x42)
177     # . . push args
178     68/push  0x42/imm32
179     51/push-ecx
180     # . . call
181     e8/call  push/disp32
182     # . . discard args
183     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
184     # check top
185     58/pop-to-eax
186     # . check-ints-equal(eax, 4, msg)
187     # . . push args
188     68/push  "F - test-push: top"/imm32
189     68/push  4/imm32
190     50/push-eax
191     # . . call
192     e8/call  check-ints-equal/disp32
193     # . . discard args
194     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
195     # check size
196     58/pop-to-eax
197     # . check-ints-equal(eax, 8, msg)
198     # . . push args
199     68/push  "F - test-push: size"/imm32
200     68/push  8/imm32
201     50/push-eax
202     # . . call
203     e8/call  check-ints-equal/disp32
204     # . . discard args
205     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
206     # first word is 0x42
207     58/pop-to-eax
208     # . check-ints-equal(eax, 0x42, msg)
209     # . . push args
210     68/push  "F - test-push: data[0..3]"/imm32
211     68/push  0x42/imm32
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     # second word is 0
218     58/pop-to-eax
219     # . check-ints-equal(eax, 0, msg)
220     # . . push args
221     68/push  "F - test-push: data[4..7]"/imm32
222     68/push  0/imm32
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     # . epilogue
229     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
230     5d/pop-to-ebp
231     c3/return
232 
233 # TODO: make this generic
234 pop:  # s: (addr stack) -> n/eax: int
235     # . prologue
236     55/push-ebp
237     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
238     # . save registers
239     51/push-ecx
240     56/push-esi
241     # esi = s
242     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
243     # if (s->top <= 0) abort
244     81          7/subop/compare     0/mod/indirect  6/rm32/esi    .           .             .           .           .               0/imm32           # compare *esi
245     7e/jump-if-<=  $pop:abort/disp8
246     # s->top -= 4
247     81          5/subop/subtract    0/mod/direct    6/rm32/esi    .           .             .           .           .               4/imm32           # subtract from *esi
248     # eax = s->data[s->top]
249     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
250     8b/copy                         1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   8/disp8         .                 # copy *(esi+ecx+8) to eax
251     # s->data[s->top] = 0
252     c7          0/subop/copy        1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   8/disp8         0/imm32           # copy to *(esi+ecx+8)
253 $pop:end:
254     # . restore registers
255     5e/pop-to-esi
256     59/pop-to-ecx
257     # . epilogue
258     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
259     5d/pop-to-ebp
260     c3/return
261 
262 $pop:abort:
263     # print(stderr, "error: pop: nothing left in stack")
264     # . write-buffered(Stderr, "error: pop: nothing left in stack")
265     # . . push args
266     68/push  "error: pop: nothing left in stack"/imm32
267     68/push  Stderr/imm32
268     # . . call
269     e8/call  write-buffered/disp32
270     # . . discard args
271     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
272     # . flush(Stderr)
273     # . . push args
274     68/push  Stderr/imm32
275     # . . call
276     e8/call  flush/disp32
277     # . . discard args
278     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
279     # . syscall_exit(1)
280     bb/copy-to-ebx  1/imm32
281     e8/call  syscall_exit/disp32
282     # never gets here
283 
284 test-pop:
285     # . prologue
286     55/push-ebp
287     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
288     # var stack/ecx = stack of size 8 containing just 0x42
289     68/push 0/imm32
290     68/push 0x42/imm32
291     68/push 8/imm32/size
292     68/push 4/imm32/top
293     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
294     # eax = pop(stack)
295     # . . push args
296     51/push-ecx
297     # . . call
298     e8/call  pop/disp32
299     # . . discard args
300     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
301     # check-ints-equal(eax, 0x42, msg)
302     # . . push args
303     68/push  "F - test-pop: result"/imm32
304     68/push  0x42/imm32
305     50/push-eax
306     # . . call
307     e8/call  check-ints-equal/disp32
308     # . . discard args
309     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
310     # check top
311     58/pop-to-eax
312     # . check-ints-equal(eax, 0, msg)
313     # . . push args
314     68/push  "F - test-pop: top"/imm32
315     68/push  0/imm32
316     50/push-eax
317     # . . call
318     e8/call  check-ints-equal/disp32
319     # . . discard args
320     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
321     # check size
322     58/pop-to-eax
323     # . check-ints-equal(eax, 8, msg)
324     # . . push args
325     68/push  "F - test-pop: size"/imm32
326     68/push  8/imm32
327     50/push-eax
328     # . . call
329     e8/call  check-ints-equal/disp32
330     # . . discard args
331     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
332     # . epilogue
333     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
334     5d/pop-to-ebp
335     c3/return
336 
337 # TODO: make this generic
338 top:  # s: (addr stack) -> n/eax: int
339     # . prologue
340     55/push-ebp
341     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
342     # . save registers
343     51/push-ecx
344     56/push-esi
345     # esi = s
346     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
347     # if (s->top <= 0) abort
348     81          7/subop/compare     0/mod/indirect  6/rm32/esi    .           .             .           .           .               0/imm32           # compare *esi
349     7e/jump-if-<=  $top:abort/disp8
350     # n = s->data[s->top - 4]
351     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
352     81          5/subop/subtract    3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # subtract from ecx
353     8b/copy                         1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   8/disp8         .                 # copy *(esi+ecx+8) to eax
354 $top:end:
355     # . restore registers
356     5e/pop-to-esi
357     59/pop-to-ecx
358     # . epilogue
359     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
360     5d/pop-to-ebp
361     c3/return
362 
363 $top:abort:
364     # print(stderr, "error: top: nothing left in stack")
365     # . write-buffered(Stderr, "error: top: nothing left in stack")
366     # . . push args
367     68/push  "error: top: nothing left in stack"/imm32
368     68/push  Stderr/imm32
369     # . . call
370     e8/call  write-buffered/disp32
371     # . . discard args
372     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
373     # . flush(Stderr)
374     # . . push args
375     68/push  Stderr/imm32
376     # . . call
377     e8/call  flush/disp32
378     # . . discard args
379     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
380     # . syscall_exit(1)
381     bb/copy-to-ebx  1/imm32
382     e8/call  syscall_exit/disp32
383     # never gets here
384 
385 test-top:
386     # . prologue
387     55/push-ebp
388     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
389     # var stack/ecx = stack of size 8 containing just 0x42
390     68/push  0/imm32
391     68/push  0x42/imm32
392     68/push  8/imm32/size
393     68/push  4/imm32/top
394     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
395     # eax = top(stack)
396     # . . push args
397     51/push-ecx
398     # . . call
399     e8/call  top/disp32
400     # . . discard args
401     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
402     # check-ints-equal(eax, 42, msg")
403     # . . push args
404     68/push  "F - test-top: result"/imm32
405     68/push  0x42/imm32
406     50/push-eax
407     # . . call
408     e8/call  check-ints-equal/disp32
409     # . . discard args
410     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
411     # . epilogue
412     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
413     5d/pop-to-ebp
414     c3/return
415 
416 # . . vim:nowrap:textwidth=0