about summary refs log tree commit diff stats
path: root/subx
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2019-05-10 17:21:17 -0700
committerKartik Agaram <vc@akkartik.com>2019-05-10 17:21:17 -0700
commitc12e85e1038f1118da0e22a810801b402a67a444 (patch)
treeb41ad0e2b9c6d90bed31a09e8ca1fe56438eeef8 /subx
parent34a005d0fbe666a22d5fa164f9b7e466400c7d9d (diff)
parent1ebb7614921a2b426ed84c4c51b100176e2a4187 (diff)
downloadmu-c12e85e1038f1118da0e22a810801b402a67a444.tar.gz
Merge branch 'master' into dquotes-1
Segfault in this branch is now fixed.
Diffstat (limited to 'subx')
-rw-r--r--subx/012elf.cc28
-rw-r--r--subx/013direct_addressing.cc2
-rw-r--r--subx/020syscalls.cc21
-rw-r--r--subx/037heap.cc29
-rw-r--r--subx/038---literal_strings.cc7
-rw-r--r--subx/039debug.cc6
-rw-r--r--subx/053new-segment.subx47
-rw-r--r--subx/069allocate.subx8
-rw-r--r--subx/070new-stream.subx15
-rw-r--r--subx/072slice.subx16
-rw-r--r--subx/074print-int-decimal.subx122
-rwxr-xr-xsubx/apps/assortbin21771 -> 21961 bytes
-rw-r--r--subx/apps/assort.subx24
-rwxr-xr-xsubx/apps/crenshaw2-1bin18959 -> 19120 bytes
-rwxr-xr-xsubx/apps/crenshaw2-1bbin19518 -> 19679 bytes
-rw-r--r--subx/apps/dquotes.subx24
-rwxr-xr-xsubx/apps/factorialbin17875 -> 18036 bytes
-rwxr-xr-xsubx/apps/handlebin18649 -> 18863 bytes
-rw-r--r--subx/apps/handle.subx74
-rwxr-xr-xsubx/apps/hexbin21968 -> 22129 bytes
-rwxr-xr-xsubx/apps/packbin36560 -> 36721 bytes
21 files changed, 277 insertions, 146 deletions
diff --git a/subx/012elf.cc b/subx/012elf.cc
index 0f058504..d0a3fbd2 100644
--- a/subx/012elf.cc
+++ b/subx/012elf.cc
@@ -66,6 +66,7 @@ void load_elf_contents(uint8_t* elf_contents, size_t size, int argc, char* argv[
   assert(overlap.find(STACK_SEGMENT) == overlap.end());
   Mem.push_back(vma(STACK_SEGMENT));
   assert(overlap.find(AFTER_STACK) == overlap.end());
+  // The stack grows downward.
   Reg[ESP].u = AFTER_STACK;
   Reg[EBP].u = 0;
   EIP = e_entry;
@@ -129,15 +130,24 @@ void load_segment_from_program_header(uint8_t* elf_contents, int segment_index,
 }
 
 :(before "End Includes")
-// Very primitive/fixed/insecure ELF segments for now: just consecutive VMAs.
-//   code: 0x09000000 -> 0x09ffffff
-//   data/heap: 0x0a000000 -> 0x0affffff
-//   stack: 0x0b000ffc -> 0x0b000000 (downward)
-const int CODE_SEGMENT = 0x09000000;
-const int DATA_SEGMENT = 0x0a000000;  // keep sync'd with `Heap.limit` in allocate.subx
-const int STACK_SEGMENT = 0x0b000000;
-const int AFTER_STACK = 0x0c000000;
-const int ARGV_DATA_SEGMENT = 0x0c000000;
+// Very primitive/fixed/insecure ELF segments for now.
+//   code:  0x09000000 -> 0x09ffffff (specified in ELF binary)
+//   data:  0x0a000000 -> 0x0affffff (specified in ELF binary)
+//   --- heap gets mmap'd somewhere here ---
+//   stack: 0x7dffffff -> 0x7d000000 (downward; not in ELF binary)
+//   argv hack: 0x7f000000 -> 0x7fffffff (not in ELF binary)
+//
+// For now we avoid addresses with the most significant bit set; SubX doesn't
+// support unsigned comparison yet (https://github.com/akkartik/mu/issues/30)
+// Once we do, we can go up to 0xc0000000; higher addresses are reserved for
+// the Linux kernel.
+const int CODE_SEGMENT      = 0x09000000;
+const int DATA_SEGMENT =      0x0a000000;
+const int STACK_SEGMENT     = 0x7d000000;
+const int AFTER_STACK       = 0x7e000000;
+const int ARGV_DATA_SEGMENT = 0x7f000000;
+// When updating the above memory map, don't forget to update `mmap`'s
+// implementation in the 'syscalls' layer.
 :(before "End Dump Info for Instruction")
 //? dump_stack();  // slow
 :(code)
diff --git a/subx/013direct_addressing.cc b/subx/013direct_addressing.cc
index 779c7e0a..c2dfa911 100644
--- a/subx/013direct_addressing.cc
+++ b/subx/013direct_addressing.cc
@@ -488,7 +488,7 @@ void test_shift_right_logical_r32_with_cl() {
 }
 
 :(before "End Op d3 Subops")
-case 5: {  // shift right r/m32 by CL, preserving sign
+case 5: {  // shift right r/m32 by CL, padding zeroes
   trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while padding zeroes" << end();
   uint8_t count = Reg[ECX].u & 0x1f;
   // OF is only defined if count is 1
diff --git a/subx/020syscalls.cc b/subx/020syscalls.cc
index a17e9525..6b9faa2c 100644
--- a/subx/020syscalls.cc
+++ b/subx/020syscalls.cc
@@ -24,14 +24,14 @@ void process_int80() {
     trace(Callstack_depth+1, "run") << "read: " << Reg[EBX].u << ' ' << Reg[ECX].u << ' ' << Reg[EDX].u << end();
     Reg[EAX].i = read(/*file descriptor*/Reg[EBX].u, /*memory buffer*/mem_addr_u8(Reg[ECX].u), /*size*/Reg[EDX].u);
     trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
+    if (Reg[EAX].i == -1) raise << "read: " << strerror(errno) << '\n' << end();
     break;
   case 4:
     trace(Callstack_depth+1, "run") << "write: " << Reg[EBX].u << ' ' << Reg[ECX].u << ' ' << Reg[EDX].u << end();
     trace(Callstack_depth+1, "run") << Reg[ECX].u << " => " << mem_addr_string(Reg[ECX].u, Reg[EDX].u) << end();
     Reg[EAX].i = write(/*file descriptor*/Reg[EBX].u, /*memory buffer*/mem_addr_u8(Reg[ECX].u), /*size*/Reg[EDX].u);
     trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
+    if (Reg[EAX].i == -1) raise << "write: " << strerror(errno) << '\n' << end();
     break;
   case 5: {
     check_flags(ECX);
@@ -40,14 +40,14 @@ void process_int80() {
     trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
     Reg[EAX].i = open(/*filename*/mem_addr_kernel_string(Reg[EBX].u), /*flags*/Reg[ECX].u, /*mode*/0640);
     trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
+    if (Reg[EAX].i == -1) raise << "open: " << strerror(errno) << '\n' << end();
     break;
   }
   case 6:
     trace(Callstack_depth+1, "run") << "close: " << Reg[EBX].u << end();
     Reg[EAX].i = close(/*file descriptor*/Reg[EBX].u);
     trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
+    if (Reg[EAX].i == -1) raise << "close: " << strerror(errno) << '\n' << end();
     break;
   case 8:
     check_mode(ECX);
@@ -55,14 +55,14 @@ void process_int80() {
     trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
     Reg[EAX].i = creat(/*filename*/mem_addr_kernel_string(Reg[EBX].u), /*mode*/0640);
     trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
+    if (Reg[EAX].i == -1) raise << "creat: " << strerror(errno) << '\n' << end();
     break;
   case 10:
     trace(Callstack_depth+1, "run") << "unlink: " << Reg[EBX].u << end();
     trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
     Reg[EAX].i = unlink(/*filename*/mem_addr_kernel_string(Reg[EBX].u));
     trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
+    if (Reg[EAX].i == -1) raise << "unlink: " << strerror(errno) << '\n' << end();
     break;
   case 38:
     trace(Callstack_depth+1, "run") << "rename: " << Reg[EBX].u << " -> " << Reg[ECX].u << end();
@@ -70,7 +70,7 @@ void process_int80() {
     trace(Callstack_depth+1, "run") << Reg[ECX].u << " => " << mem_addr_kernel_string(Reg[ECX].u) << end();
     Reg[EAX].i = rename(/*old filename*/mem_addr_kernel_string(Reg[EBX].u), /*new filename*/mem_addr_kernel_string(Reg[ECX].u));
     trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
-    if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
+    if (Reg[EAX].i == -1) raise << "rename: " << strerror(errno) << '\n' << end();
     break;
   case 45:  // brk: modify size of data segment
     trace(Callstack_depth+1, "run") << "grow data segment to " << Reg[EBX].u << end();
@@ -110,7 +110,12 @@ void check_mode(int reg) {
 }
 
 :(before "End Globals")
-uint32_t Next_segment = 0xb0000000;  // 0xc0000000 and up is reserved for Linux kernel
+// Very primitive/fixed/insecure mmap segments for now.
+// For now we avoid addresses with the most significant bit set; SubX doesn't
+// support unsigned comparison yet (https://github.com/akkartik/mu/issues/30)
+// Once we do, we can go up to 0xc0000000; higher addresses are reserved for
+// the Linux kernel.
+uint32_t Next_segment = 0x7c000000;
 const uint32_t SPACE_FOR_SEGMENT = 0x01000000;
 :(code)
 uint32_t new_segment(uint32_t length) {
diff --git a/subx/037heap.cc b/subx/037heap.cc
deleted file mode 100644
index 315fd0d5..00000000
--- a/subx/037heap.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-//: Support for dynamic allocation.
-//:
-//: Just provide a special label marking the first unused address in the data
-//: segment. Then we'll write SubX helpers to make use of it.
-
-:(before "Begin rewrite_global_variables")
-insert_heap_global_variable(p);
-:(code)
-void insert_heap_global_variable(program& p) {
-  if (SIZE(p.segments) < 2)
-    return;  // no data segment defined
-  // Start-of-heap:
-  p.segments.at(1).lines.push_back(label("Start-of-heap"));
-}
-
-line label(string s) {
-  line result;
-  result.words.push_back(word());
-  result.words.back().data = (s+":");
-  return result;
-}
-
-line imm32(const string& s) {
-  line result;
-  result.words.push_back(word());
-  result.words.back().data = s;
-  result.words.back().metadata.push_back("imm32");
-  return result;
-}
diff --git a/subx/038---literal_strings.cc b/subx/038---literal_strings.cc
index 65a7740b..9b2c3902 100644
--- a/subx/038---literal_strings.cc
+++ b/subx/038---literal_strings.cc
@@ -184,6 +184,13 @@ void skip_comment(istream& in) {
   }
 }
 
+line label(string s) {
+  line result;
+  result.words.push_back(word());
+  result.words.back().data = (s+":");
+  return result;
+}
+
 // helper for tests
 void parse_instruction_character_by_character(const string& line_data) {
   vector<line> out;
diff --git a/subx/039debug.cc b/subx/039debug.cc
index 26bb5f7a..8aa40558 100644
--- a/subx/039debug.cc
+++ b/subx/039debug.cc
@@ -46,14 +46,14 @@ else
 
 :(code)
 string debug_info(uint32_t inst_address) {
-  uint8_t op = read_mem_u8(EIP);
+  uint8_t op = read_mem_u8(inst_address);
   if (op != 0xe8) {
     ostringstream out;
     out << HEXBYTE << NUM(op);
     return out.str();
   }
-  int32_t offset = read_mem_i32(EIP+/*skip op*/1);
-  uint32_t next_eip = EIP+/*inst length*/5+offset;
+  int32_t offset = read_mem_i32(inst_address+/*skip op*/1);
+  uint32_t next_eip = inst_address+/*inst length*/5+offset;
   if (contains_key(Symbol_name, next_eip))
     return "e8/call "+get(Symbol_name, next_eip);
   ostringstream out;
diff --git a/subx/053new-segment.subx b/subx/053new-segment.subx
index 1669b097..83c890ea 100644
--- a/subx/053new-segment.subx
+++ b/subx/053new-segment.subx
@@ -1,4 +1,15 @@
-# Create a new segment (for data) using mmap().
+# Create a new segment (pool of memory for allocating chunks from) in the form
+# of an *allocation descriptor* that can be passed to the memory allocator
+# (defined in a later layer).
+#
+# Currently an allocation descriptor consists of just the bounds of the pool of
+# available memory:
+#
+#   curr : address
+#   end : address
+#
+# This isn't enough information to reclaim individual allocations. We can't
+# support arbitrary reclamation yet.
 
 == code
 #   instruction                     effective address                                                   register    displacement    immediate
@@ -6,39 +17,54 @@
 # . 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
-    # EAX = new-segment(0x1000)
+    # var ad/ECX : (address allocation-descriptor) = {0, 0}
+    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
+    # new-segment(0x1000, ad)
     # . . push args
+    51/push-ECX
     68/push  0x1000/imm32
     # . . call
     e8/call  new-segment/disp32
     # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # EAX = ad->curr
+    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
     # write to *EAX to check that we have access to the newly-allocated segment
     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0x34/imm32        # copy to *EAX
-
     # syscall(exit, EAX)
     89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
     b8/copy-to-EAX  1/imm32/exit
     cd/syscall  0x80/imm8
 
-new-segment:  # len : int -> address
+new-segment:  # len : int, ad : (address allocation-descriptor)
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # . save registers
+    50/push-EAX
     53/push-EBX
     # copy len to _mmap-new-segment->len
-    # TODO: compute _mmap-new-segment+4 before runtime
     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
-    bb/copy-to-EBX  _mmap-new-segment/imm32
-    89/copy                         1/mod/*+disp8   3/rm32/EBX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EBX+4)
+    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   _mmap-new-segment:len/disp32      # copy EAX to *_mmap-new-segment:len
     # mmap(_mmap-new-segment)
     bb/copy-to-EBX  _mmap-new-segment/imm32
     b8/copy-to-EAX  0x5a/imm32/mmap
     cd/syscall  0x80/imm8
+    # copy {EAX, EAX+len} to *ad
+    # . EBX = ad
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
+    # . *EBX = EAX
+    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EBX
+    # . *(EBX+4) = EAX+len
+    03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # add *(EBP+8) to EAX
+    89/copy                         1/mod/*+disp8   3/rm32/EBX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EBX+4)
 $new-segment:end:
-    # . epilog
+    # . restore registers
     5b/pop-to-EBX
+    58/pop-to-EAX
+    # . epilog
     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
     5d/pop-to-EBP
     c3/return
@@ -49,6 +75,7 @@ $new-segment:end:
 _mmap-new-segment:  # type mmap_arg_struct
     # addr
     0/imm32
+_mmap-new-segment:len:
     # len
     0/imm32
     # protection flags
diff --git a/subx/069allocate.subx b/subx/069allocate.subx
index 402467fe..14e9fd50 100644
--- a/subx/069allocate.subx
+++ b/subx/069allocate.subx
@@ -16,14 +16,6 @@
 # very same 'allocate' helper. They just need a new allocation descriptor for
 # their book-keeping.
 
-== data
-
-# The 'global' allocation descriptor. Pass this into 'allocate' to claim a
-# hitherto unused bit of memory.
-Heap:
-    Start-of-heap/imm32  # curr
-    0x0b000000/imm32  # limit; keep sync'd with DATA_SEGMENT + SEGMENT_ALIGNMENT
-
 == code
 #   instruction                     effective address                                                   register    displacement    immediate
 # . op          subop               mod             rm32          base        index         scale       r32
diff --git a/subx/070new-stream.subx b/subx/070new-stream.subx
index ad6ab68d..8a833581 100644
--- a/subx/070new-stream.subx
+++ b/subx/070new-stream.subx
@@ -68,20 +68,21 @@ test-new-stream:
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var ad/ECX : (address allocation-descriptor) = allocate-region(Heap, 512)
-    # . EAX = allocate-region(Heap, 512)
+    # var heap/ECX : (address allocation-descriptor) = {0, 0}
+    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
+    # heap = new-segment(512)
     # . . push args
+    51/push-ECX
     68/push  0x200/imm32
-    68/push  Heap/imm32
     # . . call
-    e8/call  allocate-region/disp32
+    e8/call  new-segment/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . ECX = EAX
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
     # var start/EDX = ad->curr
     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
-    # EAX = new-stream(ad, 3, 2)
+    # EAX = new-stream(heap, 3, 2)
     # . . push args
     68/push  2/imm32
     68/push  3/imm32
diff --git a/subx/072slice.subx b/subx/072slice.subx
index 9892f2a1..82ce883e 100644
--- a/subx/072slice.subx
+++ b/subx/072slice.subx
@@ -917,14 +917,26 @@ test-slice-to-string:
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # var heap/EDX : (address allocation-descriptor) = {0, 0}
+    68/push  0/imm32/limit
+    68/push  0/imm32/curr
+    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
+    # heap = new-segment(512)
+    # . . push args
+    52/push-EDX
+    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 slice/ECX = "Abc"
     68/push  _test-slice-data-3/imm32/end
     68/push  _test-slice-data-0/imm32/start
     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # EAX = slice-to-string(Heap, slice)
+    # EAX = slice-to-string(heap, slice)
     # . . push args
     51/push-ECX
-    68/push  Heap/imm32
+    52/push-EDX
     # . . call
     e8/call  slice-to-string/disp32
     # . . discard args
diff --git a/subx/074print-int-decimal.subx b/subx/074print-int-decimal.subx
index 86015a00..04385795 100644
--- a/subx/074print-int-decimal.subx
+++ b/subx/074print-int-decimal.subx
@@ -6,7 +6,8 @@
 # . 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 a single test, while debugging
-#?     e8/call test-print-int32-decimal/disp32
+#?     e8/call test-print-int32-decimal-negative/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
@@ -17,27 +18,30 @@ print-int32-decimal:  # out : (address stream), n : int32
     # to the stack, before popping them one by one into the stream
     #
     # pseudocode:
-    #   copy ESP to EBX
-    #   EAX = |n|
+    #   push sentinel
+    #   EAX = abs(n)
     #   while true
-    #     if (EAX == 0) break
     #     sign-extend EAX into EDX
     #     EAX, EDX = EAX/10, EAX%10
+    #     EDX += '0'
     #     push EDX
+    #     if (EAX == 0) break
     #   if n < 0
-    #     push '-' - '0' = -3
-    #   max/ECX = &out->data[out->length]
-    #   w/EAX = out->write
-    #   curr/EDI = &out->data[out->write]
+    #     push '-'
+    #   w = out->write
+    #   curr = &out->data[out->write]
+    #   max = &out->data[out->length]
     #   while true
-    #     if (ESP == EBX) break
+    #     pop into EAX
+    #     if (EAX == sentinel) break
     #     if (curr >= max) abort
-    #     pop into EDX
-    #     EDX += '0'  # convert decimal digit to ascii
-    #     *curr = DL
+    #     *curr = AL
     #     ++curr
     #     ++w
     #   out->write = w
+    # (based on K&R itoa: https://en.wikibooks.org/wiki/C_Programming/stdlib.h/itoa)
+    # (this pseudocode contains registers because operations like division
+    # require specific registers in x86)
     #
     # . prolog
     55/push-EBP
@@ -48,67 +52,63 @@ print-int32-decimal:  # out : (address stream), n : int32
     52/push-EDX
     53/push-EBX
     57/push-EDI
-    # copy ESP to EBX
-    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
     # ten/ECX = 10
     b9/copy-to-ECX  0xa/imm32
-    # EAX = |n|
+    # push sentinel
+    68/push  0/imm32/sentinel
+    # EAX = abs(n)
     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
     3d/compare-EAX-with  0/imm32
-    7d/jump-if-greater-or-equal $print-int32-decimal:read-loop/disp8
+    7d/jump-if-greater-or-equal  $print-int32-decimal:read-loop/disp8
 $print-int32-decimal:negative:
     f7          3/subop/negate      3/mod/direct    0/rm32/EAX    .           .             .           .           .               .                 # negate EAX
 $print-int32-decimal:read-loop:
-    # if (EAX == 0) break
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal $print-int32-decimal:read-break/disp8
-    # sign-extend
+    # EAX, EDX = EAX / 10, EAX % 10
     99/sign-extend-EAX-into-EDX
-    # EAX, EDX = divide-with-remainder EAX/ten, EAX%ten
-    f7          7/subop/divide-by   3/mod/direct    1/rm32/ECX    .           .             .           .           .               .                 # divide EDX:EAX by ECX, storing quotient in EAX and remainder in EDX
+    f7          7/subop/idiv        3/mod/direct    1/rm32/ECX    .           .             .           .           .               .                 # divide EDX:EAX by ECX, storing quotient in EAX and remainder in EDX
+    # EDX += '0'
+    81          0/subop/add         3/mod/direct    2/rm32/EDX    .           .             .           .           .               0x30/imm32        # add to EDX
     # push EDX
     52/push-EDX
-    eb/jump  $print-int32-decimal:read-loop/disp8
+    # if (EAX == 0) break
+    3d/compare-EAX-and  0/imm32
+    7f/jump-if-greater  $print-int32-decimal:read-loop/disp8
 $print-int32-decimal:read-break:
     # if (n < 0) push('-')
     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       0/imm32           # compare *(EBP+12)
-    7d/jump-if-greater-or-equal $print-int32-decimal:write/disp8
+    7d/jump-if-greater-or-equal  $print-int32-decimal:write/disp8
 $print-int32-decimal:push-negative:
-    68/push  -3/imm32/dash-minus-zero
-    # fall through
+    68/push  0x2d/imm32/-
 $print-int32-decimal:write:
     # EDI = out
     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    # max/ECX = &out->data[out->length]
-    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EDI+8) to ECX
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           1/r32/ECX   0xc/disp8       .                 # copy EDI+ECX+12 to ECX
-    # w/EAX = out->write
-    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy *EDI to EAX
-    # curr/EDI = &out->data[out->write]
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  0/index/EAX   .           7/r32/EDI   0xc/disp8       .                 # copy EDI+EAX+12 to EDI
+    # w/EDX = out->write
+    8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy *EDI to EDX
+    # curr/ECX = &out->data[out->write]
+    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           1/r32/ECX   0xc/disp8       .                 # copy EBX+EDX+12 to ECX
+    # max/EBX = &out->data[out->length]
+    8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           3/r32/EBX   8/disp8         .                 # copy *(EDI+8) to EBX
+    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  3/index/EBX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+EBX+12 to EBX
 $print-int32-decimal:write-loop:
-    # if (ESP == EBX) break
-    39/compare                      3/mod/direct    4/rm32/ESP    .           .             .           3/r32/EBX   .               .                 # compare ESP and EBX
+    # pop into EAX
+    58/pop-to-EAX
+    # if (EAX == sentinel) break
+    3d/compare-EAX-and  0/imm32/sentinel
     74/jump-if-equal  $print-int32-decimal:write-break/disp8
     # if (curr >= max) abort
-    39/compare                      3/mod/direct    7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # compare EDI and ECX
+    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           3/r32/EBX   .               .                 # compare ECX with EBX
     7d/jump-if-greater-or-equal  $print-int32-decimal:abort/disp8
-    # pop into EDX
-    5a/pop-into-EDX
-    # EDX += '0'
-    81          0/subop/add         3/mod/direct    2/rm32/EDX    .           .             .           .           .               0x30/imm32/zero   # add to EDX
 $print-int32-decimal:write-char:
-    # *curr = DL
-    88/copy-byte                    0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/DL    .               .                 # copy DL to byte at *ECX
+    # *curr = AL
+    88/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy AL to byte at *ECX
     # ++curr
-    47/increment-EDI
+    41/increment-ECX
     # ++w
-    40/increment-EAX
+    42/increment-EDX
     eb/jump  $print-int32-decimal:write-loop/disp8
 $print-int32-decimal:write-break:
     # out->write = w
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDI
+    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy EDX to *EDI
 $print-int32-decimal:end:
     # . restore registers
     5f/pop-to-EDI
@@ -166,6 +166,36 @@ test-print-int32-decimal:
     # . end
     c3/return
 
+test-print-int32-decimal-zero:
+    # - check that 0 converts correctly
+    # 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
+    # print-int32-decimal(_test-stream, 0)
+    # . . push args
+    68/push  0/imm32
+    68/push  _test-stream/imm32
+    # . . call
+    e8/call  print-int32-decimal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check-stream-equal(_test-stream, "0", msg)
+    # . . push args
+    68/push  "F - test-print-int32-decimal-zero"/imm32
+    68/push  "0"/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-print-int32-decimal-multiple-digits:
     # - check that a multi-digit number converts correctly
     # setup
diff --git a/subx/apps/assort b/subx/apps/assort
index b5961b66..fb24828b 100755
--- a/subx/apps/assort
+++ b/subx/apps/assort
Binary files differdiff --git a/subx/apps/assort.subx b/subx/apps/assort.subx
index 20566806..7498cc3a 100644
--- a/subx/apps/assort.subx
+++ b/subx/apps/assort.subx
@@ -26,12 +26,30 @@
 Entry:  # run tests if necessary, convert stdin if not
 
     # for debugging: run a single test
+#?     # . Heap = new-segment(4096)
+#?     # . . push args
+#?     68/push  Heap/imm32
+#?     68/push  0x1000/imm32
+#?     # . . call
+#?     e8/call  new-segment/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . test()
 #?     e8/call test-convert/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
 
     # . prolog
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # initialize heap
+    # . Heap = new-segment(4096)
+    # . . push args
+    68/push  Heap/imm32
+    68/push  0x1000/imm32
+    # . . call
+    e8/call  new-segment/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # - if argc > 1 and argv[1] == "test", then return run_tests()
     # . argc > 1
     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
@@ -1316,4 +1334,10 @@ Segment-size:
   0x100/imm32
 #?   0x1000/imm32/4KB
 
+Heap:
+  # curr
+  0/imm32
+  # limit
+  0/imm32
+
 # . . vim:nowrap:textwidth=0
diff --git a/subx/apps/crenshaw2-1 b/subx/apps/crenshaw2-1
index 8a927771..2ffc8a84 100755
--- a/subx/apps/crenshaw2-1
+++ b/subx/apps/crenshaw2-1
Binary files differdiff --git a/subx/apps/crenshaw2-1b b/subx/apps/crenshaw2-1b
index 7856904e..6144d591 100755
--- a/subx/apps/crenshaw2-1b
+++ b/subx/apps/crenshaw2-1b
Binary files differdiff --git a/subx/apps/dquotes.subx b/subx/apps/dquotes.subx
index 514c2d06..e6b24698 100644
--- a/subx/apps/dquotes.subx
+++ b/subx/apps/dquotes.subx
@@ -22,12 +22,30 @@
 Entry:  # run tests if necessary, convert stdin if not
 
     # for debugging: run a single test
+#?     # . Heap = new-segment(4096)
+#?     # . . push args
+#?     68/push  Heap/imm32
+#?     68/push  0x1000/imm32
+#?     # . . call
+#?     e8/call  new-segment/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+#?     # . test()
 #?     e8/call test-emit-string-literal-data/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
 
     # . prolog
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # initialize heap
+    # . Heap = new-segment(4096)
+    # . . push args
+    68/push  Heap/imm32
+    68/push  0x1000/imm32
+    # . . call
+    e8/call  new-segment/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # - if argc > 1 and argv[1] == "test", then return run_tests()
     # . argc > 1
     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
@@ -1692,6 +1710,12 @@ Segment-size:
 Next-string-literal:  # tracks the next auto-generated variable name
   1/imm32
 
+Heap:
+  # curr
+  0/imm32
+  # limit
+  0/imm32
+
 # length-prefixed string containing just a single space
 Space:
     # size
diff --git a/subx/apps/factorial b/subx/apps/factorial
index 92c70b86..d1bd5f17 100755
--- a/subx/apps/factorial
+++ b/subx/apps/factorial
Binary files differdiff --git a/subx/apps/handle b/subx/apps/handle
index b4bf7240..b77ebba6 100755
--- a/subx/apps/handle
+++ b/subx/apps/handle
Binary files differdiff --git a/subx/apps/handle.subx b/subx/apps/handle.subx
index f866ea59..0ed12067 100644
--- a/subx/apps/handle.subx
+++ b/subx/apps/handle.subx
@@ -79,17 +79,29 @@ test-new:
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # var heap/EDX : (address allocation-descriptor) = {0, 0}
+    68/push  0/imm32/limit
+    68/push  0/imm32/curr
+    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
+    # heap = new-segment(512)
+    # . . push args
+    52/push-EDX
+    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
     # *Next-alloc-id = 0x34
     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  0x34/imm32        # copy to *Next-alloc-id
     # var handle/ECX = {0, 0}
     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
-    # new(Heap, 2, handle/ECX)
+    # new(heap, 2, handle/ECX)
     # . . push args
     51/push-ECX
     68/push  2/imm32/size
-    68/push  Heap/imm32
+    52/push-EDX
     # . . call
     e8/call  new/disp32
     # . . discard args
@@ -234,17 +246,29 @@ test-lookup-success:
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # . save registers
+    # var heap/EBX : (address allocation-descriptor) = {0, 0}
+    68/push  0/imm32/limit
+    68/push  0/imm32/curr
+    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
+    # heap = 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 = {0, 0}
     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/EDX = Heap->curr
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           2/r32/EDX   Heap/disp32     .                 # copy *Heap to EDX
-    # new(Heap, 2, handle)
+    # var old_top/EDX = heap->curr
+    8b/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           2/r32/EDX   .               .                 # copy *EBX to EDX
+    # new(heap, 2, handle)
     # . . push args
     51/push-ECX
     68/push  2/imm32/size
-    68/push  Heap/imm32
+    53/push-EBX
     # . . call
     e8/call  new/disp32
     # . . discard args
@@ -256,7 +280,7 @@ test-lookup-success:
     e8/call  lookup/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # EAX contains old top of Heap, except skipping the alloc id in the payload
+    # EAX contains old top of heap, except skipping the alloc id in the payload
     # . check-ints-equal(EAX, old_top+4, msg)
     # . . push args
     68/push  "F - test-lookup-success"/imm32
@@ -282,38 +306,46 @@ test-lookup-failure:
     # . prolog
     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
+    # var heap/ESI : (address allocation-descriptor) = {0, 0}
+    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 h1/ECX = {0, 0}
     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
-    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Heap/disp32     .                 # copy *Heap to EBX
+    # var old_top/EBX = heap->curr
+    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           3/r32/EBX   .               .                 # copy *ESI to EBX
     # first allocation, to h1
-    # . new(Heap, 2, h1)
+    # . new(heap, 2, h1)
     # . . push args
     51/push-ECX
     68/push  2/imm32/size
-    68/push  Heap/imm32
+    56/push-ESI
     # . . call
     e8/call  new/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # reset Heap->curr to mimic reclamation
-    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Heap/disp32     .                 # copy EBX to *Heap
+    # reset heap->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 = {0, 0}
     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
-    # . new(Heap, 2, h2)
+    # . new(heap, 2, h2)
     # . . push args
     52/push-EDX
     68/push  2/imm32/size
-    68/push  Heap/imm32
+    56/push-ESI
     # . . call
     e8/call  new/disp32
     # . . discard args
@@ -338,10 +370,6 @@ test-lookup-failure:
     # clean up
     # . *Next-alloc-id = 1
     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
-    # . restore registers
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
     # . epilog
     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
     5d/pop-to-EBP
diff --git a/subx/apps/hex b/subx/apps/hex
index 796e532e..dde157e3 100755
--- a/subx/apps/hex
+++ b/subx/apps/hex
Binary files differdiff --git a/subx/apps/pack b/subx/apps/pack
index ba40c5a1..1154be78 100755
--- a/subx/apps/pack
+++ b/subx/apps/pack
Binary files differ