https://github.com/akkartik/mu/blob/master/092stack.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        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     b8/copy-to-eax  1/imm32/exit
164     cd/syscall  0x80/imm8
165     # never gets here
166 
167 test-push:
168     # . prologue
169     55/push-ebp
170     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
171     # var stack/ecx = empty stack of size 8
172     68/push 0/imm32
173     68/push 0/imm32
174     68/push 8/imm32/size
175     68/push 0/imm32/top
176     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
177     # push(stack, 0x42)
178     # . . push args
179     68/push  0x42/imm32
180     51/push-ecx
181     # . . call
182     e8/call  push/disp32
183     # . . discard args
184     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
185     # check top
186     58/pop-to-eax
187     # . check-ints-equal(eax, 4, msg)
188     # . . push args
189     68/push  "F - test-push: top"/imm32
190     68/push  4/imm32
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     # check size
197     58/pop-to-eax
198     # . check-ints-equal(eax, 8, msg)
199     # . . push args
200     68/push  "F - test-push: size"/imm32
201     68/push  8/imm32
202     50/push-eax
203     # . . call
204     e8/call  check-ints-equal/disp32
205     # . . discard args
206     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
207     # first word is 0x42
208     58/pop-to-eax
209     # . check-ints-equal(eax, 0x42, msg)
210     # . . push args
211     68/push  "F - test-push: data[0..3]"/imm32
212     68/push  0x42/imm32
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     # second word is 0
219     58/pop-to-eax
220     # . check-ints-equal(eax, 0, msg)
221     # . . push args
222     68/push  "F - test-push: data[4..7]"/imm32
223     68/push  0/imm32
224     50/push-eax
225     # . . call
226     e8/call  check-ints-equal/disp32
227     # . . discard args
228     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
229     # . epilogue
230     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
231     5d/pop-to-ebp
232     c3/return
233 
234 # TODO: make this generic
235 pop:  # s: (addr stack) -> n/eax: int
236     # . prologue
237     55/push-ebp
238     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
239     # . save registers
240     51/push-ecx
241     56/push-esi
242     # esi = s
243     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
244     # if (s->top <= 0) abort
245     81          7/subop/compare     0/mod/indirect  6/rm32/esi    .           .             .           .           .               0/imm32           # compare *esi
246     7e/jump-if-<=  $pop:abort/disp8
247     # s->top -= 4
248     81          5/subop/subtract    0/mod/direct    6/rm32/esi    .           .             .           .           .               4/imm32           # subtract from *esi
249     # eax = s->data[s->top]
250     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
251     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
252     # s->data[s->top] = 0
253     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)
254 $pop:end:
255     # . restore registers
256     5e/pop-to-esi
257     59/pop-to-ecx
258     # . epilogue
259     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
260     5d/pop-to-ebp
261     c3/return
262 
263 $pop:abort:
264     # print(stderr, "error: pop: nothing left in stack")
265     # . write-buffered(Stderr, "error: pop: nothing left in stack")
266     # . . push args
267     68/push  "error: pop: nothing left in stack"/imm32
268     68/push  Stderr/imm32
269     # . . call
270     e8/call  write-buffered/disp32
271     # . . discard args
272     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
273     # . flush(Stderr)
274     # . . push args
275     68/push  Stderr/imm32
276     # . . call
277     e8/call  flush/disp32
278     # . . discard args
279     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
280     # . syscall(exit, 1)
281     bb/copy-to-ebx  1/imm32
282     b8/copy-to-eax  1/imm32/exit
283     cd/syscall  0x80/imm8
284     # never gets here
285 
286 test-pop:
287     # . prologue
288     55/push-ebp
289     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
290     # var stack/ecx = stack of size 8 containing just 0x42
291     68/push 0/imm32
292     68/push 0x42/imm32
293     68/push 8/imm32/size
294     68/push 4/imm32/top
295     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
296     # eax = pop(stack)
297     # . . push args
298     51/push-ecx
299     # . . call
300     e8/call  pop/disp32
301     # . . discard args
302     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
303     # check-ints-equal(eax, 0x42, msg)
304     # . . push args
305     68/push  "F - test-pop: result"/imm32
306     68/push  0x42/imm32
307     50/push-eax
308     # . . call
309     e8/call  check-ints-equal/disp32
310     # . . discard args
311     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
312     # check top
313     58/pop-to-eax
314     # . check-ints-equal(eax, 0, msg)
315     # . . push args
316     68/push  "F - test-pop: top"/imm32
317     68/push  0/imm32
318     50/push-eax
319     # . . call
320     e8/call  check-ints-equal/disp32
321     # . . discard args
322     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
323     # check size
324     58/pop-to-eax
325     # . check-ints-equal(eax, 8, msg)
326     # . . push args
327     68/push  "F - test-pop: size"/imm32
328     68/push  8/imm32
329     50/push-eax
330     # . . call
331     e8/call  check-ints-equal/disp32
332     # . . discard args
333     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
334     # . epilogue
335     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
336     5d/pop-to-ebp
337     c3/return
338 
339 # TODO: make this generic
340 top:  # s: (addr stack) -> n/eax: int
341     # . prologue
342     55/push-ebp
343     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
344     # . save registers
345     51/push-ecx
346     56/push-esi
347     # esi = s
348     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
349     # if (s->top <= 0) abort
350     81          7/subop/compare     0/mod/indirect  6/rm32/esi    .           .             .           .           .               0/imm32           # compare *esi
351     7e/jump-if-<=  $top:abort/disp8
352     # n = s->data[s->top - 4]
353     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
354     81          5/subop/subtract    3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # subtract from ecx
355     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
356 $top:end:
357     # . restore registers
358     5e/pop-to-esi
359     59/pop-to-ecx
360     # . epilogue
361     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
362     5d/pop-to-ebp
363     c3/return
364 
365 $top:abort:
366     # print(stderr, "error: top: nothing left in stack")
367     # . write-buffered(Stderr, "error: top: nothing left in stack")
368     # . . push args
369     68/push  "error: top: nothing left in stack"/imm32
370     68/push  Stderr/imm32
371     # . . call
372     e8/call  write-buffered/disp32
373     # . . discard args
374     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
375     # . flush(Stderr)
376     # . . push args
377     68/push  Stderr/imm32
378     # . . call
379     e8/call  flush/disp32
380     # . . discard args
381     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
382     # . syscall(exit, 1)
383     bb/copy-to-ebx  1/imm32
384     b8/copy-to-eax  1/imm32/exit
385     cd/syscall  0x80/imm8
386     # never gets here
387 
388 test-top:
389     # . prologue
390     55/push-ebp
391     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
392     # var stack/ecx = stack of size 8 containing just 0x42
393     68/push  0/imm32
394     68/push  0x42/imm32
395     68/push  8/imm32/size
396     68/push  4/imm32/top
397     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
398     # eax = top(stack)
399     # . . push args
400     51/push-ecx
401     # . . call
402     e8/call  top/disp32
403     # . . discard args
404     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
405     # check-ints-equal(eax, 42, msg")
406     # . . push args
407     68/push  "F - test-top: result"/imm32
408     68/push  0x42/imm32
409     50/push-eax
410     # . . call
411     e8/call  check-ints-equal/disp32
412     # . . discard args
413     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
414     # . epilogue
415     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
416     5d/pop-to-ebp
417     c3/return
418 
419 # . . vim:nowrap:textwidth=0