summary refs log tree commit diff
path: root/quine.asm
diff options
context:
space:
mode:
Diffstat (limited to 'quine.asm')
-rw-r--r--quine.asm703
1 files changed, 591 insertions, 112 deletions
diff --git a/quine.asm b/quine.asm
index b828b90..d588b1c 100644
--- a/quine.asm
+++ b/quine.asm
@@ -2000,10 +2000,288 @@ cold_start:
   ; store, but the only thing early_variable is doing is returning an
   ; address.)
 
+  ;   Now we define a bunch of ordinary Forth words. Since we'll only ever be
+  ; calling these copies from within Forth, we're no longer limited by
+  ; flatassembler's syntax, so a lot of them have slightly different names
+  ; than they've had up to now. This will be noted below.
+
   dq litstring, "exit", early_create, early_self_codeword, early_here, fetch
   dq rsi, pack_popcontrol, pack_next
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "swap", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "drop", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "drop2".
+  dq litstring, "2drop", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "roll", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; Jonesforth calls this "-roll" and we could do that, but honestly the name
+  ; unroll sounds nicer and it's only a single character longer. You might say
+  ; it rolls off the tongue better.
+  dq litstring, "unroll", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "roll3".
+  dq litstring, "3roll", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "unroll3".
+  dq litstring, "3unroll", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "dup", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "dup2".
+  dq litstring, "2dup", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "add".
+  dq litstring, "+", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "sub".
+  dq litstring, "-", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "mul".
+  dq litstring, "*", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "divmod". Jonesforth calls it "/mod" but % is widely recognized
+  ; and has no special Forth significance.
+  dq litstring, "/%", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "eq". Jonesforth calls it "="; most languages would call it "==".
+  dq litstring, "=", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "ne". Jonesforth calls it "<>", but even most modern SQL dialects
+  ; recognize C's legacy and allow "!=" these days. As someone who learned C
+  ; in childhood, this is not actually a hard call for us.
+  dq litstring, "!=", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "gt".
+  dq litstring, ">", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "lt".
+  dq litstring, "<", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "ge".
+  dq litstring, ">=", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "le".
+  dq litstring, "<=", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "and", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "or", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "xor", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "invert", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "lit", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "litstring", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This is "store", but since we're finally in Forth we're no longer limited
+  ; by flatassembler's syntax, so we call it by its usual Forth name.
+  dq litstring, "!", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This is "fetch".
+  dq litstring, "@", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This is "addstore".
+  dq litstring, "+!", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This is "substore".
+  dq litstring, "-!", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "store8". Jonesforth calls it "c!" but we categorically reject
+  ; the use of letters that are meant to indicate byte sizes. It's unfriendly
+  ; to newcomers. There's some risk that this name will be confused for
+  ; meaning "store a value of 8", but that would not be a useful task, so
+  ; hopefully it'll be okay.
+  dq litstring, "8!", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "fetch8".
+  dq litstring, "8@", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "store16".
+  dq litstring, "16!", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "fetch16".
+  dq litstring, "16@", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "store32".
+  dq litstring, "32!", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "fetch32".
+  dq litstring, "32@", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "memcopy", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; TODO why does this crash?
+  ;dq litstring, "stringlen", early_create, early_self_codeword, early_here
+  ;dq fetch
+  ; TODO
+  ;dq lit, 8, packalign, early_here_store
+
+  dq litstring, "branch", early_create, early_self_codeword, early_here, fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; This was "zbranch".
+  dq litstring, "0branch", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; TODO why does this crash?
+  ;dq litstring, "sys_exit", early_create, early_self_codeword, early_here
+  ;dq fetch
+  ; TODO
+  ;dq lit, 8, packalign, early_here_store
+
+  dq litstring, "sys_write", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "emitstring", early_create, early_self_codeword, early_here
+  ; TODO this is actually in Forth
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "crash", early_create, early_self_codeword, early_here, fetch
+  ; TODO
   dq lit, 8, packalign, early_here_store
 
