summary refs log tree commit diff
path: root/quine.asm
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2025-10-24 04:21:44 -0700
committerIrene Knapp <ireneista@irenes.space>2025-10-24 04:21:44 -0700
commit2ef3227336008fffc19ec11e13c136eb39662c53 (patch)
tree50b6984ad5a195cf3c975dd27a4984f6b5dbbb4e /quine.asm
parent51367d8aa31b27f9ef5987e3a79faabf687d8ac4 (diff)
add stack manip, arithmetic, bitwise stuff, comparisons
this is all untested, but I'm highly confident in my reading of the Intel manual at this point

Force-Push: yes
Change-Id: I3dbd59da02994db58f2a53d1890136d583803bc6
Diffstat (limited to 'quine.asm')
-rw-r--r--quine.asm446
1 files changed, 444 insertions, 2 deletions
diff --git a/quine.asm b/quine.asm
index 634f660..09f5cfb 100644
--- a/quine.asm
+++ b/quine.asm
@@ -17,7 +17,7 @@
 ;;;
 ;;; A workflow you may wish to use for debugging is:
 ;;;
-;;; rm quine2; fasmg quine.asm quine && ./quine > quine2; echo "exit code:" $?; echo; hexdump -C quine; echo; hexdump -C quine2; echo; cmp -l quine quine2 ; echo cmp: $?
+;;; rm quine2; fasmg quine.asm quine && chmod 755 quine && ./quine > quine2; echo "exit code:" $?; echo; hexdump -C quine; echo; hexdump -C quine2; echo; cmp -l quine quine2 ; echo cmp: $?
 ;;;
 ;;; The reason this removes the old one first is that otherwise, there's a
 ;;; risk the error message will be scrolled off the top of the screen and
@@ -129,6 +129,72 @@ macro opcodereg opcode, reg
   db opcode or reg
 end macro
 
+macro bytereg result, register
+  match =al?, register
+    result = 0
+  else match =cl?, register
+    result = 1
+  else match =dl?, register
+    result = 2
+  else match =bl?, register
+    result = 3
+  else match =ah?, register
+    result = 4
+  else match =ch?, register
+    result = 5
+  else match =dh?, register
+    result = 6
+  else match =bh?, register
+    result = 7
+  else
+    assert 0
+  end match
+end macro
+
+macro wordreg result, register
+  match =ax?, register
+    result = 0
+  else match =cx?, register
+    result = 1
+  else match =dx?, register
+    result = 2
+  else match =bx?, register
+    result = 3
+  else match =sp?, register
+    result = 4
+  else match =bp?, register
+    result = 5
+  else match =si?, register
+    result = 6
+  else match =di?, register
+    result = 7
+  else
+    assert 0
+  end match
+end macro
+
+macro dwordreg result, register
+  match =eax?, register
+    result = 0
+  else match =ecx?, register
+    result = 1
+  else match =edx?, register
+    result = 2
+  else match =ebx?, register
+    result = 3
+  else match =esp?, register
+    result = 4
+  else match =ebp?, register
+    result = 5
+  else match =esi?, register
+    result = 6
+  else match =edi?, register
+    result = 7
+  else
+    assert 0
+  end match
+end macro
+
 macro qwordreg result, register
   match =rax?, register
     result = 0
@@ -378,6 +444,15 @@ macro add.qreg.dimm target, source
   dd source
 end macro
 
+; This subtracts a 64-bit register from another 64-bit register, in place.
+macro sub.qreg.qreg target, source
+  qwordreg treg, target
+  qwordreg sreg, source
+  rex.w
+  db 0x2B
+  modrm 3, treg, sreg
+end macro
+
 ; This subtracts a signed 8-bit immediate value from a 64-bit register, in
 ; place.
 ;
@@ -395,7 +470,7 @@ end macro
 ; place.
 ;
 ; Notice the use of 3 as the addressing mode. This says to use the register
-; itself. The 5 in th reg field is part of the opcode.
+; itself. The 5 in the reg field is part of the opcode.
 macro sub.qreg.dimm target, source
   qwordreg treg, target
   rex.w
@@ -404,6 +479,168 @@ macro sub.qreg.dimm target, source
   dd source
 end macro
 
