1 # write: like _write, but also support in-memory output streams (`ostream`s)
  2 # in addition to file descriptors.
  3 #
  4 # Our first dependency-injected and testable primitive. We can pass it either
  5 # a file descriptor or an address to an output stream or ostream. If a file
  6 # descriptor is passed in, we _write to it using the right syscall. If a 'fake
  7 # file descriptor' or ostream is passed in, we append to the output stream.
  8 # This lets us redirect output in tests and check it later.
  9 #
 10 # We assume our data segment will never begin at an address shorter than
 11 # 0x08000000, so any smaller arguments are assumed to be real file descriptors.
 12 #
 13 # An ostream looks like this:
 14 #   write: int  # index at which writes go
 15 #   data: (array byte)  # prefixed by length as usual
 16 
 17 == data
 18 
 19 # In-memory ostream for tests to write() to.
 20 # Also illustrates the layout of ostreams.
 21 Test-ostream:
 22   # current write index
 23   00 00 00 00
 24   # length (= 8)
 25   08 00 00 00
 26   # data
 27   00 00 00 00 00 00 00 00  # 8 bytes
 28 
 29 == code
 30 
 31 # instruction                     effective address                                                   operand     displacement    immediate
 32 # op          subop               mod             rm32          base        index         scale       r32
 33 # 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
 34 
 35 # main:  (manual test if this is the last file loaded)
 36   e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
 37   # syscall(exit, Num-test-failures)
 38   8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           1/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 39   b8/copy-to-EAX  1/imm32
 40   cd/syscall  0x80/imm8
 41 
 42 write:  # f : fd or (address stream), s : (address array byte) -> <void>
 43   # prolog
 44   55/push-EBP
 45   89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 46   # if (f < 0x08000000) _write(f, s), return  # f can't be a user-mode address, so treat it as a kernel file descriptor
 47   81          7/subop/compare     1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         0x08000000/imm32  # compare *(EBP+8)
 48   7f/jump-if-greater  $write:fake/disp8
 49     # push args
 50   ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 51   ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
 52     # call
 53   e8/call  _write/disp32
 54     # discard args
 55   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 56   eb/jump  $write:end/disp8
 57 $write:fake:
 58   # otherwise, treat 'f' as a stream to append to
 59   # save registers
 60   50/push-EAX
 61   51/push-ECX
 62   52/push-EDX
 63   53/push-EBX
 64   # ECX = f
 65   8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none              1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
 66   # EDX = f.write
 67   8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
 68   # EBX = f.length
 69   8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           3/r32/EBX   4/disp8         .                 # copy *(ECX+4) to EBX
 70   # EAX = _append(&f.data[f.write], &f.data[f.length], s)
 71     # push s
 72   ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 73     # push &f.data[f.length]
 74   8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ECX  3/index/EBX   .           3/r32/EBX   8/disp8         .                 # copy ECX+EBX+8 to EBX
 75   53/push-EBX
 76     # push &f.data[f.write]
 77   8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ECX  2/index/EDX   .           3/r32/EBX   8/disp8         .                 # copy ECX+EBX+8 to EBX
 78   53/push-EBX
 79     # call
 80   e8/call  _append/disp32
 81     # discard args
 82   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 83   # f.write += EAX
 84   01/add                          0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # add EAX to *ECX
 85   # restore registers
 86   5b/pop-to-EBX
 87   5a/pop-to-EDX
 88   59/pop-to-ECX
 89   58/pop-to-EAX
 90 $write:end:
 91   # epilog
 92   89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 93   5d/pop-to-EBP
 94   c3/return
 95 
 96 clear-ostream:  # f : (address ostream)
 97   # prolog
 98   55/push-EBP
 99   89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
100   # save registers
101   50/push-EAX
102   51/push-ECX
103   # EAX = f
104   8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none              0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
105   # ECX = f.length
106   8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EAX+4) to ECX
107   # ECX = &f.data[f.length]
108   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
109   # f.write = 0
110   c7/copy                         0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
111   # EAX = f.data
112   81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               8/imm32           # add to EAX
113   # while (true)
114 $clear-ostream:loop:
115   # if EAX >= ECX break
116   39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
117   7d/jump-if-greater-or-equal  $clear-ostream:end/disp8
118   # *EAX = 0
119   c7/copy                         0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
120   # EAX += 4
121   81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               4/imm32           # add to EAX
122   eb/jump  $clear-ostream:loop/disp8
123 $clear-ostream:end:
124   # restore registers
125   59/pop-to-ECX
126   58/pop-to-EAX
127   # epilog
128   89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
129   5d/pop-to-EBP
130   c3/return
131 
132 test-write-single:
133   # clear-ostream(Test-ostream)
134     # push args
135   68/push  Test-ostream/imm32
136     # call
137   e8/call  clear-ostream/disp32
138     # discard args
139   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
140   # write(Test-ostream, "Ab")
141     # push args
142   68/push  "Ab"/imm32
143   68/push  Test-ostream/imm32
144     # call
145   e8/call  write/disp32
146     # discard args
147   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
148   # check-ints-equal(*Test-ostream.data, 41/A 62/b 00 00, msg)
149     # push args
150   68/push  "F - test-write-single"/imm32
151   68/push  0x006241/imm32/Ab
152     # push *Test-ostream.data
153   b8/copy-to-EAX  Test-ostream/imm32
154   ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
155     # call
156   e8/call  check-ints-equal/disp32
157     # discard args
158   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
159   # end
160   c3/return
161 
162 test-write-appends:
163   # clear-ostream(Test-ostream)
164     # push args
165   68/push  Test-ostream/imm32
166     # call
167   e8/call  clear-ostream/disp32
168     # discard args
169   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
170   # write(Test-ostream, "C")
171     # push args
172   68/push  "C"/imm32
173   68/push  Test-ostream/imm32
174     # call
175   e8/call  write/disp32
176     # discard args
177   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
178   # write(Test-ostream, "D")
179     # push args
180   68/push  "D"/imm32
181   68/push  Test-ostream/imm32
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   # check-ints-equal(*Test-ostream.data, 43/C 44/D 00 00, msg)
187     # push args
188   68/push  "F - test-write-appends"/imm32
189   68/push  0x00004443/imm32/C-D
190     # push *Test-ostream.data
191   b8/copy-to-EAX  Test-ostream/imm32
192   ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
193     # call
194   e8/call  check-ints-equal/disp32
195     # discard args
196   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
197   # end
198   c3/return