+  ; We could in theory call this 64pack, by analogy to 32! and so on, but it's
+  ; kept like this to be more similar to reg64, imm64 etc.
+  ;
+  ; Also, unlike with ! we specify the size even for the 64-bit version,
+  ; because the user of pack64 and its variants is always doing something
+  ; where exact byte sizes are crucial.
+  dq litstring, "pack64", early_create, early_self_codeword, early_here, fetch
+  ; TODO this is actually in Forth
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "pack32", early_create, early_self_codeword, early_here, fetch
+  ; TODO this is actually in Forth
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "pack16", early_create, early_self_codeword, early_here, fetch
+  ; TODO this is actually in Forth
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "pack8", early_create, early_self_codeword, early_here, fetch
+  ; TODO this is actually in Forth
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "packstring", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO this is actually in Forth
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "packalign", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO this is actually in Forth
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "litpack64", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "litpack32", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  dq litstring, "litpack16", early_create, early_self_codeword, early_here
+  dq fetch
+  ; TODO
+  dq lit, 8, packalign, early_here_store
+
+  ; TODO why does this crash?
+  ;dq litstring, "litpack8", early_create, early_self_codeword, early_here
+  ;dq fetch
+  ; TODO
+  ;dq lit, 8, packalign, early_here_store
+
   ;;; For triage's sake, here's an inventory of everything else in the file.
   ;;;
   ;;; Macros:
@@ -2020,11 +2298,19 @@ cold_start:
   ;;;   lit, litstring
   ;;;   store, fetch
   ;;;   addstore, substore, store8, fetch8, store16, fetch16, store32, fetch32
-  ;;;   ccopy, stringlen
+  ;;;   memcopy, stringlen
   ;;;     no dependencies except next for any of these
   ;;;   branch, zbranch
   ;;;     sorta needs a label but might be avoidable
   ;;;     needs next
+  ;;;   sys_exit, sys_write
+  ;;;     nothing special
+  ;;; Forth:
+  ;;;   emitstring
+  ;;;     stringlen, sys_write, basics
+  ;;; Assembly:
+  ;;;   crash
+  ;;;     nothing special
   ;;; Forth:
   ;;;   pack64, pack32, pack16, pack8, packstring, packalign
   ;;;     only the basics above
@@ -2034,14 +2320,44 @@ cold_start:
   ;;; Forth:
   ;;;   rax, rcx, dx, rbx, rsp, rbp, rsi, rdi
   ;;;   r8, r9, r10, r11, r12, r13, r14, r15
+  ;;;   eax, ecx, edx, ebx, esp, ebp, esi, edi
+  ;;;   ax, cx, dx, bx, sp, bp, si, di
+  ;;;   al, cl, dl, bl, ah, ch, dh, bh
+  ;;;   cc_overflow, cc_no_overflow, cc_below, cc_above_equal, cc_equal,
+  ;;;   cc_not_equal, cc_below_equal, cc_above, cc_sign, cc_not_sign, cc_even,
+  ;;;   cc_odd, cc_less, cc_greater_equal, cc_less_equal, cc_greater
   ;;;     only the basics above
-  ;;;   reg64, extrareg64
+  ;;;   reg64, extrareg64, reg32, reg16, reg8, scalefield, conditioncode
   ;;;     only the basics plus optional emitstring and sys_exit
   ;;;     (notice that these are forward references!)
-  ;;;   rex_w, rex_wb, opcodereg, modrm
+  ;;;   rex_w, rex_wb, opcodereg, opcodecc, modrm, sib
   ;;;     only the basics above
