7842 - new directory organization

Baremetal is now the default build target and therefore has its sources
at the top-level. Baremetal programs build using the phase-2 Mu toolchain
that requires a Linux kernel. This phase-2 codebase which used to be at
the top-level is now under the linux/ directory. Finally, the phase-2 toolchain,
while self-hosting, has a way to bootstrap from a C implementation, which
is now stored in linux/bootstrap. The bootstrap C implementation uses some
literate programming tools that are now in linux/bootstrap/tools.

So the whole thing has gotten inverted. Each directory should build one
artifact and include the main sources (along with standard library). Tools
used for building it are relegated to sub-directories, even though those
tools are often useful in their own right, and have had lots of interesting
programs written using them.

A couple of things have gotten dropped in this process:
  - I had old ways to run on just a Linux kernel, or with a Soso kernel.
    No more.
  - I had some old tooling for running a single test at the cursor. I haven't
    used that lately. Maybe I'll bring it back one day.

The reorg isn't done yet. Still to do:
  - redo documentation everywhere. All the README files, all other markdown,
    particularly vocabulary.md.
  - clean up how-to-run comments at the start of programs everywhere
  - rethink what to do with the html/ directory. Do we even want to keep
    supporting it?

In spite of these shortcomings, all the scripts at the top-level, linux/
and linux/bootstrap are working. The names of the scripts also feel reasonable.
This is a good milestone to take stock at.
This commit is contained in:
Kartik K. Agaram 2021-03-03 22:09:50 -08:00
parent c6b928be29
commit 71e4f38129
451 changed files with 5664 additions and 37046 deletions

View File

@ -5,13 +5,6 @@
# . 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
Entry: # run all tests
#? e8/call test-compare-equal-strings/disp32
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
e8/call syscall_exit/disp32
string-equal?: # s: (addr array byte), benchmark: (addr array byte) -> result/eax: boolean
# pseudocode:
# if (s->size != benchmark->size) return false

View File

@ -8,6 +8,9 @@
# some primitives for operating on streams:
# - clear-stream (clears everything but the data size)
# - rewind-stream (resets read pointer)
#
# We need to do this in machine code because streams need to be opaque types,
# and we don't yet support opaque types in Mu.
== code
# instruction effective address register displacement immediate

View File

