commit de2b6088f2390b38e07944df86592daf11994c72
parent 6e006dde851551905e33f9231d1b177fa6443e5a
Author: Virgil Dupras <hsoft@hardcoded.net>
Date: Fri, 15 Jul 2022 08:06:20 -0400
asm/i386: add all ops that the old assembler has
Diffstat:
2 files changed, 65 insertions(+), 45 deletions(-)
diff --git a/fs/asm/i386.fs b/fs/asm/i386.fs
@@ -3,22 +3,10 @@
\ attribute set.
\ MOD/RM constants
-0 const AX
-1 const CX
-2 const DX
-3 const BX
-4 const SP
-5 const BP
-6 const SI
-7 const DI
-0 const AL
-1 const CL
-2 const DL
-3 const BL
-4 const AH
-5 const CH
-6 const DH
-7 const BH
+0 const AX 1 const CX 2 const DX 3 const BX
+4 const SP 5 const BP 6 const SI 7 const DI
+0 const AL 1 const CL 2 const DL 3 const BL
+4 const AH 5 const CH 6 const DH 7 const BH
5 const MEM \ mod 0 + r/m 5 == abs memory
\ Size modes
@@ -39,6 +27,7 @@ SZ32 value opsz
: asm$ SZ32 to opsz 1 to opdirec 3 to opmod -1 to opreg -1 to oprm 0 to imm? ;
: _err abort" argument error" ;
: _assert not if _err then ;
+: w, here w! 2 allot ;
\ "Force" a value into opreg. If opreg is -1, we simply set it, but if it's set,
\ then we move opreg to oprm first.
@@ -54,27 +43,9 @@ SZ32 value opsz
\ Writing the operation
-\ Opcode format: when listing the opcode operations, there is often multiple
-\ elements in that number. These elements follow specific rules.
-\ As a general rule, the lower byte of the number contains the opcode to write
-\ down. However, for "modrm-enabled" ops (a lot of them), two bytes are used.
-\ The lower byte contains the "modrm" opcode. Only b7:2 are significant, b1:0,
-\ in which direction and size bits are or-ed in, must be unset. For example, for
-\ op "SUB", this field is $28.
-\ In addition to this lower byte, the 2nd byte of the opcode contains the "imm
-\ opcode", that is, the opcode for the "immediate" mode of the op as well as the
-\ register constant that replaces the "reg" field. Both these fields are or-ed
-\ in *the same byte*. Because, in imm mode, b7 is always set, we don't record
-\ it. We shift the rest of the opcode by 1. b1:0 are computed fields, so they're
-\ not part of the opcode. That leaves us 3 bits to store the "reg" field. For
-\ example, for "SUB", this byte is $05 ($80 << + /5)
-\ In addition to this, the 3rd byte of for the "AX" immediate shortcut opcode.
-\ If the op has a "shortcut" op for "immediate to AX/AL" operation, its opcode
-\ will be in the 3rd byte (with b1:0 unset). This byte is 0 if there is no
-\ shortcut. For example, this byte is $2c for "SUB".
-
+\ "opcode" needs to habe bits 1 and 0 *unset*
: op, ( opcode -- ) \ write "opcode", mixing it with opdirec and opsz
- opdirec << or opsz SZ8 = not or c, ;
+ opdirec << or opsz SZ8 = not or dup $ff > if w, else c, then ;
: modrm, ( -- ) \ write down modrm, errors out if not all parts are there.
opmod 3 lshift opreg or 3 lshift oprm or dup $100 < _assert c, ;
@@ -89,7 +60,7 @@ SZ32 value opsz
: imm, ( -- ) \ write down an immediate
imm opsz case
SZ32 of = , endof
- SZ16 of = here w! 2 allot endof
+ SZ16 of = w, endof
SZ8 of = c, endof
_err
endcase ;
@@ -97,10 +68,9 @@ SZ32 value opsz
: opmodrm, ( opcode -- ) \ write the operation in "modrm" mode
op, modrm, disp, asm$ ;
-: opimm, ( opcode -- ) \ write the operation in "immediate" mode
+: opimm, ( opcode opreg -- ) \ write the operation in "immediate" mode
0 to opdirec \ TODO: allow sign-extend by making this logic variable
- dup 8 rshift 7 and opreg! ( opcode )
- 9 rshift $fc and $80 or op, modrm, disp, imm, asm$ ;
+ opreg! op, modrm, disp, imm, asm$ ;
\ Setting arguments
@@ -123,7 +93,7 @@ SZ32 value opsz
opreg 0< if \ either it's the first argument, or the "other" is a mod/rm
to opreg
else \ second argument
- opmod 0< _assert \ more than 2 args!
+ oprm 0< _assert \ more than 2 args!
( reg ) to oprm
then ;
@@ -176,10 +146,27 @@ $0f84 op jz, $0f85 op jnz,
then ;
$04e9 op jmp, $02e8 op call,
+\ Single operand
+\ opcode format 00000000 00000rrr mmmmmmmm mmmmmm00
+\ r = opreg override
+\ m = modrm opcode
+: op ( reg opcode -- ) doer , does> @ ( opcode -- )
+ dup 16 rshift opreg! $ffff and opmodrm, ;
+$0400f7 op mul, $0300f7 op neg, $0200f7 op not,
+$0100ff op dec, $0000ff op inc,
+$009f0f op setg, $009c0f op setl, $00940f op setz, $00950f op setnz,
+
\ Two operands
+\ opcode format 00000000 ssssssss iiiiirrr mmmmmm00
+\ s = "shortcut" opcode, when target is AX
+\ i = immediate opcode, with b7 (always 1) is left off
+\ r = immediate opreg override
+\ m = modrm opcode
: op ( opcode -- ) doer , does> @ ( opcode )
- imm? if opimm, else opmodrm, then ;
-$040000 op add, $3c0738 op cmp, $2c0528 op sub, $a8f084 op test,
+ imm? if
+ 8 rshift dup >> $fc and $80 or swap 7 and ( opcode opreg ) opimm,
+ else $ff and opmodrm, then ;
+$040000 op add, $3c0738 op cmp, $2c0528 op sub, $a8f084 op _test,
$240420 op and, $0c0108 op or, $340630 op xor,
\ Shifts. They come in 2 versions. The "naked" version is imm-only. The "cl"
@@ -192,9 +179,17 @@ $04c1 op shl, $05c1 op shr,
imm? not _assert opreg! c, modrm, disp, asm$ ;
$04d3 op shlcl, $05d3 op shrcl,
-\ MOV is special
+\ Push/Pop
+: op ( op -- ) doer c, does> c@ ( opcode -- )
+ oprm 0< _assert opsz SZ8 = not _assert opreg or c, asm$ ;
+$58 op pop, $50 op push,
+
+\ MOV has this special reg<-imm shortcut
: mov,
imm? if opmod 3 = if \ mov reg, imm shortcut
$b0 opsz SZ8 = not 3 lshift or ( b0 or b8 ) opreg or c, imm, asm$
- else $c000 opimm, then
+ else $c0 0 opimm, then
else $88 opmodrm, then ;
+
+\ TEST can only have one direction
+: test, 0 to opdirec _test, ;
diff --git a/fs/tests/asm/i386.fs b/fs/tests/asm/i386.fs
@@ -40,4 +40,29 @@ code foo4
ret,
foo4 84 #eq
+
+\ test single operands
+code foo5
+ ax 42 i) mov,
+ bx 3 i) mov,
+ bx mul,
+ ax ax test,
+ bl setnz,
+ al bl add,
+ bp 4 i) sub,
+ bp 0 d) ax mov,
+ ret,
+
+foo5 127 #eq
+
+\ push/pop
+code foo6
+ ax 42 i) mov,
+ ax push,
+ dx pop,
+ bp 4 i) sub,
+ bp 0 d) dx mov,
+ ret,
+
+foo6 42 #eq
testend