-  ;;;   cld, mov_reg64_imm64, mov_reg64_reg64, push_reg64, lodsq
-  ;;;   jmp_abs_indirect_reg64, syscall
+  ;;;   addressing_reg64, addressing_indirect_reg64, addressing_disp8_reg64,
+  ;;;   addressing_indexed_reg64, addressing_disp8_indexed_reg64
+  ;;;     basics plus earlier assembly stuff
+  ;;;   cld, std, mov_reg64_imm64, mov_extrareg64_imm64, mov_reg64_reg64,
+  ;;;   mov_indirect_reg64_reg64, mov_disp8_reg64_reg64,
+  ;;;   mov_reg64_indirect_reg64, mov_reg64_disp8_reg64,
+  ;;;   mov_reg64_indexed_reg64,
+  ;;;   mov_indirect_reg64_reg32, mov_disp8_reg64_reg32,
+  ;;;   mov_reg32_indirect_reg64, mov_reg32_disp8_reg64,
+  ;;;   mov_indirect_reg64_reg16, mov_disp8_reg64_reg16,
+  ;;;   mov_reg16_indirect_reg64, mov_reg16_disp8_reg64,
+  ;;;   mov_indirect_reg64_reg8, mov_disp8_reg64_reg8,
+  ;;;   mov_reg8_indirect_reg64, mov_reg8_disp8_reg64,
+  ;;;   lea_reg64_disp8_reg64, lea_reg64_indexed_reg64,
+  ;;;   lea_reg64_disp8_indexed_reg64,
+  ;;;   push_reg64, pop_reg64,
+  ;;;   lodsq, rep_movs8, rep_mov16, rep_movs32, rep_movs64, repnz_scas8,
+  ;;;   add_reg64_reg64, add_indirect_reg64_reg64, add_reg64_indirect_reg64,
+  ;;;   add_reg64_imm8,
+  ;;;   sub_reg64_reg64, sub_indirect_reg64_reg64,
+  ;;;   mul_reg64_reg64, divmod_reg64, idivmod_reg64, inc_reg64, dec_reg64,
+  ;;;   and_reg64_reg64, or_reg64_reg64, or_reg64_imm8, xox_reg64_reg64,
+  ;;;   not_reg64,
+  ;;;   cmp_reg64_reg64, test_reg64_reg64,
+  ;;;   set_reg8_cc, jmp_cc_rel_imm8, jmp_abs_indirect_reg64, jmp_rel_imm32,
+  ;;;   syscall, hlt
   ;;;     basics plus assembly helpers
   ;;; Forth, not needed on heap:
   ;;;   early_heap, early_s0, early_r0, early_latest, early_here
@@ -2054,20 +2370,12 @@ cold_start:
   ;;; Forth, subject to reconsideration:
   ;;;   quit
   ;;;     quine, sys_exit (these are forward references)
-  ;;; Assembly:
-  ;;;   sys_exit, sys_write
-  ;;;     nothing special
   ;;; Forth:
-  ;;;   emitstring
-  ;;;     stringlen, sys_write, basics
   ;;;   quine
   ;;;     early_here (removable), all_contents (forward)
   ;;;     sys_write, basics
   ;;;   use_label, set_label
   ;;;     only basics
-  ;;; Assembly:
-  ;;;   hlt
-  ;;;     nothing
   ;;; Forth:
   ;;;   all_contents, elf_file_header, elf_program_header
   ;;;   output_start_routine
@@ -2658,17 +2966,25 @@ defword fetch32, 0
 ; logic, see roll and unroll. This always copies at byte granularity, for ease
 ; of implementation.
 ;
-;   Jonesforth calls this CMOVE and has a ccopy which is a single byte. POSIX,
+;   Jonesforth calls this CMOVE and has a CCOPY which is a single byte. POSIX,
 ; however, has memcpy() for non-overlapping blocks and memmove() for
 ; overlapping blocks. We follow the latter convention, because it feels like
 ; the more important distinction.
 ;
+;   Also, the "c" was meant to indicate that it works at one-byte granularity,
+; but that isn't, uh... actually an important property here, and as a blanket
+; call we're not using letters to denote data sizes. So we call it "memcopy".
+; Apologies to C programmers but vowels are good, actually.
+;
+;   Jonesforth also offers C@C! as another name for its CCOPY, but neither
+; "@!" nor "mem@mem!" seems particulaly nice.
+;
 ; Stack in:
 ;   destination
 ;   source
 ;   length
 ; (top)
-defword ccopy, 0
+defword memcopy, 0
   dq $ + 8
   ; We need to save and restore rsi; the other registers we can trample.
   mov.qreg.qreg rdx, rsi
@@ -2724,6 +3040,83 @@ zbranch_after_jmp:
   next
 
 
