summary refs log tree commit diff
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
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
-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".