4981 - no, go back to 3 phases

Considering how much trouble a merge phase would be (commit 4978), it seems
simpler to just add the extra syntax for controlling the entry point of
the generated ELF binary.

But I wouldn't have noticed this if I hadn't taken the time to write out
the commit messages of 4976 and 4978.

Even if we happened to already have linked list primitives built, this
may still be a good idea considering that I'm saving quite a lot of code
in duplicated entrypoints.
This commit is contained in:
Kartik Agaram 2019-02-18 21:48:19 -08:00
parent 6607a30415
commit 8188bbbc94
45 changed files with 129 additions and 233 deletions

View File

@ -15,17 +15,13 @@ put_new(Help, "syntax",
"Each segment starts with a header line: a '==' delimiter followed by the name of\n"
"the segment.\n"
"\n"
"Currently SubX assumes the first segment encountered contains executable code, and\n"
"the second contains global variables. By convention we call them 'code' and 'data'\n"
"respectively.\n"
"The first instruction executed in the resulting binary is always the first\n"
"instruction of the first segment.\n"
"The first segment contains code and should be called 'code'.\n"
"The second segment should be called 'data'.\n"
"The resulting binary starts running from the start of the code segment by default.\n"
"To start elsewhere in the code segment, define a special label called 'Entry'.\n"
"\n"
"Segments with the same name get merged together. This rule helps keep functions and\n"
"their data close together in .subx files.\n"
"Later segments with the same name get their contents *prepended* to earlier ones.\n"
"This rule helps each .subx file to put forth a different entrypoint for the binary,\n"
"overriding previously loaded files.\n"
"\n"
"Lines consist of a series of words. Words can contain arbitrary metadata\n"
"after a '/', but they can never contain whitespace. Metadata has no effect\n"
@ -184,7 +180,7 @@ void flush(program& p, vector<line>& lines) {
return;
}
// End flush(p, lines) Special-cases
trace(99, "parse") << "flushing to segment" << end();
trace(99, "parse") << "flushing segment" << end();
p.segments.back().lines.swap(lines);
}
@ -228,6 +224,7 @@ typedef void (*transform_fn)(program&);
:(before "End Globals")
vector<transform_fn> Transform;
:(code)
void transform(program& p) {
trace(99, "transform") << "begin" << end();
for (int t = 0; t < SIZE(Transform); ++t)
@ -267,6 +264,7 @@ void load(const program& p) {
if (i == 0) End_of_program = addr;
}
EIP = p.segments.at(0).start;
// End Initialize EIP
trace(99, "load") << "done" << end();
}

View File

