summary refs log tree commit diff stats
path: root/INSTALL
diff options
context:
space:
mode:
Diffstat (limited to 'INSTALL')
0 files changed, 0 insertions, 0 deletions
='n58' href='#n58'>58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
# Helper to dynamically allocate memory on the heap.
#
# We'd like to be able to write tests for functions that allocate memory,
# making assertions on the precise addresses used. To achieve this we'll pass
# in an *allocation descriptor* to allocate from.
#
# Allocation descriptors are also useful outside of tests. Assembly and machine
# code are of necessity unsafe languages, and one of the most insidious kinds
# of bugs unsafe languages expose us to are dangling pointers to memory that
# has been freed and potentially even reused for something totally different.
# To reduce the odds of such "use after free" errors, SubX programs tend to not
# reclaim and reuse dynamically allocated memory. (Running out of memory is far
# easier to debug.) Long-running programs that want to reuse memory are mostly
# on their own to be careful. However, they do get one bit of help: they can
# carve out chunks of memory and then allocate from them manually using this
# very same 'allocate' helper. They just need a new allocation descriptor for
# their book-keeping.

== data

# The 'global' allocation descriptor. Pass this into 'allocate' to claim a
# hitherto unused bit of memory.
Heap:
    Start-of-heap/imm32  # curr
    0x0b000000/imm32  # limit; keep sync'd with DATA_SEGMENT + SEGMENT_ALIGNMENT

== code
#   instruction                     effective address                                                   register    displacement    immediate
# . op          subop               mod             rm32          base        index         scale       r32
# . 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

# main:
    e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
    # syscall(exit, Num-test-failures)
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8

# Claim the next 'n' bytes of memory starting at ad->curr and update ad->curr.
# If there isn't enough memory before ad->limit, return 0 and leave 'ad' unmodified.
allocate:  # ad : (address allocation-descriptor), n : int -> address-or-null/EAX
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    51/push-ECX
    52/push-EDX
    # ECX = ad
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
    # save ad->curr
    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
    # check if there's enough space
    # . EDX = ad->curr + n
    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDX
    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # add *(EBP+12) to EDX
    3b/compare                      1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # compare EDX with *(ECX+4)
    7c/jump-if-lesser  $allocate:commit/disp8
    # return null if not
    b8/copy-to-EAX  0/imm32
    eb/jump  $allocate:end/disp8
$allocate:commit:
    # update ad->curr
    89/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy EDX to *ECX
$allocate:end:
    # . restore registers
    5a/pop-to-EDX
    59/pop-to-ECX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-allocate-success:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var ad/ECX : (address allocation-descriptor) = {11, 15}
    68/push  0xf/imm32/limit
    68/push  0xb/imm32/curr
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # EAX = allocate(ad, 3)
    # . . push args
    68/push  3/imm32
    51/push-ECX
    # . . call
    e8/call  allocate/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(EAX, 11, msg)
    # . . push args
    68/push  "F - test-allocate-success: returns current pointer of allocation descriptor"/imm32
    68/push  0xb/imm32
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(ad->curr, 14, msg)
    # . . push args
    68/push  "F - test-allocate-success: updates allocation descriptor"/imm32
    68/push  0xe/imm32
    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-allocate-failure:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var ad/ECX : (address allocation-descriptor) = {11, 15}
    68/push  0xf/imm32/limit
    68/push  0xb/imm32/curr
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # EAX = allocate(ad, 6)
    # . . push args
    68/push  6/imm32
    51/push-ECX
    # . . call
    e8/call  allocate/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(EAX, 0, msg)
    # . . push args
    68/push  "F - test-allocate-failure: returns null"/imm32
    68/push  0/imm32
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # no change to ad->curr
    # . check-ints-equal(ad->curr, 11)
    # . . push args
    68/push  "F - test-allocate-failure: updates allocation descriptor"/imm32
    68/push  0xb/imm32
    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

# helper: create a nested allocation descriptor (useful for tests)
allocate-region:  # ad : (address allocation-descriptor), n : int -> new-ad : (address allocation-descriptor)
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    51/push-ECX
    # EAX = allocate(ad, n)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  allocate/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # if (EAX == 0) abort
    81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
    74/jump-if-equal  $allocate-region:abort/disp8
    # earmark 8 bytes at the start for a new allocation descriptor
    # . *EAX = EAX + 8
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               8/imm32           # add to ECX
    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
    # . *(EAX+4) = EAX + n
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0xc/disp8       .                 # add *(EBP+12) to ECX
    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy ECX to *(EAX+4)
    # . restore registers
    59/pop-to-ECX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

# We could create a more general '$abort' jump target, but then we'd need to do
# a conditional jump followed by loading the error message and an unconditional
# jump. Or we'd need to unconditionally load the error message before a
# conditional jump, even if it's unused the vast majority of the time. This way
# we bloat a potentially cold segment in RAM so we can abort with a single
# instruction.
$allocate-region:abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "allocate-region: failed to allocate"/imm32
    68/push  2/imm32/stderr
    # . . call
    e8/call  _write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . syscall(exit, 1)
    bb/copy-to-EBX  1/imm32
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

# . . vim:nowrap:textwidth=0