about summary refs log tree commit diff stats
path: root/tools
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2020-03-11 16:52:37 -0700
committerKartik Agaram <vc@akkartik.com>2020-03-11 17:21:59 -0700
commit28746b36660093dcbf53667f4f67320d278359ea (patch)
treea3feabc3190234fcb9c2dd8802250c191c53bea2 /tools
parentbfb7c601354563498433d22f4dcf3ad4280a7314 (diff)
downloadmu-28746b36660093dcbf53667f4f67320d278359ea.tar.gz
6123 - runtime helper for initializing arrays
I built this in 3 phases:
a) create a helper in the bootstrap VM to render the state of the stack.
b) interactively arrive at the right function (tools/stack_array.subx)
c) pull the final solution into the standard library (093stack_allocate.subx)

As the final layer says, this may not be the fastest approach for most
(or indeed any) Mu programs. Perhaps it's better on balance for the compiler
to just emit n/4 `push` instructions.

(I'm sure this solution can be optimized further.)
Diffstat (limited to 'tools')
-rw-r--r--tools/stack_array.subx636
1 files changed, 636 insertions, 0 deletions
diff --git a/tools/stack_array.subx b/tools/stack_array.subx
new file mode 100644
index 00000000..5affbc56
--- /dev/null
+++ b/tools/stack_array.subx
@@ -0,0 +1,636 @@
+== code
+
+# Problem: create a function which pushes n zeros on the stack.
+# This is not a regular function, so it won't be idiomatic.
+# Registers must be properly restored.
+# Registers can be spilled, but that modifies the stack and needs to be
+# cleaned up.
+
+# This file is kinda like a research notebook, to interactively arrive at the
+# solution. Nobody should have to do this without a computer. To run it:
+#   $ ./translate_subx_debug init.linux tools/stack_array.subx  &&  bootstrap --debug --trace --dump run a.elf
+# There are multiple versions. You'll need to uncomment exactly one.
+
+# The final version has its own Entry, but the others share this one.
+#? Entry:
+#?     # . prologue
+#?     89/<- %ebp 4/r32/esp
+#?     #
+#?     68/push 0xfcfdfeff/imm32
+#?     b8/copy-to-eax 0x34353637/imm32
+#? $dump-stack:
+#?     (push-n-zero-bytes 0x20)
+#? $dump-stack2:
+#?     68/push 0x20202020/imm32
+#? $dump-stack3:
+#?     b8/copy-to-eax 1/imm32/exit
+#?     cd/syscall 0x80/imm8
+
+## 0
+
+#? push-n-zero-bytes:  # n: int
+#?     # . prologue
+#?     55/push-ebp
+#?     89/<- %ebp 4/r32/esp
+#? $push-n-zero-bytes:end:
+#?     # . epilogue
+#?     89/<- %esp 5/r32/ebp
+#?     5d/pop-to-ebp
+#?     c3/return
+
+# stack at dump-stack:
+# 0 a: bdffffd0: 00000000     00000000     00000000      00000000
+# 0 a: bdffffe0: 00000000     00000000     00000000      00000000
+# 0 a: bdfffff0: 00000000     fcfdfeff     00000001      bf000000
+#
+# =>
+#
+# stack at dump-stack3:
+# 0 a: stack:
+# 0 a: bdffffd0: 00000000     00000000     00000000      00000000
+# 0 a: bdffffe0: 00000000     00000000     bdfffff8/ebp  090000cc/ra
+# 0 a: bdfffff0: 00000004/arg fcfdfeff     00000001      bf000000
+
+## 1
+
+#? push-n-zero-bytes:  # n: int
+#?     # . prologue
+#?     55/push-ebp
+#?     89/<- %ebp 4/r32/esp
+#?     # . save registers
+#?     50/push-eax
+#? $push-n-zero-bytes:end:
+#?     # . restore registers
+#?     58/pop-to-eax
+#?     # . epilogue
+#?     5d/pop-to-ebp
+#?     c3/return
+
+# stack at dump-stack3:
+# 0 a: bdffffd0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffe0: 00000000 34353637 bdfffff8 090000d1
+# 0 a: bdfffff0: 00000004 fcfdfeff 00000001 bf000000
+
+## 2
+
+#? push-n-zero-bytes:  # n: int
+#?     # . prologue
+#?     55/push-ebp
+#?     89/<- %ebp 4/r32/esp
+#?     # . save registers
+#?     50/push-eax
+#?     #
+#?     8b/-> *(esp+8) 0/r32/eax
+#?     2b/subtract *(ebp+8) 4/r32/esp
+#?     89/<- *(esp+8) 0/r32/eax
+#? $push-n-zero-bytes:end:
+#?     # . restore registers
+#?     58/pop-to-eax
+#?     # . epilogue
+#?     5d/pop-to-ebp
+#?     c3/return
+
+# stack at dump-stack3:
+# 0 a: bdffff90: 00000000 00000000 00000000 00000000
+# 0 a: bdffffa0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffb0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffc0: 00000000 00000000 00000000 090000d1
+# 0 a: bdffffd0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffe0: 00000000 34353637 bdfffff8 090000d1
+# 0 a: bdfffff0: 00000020 fcfdfeff 00000001 bf000000
+
+## 3
+
+#? push-n-zero-bytes:  # n: int
+#?     # . prologue
+#?     55/push-ebp
+#?     89/<- %ebp 4/r32/esp
+#?     # . save registers
+#?     # -- esp = ebp
+#?     50/push-eax
+#?     # -- esp+8 = ebp+4
+#?     8b/-> *(esp+8) 0/r32/eax
+#?     2b/subtract *(ebp+8) 4/r32/esp
+#?     89/<- *(esp+8) 0/r32/eax
+#?     c7 0/subop/copy *(ebp+4) 0/imm32
+#? $push-n-zero-bytes:end:
+#?     # . restore registers
+#?     58/pop-to-eax
+#?     # . epilogue
+#?     5d/pop-to-ebp
+#?     c3/return
+
+# stack at dump-stack3:
+# 0 a: bdffff90: 00000000 00000000 00000000 00000000
+# 0 a: bdffffa0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffb0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffc0: 00000000 00000000 00000000 090000d1
+# 0 a: bdffffd0: 20202020 00000000 00000000 00000000
+# 0 a: bdffffe0: 00000000 34353637 bdfffff8 00000000
+# 0 a: bdfffff0: 00000020 fcfdfeff 00000001 bf000000
+
+## 4
+
+#? push-n-zero-bytes:  # n: int
+#?     # . prologue
+#?     55/push-ebp
+#?     89/<- %ebp 4/r32/esp
+#?     # . save registers
+#?     # -- esp = ebp
+#?     50/push-eax
+#?     # copy return address over
+#?     # -- esp+8 = ebp+4
+#?     8b/-> *(esp+8) 0/r32/eax
+#?     2b/subtract *(ebp+8) 4/r32/esp
+#?     89/<- *(esp+8) 0/r32/eax
+#?     58/pop-to-eax
+#?     c7 0/subop/copy *(ebp+8) 0/imm32
+#?     c7 0/subop/copy *(ebp+4) 0/imm32
+#?     c7 0/subop/copy *(ebp+0) 0/imm32
+#?     c7 0/subop/copy *(ebp-4) 0/imm32
+#?     # . epilogue
+#?     5d/pop-to-ebp
+#?     c3/return
+
+# stack at dump-stack3:
+# 0 a: bdffff90: 00000000 00000000 00000000 00000000
+# 0 a: bdffffa0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffb0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffc0: 00000000 00000000 00000000 090000d1
+# 0 a: bdffffd0: 20202020 00000000 00000000 00000000
+# 0 a: bdffffe0: 00000000 00000000 00000000 00000000
+# 0 a: bdfffff0: 00000000 fcfdfeff 00000001 bf000000
+
+# Stack looks good now (the 20202020 marks where the array length 0x20 will
+# go, and the next 0x20 bytes show the space for the array has been zeroed
+# out).
+# Final issue: ebp has been clobbered on return.
+
+## 5
+
+# I'd like to translate ebp to esp so we can stop pushing ebp. But we need to
+# hold 'n' somewhere, which would require a register, which we then need to
+# push.
+
+#? push-n-zero-bytes:  # n: int
+#?     55/push-ebp
+#?     89/<- %ebp 4/r32/esp
+#?     # -- esp = ebp
+#?     50/push-eax
+#? $push-n-zero-bytes:bulk-cleaning:
+#? $push-n-zero-bytes:copy-ra:
+#?     # -- esp+8 = ebp+4
+#?     8b/-> *(esp+8) 0/r32/eax
+#?     2b/subtract *(esp+0xc) 4/r32/esp
+#?     # -- esp+8+n = ebp+4
+#?     89/<- *(esp+8) 0/r32/eax
+#?     58/pop-to-eax
+#?     # -- esp+n = ebp
+#? $push-n-zero-bytes:spot-cleaning:
+#?     c7 0/subop/copy *(ebp+8) 0/imm32
+#?     c7 0/subop/copy *(ebp+4) 0/imm32
+#?     c7 0/subop/copy *(ebp+0) 0/imm32
+#?     c7 0/subop/copy *(ebp-4) 0/imm32
+#?     5d/pop-to-ebp
+#?     c3/return
+
+# stack at dump-stack3:
+# 0 a: bdffff90: 00000000 00000000 00000000 00000000
+# 0 a: bdffffa0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffb0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffc0: 00000000 00000000 00000000 090000d1
+# 0 a: bdffffd0: 20202020 00000000 00000000 00000000
+# 0 a: bdffffe0: 00000000 00000000 00000000 00000000
+# 0 a: bdfffff0: 00000000 fcfdfeff 00000001 bf000000
+
+# Bah. May be simpler to just create a new segment of global space for this
+# function.
+
+## 6
+
+#? push-n-zero-bytes:  # n: int
+#?     89/<- *Push-n-zero-bytes-ebp 5/r32/ebp  # spill ebp without affecting stack
+#?     89/<- %ebp 4/r32/esp
+#?     # -- esp = ebp
+#?     50/push-eax
+#? $push-n-zero-bytes:bulk-cleaning:
+#? $push-n-zero-bytes:copy-ra:
+#?     # -- esp+8 = ebp+4
+#?     # -- esp+4 = ebp
+#?     8b/-> *(esp+4) 0/r32/eax
+#?     2b/subtract *(ebp+4) 4/r32/esp
+#?     # -- esp+4+n = ebp
+#?     89/<- *(esp+4) 0/r32/eax
+#?     58/pop-to-eax
+#?     # -- esp+n = ebp
+#? $push-n-zero-bytes:spot-cleaning:
+#?     c7 0/subop/copy *(ebp+4) 0/imm32
+#?     c7 0/subop/copy *(ebp+0) 0/imm32
+#?     c7 0/subop/copy *(ebp-4) 0/imm32
+#?     c7 0/subop/copy *(ebp-8) 0/imm32
+#?     8b/-> *Push-n-zero-bytes-ebp 5/r32/ebp  # restore spill
+#?     c3/return
+#? 
+#? == data
+#? Push-n-zero-bytes-ebp:  # (addr int)
+#?   0/imm32
+#? == code
+
+# stack at dump-stack3:
+# 0 a: bdffff90: 00000000 00000000 00000000 00000000
+# 0 a: bdffffa0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffb0: 00000000 00000000 00000000 00000000
+# 0 a: bdffffc0: 00000000 00000000 00000000 090000d1
+# 0 a: bdffffd0: 20202020 00000000 00000000 00000000
+# 0 a: bdffffe0: 00000000 00000000 00000000 00000000
+# 0 a: bdfffff0: 00000000 fcfdfeff 00000001 bf000000
+
+# Ok, we're there. Now start using zero-out rather than spot-cleaning.
+
+## 7: we need to zero out the return address, but we can't do it inside the function.
+## So we'll change the signature slightly.
+## Before: clear N bytes and then push N as the array length.
+## After: clear N bytes, set *esp to N.
+## The helper adds and clears N bytes *before* esp. esp can't be cleared since
+## it contains the return address.
+
+#? Entry:
+#?     # . prologue
+#?     89/<- %ebp 4/r32/esp
+#?     #
+#?     68/push 0xfcfdfeff/imm32
+#?     b8/copy-to-eax 0x34353637/imm32
+#? $dump-stack0:
+#?     (push-n-zero-bytes 0x20)
+#? $dump-stack9:
+#?     c7 0/subop/copy *esp 0x20/imm32
+#? $dump-stacka:
+#?     b8/copy-to-eax 1/imm32/exit
+#?     cd/syscall 0x80/imm8
+#? 
+#? push-n-zero-bytes:  # n: int
+#? $push-n-zero-bytes:prologue:
+#?     89/<- *Push-n-zero-bytes-ebp 5/r32/ebp  # spill ebp without affecting stack
+#?     89/<- %ebp 4/r32/esp
+#? $push-n-zero-bytes:copy-ra:
+#? $dump-stack1:
+#?     # -- esp = ebp
+#?     50/push-eax
+#? $dump-stack2:
+#?     # -- esp+8 = ebp+4
+#?     # -- esp+4 = ebp
+#?     8b/-> *(esp+4) 0/r32/eax
+#? $dump-stack3:
+#?     2b/subtract *(ebp+4) 4/r32/esp
+#? $dump-stack4:
+#?     # -- esp+4+n = ebp
+#?     89/<- *(esp+4) 0/r32/eax
+#? $dump-stack5:
+#?     58/pop-to-eax
+#?     # -- esp+n = ebp
+#? $push-n-zero-bytes:bulk-cleaning:
+#? $dump-stack6:
+#?     89/<- *Push-n-zero-bytes-esp 4/r32/esp
+#?     81 0/subop/add *Push-n-zero-bytes-esp 4/imm32
+#? $dump-stack7:
+#?     (zero-out *Push-n-zero-bytes-esp *(ebp+4))  # n
+#? $push-n-zero-bytes:epilogue:
+#? $dump-stack8:
+#?     8b/-> *Push-n-zero-bytes-ebp 5/r32/ebp  # restore spill
+#?     c3/return
+#? 
+#? zero-out:  # start: (addr byte), len: int
+#?     # pseudocode:
+#?     #   curr/esi = start
+#?     #   i/ecx = 0
+#?     #   while true
+#?     #     if (i >= len) break
+#?     #     *curr = 0
+#?     #     ++curr
+#?     #     ++i
+#?     #
+#?     # . prologue
+#?     55/push-ebp
+#?     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+#?     # . save registers
+#?     50/push-eax
+#?     51/push-ecx
+#?     52/push-edx
+#?     56/push-esi
+#?     # curr/esi = start
+#?     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
+#?     # var i/ecx: int = 0
+#?     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
+#?     # edx = len
+#?     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
+#? $zero-out:loop:
+#?     # if (i >= len) break
+#?     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
+#?     7d/jump-if->=  $zero-out:end/disp8
+#?     # *curr = 0
+#?     c6          0/subop/copy        0/mod/direct    6/rm32/esi    .           .             .           .           .               0/imm8            # copy byte to *esi
+#?     # ++curr
+#?     46/increment-esi
+#?     # ++i
+#?     41/increment-ecx
+#?     eb/jump  $zero-out:loop/disp8
+#? $zero-out:end:
+#?     # . restore registers
+#?     5e/pop-to-esi
+#?     5a/pop-to-edx
+#?     59/pop-to-ecx
+#?     58/pop-to-eax
+#?     # . epilogue
+#?     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+#?     5d/pop-to-ebp
+#?     c3/return
+#? 
+#? == data
+#? Push-n-zero-bytes-ebp:  # (addr int)
+#?   0/imm32
+#? Push-n-zero-bytes-esp:  # (addr int)
+#?   0/imm32
+#? == code
+
+# stack at dump-stack0:
+# 0 a: bdffffb0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffc0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffd0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000 
+# 0 a: bdfffff0:  00000000  [fcfdfeff]  00000001   bf000000 
+
+# desired state after push-n-zero-bytes:
+# 0 a: bdffff90:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffa0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffb0:  00000000   00000000   00000000   bdffffec 
+# 0 a: bdffffc0:  0900012a   bdffffd0   00000020   090000d1 
+# 0 a: bdffffd0: [rrrrrrrr]  00000000   00000000   00000000 
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000 
+# 0 a: bdfffff0:  00000000   fcfdfeff   00000001   bf000000 
+
+# Stack pointer contains ra is caller's responsibility to over-write with array length.
+
+# actual state:
+# 0 a: bdffff90:  00000000   00000000   00000000   00000000
+# 0 a: bdffffa0:  00000000   00000000   00000000   00000000
+# 0 a: bdffffb0:  00000000   00000000   00000000   bdffffec
+# 0 a: bdffffc0:  0900012a   bdffffd0   00000020   090000d1
+# 0 a: bdffffd0:  00000000  [00000000]  00000000   00000000
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000
+# 0 a: bdfffff0:  00000020   fcfdfeff   00000001   bf000000
+
+# Couple of issues. But where does the return address disappear to?
+
+## 8:
+
+#? Entry:
+#?     # . prologue
+#?     89/<- %ebp 4/r32/esp
+#?     #
+#?     68/push 0xfcfdfeff/imm32
+#?     b8/copy-to-eax 0x34353637/imm32
+#? $dump-stack0:
+#?     (push-n-zero-bytes 0x20)
+#? $dump-stack9:
+#?     68/push 0x20/imm32
+#? #?     c7 0/subop/copy *esp 0x20/imm32
+#? $dump-stacka:
+#?     b8/copy-to-eax 1/imm32/exit
+#?     cd/syscall 0x80/imm8
+#? 
+#? push-n-zero-bytes:  # n: int
+#? $push-n-zero-bytes:prologue:
+#?     89/<- *Push-n-zero-bytes-ebp 5/r32/ebp  # spill ebp without affecting stack
+#?     89/<- %ebp 4/r32/esp
+#? $push-n-zero-bytes:copy-ra:
+#? $dump-stack1:
+#?     # -- esp = ebp
+#?     50/push-eax
+#? $dump-stack2:
+#?     # -- esp+8 = ebp+4
+#?     # -- esp+4 = ebp
+#?     8b/-> *(esp+4) 0/r32/eax
+#? $dump-stack3:
+#?     2b/subtract *(ebp+4) 4/r32/esp
+#? $dump-stack4:
+#?     # -- esp+4+n = ebp
+#?     89/<- *(esp+4) 0/r32/eax
+#? $dump-stack5:
+#?     58/pop-to-eax
+#?     # -- esp+n = ebp
+#? $push-n-zero-bytes:bulk-cleaning:
+#? $dump-stack6:
+#?     89/<- *Push-n-zero-bytes-esp 4/r32/esp
+#?     81 0/subop/add *Push-n-zero-bytes-esp 4/imm32
+#? $dump-stack7:
+#?     (zero-out *Push-n-zero-bytes-esp *(ebp+4))  # n
+#? $push-n-zero-bytes:epilogue:
+#? $dump-stack8:
+#?     8b/-> *Push-n-zero-bytes-ebp 5/r32/ebp  # restore spill
+#?     c3/return
+#? 
+#? zero-out:  # start: (addr byte), len: int
+#?     # pseudocode:
+#?     #   curr/esi = start
+#?     #   i/ecx = 0
+#?     #   while true
+#?     #     if (i >= len) break
+#?     #     *curr = 0
+#?     #     ++curr
+#?     #     ++i
+#?     #
+#?     # . prologue
+#?     55/push-ebp
+#?     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+#?     # . save registers
+#?     50/push-eax
+#?     51/push-ecx
+#?     52/push-edx
+#?     56/push-esi
+#?     # curr/esi = start
+#?     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
+#?     # var i/ecx: int = 0
+#?     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
+#?     # edx = len
+#?     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
+#? $zero-out:loop:
+#?     # if (i >= len) break
+#?     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
+#?     7d/jump-if->=  $zero-out:end/disp8
+#?     # *curr = 0
+#?     c6          0/subop/copy        0/mod/direct    6/rm32/esi    .           .             .           .           .               0/imm8            # copy byte to *esi
+#?     # ++curr
+#?     46/increment-esi
+#?     # ++i
+#?     41/increment-ecx
+#?     eb/jump  $zero-out:loop/disp8
+#? $zero-out:end:
+#?     # . restore registers
+#?     5e/pop-to-esi
+#?     5a/pop-to-edx
+#?     59/pop-to-ecx
+#?     58/pop-to-eax
+#?     # . epilogue
+#?     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+#?     5d/pop-to-ebp
+#?     c3/return
+#? 
+#? == data
+#? Push-n-zero-bytes-ebp:  # (addr int)
+#?   0/imm32
+#? Push-n-zero-bytes-esp:  # (addr int)
+#?   0/imm32
+#? == code
+
+# stack at dump-stack0:
+# 0 a: bdffffb0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffc0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffd0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000 
+# 0 a: bdfffff0:  00000000  [fcfdfeff]  00000001   bf000000 
+
+# desired state after push-n-zero-bytes:
+# 0 a: bdffff90:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffa0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffb0:  00000000   00000000   00000000   bdffffec 
+# 0 a: bdffffc0:  0900012a   bdffffd0   00000020   090000d1 
+# 0 a: bdffffd0: [rrrrrrrr]  00000000   00000000   00000000 
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000 
+# 0 a: bdfffff0:  00000000   fcfdfeff   00000001   bf000000 
+
+# actual state:
+# 0 a: bdffff90:  00000000   00000000   00000000   00000000
+# 0 a: bdffffa0:  00000000   00000000   00000000   00000000
+# 0 a: bdffffb0:  00000000   00000000   00000000   bdffffec
+# 0 a: bdffffc0:  09000124   bdffffd0   00000020   090000d1
+# 0 a: bdffffd0: [00000000]  00000000   00000000   00000000
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000
+# 0 a: bdfffff0:  00000020   fcfdfeff   00000001   bf000000
+
+# Ok, just one diff, at bdfffff0
+
+## 9:
+
+Entry:
+    # . prologue
+    89/<- %ebp 4/r32/esp
+    #
+    68/push 0xfcfdfeff/imm32
+    b8/copy-to-eax 0x34353637/imm32
+$dump-stack0:
+    (push-n-zero-bytes 0x20)
+$dump-stack9:
+    68/push 0x20/imm32
+$dump-stacka:
+    b8/copy-to-eax 1/imm32/exit
+    cd/syscall 0x80/imm8
+
+push-n-zero-bytes:  # n: int
+$push-n-zero-bytes:prologue:
+    89/<- *Push-n-zero-bytes-ebp 5/r32/ebp  # spill ebp without affecting stack
+    89/<- %ebp 4/r32/esp
+$push-n-zero-bytes:copy-ra:
+$dump-stack1:
+    # -- esp = ebp
+    50/push-eax
+$dump-stack2:
+    # -- esp+8 = ebp+4
+    # -- esp+4 = ebp
+    8b/-> *(esp+4) 0/r32/eax
+$dump-stack3:
+    2b/subtract *(ebp+4) 4/r32/esp
+$dump-stack4:
+    # -- esp+4+n = ebp
+    89/<- *(esp+4) 0/r32/eax
+$dump-stack5:
+    58/pop-to-eax
+    # -- esp+n = ebp
+$push-n-zero-bytes:bulk-cleaning:
+$dump-stack6:
+    89/<- *Push-n-zero-bytes-esp 4/r32/esp
+    81 0/subop/add *Push-n-zero-bytes-esp 4/imm32
+$dump-stack7:
+    81 0/subop/add *(ebp+4) 4/imm32
+    (zero-out *Push-n-zero-bytes-esp *(ebp+4))  # n
+$push-n-zero-bytes:epilogue:
+$dump-stack8:
+    8b/-> *Push-n-zero-bytes-ebp 5/r32/ebp  # restore spill
+    c3/return
+
+zero-out:  # start: (addr byte), len: int
+    # pseudocode:
+    #   curr/esi = start
+    #   i/ecx = 0
+    #   while true
+    #     if (i >= len) break
+    #     *curr = 0
+    #     ++curr
+    #     ++i
+    #
+    # . prologue
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    52/push-edx
+    56/push-esi
+    # curr/esi = start
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
+    # var i/ecx: int = 0
+    31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
+    # edx = len
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
+$zero-out:loop:
+    # if (i >= len) break
+    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
+    7d/jump-if->=  $zero-out:end/disp8
+    # *curr = 0
+    c6          0/subop/copy        0/mod/direct    6/rm32/esi    .           .             .           .           .               0/imm8            # copy byte to *esi
+    # ++curr
+    46/increment-esi
+    # ++i
+    41/increment-ecx
+    eb/jump  $zero-out:loop/disp8
+$zero-out:end:
+    # . restore registers
+    5e/pop-to-esi
+    5a/pop-to-edx
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+== data
+Push-n-zero-bytes-ebp:  # (addr int)
+  0/imm32
+Push-n-zero-bytes-esp:  # (addr int)
+  0/imm32
+== code
+
+# stack at dump-stack0:
+# 0 a: bdffffb0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffc0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffd0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000 
+# 0 a: bdfffff0:  00000000  [fcfdfeff]  00000001   bf000000 
+
+# desired state after push-n-zero-bytes:
+# 0 a: bdffff90:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffa0:  00000000   00000000   00000000   00000000 
+# 0 a: bdffffb0:  00000000   00000000   00000000   bdffffec 
+# 0 a: bdffffc0:  0900012a   bdffffd0   00000020   090000d1 
+# 0 a: bdffffd0: [xxxxxxxx]  00000000   00000000   00000000 
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000 
+# 0 a: bdfffff0:  00000000   fcfdfeff   00000001   bf000000 
+
+# actual state:
+# 0 a: bdffff90:  00000000   00000000   00000000   00000000
+# 0 a: bdffffa0:  00000000   00000000   00000000   00000000
+# 0 a: bdffffb0:  00000000   00000000   00000000   bdffffec
+# 0 a: bdffffc0:  0900012f   bdffffd0   00000024   090000d1
+# 0 a: bdffffd0: [00000000]  00000000   00000000   00000000
+# 0 a: bdffffe0:  00000000   00000000   00000000   00000000
+# 0 a: bdfffff0:  00000000   fcfdfeff   00000001   bf000000