https://github.com/akkartik/mu/blob/master/subx/069allocate.subx
  1 # Helper to dynamically allocate memory on the heap.
  2 #
  3 # We'd like to be able to write tests for functions that allocate memory,
  4 # making assertions on the precise addresses used. To achieve this we'll pass
  5 # in an *allocation descriptor* to allocate from.
  6 #
  7 # Allocation descriptors are also useful outside of tests. Assembly and machine
  8 # code are of necessity unsafe languages, and one of the most insidious kinds
  9 # of bugs unsafe languages expose us to are dangling pointers to memory that
 10 # has been freed and potentially even reused for something totally different.
 11 # To reduce the odds of such "use after free" errors, SubX programs tend to not
 12 # reclaim and reuse dynamically allocated memory. (Running out of memory is far
 13 # easier to debug.) Long-running programs that want to reuse memory are mostly
 14 # on their own to be careful. However, they do get one bit of help: they can
 15 # carve out chunks of memory and then allocate from them manually using this
 16 # very same 'allocate' helper. They just need a new allocation descriptor for
 17 # their book-keeping.
 18 
 19 == code
 20 #   instruction                     effective address                                                   register    displacement    immediate
 21 # . op          subop               mod             rm32          base        index         scale       r32
 22 # . 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
 23 
 24 # Claim the next 'n' bytes of memory starting at ad->curr and update ad->curr.
 25 # If there isn't enough memory before ad->limit, return 0 and leave 'ad' unmodified.
 26 allocate:  # ad : (address allocation-descriptor), n : int -> address-or-null/EAX
 27     # . prolog
 28     55/push-EBP
 29     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 30     # . save registers
 31     51/push-ECX
 32     52/push-EDX
 33     # ECX = ad
 34     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
 35     # save ad->curr
 36     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
 37     # check if there's enough space
 38     # . EDX = ad->curr + n
 39     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDX
 40     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # add *(EBP+12) to EDX
 41     3b/compare                      1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # compare EDX with *(ECX+4)
 42     72/jump-if-lesser-signed  $allocate:commit/disp8
 43     # return null if not
 44     b8/copy-to-EAX  0/imm32
 45     eb/jump  $allocate:end/disp8
 46 $allocate:commit:
 47     # update ad->curr
 48     89/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy EDX to *ECX
 49 $allocate:end:
 50     # . restore registers
 51     5a/pop-to-EDX
 52     59/pop-to-ECX
 53     # . epilog
 54     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 55     5d/pop-to-EBP
 56     c3/return
 57 
 58 test-allocate-success:
 59     # . prolog
 60     55/push-EBP
 61     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 62     # var ad/ECX : (address allocation-descriptor) = {11, 15}
 63     68/push  0xf/imm32/limit
 64     68/push  0xb/imm32/curr
 65     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 66     # EAX = allocate(ad, 3)
 67     # . . push args
 68     68/push  3/imm32
 69     51/push-ECX
 70     # . . call
 71     e8/call  allocate/disp32
 72     # . . discard args
 73     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 74     # check-ints-equal(EAX, 11, msg)
 75     # . . push args
 76     68/push  "F - test-allocate-success: returns current pointer of allocation descriptor"/imm32
 77     68/push  0xb/imm32
 78     50/push-EAX
 79     # . . call
 80     e8/call  check-ints-equal/disp32
 81     # . . discard args
 82     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 83     # check-ints-equal(ad->curr, 14, msg)
 84     # . . push args
 85     68/push  "F - test-allocate-success: updates allocation descriptor"/imm32
 86     68/push  0xe/imm32
 87     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 88     # . . call
 89     e8/call  check-ints-equal/disp32
 90     # . . discard args
 91     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 92     # . epilog
 93     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 94     5d/pop-to-EBP
 95     c3/return
 96 
 97 test-allocate-failure:
 98     # . prolog
 99     55/push-EBP
100     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
101     # var ad/ECX : (address allocation-descriptor) = {11, 15}
102     68/push  0xf/imm32/limit
103     68/push  0xb/imm32/curr
104     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
105     # EAX = allocate(ad, 6)
106     # . . push args
107     68/push  6/imm32
108     51/push-ECX
109     # . . call
110     e8/call  allocate/disp32
111     # . . discard args
112     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
113     # check-ints-equal(EAX, 0, msg)
114     # . . push args
115     68/push  "F - test-allocate-failure: returns null"/imm32
116     68/push  0/imm32
117     50/push-EAX
118     # . . call
119     e8/call  check-ints-equal/disp32
120     # . . discard args
121     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
122     # no change to ad->curr
123     # . check-ints-equal(ad->curr, 11)
124     # . . push args
125     68/push  "F - test-allocate-failure: updates allocation descriptor"/imm32
126     68/push  0xb/imm32
127     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
128     # . . call
129     e8/call  check-ints-equal/disp32
130     # . . discard args
131     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
132     # . epilog
133     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
134     5d/pop-to-EBP
135     c3/return
136 
137 # helper: create a nested allocation descriptor (useful for tests)
138 allocate-region:  # ad : (address allocation-descriptor), n : int -> new-ad : (address allocation-descriptor)
139     # . prolog
140     55/push-EBP
141     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
142     # . save registers
143     51/push-ECX
144     # EAX = allocate(ad, n)
145     # . . push args
146     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
147     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
148     # . . call
149     e8/call  allocate/disp32
150     # . . discard args
151     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
152     # if (EAX == 0) abort
153     3d/compare-EAX-and  0/imm32
154     74/jump-if-equal  $allocate-region:abort/disp8
155     # earmark 8 bytes at the start for a new allocation descriptor
156     # . *EAX = EAX + 8
157     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
158     81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               8/imm32           # add to ECX
159     89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
160     # . *(EAX+4) = EAX + n
161     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
162     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0xc/disp8       .                 # add *(EBP+12) to ECX
163     89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy ECX to *(EAX+4)
164     # . restore registers
165     59/pop-to-ECX
166     # . epilog
167     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
168     5d/pop-to-EBP
169     c3/return
170 
171 # We could create a more general '$abort' jump target, but then we'd need to do
172 # a conditional jump followed by loading the error message and an unconditional
173 # jump. Or we'd need to unconditionally load the error message before a
174 # conditional jump, even if it's unused the vast majority of the time. This way
175 # we bloat a potentially cold segment in RAM so we can abort with a single
176 # instruction.
177 $allocate-region:abort:
178     # . _write(2/stderr, error)
179     # . . push args
180     68/push  "allocate-region: failed to allocate\n"/imm32
181     68/push  2/imm32/stderr
182     # . . call
183     e8/call  _write/disp32
184     # . . discard args
185     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
186     # . syscall(exit, 1)
187     bb/copy-to-EBX  1/imm32
188     b8/copy-to-EAX  1/imm32/exit
189     cd/syscall  0x80/imm8
190     # never gets here
191 
192 # . . vim:nowrap:textwidth=0