+; This multiplies rax, as 64-bits, with another 64-bit register, in place.
+;
+; The 4 in the reg field is part of the opcode.
+macro mul.rax.qreg source
+  qwordreg sreg, source
+  rex.w
+  db 0xF7
+  modrm 3, 4, sreg
+end macro
+
+; The official mnemonic for this is "div", but it's divmod: It takes a 128-bit
+; dividend formed from concatenating rdx as the high half with rax as the low
+; half, and divides it by a 64-bit divisor from a specified register. It
+; stores the quotient, truncated towards zero, in rax, and it stores the
+; remainder in rdx. This entire process is unsigned.
+;
+; The 6 in the reg field is part of the opcode.
+macro div.rdxrax.qreg source
+  qwordreg sreg, source
+  rex.w
+  db 0xF7
+  modrm 3, 6, sreg
+end macro
+
+; Same as div, but signed.
+;
+; The 7 in the reg field is part of the opcode.
+macro idiv.rdxrax.qreg source
+  qwordreg sreg, source
+  rex.w
+  db 0xF7
+  modrm 3, 7, sreg
+end macro
+
+
+macro and.qreg.qreg target, source
+  qwordreg treg, target
+  qwordreg sreg, source
+  rex.w
+  db 0x23
+  modrm 3, treg, sreg
+end macro
+
+macro and.qreg.bimm target, source
+  qwordreg treg, target
+  rex.w
+  db 0x83
+  modrm 3, 4, treg
+    ; The 4 is part of the opcode.
+  db source
+end macro
+
+macro or.qreg.qreg target, source
+  qwordreg treg, target
+  qwordreg sreg, source
+  rex.w
+  db 0x0B
+  modrm 3, treg, sreg
+end macro
+
+macro xor.qreg.qreg target, source
+  qwordreg treg, target
+  qwordreg sreg, source
+  rex.w
+  db 0x33
+  modrm 3, treg, sreg
+end macro
+
+macro not.qreg target
+  qwordreg treg, target
+  rex.w
+  db 0xF7
+  modrm 3, 2, treg
+    ; The 2 is part of the opcode.
+end macro
+
+
+; This sets the flags to the same things they'd be set by if subtracting
+; right from left.
+macro cmp.qreg.qreg left, right
+  qwordreg lreg, left
+  qwordreg rreg, right
+  rex.w
+  db 0x3B
+  modrm 3, lreg, rreg
+end macro
+
+
+; Yep, there sure is a lot of duplication in these. This is based on Intel's
+; documented mnemonics...
+;
+; "Above" and "below" are for unsigned comparisons. "Greater" and "less" are
+; for signed comparisons.
+macro set.breg.cc target, condition
+  bytereg treg, target
+  db 0x0F
+  match =above, condition
+    db 0x97
+  else match =above.equal, condition
+    db 0x93
+  else match =below, condition
+    db 0x92
+  else match =below.equal, condition
+    db 0x96
+  else match =carry, condition
+    db 0x92
+  else match =equal, condition
+    db 0x94
+  else match =greater, condition
+    db 0x9F
+  else match =greater.equal, condition
+    db 0x9D
+  else match =less, condition
+    db 0x9C
+  else match =less.equal, condition
+    db 0x9E
+  else match =not.above, condition
+    db 0x96
+  else match =not.above.equal, condition
+    db 0x92
+  else match =not.below, condition
+    db 0x93
+  else match =not.below.equal, condition
+    db 0x97
+  else match =not.carry, condition
+    db 0x93
+  else match =not.equal, condition
+    db 0x95
+  else match =not.greater, condition
+    db 0x9E
+  else match =not.greater.equal, condition
+    db 0x9C
+  else match =not.less, condition
+    db 0x9D
+  else match =not.less.equal, condition
+    db 0x9F
+  else match =not.overflow, condition
+    db 0x91
+  else match =not.parity, condition
+    db 0x9B
+  else match =not.sign, condition
+    db 0x99
+  else match =not.zero, condition
+    db 0x95
+  else match =overflow, condition
+    db 0x90
+  else match =parity, condition
+    db 0x9A
+  else match =parity.even, condition
+    db 0x9A
+  else match =parity.odd, condition
+    db 0x9B
+  else match =sign, condition
+    db 0x98
+  else match =zero, condition
+    db 0x94
+  else
+    assert 0
+  end match
+  modrm 3, 0, treg
+end macro
+
 ; Move from an 8-bit immediate value, to a location relative to a 64-bit
 ; register, with an 8-bit displacement and no indexing.
 ;
@@ -1263,6 +1500,211 @@ defword EXIT, 0
   POPCONTROL rsi
   NEXT
 