@ -1,46 +1,20 @@
# write: like _write, but also support in-memory streams in addition to file
# descriptors.
# write: write to in-memory streams
#
# Our first dependency-injected and testable primitive. We can pass it either
# a file descriptor or an address to a stream. If a file descriptor is passed
# in, we _write to it using the right syscall. If a 'fake file descriptor' or
# stream is passed in, we append to the stream. This lets us redirect output
# in tests and check it later.
#
# We assume our data segment will never begin at an address shorter than
# 0x08000000, so any smaller arguments are assumed to be real file descriptors.
#
# A stream looks like this:
# read: int # index at which to read next
# write: int # index at which writes go
# data: (array byte) # prefixed by size as usual
# We need to do this in machine code because streams need to be opaque types,
# and we don't yet support opaque types in Mu.
== 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
# TODO: come up with a way to signal when a write to disk fails
write: # f: fd or (addr stream byte), s: (addr array byte)
write: # f: (addr stream byte), s: (addr array byte)
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# if (s == 0) return
81 7/subop/compare 1/mod/*+disp8 5/rm32/ebp . . . . 0xc/disp8 0/imm32 # compare *(ebp+12)
74/jump-if-= $write:end/disp8
# if (f < 0x08000000) _write(f, s) and return # f can't be a user-mode address, so treat it as a kernel file descriptor
81 7/subop/compare 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 0x08000000/imm32 # compare *(ebp+8)
73/jump-if-addr>= $write:fake/disp8
# . . 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 _write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
eb/jump $write:end/disp8
$write:fake:
# otherwise, treat 'f' as a stream to append to
# . save registers
50/push-eax
51/push-ecx
@ -159,4 +133,88 @@ _test-stream: # (stream byte)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
== code
# 3-argument variant of _append
_append-3: # out: (addr byte), outend: (addr byte), s: (addr array byte) -> num_bytes_appended/eax
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
# eax = _append-4(out, outend, &s->data[0], &s->data[s->size])
# . . push &s->data[s->size]
8b/copy 1/mod/*+disp8 5/rm32/ebp . . 0/r32/eax 0x10/disp8 . # copy *(ebp+16) to eax
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
51/push-ecx
# . . push &s->data[0]
8d/copy-address 1/mod/*+disp8 0/rm32/eax . . . 1/r32/ecx 4/disp8 . # copy eax+4 to ecx
51/push-ecx
# . . push outend
ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 0xc/disp8 . # push *(ebp+12)
# . . push out
ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 . # push *(ebp+8)
# . . call
e8/call _append-4/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x10/imm32 # add to esp
$_append-3:end:
# . restore registers
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
# 4-argument variant of _append
_append-4: # out: (addr byte), outend: (addr byte), in: (addr byte), inend: (addr byte) -> num_bytes_appended/eax: int
# . prologue
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
53/push-ebx
56/push-esi
57/push-edi
# num_bytes_appended = 0
b8/copy-to-eax 0/imm32
# edi = out
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 7/r32/edi 8/disp8 . # copy *(ebp+8) to edi
# edx = outend
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 2/r32/edx 0xc/disp8 . # copy *(ebp+12) to edx
# esi = in
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 6/r32/esi 0x10/disp8 . # copy *(ebp+16) to esi
# ecx = inend
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 0x14/disp8 . # copy *(ebp+20) to ecx
$_append-4:loop:
# if (in >= inend) break
39/compare 3/mod/direct 6/rm32/esi . . . 1/r32/ecx . . # compare esi with ecx
73/jump-if-addr>= $_append-4:end/disp8
# if (out >= outend) abort # just to catch test failures fast
39/compare 3/mod/direct 7/rm32/edi . . . 2/r32/edx . . # compare edi with edx
73/jump-if-addr>= $_append-4:end/disp8 # TODO: abort
# *out = *in
8a/copy-byte 0/mod/indirect 6/rm32/esi . . . 3/r32/BL . . # copy byte at *esi to BL
88/copy-byte 0/mod/indirect 7/rm32/edi . . . 3/r32/BL . . # copy byte at BL to *edi
# ++num_bytes_appended
40/increment-eax
# ++in
46/increment-esi
# ++out
47/increment-edi
eb/jump $_append-4:loop/disp8
$_append-4:end:
# . restore registers
5f/pop-to-edi
5e/pop-to-esi
5b/pop-to-ebx
5a/pop-to-edx
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
# . . vim:nowrap:textwidth=0

View File

@ -1,273 +1,13 @@
# read-byte-buffered: one higher-level abstraction atop 'read'.
# Read a single byte from a stream.
#
# There are many situations where 'read' is a lot to manage, and we need
# to abstract some details away. One of them is when we want to read a file
# character by character. In this situation we follow C's FILE data structure,
# which manages the underlying file descriptor together with the buffer it
# reads into. We call our version 'buffered-file'. Should be useful with other
# primitives as well, in later layers.
== data
# The buffered file for standard input. Also illustrates the layout for
# buffered-file: a pointer to the backing store, followed by a 'buffer' stream
Stdin: # buffered-file
# file descriptor or (addr stream byte)
0/imm32 # standard input
$Stdin->buffer:
# inlined fields for a stream
# current write index
0/imm32
# current read index
0/imm32
# size
8/imm32
# data
00 00 00 00 00 00 00 00 # 8 bytes
# TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
# I don't want to type in 1024 bytes here.
# We need to do this in machine code because streams need to be opaque types,
# and we don't yet support opaque types in Mu.
== 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
# Return next byte value in eax, with top 3 bytes cleared.
# On reaching end of file, return 0xffffffff (Eof).
read-byte-buffered: # f: (addr buffered-file) -> byte-or-Eof/eax: byte
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
56/push-esi
# esi = f
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 6/r32/esi 8/disp8 . # copy *(ebp+8) to esi
# ecx = f->read
8b/copy 1/mod/*+disp8 6/rm32/esi . . . 1/r32/ecx 8/disp8 . # copy *(esi+8) to ecx
# if (f->read >= f->write) populate stream from file
3b/compare 1/mod/*+disp8 6/rm32/esi . . . 1/r32/ecx 4/disp8 . # compare ecx with *(esi+4)
7c/jump-if-< $read-byte-buffered:from-stream/disp8
# . clear-stream(stream = f+4)
# . . push args
8d/copy-address 1/mod/*+disp8 6/rm32/esi . . . 0/r32/eax 4/disp8 . # copy esi+4 to eax
50/push-eax
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . f->read must now be 0; update its cache at ecx
31/xor 3/mod/direct 1/rm32/ecx . . . 1/r32/ecx . . # clear ecx
# . eax = read(f->fd, stream = f+4)
# . . push args
50/push-eax
ff 6/subop/push 0/mod/indirect 6/rm32/esi . . . . . . # push *esi
# . . call
e8/call read/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# if (eax == 0) return 0xffffffff
3d/compare-eax-and 0/imm32
75/jump-if-!= $read-byte-buffered:from-stream/disp8
b8/copy-to-eax 0xffffffff/imm32/Eof
eb/jump $read-byte-buffered:end/disp8
$read-byte-buffered:from-stream:
# byte-or-Eof = f->data[f->read]
31/xor 3/mod/direct 0/rm32/eax . . . 0/r32/eax . . # clear eax
8a/copy-byte 1/mod/*+disp8 4/rm32/sib 6/base/esi 1/index/ecx . 0/r32/AL 0x10/disp8 . # copy byte at *(esi+ecx+16) to AL
# ++f->read
ff 0/subop/increment 1/mod/*+disp8 6/rm32/esi . . . . 8/disp8 . # increment *(esi+8)
$read-byte-buffered:end:
# . restore registers
5e/pop-to-esi
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
# - tests
test-read-byte-buffered-single:
# - check that read-byte-buffered returns first byte of 'file'
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream(_test-buffered-file->buffer)
# . . push args
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . write(_test-stream, "Ab")
# . . push args
68/push "Ab"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# read-byte-buffered(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call read-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 'A', msg)
# . . push args
68/push "F - test-read-byte-buffered-single"/imm32
68/push 0x41/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
# . end
c3/return
test-read-byte-buffered-multiple:
# - call read-byte-buffered twice, check that second call returns second byte
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream($_test-buffered-file->buffer)
# . . push args
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . write(_test-stream, "Ab")
# . . push args
68/push "Ab"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# read-byte-buffered(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call read-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# read-byte-buffered(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call read-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 'b', msg)
# . . push args
68/push "F - test-read-byte-buffered-multiple"/imm32
68/push 0x62/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
# . end
c3/return
test-read-byte-buffered-end-of-file:
# - call read-byte-buffered on an empty 'file', check that it returns Eof
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream($_test-buffered-file->buffer)
# . . push args
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# read-byte-buffered(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call read-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0xffffffff, msg)
# . . push args
68/push "F - test-read-byte-buffered-end-of-file"/imm32
68/push 0xffffffff/imm32/Eof
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
# . end
c3/return
test-read-byte-buffered-refills-buffer:
# - consume buffered-file's buffer, check that next read-byte-buffered still works
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream($_test-buffered-file->buffer)
# . . push args
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . write(_test-stream, "Abcdefgh")
# . . push args
68/push "Abcdefgh"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# pretend buffer is full
# . _test-buffered-file->read = 6 # >= _test-buffered-file->size
b8/copy-to-eax _test-buffered-file/imm32
c7 0/subop/copy 1/mod/*+disp8 0/rm32/eax . . . . 8/disp8 6/imm32 # copy to *(eax+8)
# read-byte-buffered(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call read-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 'A', msg)
# . . push args
68/push "F - test-read-byte-buffered-refills-buffer"/imm32
68/push 0x41/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
# . end
c3/return
# Return next byte value in eax, with top 3 bytes cleared.
# Abort on reaching end of stream.
read-byte: # s: (addr stream byte) -> result/eax: byte
@ -299,35 +39,14 @@ $read-byte:end:
c3/return
$read-byte:abort:
# . _write(2/stderr, error)
# . . push args
68/push "read-byte: empty stream\n"/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
e8/call syscall_exit/disp32
(draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "read-byte: empty stream" 3 0) # 3=cyan
{
eb/jump loop/disp8
}
# never gets here
== data
# a test buffered file for _test-stream
_test-buffered-file: # buffered-file
# file descriptor or (addr stream byte)
_test-stream/imm32
$_test-buffered-file->buffer:
# current write index
0/imm32
# current read index
0/imm32
# size
6/imm32
# data
00 00 00 00 00 00 # 6 bytes
_test-input-stream: # (stream byte)
# current write index
0/imm32
@ -401,18 +120,4 @@ _test-input-stream: # (stream byte)
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# a test buffered file for _test-input-stream
_test-input-buffered-file: # buffered-file
# file descriptor or (addr stream byte)
_test-input-stream/imm32
$_test-input-buffered-file->buffer:
# current write index
0/imm32
# current read index
0/imm32
# size
6/imm32
# data
00 00 00 00 00 00 # 6 bytes
# . . vim:nowrap:textwidth=0

View File

@ -5,32 +5,10 @@
# . 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
#? Entry: # manual test
#? # write-stream(stdout, _test-stream2)
#? 68/push _test-stream2/imm32
#? 68/push 1/imm32/stdout
#? e8/call write-stream/disp32
#? # 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
#? e8/call syscall_exit/disp32
write-stream: # f: fd or (addr stream byte), s: (addr stream byte)
write-stream: # f: (addr stream byte), s: (addr stream byte)
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# if (f < 0x08000000) _write-stream(f, s), return # f can't be a user-mode address, so treat it as a kernel file descriptor
81 7/subop/compare 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 0x08000000/imm32 # compare *(ebp+8)
73/jump-if-addr>= $write-stream:fake/disp8
# . . 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 _write-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
eb/jump $write-stream:end/disp8
$write-stream:fake:
# otherwise, treat 'f' as a stream to append to
# . save registers
50/push-eax
56/push-esi
@ -64,73 +42,16 @@ $write-stream:fake:
01/add 0/mod/indirect 7/rm32/edi . . . 0/r32/eax . . # add eax to *edi
# s->read += eax
01/add 1/mod/*+disp8 6/rm32/esi . . . 0/r32/eax 4/disp8 . # add eax to *(esi+4)
# . restore registers
5f/pop-to-edi
5e/pop-to-esi
58/pop-to-eax
$write-stream:end:
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
_write-stream: # fd: int, s: (addr stream byte)
# . 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
53/push-ebx
56/push-esi
57/push-edi
# esi = s
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 6/r32/esi 0xc/disp8 . # copy *(ebp+12) to esi
# edi = s->read
8b/copy 1/mod/*+disp8 6/rm32/esi . . . 7/r32/edi 4/disp8 . # copy *(esi+4) to edi
# edx = s->write
8b/copy 0/mod/indirect 6/rm32/esi . . . 2/r32/edx . . # copy *esi to edx
# syscall(write, fd, &s->data[s->read], s->write - s->read)
# . . fd: ebx
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 3/r32/ebx 8/disp8 . # copy *(ebp+8) to ebx
# . . data: ecx = &s->data[s->read]
8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/esi 7/index/edi . 1/r32/ecx 0xc/disp8 . # copy esi+edi+12 to ecx
# . . size: edx = s->write - s->read
29/subtract 3/mod/direct 2/rm32/edx . . . 7/r32/edi . . # subtract edi from edx
# . . syscall
e8/call syscall_write/disp32
# if (eax < 0) abort
3d/compare-eax-with 0/imm32
0f 8c/jump-if-< $_write-stream:abort/disp32
# s->read += eax
01/add 1/mod/*+disp8 6/rm32/esi . . . 0/r32/eax 4/disp8 . # add eax to *(esi+4)
# . restore registers
5f/pop-to-edi
5e/pop-to-esi
5b/pop-to-ebx
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
$_write-stream:abort:
# . _write(2/stderr, error)
# . . push args
68/push "_write-stream: failed to write to file\n"/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
e8/call syscall_exit/disp32
# never gets here
test-write-stream-single:
# setup
# . clear-stream(_test-stream)

View File

@ -1,209 +1,13 @@
# write-byte-buffered: add a single byte to a buffered-file.
# flush: write out any buffered writes to disk.
# Write a single byte to a stream.
#
# TODO: Come up with a way to signal failure to write to disk. This is hard
# since the failure may impact previous calls that were buffered.
== data
# The buffered file for standard output.
Stdout: # buffered-file
# file descriptor or (addr stream byte)
1/imm32 # standard output
$Stdout->buffer:
# inlined fields for a stream
# current write index
0/imm32
# current read index
0/imm32
# size
8/imm32
# data
00 00 00 00 00 00 00 00 # 8 bytes
# TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
# I don't want to type in 1024 bytes here.
# We need to do this in machine code because streams need to be opaque types,
# and we don't yet support opaque types in Mu.
== 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
# Write lower byte of 'n' to 'f'.
write-byte-buffered: # f: (addr buffered-file), n: int
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
57/push-edi
# edi = f
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 7/r32/edi 8/disp8 . # copy *(ebp+8) to edi
# ecx = f->write
8b/copy 1/mod/*+disp8 7/rm32/edi . . . 1/r32/ecx 4/disp8 . # copy *(edi+4) to ecx
# if (f->write >= f->size) flush and clear f's stream
3b/compare 1/mod/*+disp8 7/rm32/edi . . . 1/r32/ecx 0xc/disp8 . # compare ecx with *(edi+12)
7c/jump-if-< $write-byte-buffered:to-stream/disp8
# . flush(f)
# . . push args
57/push-edi
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream(stream = f+4)
# . . push args
8d/copy-address 1/mod/*+disp8 7/rm32/edi . . . 0/r32/eax 4/disp8 . # copy edi+4 to eax
50/push-eax
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . f->write must now be 0; update its cache at ecx
31/xor 3/mod/direct 1/rm32/ecx . . . 1/r32/ecx . . # clear ecx
$write-byte-buffered:to-stream:
# write to stream
# f->data[f->write] = LSB(n)
31/xor 3/mod/direct 0/rm32/eax . . . 0/r32/eax . . # clear eax
8a/copy-byte 1/mod/*+disp8 5/rm32/ebp . . . 0/r32/AL 0xc/disp8 . # copy byte at *(ebp+12) to AL
88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/edi 1/index/ecx . 0/r32/AL 0x10/disp8 . # copy AL to *(edi+ecx+16)
# ++f->write
ff 0/subop/increment 1/mod/*+disp8 7/rm32/edi . . . . 4/disp8 . # increment *(edi+4)
$write-byte-buffered:end:
# . restore registers
5f/pop-to-edi
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
flush: # f: (addr buffered-file)
# . 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
# eax = f
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 0/r32/eax 8/disp8 . # copy *(ebp+8) to eax
# write-stream(f->fd, data = f+4)
# . . push args
8d/copy-address 1/mod/*+disp8 0/rm32/eax . . . 1/r32/ecx 4/disp8 . # copy eax+4 to ecx
51/push-ecx
ff 6/subop/push 0/mod/indirect 0/rm32/eax . . . . . . # push *eax
# . . call
e8/call write-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
$flush:end:
# . restore registers
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
test-write-byte-buffered-single:
# - check that write-byte-buffered writes to first byte of 'file'
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream($_test-buffered-file->buffer)
# . . push args
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# write-byte-buffered(_test-buffered-file, 'A')
# . . push args
68/push 0x41/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call write-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# flush(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-stream-equal(_test-stream, "A", msg)
# . . push args
68/push "F - test-write-byte-buffered-single"/imm32
68/push "A"/imm32
68/push _test-stream/imm32
# . . call
e8/call check-stream-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . end
c3/return
test-write-byte-buffered-multiple-flushes:
# - check that write-byte-buffered correctly flushes buffered data
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream($_test-buffered-file->buffer)
# . . push args
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# fill up the buffer for _test-buffered-file
# . write($_test-buffered-file->buffer, "abcdef")
# . . push args
68/push "abcdef"/imm32
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# write-byte-buffered(_test-buffered-file, 'g')
# . . push args
68/push 0x67/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call write-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# flush(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-stream-equal(_test-stream, "abcdefg", msg)
# . . push args
68/push "F - test-write-byte-buffered-multiple-flushes"/imm32
68/push "abcdefg"/imm32
68/push _test-stream/imm32
# . . call
e8/call check-stream-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . end
c3/return
# - variant without buffering
# Write lower byte of 'n' to 'f'.
append-byte: # f: (addr stream byte), n: int
# . prologue
@ -239,17 +43,10 @@ $append-byte:end:
c3/return
$append-byte:abort:
# . _write(2/stderr, error)
# . . push args
68/push "append-byte: out of space\n"/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
e8/call syscall_exit/disp32
(draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "append-byte: out of space\n" 3 0) # 3=cyan
{
eb/jump loop/disp8
}
# never gets here
test-append-byte-single:
@ -282,196 +79,4 @@ test-append-byte-single:
# . end
c3/return
== data
_test-output-stream: # (stream byte)
# current write index
0/imm32
# current read index
0/imm32
# size
0x800/imm32 # 2048 bytes
# data (128 lines x 16 bytes/line)
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# a test buffered file for _test-output-stream
_test-output-buffered-file: # buffered-file
# file descriptor or (addr stream byte)
_test-output-stream/imm32
$_test-output-buffered-file->buffer:
# current write index
0/imm32
# current read index
0/imm32
# size
6/imm32
# data
00 00 00 00 00 00 # 6 bytes
_test-error-stream: # (stream byte)
# current write index
0/imm32
# current read index
0/imm32
# line
0x100/imm32 # 256 bytes
# data (16 lines x 16 bytes/line)
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# a test buffered file for _test-error-stream
_test-error-buffered-file: # buffered-file
# file descriptor or (addr stream byte)
_test-error-stream/imm32
$_test-error-buffered-file->buffer:
# current write index
0/imm32
# current read index
0/imm32
# size
6/imm32
# data
00 00 00 00 00 00 # 6 bytes
# . . vim:nowrap:textwidth=0

View File

@ -89,92 +89,6 @@ test-append-byte-hex:
# . end
c3/return
# print the hex representation for the lowest byte of a number
write-byte-hex-buffered: # f: (addr buffered-file), n: int
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
50/push-eax
# AL = convert upper nibble to hex
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 0/r32/eax 0xc/disp8 . # copy *(ebp+12) to eax
c1/shift 5/subop/logic-right 3/mod/direct 0/rm32/eax . . . . . 4/imm8 # shift eax right by 4 bits, while padding zeroes
25/and-eax 0xf/imm32
# . AL = to-hex-char(AL)
e8/call to-hex-char/disp32
# write-byte-buffered(f, AL)
# . . push args
50/push-eax
ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 . # push *(ebp+8)
# . . call
e8/call write-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# AL = convert lower nibble to hex
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 0/r32/eax 0xc/disp8 . # copy *(ebp+12) to eax
25/and-eax 0xf/imm32
# . AL = to-hex-char(AL)
e8/call to-hex-char/disp32
# write-byte-buffered(f, AL)
# . . push args
50/push-eax
ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 . # push *(ebp+8)
# . . call
e8/call write-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
$write-byte-hex-buffered:end:
# . restore registers
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
test-write-byte-hex-buffered:
# - check that write-byte-hex-buffered prints the hex textual representation
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream($_test-buffered-file->buffer)
# . . push args
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# write-byte-hex-buffered(_test-buffered-file, 0xa) # exercises digit, non-digit as well as leading zero
# . . push args
68/push 0xa/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call write-byte-hex-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# flush(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-stream-equal(_test-stream, "0a", msg)
# . . push args
68/push "F - test-write-byte-hex-buffered"/imm32
68/push "0a"/imm32
68/push _test-stream/imm32
# . . call
e8/call check-stream-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . end
c3/return
write-int32-hex: # f: (addr stream byte), n: int
# . prologue
55/push-ebp
@ -285,146 +199,4 @@ test-write-int32-hex:
# . end
c3/return
write-int32-hex-buffered: # f: (addr buffered-file), n: int
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
$write-int32-hex-buffered:hex-prefix:
# write-buffered(f, "0x")
# . . push args
68/push "0x"/imm32
ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 . # push *(ebp+8)
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
$write-int32-hex-buffered:rest:
# write-int32-hex-bits-buffered(f, n, 32)
# . . push args
68/push 0x20/imm32
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 write-int32-hex-bits-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
$write-int32-hex-buffered:end:
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
# print rightmost 'bits' of 'n'
# bits must be multiple of 4
write-int32-hex-bits-buffered: # f: (addr buffered-file), n: int, bits: int
# pseudocode:
# bits -= 4
# while true
# if (bits < 0) break
# eax = n >> bits
# eax = eax & 0xf
# write-byte-buffered(f, AL)
# bits -= 4
#
# . 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
# ecx = bits-4
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 0x10/disp8 . # copy *(ebp+16) to ecx
81 5/subop/subtract 3/mod/direct 1/rm32/ecx . . . . . 4/imm32 # subtract from ecx
$write-int32-hex-bits-buffered:loop:
# if (bits < 0) break
81 7/subop/compare 3/mod/direct 1/rm32/ecx . . . . . 0/imm32 # compare ecx
7c/jump-if-< $write-int32-hex-bits-buffered:end/disp8
# eax = n >> bits
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 0/r32/eax 0xc/disp8 . # copy *(ebp+12) to eax
d3/>>ecx 5/subop/pad-zeroes 3/mod/direct 0/rm32/eax . . . . . . # shift eax right by ecx bits, padding zeroes
# eax = to-hex-char(AL)
25/and-eax 0xf/imm32
e8/call to-hex-char/disp32
# write-byte-buffered(f, AL)
# . . push args
50/push-eax
ff 6/subop/push 1/mod/*+disp8 5/rm32/ebp . . . . 8/disp8 . # push *(ebp+8)
# . . call
e8/call write-byte-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# bits -= 4
81 5/subop/subtract 3/mod/direct 1/rm32/ecx . . . . . 4/imm32 # subtract from ecx
eb/jump $write-int32-hex-bits-buffered:loop/disp8
$write-int32-hex-bits-buffered:end:
# . restore registers
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
test-write-int32-hex-buffered:
# - check that write-int32-hex-buffered prints the hex textual representation
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . clear-stream($_test-buffered-file->buffer)
# . . push args
68/push $_test-buffered-file->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# write-int32-hex-buffered(_test-buffered-file, 0x8899aa)
# . . push args
68/push 0x8899aa/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call write-int32-hex-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# flush(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
#? # dump line {{{
#? # . write-stream(2/stderr, line)
#? # . . push args
#? 68/push _test-stream/imm32
#? 68/push 2/imm32/stderr
#? # . . call
#? e8/call write-stream/disp32
#? # . . discard args
#? 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
#? # . write(2/stderr, "$\n")
#? # . . push args
#? 68/push "$\n"/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
#? # }}}
# check-stream-equal(_test-stream, "0x008899aa", msg)
# . . push args
68/push "F - test-write-int32-hex-buffered"/imm32
68/push "0x008899aa"/imm32
68/push _test-stream/imm32
# . . call
e8/call check-stream-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . end
c3/return
# . . vim:nowrap:textwidth=0

View File

@ -888,51 +888,10 @@ $from-hex-char:letter:
c3/return
$from-hex-char:abort:
# . _write(2/stderr, error)
# . . push args
68/push "invalid hex char: "/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
# . clear-stream($Stderr->buffer)
# . . save eax
50/push-eax
# . . push args
68/push $Stderr->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . . restore eax
58/pop-to-eax
# . write-int32-hex-buffered(Stderr, eax)
# . . push args
50/push-eax
68/push Stderr/imm32
# . . call
e8/call write-int32-hex-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . _write(2/stderr, "\n")
# . . push args
68/push Newline/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
e8/call syscall_exit/disp32
(draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "invalid hex char" 3 0) # 3=cyan
{
eb/jump loop/disp8
}
# never gets here
# . . vim:nowrap:textwidth=0

View File

@ -26,13 +26,9 @@ Handle-size: # (addr int)
# A default allocation descriptor for programs to use.
Heap: # allocation-descriptor
# curr
0/imm32
0x01000000/imm32 # 16 MB
# limit
0/imm32
# a reasonable default
Heap-size: # int
0x800000/imm32/8MB
0x02000000/imm32 # 32 MB
Next-alloc-id: # int
0x100/imm32 # save a few alloc ids for fake handles
@ -42,25 +38,6 @@ Next-alloc-id: # int
# . 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
# Let's start initializing the default allocation descriptor.
Entry:
# initialize heap
# . Heap = new-segment(Heap-size)
# . . push args
68/push Heap/imm32
ff 6/subop/push 0/mod/indirect 5/rm32/.disp32 . . . Heap-size/disp32 # push *Heap-size
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
e8/call run-tests/disp32 # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
$array-equal-main:end:
# 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
e8/call syscall_exit/disp32
# Allocate and clear 'n' bytes of memory from an allocation-descriptor 'ad'.
# Abort if there isn't enough memory in 'ad'.
allocate: # ad: (addr allocation-descriptor), n: int, out: (addr handle _)
@ -153,35 +130,26 @@ $allocate-raw:end:
c3/return
$allocate-raw:abort:
# . _write(2/stderr, error)
# . . push args
68/push "allocate: failed\n"/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
e8/call syscall_exit/disp32
(draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "allocate: failed" 3 0) # 3=cyan
{
eb/jump loop/disp8
}
# never gets here
test-allocate-raw-success:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var ad/ecx: allocation-descriptor
68/push 0/imm32/limit
68/push 0/imm32/curr
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# ad = new-segment(512)
# . . push args
# var ad/ecx: allocation-descriptor containing 16 bytes
# . var end/ecx: (addr byte)
89/<- %ecx 4/r32/esp
# . var start/edx: (addr byte) = end - 16
81 5/subop/subtract %esp 0x10/imm32
89/<- %edx 4/r32/esp
# . ad = {start, end}
51/push-ecx
68/push 0x200/imm32
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
52/push-edx
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# var expected-payload/ebx = ad->curr
8b/copy 0/mod/indirect 1/rm32/ecx . . . 3/r32/ebx . . # copy *ecx to ebx
# var h/edx: handle = {0, 0}
@ -249,7 +217,7 @@ test-allocate-raw-success:
# clean up
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 0x100/imm32 # copy to *Next-alloc-id
# . reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x10/imm32 # add to esp
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x20/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
@ -284,34 +252,26 @@ $lookup:end:
c3/return
$lookup:abort:
# . _write(2/stderr, msg)
# . . push args
68/push "lookup failed\n"/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/exit-status
e8/call syscall_exit/disp32
(draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "lookup: failed" 3 0) # 3=cyan
{
eb/jump loop/disp8
}
# never gets here
test-lookup-success:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var ad/ebx: allocation-descriptor
68/push 0/imm32/limit
68/push 0/imm32/curr
# var ad/ebx: allocation-descriptor containing 16 bytes
# . var end/ecx: (addr byte)
89/<- %ecx 4/r32/esp
# . var start/edx: (addr byte) = end - 16
81 5/subop/subtract %esp 0x10/imm32
89/<- %edx 4/r32/esp
# . ad = {start, end}
51/push-ecx
52/push-edx
89/copy 3/mod/direct 3/rm32/ebx . . . 4/r32/esp . . # copy esp to ebx
# ad = new-segment(512)
# . . push args
53/push-ebx
68/push 0x200/imm32
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# var handle/ecx: handle
68/push 0/imm32/address
68/push 0/imm32/alloc-id
@ -349,7 +309,7 @@ test-lookup-success:
# clean up
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 0x100/imm32 # copy to *Next-alloc-id
# . reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x10/imm32 # add to esp
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x20/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
@ -391,26 +351,24 @@ _pending-test-lookup-failure:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var heap/esi: allocation-descriptor
68/push 0/imm32/limit
68/push 0/imm32/curr
89/copy 3/mod/direct 6/rm32/esi . . . 4/r32/esp . . # copy esp to esi
# heap = new-segment(512)
# . . push args
56/push-esi
68/push 0x200/imm32
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# var ad/ecx: allocation-descriptor containing 16 bytes
# . var end/ecx: (addr byte)
89/<- %ecx 4/r32/esp
# . var start/edx: (addr byte) = end - 16
81 5/subop/subtract %esp 0x10/imm32
89/<- %edx 4/r32/esp
# . ad = {start, end}
51/push-ecx
52/push-edx
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# var h1/ecx: handle
68/push 0/imm32/address
68/push 0/imm32/alloc-id
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# var old_top/ebx = heap->curr
# var old_top/ebx = ad->curr
8b/copy 0/mod/indirect 6/rm32/esi . . . 3/r32/ebx . . # copy *esi to ebx
# first allocation, to h1
# . allocate(heap, 2, h1)
# . allocate(ad, 2, h1)
# . . push args
51/push-ecx
68/push 2/imm32/size
@ -419,14 +377,14 @@ _pending-test-lookup-failure:
e8/call allocate/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# reset heap->curr to mimic reclamation
# reset ad->curr to mimic reclamation
89/copy 0/mod/indirect 6/rm32/esi . . . 3/r32/ebx . . # copy ebx to *esi
# second allocation that returns the same address as the first
# var h2/edx: handle
68/push 0/imm32/address
68/push 0/imm32/alloc-id
89/copy 3/mod/direct 2/rm32/edx . . . 4/r32/esp . . # copy esp to edx
# . allocate(heap, 2, h2)
# . allocate(ad, 2, h2)
# . . push args
52/push-edx
68/push 2/imm32/size
@ -456,7 +414,7 @@ _pending-test-lookup-failure:
# clean up
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 0x100/imm32 # copy to *Next-alloc-id
# . reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x20/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
@ -566,17 +524,10 @@ allocate-region: # ad: (addr allocation-descriptor), n: int, out: (addr handle
# 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\n"/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
e8/call syscall_exit/disp32
(draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "allocate-region: failed to allocate" 3 0) # 3=cyan
{
eb/jump loop/disp8
}
# never gets here
# Claim the next 'n+4' bytes of memory and initialize the first 4 to n.
@ -623,18 +574,16 @@ test-allocate-array:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var ad/ecx: allocation-descriptor
68/push 0/imm32/limit
68/push 0/imm32/curr
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# ad = new-segment(512)
# . . push args
# var ad/ecx: allocation-descriptor containing 16 bytes
# . var end/ecx: (addr byte)
89/<- %ecx 4/r32/esp
# . var start/edx: (addr byte) = end - 16
81 5/subop/subtract %esp 0x10/imm32
89/<- %edx 4/r32/esp
# . ad = {start, end}
51/push-ecx
68/push 0x200/imm32
# . . call
e8/call new-segment/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
52/push-edx
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# var expected-payload/ebx = ad->curr
8b/copy 0/mod/indirect 1/rm32/ecx . . . 3/r32/ebx . . # copy *ecx to ebx
# var h/edx: handle = {0, 0}
@ -711,7 +660,7 @@ test-allocate-array:
# clean up
c7 0/subop/copy 0/mod/indirect 5/rm32/.disp32 . . . Next-alloc-id/disp32 1/imm32 # copy to *Next-alloc-id
# . reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x10/imm32 # add to esp
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0x20/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
@ -783,18 +732,16 @@ test-copy-array:
68/push 3/imm32
68/push 0xc/imm32/size
89/copy 3/mod/direct 6/rm32/esi . . . 4/r32/esp . . # copy esp to esi
# var ad/ecx: allocation-descriptor
68/push 0/imm32/limit
68/push 0/imm32/curr
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# ad = new-segment(512)
# . . push args
# var ad/ecx: allocation-descriptor containing 32 bytes