From 2ef3227336008fffc19ec11e13c136eb39662c53 Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Fri, 24 Oct 2025 04:21:44 -0700 Subject: 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 --- quine.asm | 446 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file 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". -- cgit 1.4.1