+
+;;;
+;;; Stack manipulation routines
+;;; ---------------------------
+;;;
+;;;   We start with the three traditional stack operations, SWAP DROP and
+;;; ROLL. Sorry to fans of the name "ROT"; we were an HP calculator kid. It'll
+;;; always be ROLL to us. Anyway, we do a couple other operations too. Since
+;;; our goal right now is just to bootstrap the heap, we keep this short and
+;;; sweet.
+;;;
+;;;   There is definitely plenty of optimization that could be done.
+;;;
+
+defword SWAP, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rax
+  pop.qreg rbx
+  push.qreg rax
+  push.qreg rbx
+  NEXT
+
+defword DROP, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rax
+  NEXT
+
+defword DROP2, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rax
+  pop.qreg rax
+  NEXT
+
+; Rotates "up" (third item becomes current item)
+defword ROLL3, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rax
+  pop.qreg rbx
+  pop.qreg rcx
+  push.qreg rbx
+  push.qreg rax
+  push.qreg rcx
+  NEXT
+
+; Rotates "down" (current item becomes third item)
+defword ROLLD3, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rax
+  pop.qreg rbx
+  pop.qreg rcx
+  push.qreg rax
+  push.qreg rcx
+  push.qreg rbx
+  NEXT
+
+defword DUP, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rax
+  push.qreg rax
+  push.qreg rax
+  NEXT
+
+;;;
+;;; Arithmetic routines
+;;; -------------------
+;;;
+;;;   No surprises here. Again, since our goal is to bootstrap the heap, we
+;;; keep it short. Also again, this is nowhere near optimal.
+;;;
+
+defword ADD, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  add.qreg.qreg rax, rbx
+  push.qreg rax
+  NEXT
+
+defword SUB, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  sub.qreg.qreg rax, rbx
+  push.qreg rax
+  NEXT
+
+defword MUL, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rax
+  pop.qreg rbx
+  mul.rax.qreg rbx
+  push.qreg rax
+  NEXT
+
+defword DIVMOD, 0
+  dq $ + 0x8                     ; codeword
+  xor.qreg.qreg rdx, rdx         ; rdx is the high bits of the input; zero it
+  pop.qreg rbx
+  pop.qreg rax
+  div.rdxrax.qreg rbx
+  push.qreg rdx                  ; remainder
+  push.qreg rax                  ; quotient
+  NEXT
+
+;;;
+;;; Comparison routines
+;;; -------------------
+;;;
+
+defword EQ, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  cmp.qreg.qreg rax, rbx
+  set.breg.cc al, equal
+  and.qreg.bimm rax, 0x01
+  push.qreg rax
+  NEXT
+
+defword NE, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  cmp.qreg.qreg rax, rbx
+  set.breg.cc al, not.equal
+  and.qreg.bimm rax, 0x01
+  push.qreg rax
+  NEXT
+
+defword GT, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  cmp.qreg.qreg rax, rbx
+  set.breg.cc al, greater
+  and.qreg.bimm rax, 0x01
+  push.qreg rax
+  NEXT
+
+; Is the top of the stack less than the second item in the stack?
+defword LT, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  cmp.qreg.qreg rax, rbx
+  set.breg.cc al, less
+  and.qreg.bimm rax, 0x01
+  push.qreg rax
+  NEXT
+
+defword GE, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  set.breg.cc al, greater.equal
+  cmp.qreg.qreg rax, rbx
+  push.qreg rax
+  NEXT
+
+defword LE, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  set.breg.cc al, less.equal
+  cmp.qreg.qreg rax, rbx
+  push.qreg rax
+  NEXT
+
+;;;
+;;; Bitwise routines
+;;; ----------------
+;;;
+
+defword AND, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  and.qreg.qreg rax, rbx
+  push.qreg rax
+  NEXT
+
+defword OR, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  or.qreg.qreg rax, rbx
+  push.qreg rax
+  NEXT
+
+defword XOR, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rbx
+  pop.qreg rax
+  xor.qreg.qreg rax, rbx
+  push.qreg rax
+  NEXT
+
+; The HP overloads the name "not", so we follow the Forth convention.
+defword INVERT, 0
+  dq $ + 0x8                     ; codeword
+  pop.qreg rax
+  not.qreg rax
+  push.qreg rax
+  NEXT
+
 ;;;
 ;;;   One of the most charming naming traditions in Forth is that the
 ;;; top-level word that stays running forever, is called "quit".