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&quo
ref='116write-buffered.subx.html#L8'>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