diff --git a/quine.asm b/quine.asm
index ed0be32..e1f91c6 100644
--- a/quine.asm
+++ b/quine.asm
@@ -236,7 +236,7 @@ macro mov.qreg.indirect.qreg target, source
qwordreg sreg, source
qwordreg treg, target
rex.w
- rb 0x8B
+ db 0x8B
modrm 1, treg, sreg
db 0
end match
@@ -295,7 +295,7 @@ macro mov.oreg.qreg target, source
owordreg treg, target
qwordreg sreg, source
rex.wr
- rb 0x8B
+ db 0x8B
modrm 3, treg, sreg
end macro
@@ -319,7 +319,7 @@ macro mov.qreg.oreg target, source
qwordreg treg, target
owordreg sreg, source
rex.wr
- rb 0x89
+ db 0x89
modrm 3, sreg, treg
end macro
@@ -632,6 +632,14 @@ macro syscall
end macro
+; Halts the CPU. We can't actually run this in userspace, but the kernel will
+; kill our process or the debugger will break, and those are the outcomes we
+; actually want.
+macro hlt
+ db 0xf4
+end macro
+
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Executable file format ;;;
@@ -1129,6 +1137,16 @@ cold_start:
dq QUIT
;;;
+;;; This is the mechanism to "return" from a word interpreted by DOCOL.
+;;; We pop the control stack, and then, since this is threaded execution, we
+;;; do the next thing the caller wants to do, by inlining NEXT.
+;;;
+EXIT:
+ dq $ + 0x8 ; codeword
+ POPCONTROL rsi
+ NEXT
+
+;;;
;;; One of the most charming naming traditions in Forth is that the
;;; top-level word that stays running forever, is called "quit".
;;;
@@ -1158,18 +1176,31 @@ QUIT:
;;; If the repl ever exits, do it all again.
;;;
;dq BRANCH, QUIT - $
+ dq SYS_EXIT
-END_PROCESS:
+;;;
+;;; This does the Linux exit() system call, passing it exit code zero.
+;;;
+SYS_EXIT:
dq $ + 0x8 ; codeword
+
mov.b rax, 60 ; syscall number
mov.b rdi, 0 ; exit code
syscall
+ ; In the event we're still here, let's minmize confusion.
+ hlt
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; (new) Implementation strategy ;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;
QUINE:
dq DOCOL ; codeword
dq OLD_CODE
- dq END_PROCESS
+ dq EXIT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|