@ -97,13 +97,14 @@ void write_elf_header(ostream& out, const program& p) {
// e_version
O(0x01); O(0x00); O(0x00); O(0x00);
// e_entry
int e_entry = p.segments.at(0).start; // convention
uint32_t e_entry = p.segments.at(0).start; // convention
// Override e_entry
emit(e_entry);
// e_phoff -- immediately after ELF header
int e_phoff = 0x34;
uint32_t e_phoff = 0x34;
emit(e_phoff);
// e_shoff; unused
int dummy32 = 0;
uint32_t dummy32 = 0;
emit(dummy32);
// e_flags; unused
emit(dummy32);

View File

@ -17,8 +17,6 @@
//: Update the parser to handle non-numeric segment name.
//:
//: We'll also support repeated segments with non-numeric names.
//: When we encounter a new reference to an existing segment we'll *prepend*
//: the new data to existing data for the segment.
:(before "End Globals")
map</*name*/string, int> Segment_index;
@ -46,7 +44,7 @@ if (!starts_with(segment_title, "0x")) {
out.segments.push_back(segment());
}
else {
trace(99, "parse") << "prepending to segment '" << segment_title << "'" << end();
trace(99, "parse") << "appending to segment '" << segment_title << "'" << end();
}
Currently_parsing_segment_index = get(Segment_index, segment_title);
}
@ -54,9 +52,9 @@ if (!starts_with(segment_title, "0x")) {
:(before "End flush(p, lines) Special-cases")
if (Currently_parsing_named_segment) {
assert(!p.segments.empty());
trace(99, "parse") << "flushing to segment" << end();
trace(99, "parse") << "flushing segment" << end();
vector<line>& curr_segment_data = p.segments.at(Currently_parsing_segment_index).lines;
curr_segment_data.insert(curr_segment_data.begin(), lines.begin(), lines.end());
curr_segment_data.insert(curr_segment_data.end(), lines.begin(), lines.end());
lines.clear();
Currently_parsing_named_segment = false;
Currently_parsing_segment_index = -1;
@ -69,17 +67,19 @@ if (Currently_parsing_named_segment) {
== code
2d/subtract-from-EAX 0xddccbbaa/imm32
+parse: new segment 'code'
+parse: prepending to segment 'code'
+load: 0x09000054 -> 2d
+load: 0x09000055 -> aa
+load: 0x09000056 -> bb
+load: 0x09000057 -> cc
+load: 0x09000058 -> dd
+load: 0x09000059 -> 05
+load: 0x0900005a -> 0a
+load: 0x0900005b -> 0b
+load: 0x0900005c -> 0c
+load: 0x0900005d -> 0d
+parse: appending to segment 'code'
# first segment
+load: 0x09000054 -> 05
+load: 0x09000055 -> 0a
+load: 0x09000056 -> 0b
+load: 0x09000057 -> 0c
+load: 0x09000058 -> 0d
# second segment
+load: 0x09000059 -> 2d
+load: 0x0900005a -> aa
+load: 0x0900005b -> bb
+load: 0x0900005c -> cc
+load: 0x0900005d -> dd
:(scenario error_on_missing_segment_header)
% Hide_errors = true;

View File

@ -19,6 +19,25 @@
//: Later layers may add more conventions partitioning the space of names. But
//: the above rules will remain inviolate.
//: One special label: the address to start running the program at.
:(scenario entry_label)
== 0x1
05 0x0d0c0b0a/imm32
Entry:
05 0x0d0c0b0a/imm32
+run: inst: 0x00000006
-run: inst: 0x00000001
:(before "End Globals")
uint32_t Entry_address = 0;
:(before "End Reset")
Entry_address = 0;
:(before "End Initialize EIP")
if (Entry_address) EIP = Entry_address;
:(after "Override e_entry")
if (Entry_address) e_entry = Entry_address;
:(before "End looks_like_hex_int(s) Detectors")
if (SIZE(s) == 2) return true;
@ -87,6 +106,8 @@ void rewrite_labels(program& p) {
drop_labels(code);
if (trace_contains_errors()) return;
replace_labels_with_displacements(code, byte_index);
if (contains_key(byte_index, "Entry"))
Entry_address = code.start + get(byte_index, "Entry");
}
void compute_byte_indices_for_labels(const segment& code, map<string, int32_t>& byte_index) {
@ -125,7 +146,7 @@ void compute_byte_indices_for_labels(const segment& code, map<string, int32_t>&
raise << "'" << to_string(inst) << "': labels can only be the first word in a line.\n" << end();
if (Map_file.is_open())
Map_file << "0x" << HEXWORD << (code.start + current_byte) << ' ' << label << '\n';
if (contains_key(byte_index, label)) {
if (contains_key(byte_index, label) && label != "Entry") {
raise << "duplicate label '" << label << "'\n" << end();
return;
}

View File

@ -5,8 +5,8 @@
# . 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:
# syscall(exit, 0) -- can't test _write just yet
Entry: # just exit; can't test _write just yet
# . syscall(exit, 0)
bb/copy-to-EBX 0/imm32
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8

View File

@ -5,7 +5,7 @@
# . 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: (manual test if this is the last file loaded)
Entry: # manual test
# check-ints-equal(34, 34)
# . . push args
68/push "error in check-ints-equal"/imm32

View File

@ -19,7 +19,7 @@
# . 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:
Entry: # run all tests
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

View File

@ -5,7 +5,7 @@
# . 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: (manual test if this is the last file loaded)
Entry: # manual test
# EAX = new-segment(0x1000)
# . . push args
68/push 0x1000/imm32

View File

@ -5,8 +5,7 @@
# . 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:
# run-tests()
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)

View File

@ -14,13 +14,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
# 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
clear-stream: # f : (address stream) -> <void>
# . prolog
55/push-EBP

View File

@ -41,14 +41,6 @@ _test-trace-stream:
# . 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:
# run-tests()
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
# Allocate a new segment for the trace stream, initialize its length, and save its address to Trace-stream.
# The Trace-stream segment will consist of variable-length lines separated by newlines (0x0a)
initialize-trace-stream:

View File

@ -20,13 +20,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
# 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
# TODO: come up with a way to signal when a write to disk fails
write: # f : fd or (address stream), s : (address array byte) -> <void>
# . prolog

View File

@ -5,13 +5,12 @@
# . 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:
#? Entry: # run a single test, while debugging
#? e8/call test-next-stream-line-equal-stops-at-newline/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
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
#? # 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
# compare all the data in a stream (ignoring the read pointer)
stream-data-equal?: # f : (address stream), s : (address string) -> EAX : boolean

View File

@ -37,13 +37,12 @@
# . 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-'.
#? Entry: # run a single test, while debugging
#? e8/call test-stop-skips-returns-on-exit/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
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
#? # 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
# Configure an exit-descriptor for a call pushing 'nbytes' bytes of args to
# the stack.

View File

@ -45,13 +45,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
# 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
read: # f : fd or (address stream), s : (address stream) -> num-bytes-read/EAX
# . prolog
55/push-EBP

View File

@ -31,14 +31,13 @@ Stdin:
# . 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-'.
#? Entry: # run a single test, while debugging
#? e8/call test-read-byte-multiple/disp32
#? e8/call test-read-byte-refills-buffer/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
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
#? # 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
# return next byte value in EAX, with top 3 bytes cleared.
# On EOF, return 0xffffffff.

View File

@ -5,19 +5,15 @@
# . 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:
# manual test
#? Entry: # manual test
#? # write-stream(stdout, _test-stream2)
#? 68/push _test-stream2/imm32
#? 68/push 1/imm32/stdout
#? e8/call write-stream/disp32
# automatic test
#? e8/call test-write-stream-single/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
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
#? # 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
write-stream: # f : fd or (address stream), s : (address stream) -> <void>
# . prolog

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
# 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
# write(out, "Error: "+msg+"\n") then stop(ed, 1)
error: # ed : (address exit-descriptor), out : fd or (address stream), msg : (address array byte) -> <void>
# . prolog

View File

@ -27,13 +27,6 @@ Stdout:
# . 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
# Write lower byte of 'n' to 'f'.
write-byte: # f : (address buffered-file), n : int -> <void>
# . prolog

View File

@ -6,13 +6,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
# 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
is-hex-int?: # in : (address slice) -> EAX : boolean
# . prolog
55/push-EBP

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
# 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
print-byte: # f : (address buffered-file), n : int -> <void>
# . prolog
55/push-EBP

View File

@ -5,14 +5,13 @@
# . 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:
#? Entry: # run a single test, while debugging
#? e8/call test-write-buffered/disp32
#? e8/call test-write-buffered-with-intermediate-flush/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
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
#? # 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
write-buffered: # f : (address buffered-file), msg : (address array byte) -> <void>
# pseudocode:

View File

@ -5,8 +5,7 @@
# . 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:
# manual test
#? Entry: # manual test
#? # . var ed/EAX : exit-descriptor
#? 81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
#? 89/copy 3/mod/direct 0/rm32/EAX . . . 4/r32/ESP . . # copy ESP to EAX
@ -19,12 +18,10 @@
#? 68/push Stderr/imm32
#? 50/push-EAX
#? e8/call error-byte/disp32
# automatic test
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
#? # . 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
# write(out, "Error: "+msg+": "+byte) then stop(ed, 1)
error-byte: # ed : (address exit-descriptor), out : (address buffered-file), msg : (address array byte), n : byte -> <void>

View File

@ -29,13 +29,6 @@ Heap:
# . 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

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
# 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
new-stream: # ad : (address allocation-descriptor), length : int, elemsize : int -> address/EAX
# . prolog
55/push-EBP

View File

@ -3,13 +3,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
# 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
# read bytes from 'f' until (and including) a newline and store them into 's'
# return true if no data found, false otherwise
# just abort if 's' is too small

View File

@ -6,14 +6,13 @@
# . 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:
#? Entry: # run a single test, while debugging
#? e8/call test-slice-starts-with-fails/disp32
#? e8/call test-slice-starts-with-single-character/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
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
#? # 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
slice-empty?: # s : (address slice) -> EAX : boolean
# . prolog

View File

@ -3,13 +3,12 @@
# . 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:
#? Entry: # run a single test, while debugging
#? e8/call test-next-token-from-slice/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
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
#? # 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
# extract the next run of characters that are different from a given 'delimiter' (skipping multiple delimiters if necessary)
# on eof return an empty interval

View File

@ -278,7 +278,7 @@ distinguish between code and data. Correspondingly, SubX programs consist of a
series of segments, each starting with a header line: `==` followed by a name.
The first segment must be named `code`; the second must be named `data`.
Execution always begins at the start of the `code` segment.
Execution begins at the start of the `code` segment by default.
You can reuse segment names:
@ -293,17 +293,9 @@ You can reuse segment names:
...C...
```
The code segment now contains the instructions of `A` as well as `C`. `C`
comes _before_ `A`. (Why this order? I'd like to organize SubX programs into
sequences of [_layers_](http://akkartik.name/post/wart-layers) that permit
incrementally building a subset of layers into a working program with a subset
of functionality. This organization requires, among other things, letting each
layer control the code that runs when the binary starts up. Letting each layer
override the starting address would require additional syntax. Instead, I
choose to always begin execution at the start of the code segment, but allow
layers to prepend to segments.)
The `code` segment now contains the instructions of `A` as well as `C`.
Within the code segment, each line contains a comment, label or instruction.
Within the `code` segment, each line contains a comment, label or instruction.
Comments start with a `#` and are ignored. Labels should always be the first
word on a line, and they end with a `:`.
@ -346,11 +338,18 @@ The latter is mostly useful for `jump` and `call` instructions.
Functions are defined using labels. By convention, labels internal to functions
(that must only be jumped to) start with a `$`. Any other labels must only be
called, never jumped to.
called, never jumped to. All labels must be unique.
A special label is `Entry`, which can be used to specify/override the entry
point of the program. It doesn't have to be unique, and the latest definition
will override earlier ones.
(The `Entry` label, along with duplicate segment headers, allows programs to
be built up incrementally out of multiple _layers_](http://akkartik.name/post/wart-layers).)
The data segment consists of labels as before and byte values. Referring to
data labels in either code segment instructions or data segment values (using
the `imm32` metadata either way) yields their address.
data labels in either `code` segment instructions or `data` segment values
(using the `imm32` metadata either way) yields their address.
Automatic tests are an important part of SubX, and there's a simple mechanism
to provide a test harness: all functions that start with `test-` are called in

Binary file not shown.

View File

@ -30,11 +30,12 @@
# . 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 tests if necessary, call 'compile' if not
#? # for debugging: run a single test; don't bother setting status code
#? e8/call test-get-num-reads-single-digit/disp32
#? eb/jump $main:end/disp8
# main: run tests if necessary, call 'compile' if not
# . prolog
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# - if argc > 1 and argv[1] == "test", then return run_tests()

Binary file not shown.

View File

@ -30,11 +30,12 @@
# . 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 tests if necessary, call 'compile' if not
#? # for debugging: run a single test; don't bother setting status code
#? e8/call test-get-num-reads-single-digit/disp32
#? eb/jump $main:end/disp8
# main: run tests if necessary, call 'compile' if not
# . prolog
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# - if argc > 1 and argv[1] == "test", then return run_tests()

Binary file not shown.

View File

@ -18,7 +18,12 @@
# . 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:
Entry: # run tests if necessary, compute `factorial(5)` if not
#? # for debugging: run a single test; don't bother setting status code
#? e8/call test-get-num-reads-single-digit/disp32
#? eb/jump $main:end/disp8
# . prolog
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# - if argc > 1 and argv[1] == "test", then return run_tests()

Binary file not shown.

View File

@ -25,12 +25,7 @@
# . 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
# no Entry; the standard library runs all tests by default
new: # ad : (address allocation-descriptor), n : int, out : (address handle)
# . prolog

Binary file not shown.

View File

@ -17,12 +17,13 @@
# . 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
# for debugging: run a single test
Entry: # run tests if necessary, convert stdin if not
#? # for debugging: run a single test
#? e8/call test-skip-until-newline/disp32
#? 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX
#? eb/jump $main:end/disp8
# main: run tests if necessary, convert stdin if not
# . prolog
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# - if argc > 1 and argv[1] == "test", then return run_tests()

View File

@ -1,36 +0,0 @@
# Read a text file of SubX segment 'fragments' with duplicate names, and emit
# a list of 'merged' segments.
#
# Example input:
# == A
# a
# b
# c
# == B
# d
# e
# == A
# f
# g
# == A
# h
#
# Output:
# == A
# h
# f
# g
# a
# b
# c
# == B
# d
# e
#
# The output gives each segment the contents of all its fragments, with later
# fragments *prepended* to earlier ones.
#
# Prepending necessitates buffering output until the end. We'll convert
# fragments to distinct streams, maintain each segment as a linked list of
# fragments that's easy to prepend to, and finally emit the linked lists in
# order.

Binary file not shown.

View File

@ -20,12 +20,13 @@
# . 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
# for debugging: run a single test
Entry: # run tests if necessary, convert stdin if not
#? # for debugging: run a single test
#? e8/call test-convert-instruction-passes-labels-through/disp32
#? 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX
#? eb/jump $main:end/disp8
# main: run tests if necessary, convert stdin if not
# . prolog
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# - if argc > 1 and argv[1] == "test", then return run_tests()

View File

@ -12,7 +12,7 @@
# . 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: return argv-equal(argv[1], argv[2])
Entry: # return argv-equal(argv[1], argv[2])
# At the start of a SubX program:
# argc: *ESP
# argv[0]: *(ESP+4)

View File

@ -19,7 +19,7 @@
# . 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:
Entry: # run all tests
e8/call run-tests/disp32 # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
# syscall(exit, EAX)
89/copy 3/mod/direct 3/rm32/EBX . . . 0/r32/EAX . . # copy EAX to EBX

View File

@ -9,7 +9,8 @@
# . 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:
Entry:
# allocate x on the stack
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # subtract from ESP