+;;;;;;;;;;;;;;;;;;;;
+;;; System calls ;;;
+;;;;;;;;;;;;;;;;;;;;
+;;;
+;;;   The kernel preserves every register except rax, rcx, and r11. The system
+;;; call number goes in rax, as does the return value. Parameters go in rdi,
+;;; rsi, rdx, r10, r8, and r9, in that order. [SysV] A.2.1.
+;;;
+;;;   Notice that rsi is our control stack, so we have to save it (for
+;;; syscalls with at least two parameters). We can use the value stack to do
+;;; that, since rsp is preserved. We don't save other registers because our
+;;; caller should do that, if it cares.
+;;;
+
+;;;
+;;;   This does the Linux exit() system call, passing it an exit code taken
+;;; from the stack.
+;;;
+defword sys_exit, 0
+  dq $ + 8
+
+  mov.qreg.qimm rax, 60          ; syscall number
+  pop.qreg rdi                   ; exit code
+  syscall
+
+  ; In the event we're still here, let's minimize confusion.
+  hlt
+
+
+;;;
+;;;   This does the Linux write() system call, passing it an address from the
+;;; top of the stack and a length from the second position on the stack. It
+;;; writes to file descriptor 1, which is stdout.
+;;;
+;;;   For our length parameter, we can pop directly from the stack into rdx,
+;;; which directly becomes the syscall parameter. For our address parameter,
+;;; the syscall wants it in rsi, which we also care about, so we have to do a
+;;; little juggling.
+;;;
+defword sys_write, 0
+  dq $ + 8
+  pop.qreg rcx                   ; address from stack
+  pop.qreg rdx                   ; length from stack, passed directly
+  push.qreg rsi                  ; save rsi
+  mov.qreg.qimm rax, 1           ; syscall number
+  mov.qreg.qimm rdi, 1           ; file descriptor
+  mov.qreg.qreg rsi, rcx         ; pass address
+  syscall
+  pop.qreg rsi                   ; restore rsi
+  next
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;
+;;; I/O conveniences ;;;
+;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+defword emitstring, 0
+  dq docol, dup, stringlen, swap, sys_write, exit
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Development utilities ;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;   This peforms the "hlt" instruction (Intel's mnemomic, short for "halt"),
+; which will cause the program to exit with a segmentation fault. If you're
+; running under a debugger, this is a convenient way to get execution to stop
+; at a certain point.
+;
+;   It's called "crash" rather than "hlt" to distinguish it from the word
+; which outputs the instruction as machine code.
+defword crash, 0
+  dq $ + 8
+  hlt
+
+
 ;;;;;;;;;;;;;;;;;;;;;;
 ;;; Output helpers ;;;
 ;;;;;;;;;;;;;;;;;;;;;;
@@ -2770,7 +3163,7 @@ defword packstring, 0
   ; destination, source, length, length, base/destination
   dq add, lit, 4, unroll
   ; new base, destination, source, length
-  dq ccopy
+  dq memcopy
   dq exit
 
 ; Stack in:
@@ -2859,53 +3252,85 @@ defword r14, 0
 defword r15, 0
   dq docol, lit, r15, exit
 defword eax, 0
-  dq docol, lit, rax, exit
+  dq docol, lit, eax, exit
 defword ecx, 0
-  dq docol, lit, rcx, exit
+  dq docol, lit, ecx, exit
 defword edx, 0
-  dq docol, lit, rdx, exit
+  dq docol, lit, edx, exit
 defword ebx, 0
-  dq docol, lit, rbx, exit
+  dq docol, lit, ebx, exit
 defword esp, 0
-  dq docol, lit, rsp, exit
+  dq docol, lit, esp, exit
 defword ebp, 0
-  dq docol, lit, rbp, exit
+  dq docol, lit, ebp, exit
 defword esi, 0
-  dq docol, lit, rsi, exit
+  dq docol, lit, esi, exit
 defword edi, 0
-  dq docol, lit, rdi, exit
+  dq docol, lit, edi, exit
 defword ax, 0
-  dq docol, lit, rax, exit
+  dq docol, lit, ax, exit
 defword cx, 0
-  dq docol, lit, rcx, exit
+  dq docol, lit, cx, exit
 defword dx, 0
-  dq docol, lit, rdx, exit
+  dq docol, lit, dx, exit
 defword bx, 0
-  dq docol, lit, rbx, exit
+  dq docol, lit, bx, exit
 defword sp, 0
-  dq docol, lit, rsp, exit
+  dq docol, lit, sp, exit
 defword bp, 0
-  dq docol, lit, rbp, exit
+  dq docol, lit, bp, exit
 defword si, 0
-  dq docol, lit, rsi, exit
+  dq docol, lit, si, exit
 defword di, 0
-  dq docol, lit, rdi, exit
+  dq docol, lit, di, exit
 defword al, 0
-  dq docol, lit, rax, exit
+  dq docol, lit, al, exit
 defword cl, 0
-  dq docol, lit, rcx, exit
+  dq docol, lit, cl, exit
 defword dl, 0
-  dq docol, lit, rdx, exit
+  dq docol, lit, dl, exit
 defword bl, 0
-  dq docol, lit, rbx, exit
+  dq docol, lit, bl, exit
 defword ah, 0
-  dq docol, lit, rsp, exit
+  dq docol, lit, ah, exit
 defword ch, 0
-  dq docol, lit, rbp, exit
+  dq docol, lit, ch, exit
 defword dh, 0
-  dq docol, lit, rsi, exit
+  dq docol, lit, dh, exit
 defword bh, 0
-  dq docol, lit, rdi, exit
+  dq docol, lit, bh, exit
+defword cc_overflow, 0
+  dq docol, lit, cc_overflow, exit
+defword cc_no_overflow, 0
+  dq docol, lit, cc_no_overflow, exit
+defword cc_below, 0
+  dq docol, lit, cc_below, exit
+defword cc_above_equal, 0
+  dq docol, lit, cc_above_equal, exit
+defword cc_equal, 0
+  dq docol, lit, cc_equal, exit
+defword cc_not_equal, 0
+  dq docol, lit, cc_not_equal, exit
+defword cc_below_equal, 0
+  dq docol, lit, cc_below_equal, exit
+defword cc_above, 0
+  dq docol, lit, cc_above, exit
+defword cc_sign, 0
+  dq docol, lit, cc_sign, exit
+defword cc_not_sign, 0
+  dq docol, lit, cc_not_sign, exit
+defword cc_even, 0
+  dq docol, lit, cc_even, exit
+defword cc_odd, 0
+  dq docol, lit, cc_odd, exit
+defword cc_less, 0
+  dq docol, lit, cc_less, exit
+defword cc_greater_equal, 0
+  dq docol, lit, cc_greater_equal, exit
+defword cc_less_equal, 0
+  dq docol, lit, cc_less_equal, exit
+defword cc_greater, 0
+  dq docol, lit, cc_greater, exit
 
 ; Stack in:
 ;   register name
@@ -3004,6 +3429,35 @@ defword scalefield, 0
   dq litstring, "Parameter to scalefield is not 1, 2, 4, or 8.", emitstring
   dq lit, 1, sys_exit
 
+; [Intel] volume 2D, appendix B, section B-1.4.7, table B-10. Also see the
+; individual opcode pages.
+;
+; Stack in:
+;   condition name
+; Stack out:
+;   4-bit encoded value for condition code in opcodes
+defword conditioncode, 0
+  dq docol
+  dq dup, cc_overflow, eq, zbranch, 5*8, drop, lit, 0, exit
+  dq dup, cc_no_overflow, eq, zbranch, 5*8, drop, lit, 1, exit
+  dq dup, cc_below, eq, zbranch, 5*8, drop, lit, 2, exit
+  dq dup, cc_above_equal, eq, zbranch, 5*8, drop, lit, 3, exit
+  dq dup, cc_equal, eq, zbranch, 5*8, drop, lit, 4, exit
+  dq dup, cc_not_equal, eq, zbranch, 5*8, drop, lit, 5, exit
+  dq dup, cc_below_equal, eq, zbranch, 5*8, drop, lit, 6, exit
+  dq dup, cc_above, eq, zbranch, 5*8, drop, lit, 7, exit
+  dq dup, cc_sign, eq, zbranch, 5*8, drop, lit, 8, exit
+  dq dup, cc_not_sign, eq, zbranch, 5*8, drop, lit, 9, exit
+  dq dup, cc_even, eq, zbranch, 5*8, drop, lit, 10, exit
+  dq dup, cc_odd, eq, zbranch, 5*8, drop, lit, 11, exit
+  dq dup, cc_less, eq, zbranch, 5*8, drop, lit, 12, exit
+  dq dup, cc_greater_equal, eq, zbranch, 5*8, drop, lit, 13, exit
+  dq dup, cc_less_equal, eq, zbranch, 5*8, drop, lit, 14, exit
+  dq dup, cc_greater, eq, zbranch, 5*8, drop, lit, 15, exit
+  dq litstring, "Parameter to conditioncode is not a condition code."
+  dq emitstring
+  dq lit, 1, sys_exit
+
 ; Stack:
 ;   output point
 defword rex_w, 0
@@ -3018,6 +3472,13 @@ defword rex_wb, 0
 defword opcodereg, 0
   dq docol, or, pack8, exit
 
+; Stack:
+;   output point
+;   4-bit encoded value for condition code
+;   opcode byte
+defword opcodecc, 0
+  dq docol, or, pack8, exit
+
 ;   The low-level word that outputs a modrm byte given fully-processed,
 ; numeric values for its fields. Most code will want to call one of the
 ; higher-level modrm_* words, instead.
@@ -3397,11 +3858,43 @@ defword push_reg64, 0
 defword pop_reg64, 0
   dq docol, reg64, lit, 0x58, opcodereg, exit
 
+; TODO: Contemplate renaming this to lods64.
+;
 ; Stack:
 ;   output point
 defword lodsq, 0
   dq docol, rex_w, lit, 0xAD, pack8, exit
 
+;   We break with the Intel mnemonics, which are movsb/movsw/movsd/movsq,
+; because this would otherwise be the only place we use the b/w/d/q thing
+; instead of 8/16/32/64. Tradition and pronounceability are both nice things,
+; but approachability to newcomers is important, too.
+;
+; Stack:
+;   output point
+defword rep_movs8, 0
+  dq docol, lit, 0xF3, pack8, lit, 0xA4, pack8, exit
+
+; Stack:
+;   output point
+defword rep_movs16, 0
+  dq docol, lit, 0xF3, pack8, lit, 0x66, pack8, lit, 0xA5, pack8, exit
+
+; Stack:
+;   output point
+defword rep_movs32, 0
+  dq docol, lit, 0xF3, pack8, lit, 0xA5, pack8, exit
+
+; Stack:
+;   output point
+defword rep_movs64, 0
+  dq docol, lit, 0xF3, pack8, rex_w, lit, 0xA5, pack8, exit
+
+; Stack:
+;   output point
+defword repnz_scas8, 0
+  dq docol, lit, 0xF2, pack8, lit, 0xAE, pack8, exit
+
 ; Stack:
 ;   output point
 ;   source register name
@@ -3594,6 +4087,38 @@ defword test_reg64_reg64, 0
 
 ; Stack:
 ;   output point
+;   target register name
+;   condition code name
+defword set_reg8_cc, 0
+  dq docol
+  dq unroll3, lit, 0x0F, pack8
+  dq swap, conditioncode, lit, 0x90, opcodecc
+  dq swap, reg8, lit, 3, lit, 0, roll3, modrm
+  dq exit
+
+; Stack:
+;   output point
+;   address offset value
+;   condition code name
+defword jmp_cc_rel_imm8, 0
+  dq docol
+  dq unroll3, swap, conditioncode, lit, 0x70, opcodecc
+  dq swap, pack8
+  dq exit
+
+; Stack:
+;   output point
+;   address offset value
+;   condition code name
+defword jmp_cc_rel_imm32, 0
+  dq docol
+  dq unroll3, lit, 0x0F, pack8
+  dq swap, conditioncode, lit, 0x70, opcodecc
+  dq swap, pack32
+  dq exit
+
+; Stack:
+;   output point
 ;   register name
 defword jmp_abs_indirect_reg64, 0
   dq docol
@@ -3603,15 +4128,23 @@ defword jmp_abs_indirect_reg64, 0
 
 ; Stack:
 ;   output point
+;   address offset value
+defword jmp_rel_imm32, 0
+  dq docol
+  dq swap, lit, 0xE9, pack8
+  dq swap, pack32
+  dq exit
+
+; Stack:
+;   output point
 defword syscall, 0
   dq docol, lit, 0x0F, pack8, lit, 0x05, pack8, exit
 
-; TODO rep movsb
-; TODO rep movsq
-; TODO repnz scasb
-; TODO set_reg8_cc
-;   (and condition codes)
-; TODO jmp_cc_rel_imm8
+; Stack:
+;   output point
+defword hlt, 0
+  dq docol, lit, 0xF4, pack8, exit
+
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; Runtime word definition ;;;
@@ -3747,6 +4280,17 @@ defword pack_next, 0
   dq docol, lodsq, rax, jmp_abs_indirect_reg64, exit
 
 ;   This is another helper "macro" that we'll use in defining assembly words
+; from Forth. As before, see the flatassembler version for more explanation.
+;
+; Stack in:
+;   base address
+;   destination address (absolute)
+; Stack out:
+;   new base address
+defword pack_beforenext, 0
+  dq docol, rax, swap, mov_reg64_imm64, rax, jmp_abs_indirect_reg64, exit
+
+;   This is another helper "macro" that we'll use in defining assembly words
 ; from Forth. In particular, this one is used in docol. As before, see the
 ; flatassembler version for more explanation.
 ;
@@ -3837,66 +4381,6 @@ defword quit, 0
   dq lit, 0, sys_exit
 
 
-;;;;;;;;;;;;;;;;;;;;
-;;; System calls ;;;
-;;;;;;;;;;;;;;;;;;;;
-;;;
-;;;   The kernel preserves every register except rax, rcx, and r11. The system
-;;; call number goes in rax, as does the return value. Parameters go in rdi,
-;;; rsi, rdx, r10, r8, and r9, in that order. [SysV] A.2.1.
-;;;
-;;;   Notice that rsi is our control stack, so we have to save it (for
-;;; syscalls with at least two parameters). We can use the value stack to do
-;;; that, since rsp is preserved. We don't save other registers because our
-;;; caller should do that, if it cares.
-;;;
-
-;;;
-;;;   This does the Linux exit() system call, passing it an exit code taken
-;;; from the stack.
-;;;
-defword sys_exit, 0
-  dq $ + 8
-
-  mov.qreg.qimm rax, 60          ; syscall number
-  pop.qreg rdi                   ; exit code
-  syscall
-
-  ; In the event we're still here, let's minimize confusion.
-  hlt
-
-
-;;;
-;;;   This does the Linux write() system call, passing it an address from the
-;;; top of the stack and a length from the second position on the stack. It
-;;; writes to file descriptor 1, which is stdout.
-;;;
-;;;   For our length parameter, we can pop directly from the stack into rdx,
-;;; which directly becomes the syscall parameter. For our address parameter,
-;;; the syscall wants it in rsi, which we also care about, so we have to do a
-;;; little juggling.
-;;;
-defword sys_write, 0
-  dq $ + 8
-  pop.qreg rcx                   ; address from stack
-  pop.qreg rdx                   ; length from stack, passed directly
-  push.qreg rsi                  ; save rsi
-  mov.qreg.qimm rax, 1           ; syscall number
-  mov.qreg.qimm rdi, 1           ; file descriptor
-  mov.qreg.qreg rsi, rcx         ; pass address
-  syscall
-  pop.qreg rsi                   ; restore rsi
-  next
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;
-;;; I/O conveniences ;;;
-;;;;;;;;;;;;;;;;;;;;;;;;
-
-
-defword emitstring, 0
-  dq docol, dup, stringlen, swap, sys_write, exit
-
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; (new) Implementation strategy ;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -4033,11 +4517,6 @@ defword set_label, 0
   dq exit
 
 
-defword hlt, 0
-  dq $ + 8
-  hlt
-
-
 ; This takes an address to write to on the stack, writes stuff there, and
 ; returns the next address after where it stopped Writing.
 ;
@@ -4141,7 +4620,7 @@ defword self_raw, 0
   ; destination destination source length
   dq dup, lit, 4, roll, add, lit, 4, unroll
   ; result destination source length
-  dq ccopy
+  dq memcopy
   dq exit