summary refs log tree commit diff
path: root/quine.asm
diff options
context:
space:
mode:
Diffstat (limited to 'quine.asm')
-rw-r--r--quine.asm112
1 files changed, 83 insertions, 29 deletions
diff --git a/quine.asm b/quine.asm
index b8cf76c..f44f608 100644
--- a/quine.asm
+++ b/quine.asm
@@ -951,6 +951,25 @@ macro push.qreg source
   opcodereg 0x50, sreg
 end macro
 
+macro push.bimm source
+  db 0x6A
+  db source
+end macro
+
+; Operand-size prefix makes it 16-bit.
+macro push.wimm source
+  db 0x66
+  db 0x68
+  dw source
+end macro
+
+; There is no 64-bit immediate push. To fake it, push the low half, then the
+; high half. [Intel] volume 1, chapter 9, section 9-2.4, "Memory Data Formats".
+macro push.dimm source
+  db 0x68
+  dd source
+end macro
+
 ; Pop a 64-bit value into a register from the stack (the one pointed to by
 ; rsp). Read the value from the old location, then increment rsp.
 ;
@@ -1206,7 +1225,7 @@ code_start:
 ;;;   grows downwards in memory. Since values are kept separately, the only
 ;;;   thing on the control stack is return addresses, one per layer of call.
 ;;;
-;;; * esp points to the top of the value stack
+;;; * rsp points to the top of the value stack
 ;;;     The value stack has no specific format, but it grows downwards in
 ;;;   memory. In particular there's no concept of stack frames, because items
 ;;;   on the stack don't belong to any particular word; the value stack in
@@ -1923,8 +1942,22 @@ defword QUIT, 0
   dq SYS_EXIT
 
 
+;;;;;;;;;;;;;;;;;;;;
+;;; System calls ;;;
+;;;;;;;;;;;;;;;;;;;;
 ;;;
-;;; This does the Linux exit() system call, passing it exit code zero.
+;;;   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 exit code zero.
 ;;;
 defword SYS_EXIT, 0
   dq $ + 0x8                     ; codeword
@@ -1937,15 +1970,59 @@ defword SYS_EXIT, 0
   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 $ + 0x8                     ; codeword
+  pop.qreg rcx                   ; address from stack
+  pop.qreg rdx                   ; length from stack, passed directly
+  push.qreg rsi                  ; save rsi
+  mov.b 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
+
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; (new) Implementation strategy ;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;
 defword QUINE, 0
   dq DOCOL                       ; codeword
+
+  ; This stack-allocates a buffer, then finishes by pushing its length and
+  ; address on the value stack. Thus we don't need to care about how it
+  ; internally uses registers.
   dq OLD_CODE
+
+  ; write() from stack-allocated buffer
+  dq SYS_WRITE
+
+  ; write() the machine code by using self-reference
+  ; TODO do this in a "real" quine way
+  dq WRITE_SELF_RAW_H
+  dq SYS_WRITE
+
   dq EXIT
 
+defword WRITE_SELF_RAW_H, 0
+  dq $ + 0x8                     ; codeword
+  mov.qreg.qimm rax, file_size - 0x78
+  push.qreg rax
+  mov.qreg.qimm rax, elf_header + 0x78
+  push.qreg rax
+  NEXT
+
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; (old) Implementation strategy ;;;
@@ -2053,36 +2130,13 @@ defword OLD_CODE, 0
   mov.qreg.disp8.qreg rcx, 0x68, rdx       ; size in memory
 
   ;;;
-  ;;; The buffer is ready; output the file.
+  ;;; The buffer is ready; push its length and address on the value stack, so
+  ;;; our caller can handle write()ing it out.
   ;;;
 
-  ; write() from stack-allocated buffer
-  ; we have real stuff using rsi now, so don't forget to save and restore it
-  ; also rcx is the only "low" register the kernel doesn't preserve
-  push.qreg rsi
+  push.dimm 0
+  push.dimm 0x78
   push.qreg rcx
-  mov.b rax,1
-  mov.qreg.qimm rdi, 1
-  mov.qreg.qreg rsi, rcx
-  mov.qreg.qimm rdx, 0x78
-  syscall
-  pop.qreg rcx
-  pop.qreg rsi
-
-  ; write() the machine code by using self-reference
-  ; we have real stuff using rsi now, so don't forget to save and restore it
-  ; also rcx is the only "low" register the kernel doesn't preserve
-  ;
-  ; TODO do this in a "real" quine way
-  push.qreg rsi
-  push.qreg rcx
-  mov.b rax, 1
-  mov.qreg.qimm rdi, 1
-  mov.qreg.qimm rsi, elf_header + 0x78
-  mov.qreg.qimm rdx, file_size - 0x78
-  syscall
-  pop.qreg rcx
-  pop.qreg rsi
 
   NEXT