1388 lines
42 KiB
Text
1388 lines
42 KiB
Text
~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
~ ~~ Assembly language for the AMD64 architecture ~~
|
|
~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
~
|
|
~ This is also often called the x86-64 architecture, but Intel didn't
|
|
~ invent it (they had their chance) and there's no reason to name it after
|
|
~ their product line. We have a bunch of assembler words that, taken as a
|
|
~ whole, form a sort of assembly language inside of the Forth-style language.
|
|
~
|
|
~ It's all backwards and stuff.
|
|
~
|
|
~ Okay, but seriously, the convention is: target on the top of the stack,
|
|
~ source behind it. This is similar to how the Forth "!" and "@" words work.
|
|
~
|
|
~ These routines use the binary packing routines such as pack64, defined in
|
|
~ core.e. They're called in the same way: an output address which we call the
|
|
~ "output point", followed by data items specific to what's being output. They
|
|
~ also chain together in the same way, returning the updated output point.
|
|
~
|
|
~ TODO cite the Intel reference manual here and explain the notation used
|
|
~ for the section citations below
|
|
~
|
|
~ TODO define instructions, assembly code, machine code, opcodes. if we ever
|
|
~ also want to recommend a childrens' introduction to binary, this might be
|
|
~ the place to do it.
|
|
~
|
|
~
|
|
~ Hex-transformed output
|
|
~ ~~~~~~~~~~~~~~~~~~~~~~
|
|
~
|
|
~ In addition to the actual semantics of the assembly words, there's an
|
|
~ important layer of meaning atop them. The hex transform, described in detail
|
|
~ in transform.e, modifies a compilation process to produce a commented hex
|
|
~ dump instead of a binary. It includes a magic-comment feature which allows
|
|
~ words that have opinions on what their output's hex dump should look like to
|
|
~ express them.
|
|
~
|
|
~ The code in this file makes extensive use of the magic-comment feature.
|
|
~ Any comment which starts with : as its first word is a magic comment meant
|
|
~ for the hex transform. When the hex transform is not in use, these are
|
|
~ ordinary comments and are ignored. When it is in use, they create and modify
|
|
~ metadata annotations in a complex way.
|
|
~
|
|
~ The magic comments in this file follows two important conventions: First,
|
|
~ entries are created by whichever word directly corresponds to a top-level
|
|
~ assembly instruction from the perspective of someone programming with it,
|
|
~ at some point prior to calling any word which might pack output. Second,
|
|
~ output bytes are accounted for by whichever word directly calls a pack* word
|
|
~ for them.
|
|
~
|
|
~ Thus, for example, the word "syscall" has a comment which creates an entry
|
|
~ describing its length as 2 bytes, which never varies.
|
|
~
|
|
~ The word "mov-reg64-imm32", on the other hand, has a comment which
|
|
~ creates an entry describing is length as 5 bytes - the opcode and the
|
|
~ immediate value, which are output with calls to pack8 and pack32 in that
|
|
~ word. However, the actual length of the instruction is 5 bytes. When
|
|
~ mov-reg64-imm32 calls rex-w, a magic comment there modifies the most recent
|
|
~ entry in-place, marking it as being one byte longer. Subsequently,
|
|
~ mov-reg64-imm32 calls addressing-reg64, which calls modrm, which again
|
|
~ modifies the same entry. Notice that addressing-reg64 didn't need to do
|
|
~ anything, because it isn't directly responsible for any packing.
|
|
~
|
|
~ Notice that when an entry is modified in place, it needs to have been
|
|
~ created first. Attempting to do it in the wrong order will have an
|
|
~ undesired effect, most likely editing the entry of an unrelated instruction.
|
|
~ The hex transform doesn't have detailed knowledge of assembly, and isn't in
|
|
~ a position to know that the edits that are happening are sensible.
|
|
~
|
|
~ Hopefully these examples show why an accounting convention is needed. The
|
|
~ good news is that the convention is quite robust and needs essentially no
|
|
~ play-by-play commentary, just this explainer at the top.
|
|
|
|
|
|
~ Keywords
|
|
~ ~~~~~~~~
|
|
~
|
|
~ We define a bunch of keywords, which evaluate to their own codeword
|
|
~ addresses. We use these to refer to registers and condition codes by name.
|
|
~
|
|
~
|
|
~ On registers
|
|
~ ~~~~~~~~~~~~
|
|
~
|
|
~ The x86 architecture has been around a while, it has been through
|
|
~ several transitions from smaller word sizes to larger ones. Therefore it
|
|
~ has different names for the "same" registers, depending on how much of
|
|
~ them you're using.
|
|
~
|
|
~ TODO there's more to write here
|
|
|
|
~ The names of the 64-bit registers. The second half of these are considered
|
|
~ "extended" registers because they don't correspond to 32-bit registers in
|
|
~ the way the first eight do.
|
|
s" :rax" keyword
|
|
s" :rcx" keyword
|
|
s" :rdx" keyword
|
|
s" :rbx" keyword
|
|
s" :rsp" keyword
|
|
s" :rbp" keyword
|
|
s" :rsi" keyword
|
|
s" :rdi" keyword
|
|
s" :r8" keyword
|
|
s" :r9" keyword
|
|
s" :r10" keyword
|
|
s" :r11" keyword
|
|
s" :r12" keyword
|
|
s" :r13" keyword
|
|
s" :r14" keyword
|
|
s" :r15" keyword
|
|
|
|
~ The names of the 32-bit registers. The processor treats these as being
|
|
~ alternate names for the low halves of the 64-bit registers. There is a
|
|
~ very finicky distinction about what that means in different settings: Some
|
|
~ instructions operate on a 32-bit source or target, while others merely
|
|
~ accept a 32-bit value that gets sign-extended to 64 bits. We've taken pains
|
|
~ to clarify these cases in the instruction-specific notes, as they come up.
|
|
s" :eax" keyword
|
|
s" :ecx" keyword
|
|
s" :edx" keyword
|
|
s" :ebx" keyword
|
|
s" :esp" keyword
|
|
s" :ebp" keyword
|
|
s" :esi" keyword
|
|
s" :edi" keyword
|
|
|
|
~ The names of the 16-bit registers. Similarly, the processor treats these
|
|
~ as being alternate names for the low halves of the 32-bit registers.
|
|
s" :ax" keyword
|
|
s" :cx" keyword
|
|
s" :dx" keyword
|
|
s" :bx" keyword
|
|
s" :sp" keyword
|
|
s" :bp" keyword
|
|
s" :si" keyword
|
|
s" :di" keyword
|
|
|
|
~ The names of the 8-bit registers. The pattern here is a little bit
|
|
~ different; these come in "low" and "high" pairs, where for example :al is
|
|
~ the low half of :ax and :ah is the high half. Yes, this architecture grows
|
|
~ like a tree, with all the old things being still present, surrounded in the
|
|
~ new ones.
|
|
s" :al" keyword
|
|
s" :cl" keyword
|
|
s" :dl" keyword
|
|
s" :bl" keyword
|
|
s" :ah" keyword
|
|
s" :ch" keyword
|
|
s" :dh" keyword
|
|
s" :bh" keyword
|
|
|
|
~ The condition codes. Yes, there sure is a lot of duplication in these
|
|
~ names. The names are based on Intel's documented mnemonics...
|
|
~
|
|
~ "Above" and "below" are for unsigned comparisons. "Greater" and "less" are
|
|
~ for signed comparisons.
|
|
~
|
|
~ This is documented on the individual opcode pages, and also in B.1.4.7.
|
|
s" :cc-overflow" keyword
|
|
s" :cc-no-overflow" keyword
|
|
s" :cc-below" keyword
|
|
s" :cc-above-equal" keyword
|
|
s" :cc-equal" keyword
|
|
s" :cc-not-equal" keyword
|
|
s" :cc-below-equal" keyword
|
|
s" :cc-above" keyword
|
|
s" :cc-sign" keyword
|
|
s" :cc-not-sign" keyword
|
|
s" :cc-even" keyword
|
|
s" :cc-odd" keyword
|
|
s" :cc-less" keyword
|
|
s" :cc-greater-equal" keyword
|
|
s" :cc-less-equal" keyword
|
|
s" :cc-greater" keyword
|
|
|
|
|
|
~ Bits and pieces
|
|
~ ~~~~~~~~~~~~~~~
|
|
~
|
|
~ Here, we have a bunch of helpers which generate specific encoded
|
|
~ representations that are part of many instructions. We start with the
|
|
~ trivial ones that handle individual fields, then work up to combinations of
|
|
~ fields.
|
|
~
|
|
~ When we say that a word accepts a register as a parameter, what we mean
|
|
~ is it accepts the name keyword for that register. When we say that a word
|
|
~ accepts a scale factor, what we mean is that it accepts a byte count for
|
|
~ that scale factor. In the cases where we mean the encoded form, we'll say
|
|
~ "encoded value" or "value".
|
|
~
|
|
~ (register -- 3-bit encoded value for register)
|
|
: reg64
|
|
~ : provide-keyword
|
|
dup :rax = { drop 0 exit } if
|
|
dup :rcx = { drop 1 exit } if
|
|
dup :rdx = { drop 2 exit } if
|
|
dup :rbx = { drop 3 exit } if
|
|
dup :rsp = { drop 4 exit } if
|
|
dup :rbp = { drop 5 exit } if
|
|
dup :rsi = { drop 6 exit } if
|
|
dup :rdi = { drop 7 exit } if
|
|
." Parameter to reg64 is not a reg64." 1 sys-exit ;
|
|
|
|
~ (register -- 3-bit encoded value for register)
|
|
: extrareg64
|
|
~ : provide-keyword
|
|
dup :r8 = { drop 0 exit } if
|
|
dup :r9 = { drop 1 exit } if
|
|
dup :r10 = { drop 2 exit } if
|
|
dup :r11 = { drop 3 exit } if
|
|
dup :r12 = { drop 4 exit } if
|
|
dup :r13 = { drop 5 exit } if
|
|
dup :r14 = { drop 6 exit } if
|
|
dup :r15 = { drop 7 exit } if
|
|
." Parameter to extrareg64 is not an extrareg64." 1 sys-exit ;
|
|
|
|
~ (register -- 3-bit encoded value for register)
|
|
: reg32
|
|
~ : provide-keyword
|
|
dup :eax = { drop 0 exit } if
|
|
dup :ecx = { drop 1 exit } if
|
|
dup :edx = { drop 2 exit } if
|
|
dup :ebx = { drop 3 exit } if
|
|
dup :esp = { drop 4 exit } if
|
|
dup :ebp = { drop 5 exit } if
|
|
dup :esi = { drop 6 exit } if
|
|
dup :edi = { drop 7 exit } if
|
|
." Parameter to reg32 is not a reg32." 1 sys-exit ;
|
|
|
|
~ (register -- 3-bit encoded value for register)
|
|
: reg16
|
|
~ : provide-keyword
|
|
dup :ax = { drop 0 exit } if
|
|
dup :cx = { drop 1 exit } if
|
|
dup :dx = { drop 2 exit } if
|
|
dup :bx = { drop 3 exit } if
|
|
dup :sp = { drop 4 exit } if
|
|
dup :bp = { drop 5 exit } if
|
|
dup :si = { drop 6 exit } if
|
|
dup :di = { drop 7 exit } if
|
|
." Parameter to reg16 is not a reg16." 1 sys-exit ;
|
|
|
|
~ (register -- 3-bit encoded value for register)
|
|
: reg8
|
|
~ : provide-keyword
|
|
dup :al = { drop 0 exit } if
|
|
dup :cl = { drop 1 exit } if
|
|
dup :dl = { drop 2 exit } if
|
|
dup :bl = { drop 3 exit } if
|
|
dup :ah = { drop 4 exit } if
|
|
dup :ch = { drop 5 exit } if
|
|
dup :dh = { drop 6 exit } if
|
|
dup :bh = { drop 7 exit } if
|
|
." Parameter to reg8 is not a reg8." 1 sys-exit ;
|
|
|
|
|
|
~ There's a packed format called the SIB byte, which we'll get to in a
|
|
~ second. One of its bitfields is called the scale field. This word produces
|
|
~ an encoded value for that field.
|
|
~
|
|
~ The input value is a byte count; the output value is suitable for use in
|
|
~ the SIB byte.
|
|
~
|
|
~ (scale factor -- 2-bit encoded value)
|
|
: scalefield
|
|
~ : provide-decimal
|
|
dup 1 = { drop 0 exit } if
|
|
dup 2 = { drop 1 exit } if
|
|
dup 4 = { drop 2 exit } if
|
|
dup 8 = { drop 3 exit } if
|
|
." Parameter to scalefield is not 1, 2, 4, or 8." 1 sys-exit ;
|
|
|
|
|
|
~ [Intel] volume 2D, appendix B, section B-1.4.7, table B-10. Also see the
|
|
~ individual opcode pages.
|
|
~
|
|
~ Every instruction has an "opcode", a specific byte or sequence of bytes
|
|
~ which uniquely identifies the combination of operation, addressing mode,
|
|
~ and certain miscellaneous characteristics. This is not just another way of
|
|
~ referring to the entire sequence of bytes corresponding to the instruction;
|
|
~ the opcode is a specific part within that, as distinct from ie. the rex
|
|
~ byte, the SIB byte, the Mod/RM byte, and various immediate values and other
|
|
~ rare tidbits.
|
|
~
|
|
~ Some of these opcodes have bitfields within them, to specify condition
|
|
~ codes. This word produces an encoded value for that condition-code field.
|
|
~
|
|
~ (condition -- 4-bit encoded value)
|
|
: condition-code
|
|
~ : provide-keyword
|
|
dup :cc-overflow = { drop 0 exit } if
|
|
dup :cc-no-overflow = { drop 1 exit } if
|
|
dup :cc-below = { drop 2 exit } if
|
|
dup :cc-above-equal = { drop 3 exit } if
|
|
dup :cc-equal = { drop 4 exit } if
|
|
dup :cc-not-equal = { drop 5 exit } if
|
|
dup :cc-below-equal = { drop 6 exit } if
|
|
dup :cc-above = { drop 7 exit } if
|
|
dup :cc-sign = { drop 8 exit } if
|
|
dup :cc-not-sign = { drop 9 exit } if
|
|
dup :cc-even = { drop 10 exit } if
|
|
dup :cc-odd = { drop 11 exit } if
|
|
dup :cc-less = { drop 12 exit } if
|
|
dup :cc-greater-equal = { drop 13 exit } if
|
|
dup :cc-less-equal = { drop 14 exit } if
|
|
dup :cc-greater = { drop 15 exit } if
|
|
." Parameter to condition-code is not a condition code." 1 sys-exit ;
|
|
|
|
|
|
~ The "rex" byte appears before an opcode to modify its behavior in various
|
|
~ ways. It has four distinct bits within it, leading to sixteen variations,
|
|
~ as you can see.
|
|
~
|
|
~ The way these are all spelled out like this is slightly ridiculous, there
|
|
~ must be a better way. We only ever use rex-w and rex-wb, so it's tempting to
|
|
~ get rid of the rest, but they're worth having so that our future selves
|
|
~ don't have to revisit this topic.
|
|
~
|
|
~ (output point -- output point)
|
|
: rex-0
|
|
~ : 1 adjust-length
|
|
0x40 pack8 ;
|
|
: rex-w
|
|
~ : 1 adjust-length
|
|
0x48 pack8 ;
|
|
: rex-r
|
|
~ : 1 adjust-length
|
|
0x44 pack8 ;
|
|
: rex-x
|
|
~ : 1 adjust-length
|
|
0x42 pack8 ;
|
|
: rex-b
|
|
~ : 1 adjust-length
|
|
0x41 pack8 ;
|
|
: rex-wr
|
|
~ : 1 adjust-length
|
|
0x4C pack8 ;
|
|
: rex-wx
|
|
~ : 1 adjust-length
|
|
0x4A pack8 ;
|
|
: rex-wb
|
|
~ : 1 adjust-length
|
|
0x49 pack8 ;
|
|
: rex-rx
|
|
~ : 1 adjust-length
|
|
0x46 pack8 ;
|
|
: rex-rb
|
|
~ : 1 adjust-length
|
|
0x45 pack8 ;
|
|
: rex-xb
|
|
~ : 1 adjust-length
|
|
0x43 pack8 ;
|
|
: rex-wrx
|
|
~ : 1 adjust-length
|
|
0x4E pack8 ;
|
|
: rex-wrb
|
|
~ : 1 adjust-length
|
|
0x4D pack8 ;
|
|
: rex-wxb
|
|
~ : 1 adjust-length
|
|
0x4B pack8 ;
|
|
: rex-rxb
|
|
~ : 1 adjust-length
|
|
0x47 pack8 ;
|
|
: rex-wrxb
|
|
~ : 1 adjust-length
|
|
0x4F pack8 ;
|
|
|
|
|
|
~ Some opcodes use their low three bits as a field to give a register name.
|
|
~ This is usually in addition to a register name given in a Mod/RM byte,
|
|
~ serving a different role for the instruction.
|
|
~
|
|
~ This word accepts an opcode byte with those three bits clear, and combines
|
|
~ it with a register value, then outputs the resulting byte. Each opcode
|
|
~ accepts some specific kind of register; to allow different kinds, here we
|
|
~ expect the step of converting the register name to the encoded bits to have
|
|
~ already been done.
|
|
~
|
|
~ (output point, 3-bit encoded value for register, opcode byte
|
|
~ -- output point)
|
|
: opcodereg
|
|
~ : 1 adjust-length
|
|
| pack8 ;
|
|
|
|
|
|
~ Some opcodes use their low four bits as a field to give a condition code.
|
|
~ This word accepts an opcode byte with those four bits clear, and combines it
|
|
~ with a condition code value, then outputs the resulting byte. For
|
|
~ consistency with opcodereg, we expect the step of converting the condition
|
|
~ code name to the encoded bits to have already been done.
|
|
~
|
|
~ (output point, 4-bit encoded value for condition code, opcode byte
|
|
~ -- output point)
|
|
: opcodecc
|
|
~ : 1 adjust-length
|
|
| pack8 ;
|
|
|
|
|
|
~ A Mod/RM byte ("mode / register-or-memory") is part of the encoding of
|
|
~ many instructions. It's divided into three fields: "mod" (mode),
|
|
~ register/opcode, and register/memory ("RM").
|
|
~
|
|
~ This word outputs a Mod/RM byte given fully-processed, numeric values for
|
|
~ its fields. Most code will want to call one of the higher-level
|
|
~ addressing-* words, instead.
|
|
~
|
|
~ (output point, mod field, register/opcode field, register/memory field
|
|
~ -- output point)
|
|
: modrm
|
|
~ : 1 adjust-length
|
|
swap 8 * | swap 64 * | pack8 ;
|
|
|
|
~ An SIB byte ("scale, index, base") is part of the encoding of many
|
|
~ instructions. It's divided into three fields, with the names you've already
|
|
~ guessed.
|
|
~
|
|
~ This word outputs an SIB byte given fully-processed, numeric values for
|
|
~ its fields.
|
|
~
|
|
~ (output point, scale field, index field, base field -- output point)
|
|
: sib
|
|
~ : 1 adjust-length
|
|
swap 8 * | swap 64 * | pack8 ;
|
|
|
|
|
|
~ Addressing modes
|
|
~ ~~~~~~~~~~~~~~~~
|
|
~
|
|
~ These are higher-level words meant to be easier to use than the bits and
|
|
~ pieces above. Each corresponds to some specific addressing mode. When
|
|
~ applicable, they accept keywords rather than pre-encoded values.
|
|
~
|
|
~ That's not all the time, because there are cases, such as the reg/op
|
|
~ field, where the meaning is up to the individual instruction. In those
|
|
~ cases, these words do accept fully-processed, numeric values.
|
|
~
|
|
~ The general rule is that the responsibility of these addressing-mode words
|
|
~ is for the parts that are common to all instructions using that addressing
|
|
~ mode.
|
|
|
|
|
|
~ The simplest of the addressing modes: Direct register addressing. There
|
|
~ are no special cases to check.
|
|
~
|
|
~ It's important to notice that the R/M field may describe either a source,
|
|
~ or a target, depending on what the instruction is. So, this helper doesn't
|
|
~ get to know that. It also doesn't get to know whether the value in the
|
|
~ reg/op field describes a register, or if instead it's an extension of the
|
|
~ opcode. The caller is responsible for figuring that all out.
|
|
~
|
|
~ (output point, reg/op field value, reg/mem field register
|
|
~ -- output point)
|
|
: addressing-reg64 reg64 3 3unroll modrm ;
|
|
: addressing-reg8 reg8 3 3unroll modrm ;
|
|
|
|
|
|
~ This is a helper for assembly instructions that want to do a form of
|
|
~ addressing that requires a value of 1 in the modrm byte's mode field, and
|
|
~ do not want to do any indexing. That's the indirect mode, which takes a
|
|
~ 64-bit register, treats it as an address, and looks up the 64-bit value it
|
|
~ points to.
|
|
~
|
|
~ The helper's main responsibility is to deal with the scenario that
|
|
~ requires an SIB byte, which happens when the R/M field has a value of 4,
|
|
~ which would otherwise refer to the register rsp. In that situation, it also
|
|
~ generates an SIB byte which indicates a scale of 1, no indexing, and rsp as
|
|
~ the base register.
|
|
~
|
|
~ When the register is :rbp, the only modes available also have
|
|
~ displacement; we disallow that. For that case, use an instruction that
|
|
~ uses a disp8 mode, and set a displacement of 0.
|
|
~
|
|
~ In understanding this, pay close attention to the Op/En column in the
|
|
~ opcode table. The "RM" variant means the ModRM byte's R/M field (the third
|
|
~ one) is the source, while its reg field (the middle one) is the target. This
|
|
~ is what we want, because the R/M field is the one that gets indirection
|
|
~ applied to it. Opcode 0x8B with an REX.W prefix is the all-64-bit RM
|
|
~ variant. [Intel] volume 2B, chapter 4, section 4-3, "MOV".
|
|
~
|
|
~ For the indirection modes, don't be confused by the many similar tables.
|
|
~ 64-bit mode is encoded the same as 32-bit mode except for adding a REX.W
|
|
~ prefix, as per 2.2.1.1, so you want table 2-2 to understand the ModRM byte.
|
|
~ The presence or absence of an SIB byte is determined by where in that table
|
|
~ we fall, and we aren't using a mode that has one. [Intel] volume 2A,
|
|
~ chapter 2, section 2-1.5, table 2-2.
|
|
~
|
|
~ (output point, reg/op field value, reg/mem field register
|
|
~ -- output point)
|
|
: addressing-indirect-reg64
|
|
~ Exit with an error if the R/M register is :rbp.
|
|
dup :rbp != {
|
|
~ Check whether the R/M register is :rsp. Save the test result for later.
|
|
dup :rsp = 4 unroll
|
|
~ (equality result, output point, reg/op value, reg/mem name)
|
|
reg64 0 3unroll modrm
|
|
~ (equality result, output point)
|
|
~ If the R/M register was rsp, we need an SIB byte; otherwise, skip it.
|
|
swap {
|
|
0 4 :rsp reg64
|
|
~ : drop-subitem
|
|
sib
|
|
} if
|
|
exit
|
|
} if
|
|
." R/M parameter to addressing-indirect-reg64 is :rbp." 1 sys-exit ;
|
|
|
|
~ (output point, reg/op field value, reg/mem field register,
|
|
~ displacement value -- output point)
|
|
: addressing-disp8-reg64
|
|
~ This mode can do :rbp fine, so no need to check for that.
|
|
~ Check whether the R/M register is :rsp. Save the test result for later.
|
|
swap dup :rsp = 5 unroll swap
|
|
~ Stash the displacement value out of the way, too.
|
|
4 unroll
|
|
reg64 1 3unroll modrm
|
|
~ If the R/M register was rsp, we need an SIB byte; otherwise, skip it.
|
|
3roll {
|
|
0 4 :rsp reg64
|
|
~ : drop-subitem
|
|
sib
|
|
} if
|
|
|
|
~ The displacement byte.
|
|
~
|
|
~ When addressing-disp8-reg64 references a source, as in
|
|
~ lea-reg64-disp8-reg64, the first two parameters of the assembly word
|
|
~ calling it are the source register and the source displacement value. When
|
|
~ it references a target or destination, as in mov-disp8-reg64-reg64, the
|
|
~ LAST two parameters are the target registera and target displacement
|
|
~ value.
|
|
~
|
|
~ In both cases, the register comes first and the displacement value
|
|
~ comes second. When these are used in a comment template without any other
|
|
~ rearranging going on, whichever is on the top of the stack will be popped
|
|
~ off first. So, we swap them here, thus putting the register on the top.
|
|
swap
|
|
~ : 1 adjust-length
|
|
~ : provide-hex8
|
|
~ : swap-subitems
|
|
pack8 ;
|
|
|
|
~ (output point, reg/op field value, reg/mem field register,
|
|
~ displacement value -- output point)
|
|
: addressing-disp32-reg64
|
|
~ This mode can do :rbp fine, so no need to check for that.
|
|
~ Check whether the R/M register is :rsp. Save the test result for later.
|
|
swap dup :rsp = 5 unroll swap
|
|
~ Stash the displacement value out of the way, too.
|
|
4 unroll
|
|
reg64 2 3unroll modrm
|
|
~ If the R/M register was rsp, we need an SIB byte; otherwise, skip it.
|
|
3roll {
|
|
0 4 :rsp reg64
|
|
~ : drop-subitem
|
|
sib
|
|
} if
|
|
|
|
~ The displacement value. This is subject to the same considerations as in
|
|
~ addressing-disp8-reg64.
|
|
swap
|
|
~ : 4 adjust-length
|
|
~ : provide-hex32
|
|
~ : swap-subitems
|
|
pack32 ;
|
|
|
|
~ (output point, reg/op field value,
|
|
~ scale factor, index register, base field register
|
|
~ -- output point)
|
|
: addressing-indexed-reg64
|
|
~ Exit with an error if the base register is :rbp.
|
|
dup :rbp != {
|
|
~ Reg/mem value 4 means to use an SIB byte (at least, with this mode).
|
|
5 roll 0 6 roll 4 modrm 4 unroll
|
|
reg64 3unroll reg64 3unroll scalefield 3unroll
|
|
~ We want the scale factor to show after the registers, so we put it
|
|
~ last on the stack. We also want the base register to show before the
|
|
~ index register, so the base comes first.
|
|
~
|
|
~ Importantly, we have to do this before the sib so it'll count as being
|
|
~ inside the span the assembly word's description attaches to.
|
|
~
|
|
~ : -3 roll-subitems
|
|
~ : swap-subitems
|
|
sib
|
|
exit
|
|
} if
|
|
." Base parameter to addressing-indexed-reg64 is :rbp." 1 sys-exit ;
|
|
|
|
~ (output point, reg/op field value,
|
|
~ scale factor, index register, base field register,
|
|
~ displacement value -- output point)
|
|
: addressing-disp8-indexed-reg64
|
|
~ This mode can do :rbp fine, so no need to check for that.
|
|
~ Reg/mem value 4 means to use an SIB byte (at least, with this mode).
|
|
6 roll 1 7 roll 4 modrm 5 unroll
|
|
5 unroll reg64 3unroll reg64 3unroll scalefield 3unroll sib
|
|
swap
|
|
~ : 1 adjust-length
|
|
~ : provide-hex8
|
|
~
|
|
~ We want the parameters to be shown in the order:
|
|
~
|
|
~ base register, index register, index scale factor, displacement
|
|
~
|
|
~ So, we put them on the stack in the opposite of that order.
|
|
~
|
|
~ Importantly, we have to do this before the pack8 so it'll count as being
|
|
~ inside the span the assembly word's description attaches to.
|
|
~
|
|
~ : -4 roll-subitems
|
|
~ : -3 roll-subitems
|
|
~ : swap-subitems
|
|
pack8
|
|
;
|
|
|
|
|
|
~ Easy instructions
|
|
~ ~~~~~~~~~~~~~~~~~
|
|
~
|
|
~ It's not worth pretending there's a coherent category behind this
|
|
~ grouping. These are the ones that were easy to deal with.
|
|
|
|
~ (output point -- output point)
|
|
: cld
|
|
~ : 1 cld
|
|
0xFC pack8 ;
|
|
: std
|
|
~ : 1 std
|
|
0xFD pack8 ;
|
|
: syscall
|
|
~ : 2 syscall
|
|
0x0F pack8 0x05 pack8 ;
|
|
: hlt
|
|
~ : 1 hlt
|
|
0xF4 pack8 ;
|
|
|
|
~ (output point, source register -- output point)
|
|
: push-reg64
|
|
~ : 0 # push-reg64
|
|
reg64 0x50 opcodereg ;
|
|
: push-extrareg64
|
|
~ : 0 # push-extrareg64
|
|
swap rex-b swap extrareg64 0x50 opcodereg ;
|
|
~ Note the use of the B rex bit here; this instruction puts the register
|
|
~ number in the opcode field, so it uses Table 3-1.
|
|
|
|
~ (output point, target register -- output point)
|
|
: pop-reg64
|
|
~ : 0 # pop-reg64
|
|
reg64 0x58 opcodereg ;
|
|
: pop-extrareg64
|
|
~ : 0 # pop-extrareg64
|
|
swap rex-b swap extrareg64 0x58 opcodereg ;
|
|
~ Note the use of the B rex bit here; this instruction puts the register
|
|
~ number in the opcode field, so it uses Table 3-1.
|
|
|
|
~ (output point, immediate value -- output point)
|
|
: push-imm32-extended64
|
|
~ : 5 # push-imm32-extended64
|
|
swap 0x68 pack8 swap
|
|
~ : provide-hex
|
|
pack32 ;
|
|
|
|
~ (output point, source register, source displacement value, target register
|
|
~ -- output point)
|
|
: lea-reg64-disp8-reg64
|
|
~ : 1 # # # lea-reg64-disp8-reg64
|
|
4 roll rex-w 0x8D pack8 4 unroll
|
|
reg64 3unroll addressing-disp8-reg64 ;
|
|
|
|
~ (output point, source register, source displacement value, target register
|
|
~ -- output point)
|
|
: lea-reg64-disp32-reg64
|
|
~ : 1 # # # lea-reg64-disp32-reg64
|
|
4 roll rex-w 0x8D pack8 4 unroll
|
|
reg64 3unroll addressing-disp32-reg64 ;
|
|
|
|
~ (output point,
|
|
~ source base register, source index register, source index scale factor,
|
|
~ target register -- output point)
|
|
: lea-reg64-indexed-reg64
|
|
~ : 1 # # # # lea-reg64-indexed-reg64
|
|
5 roll rex-w 0x8D pack8 5 unroll
|
|
reg64 4 unroll 3unroll swap addressing-indexed-reg64 ;
|
|
|
|
~ (output point,
|
|
~ source base register, source index register, source index scale factor,
|
|
~ source displacement value,
|
|
~ target register -- output point)
|
|
: lea-reg64-disp8-indexed-reg64
|
|
~ : 1 # # # # # lea-reg64-disp8-indexed-reg64
|
|
6 roll rex-w 0x8D pack8 6 unroll
|
|
reg64 5 unroll 3 roll 4 roll 3 roll addressing-disp8-indexed-reg64 ;
|
|
|
|
|
|
~ Move instructions
|
|
~ ~~~~~~~~~~~~~~~~~
|
|
~
|
|
~ These are, like, MOST of what we care about, so they get their own
|
|
~ section. Although it's very much the case that almost every two-operand
|
|
~ instruction offers this many distinct modes, we don't care about most of
|
|
~ those and don't yet implement them. We do care about all the modes for move
|
|
~ instructions.
|
|
~
|
|
~ Someday perhaps we'll have extra-high-level features which generate all
|
|
~ the distinct versions of each instruction in a concise way, but that is not
|
|
~ this day.
|
|
|
|
~ (output point, immediate value, register -- output point)
|
|
: mov-reg64-imm32
|
|
~ : 5 # # mov-reg64-imm32
|
|
3roll
|
|
rex-w 0xC7 pack8 swap
|
|
0 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack32 ;
|
|
: mov-reg64-imm64
|
|
~ : 8 # # mov-reg64-imm64
|
|
3roll rex-w swap reg64 0xB8 opcodereg swap
|
|
~ : provide-hex
|
|
pack64 ;
|
|
: mov-extrareg64-imm64
|
|
~ : 8 # # mov-extrareg64-imm64
|
|
~ Note the use of the B rex bit here; this instruction puts the register
|
|
~ number in the opcode field, so it uses Table 3-1.
|
|
3roll rex-wb swap extrareg64 0xB8 opcodereg swap
|
|
~ : provide-hex
|
|
pack64 ;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-reg64-reg64
|
|
~ : 1 # # mov-reg64-reg64
|
|
3roll rex-w 0x89 pack8 3unroll
|
|
swap reg64 swap addressing-reg64
|
|
~ : swap-subitems
|
|
;
|
|
: mov-indirect-reg64-reg64
|
|
~ : 1 # # mov-indirect-reg64-reg64
|
|
3roll rex-w 0x89 pack8 3unroll
|
|
swap reg64 swap addressing-indirect-reg64
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register, target displacement value
|
|
~ -- output point)
|
|
: mov-disp8-reg64-reg64
|
|
~ : 1 # # # mov-disp8-reg64-reg64
|
|
4 roll rex-w 0x89 pack8 4 unroll
|
|
3roll reg64 3unroll addressing-disp8-reg64
|
|
~ : 3 roll-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-reg64-indirect-reg64
|
|
~ : 1 # # mov-reg64-indirect-reg64
|
|
3roll rex-w 0x8B pack8 3unroll
|
|
reg64 swap addressing-indirect-reg64 ;
|
|
|
|
~ (output point, source register, source displacement value, target register
|
|
~ -- output point)
|
|
: mov-reg64-disp8-reg64
|
|
~ : 1 # # # mov-reg64-disp8-reg64
|
|
4 roll rex-w 0x8B pack8 4 unroll
|
|
reg64 3unroll addressing-disp8-reg64 ;
|
|
: mov-reg64-disp32-reg64
|
|
~ : 1 # # # mov-reg64-disp32-reg64
|
|
4 roll rex-w 0x89 pack8 4 unroll
|
|
3roll reg64 swap 3roll addressing-disp32-reg64
|
|
~ : swap-subitems
|
|
~ : 3 roll-subitems
|
|
;
|
|
|
|
~ (output point,
|
|
~ source base register, source index register, source index scale factor,
|
|
~ target register -- output point)
|
|
: mov-reg64-indexed-reg64
|
|
~ : 1 # # # # mov-reg64-indexed-reg64
|
|
5 roll rex-w 0x8B pack8 5 unroll
|
|
reg64 4 unroll 3unroll swap addressing-indexed-reg64 ;
|
|
|
|
~ (output point, source register,
|
|
~ target base register, target index register, target index scale factor
|
|
~ -- output point)
|
|
: mov-indexed-reg64-reg64
|
|
~ : 1 # # # # mov-indexed-reg64-reg64
|
|
5 roll rex-w 0x89 pack8 5 unroll
|
|
4 roll reg64 4 unroll
|
|
3unroll swap addressing-indexed-reg64
|
|
~ : 4 roll-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-indirect-reg64-reg32
|
|
~ : 1 # # mov-indirect-reg64-reg32
|
|
3roll 0x89 pack8 3unroll
|
|
swap reg32 swap addressing-indirect-reg64
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register, target displacement value
|
|
~ -- output point)
|
|
: mov-disp8-reg64-reg32
|
|
~ : 1 # # # mov-disp8-reg64-reg32
|
|
4 roll 0x89 pack8 4 unroll
|
|
3roll reg32 3unroll addressing-disp8-reg64
|
|
~ : 3 roll-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-reg32-indirect-reg64
|
|
~ : 1 # # mov-reg32-indirect-reg64
|
|
3roll 0x8B pack8 3unroll
|
|
reg32 swap addressing-indirect-reg64 ;
|
|
|
|
~ (output point, source register, source displacement value, target register
|
|
~ -- output point)
|
|
: mov-reg32-disp8-reg64
|
|
~ : 1 # # # mov-reg32-disp8-reg64
|
|
4 roll 0x8B pack8 4 unroll
|
|
reg32 3unroll addressing-disp8-reg64 ;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-indirect-reg64-reg16
|
|
~ : 2 # # mov-indirect-reg64-reg16
|
|
3roll 0x66 pack8 0x89 pack8 3unroll
|
|
swap reg16 swap addressing-indirect-reg64
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register, target displacement value
|
|
~ -- output point)
|
|
: mov-disp8-reg64-reg16
|
|
~ : 2 # # # mov-disp8-reg64-reg16
|
|
4 roll 0x66 pack8 0x89 pack8 4 unroll
|
|
3roll reg16 3unroll addressing-disp8-reg64
|
|
~ : 3 roll-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-reg16-indirect-reg64
|
|
~ : 2 # # mov-reg16-indirect-reg64
|
|
3roll 0x66 pack8 0x8B pack8 3unroll
|
|
reg16 swap addressing-indirect-reg64 ;
|
|
|
|
~ (output point, source register, target displacement value, target register
|
|
~ -- output point)
|
|
: mov-reg16-disp8-reg64
|
|
~ : 2 # # # mov-reg16-disp8-reg64
|
|
4 roll 0x66 pack8 0x8B pack8 4 unroll
|
|
reg16 3unroll addressing-disp8-reg64 ;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-indirect-reg64-reg8
|
|
~ : 1 # # mov-indirect-reg64-reg8
|
|
3roll 0x88 pack8 3unroll
|
|
swap reg8 swap addressing-indirect-reg64
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register, target displacement value
|
|
~ -- output point)
|
|
: mov-disp8-reg64-reg8
|
|
~ : 1 # # # mov-disp8-reg64-reg8
|
|
4 roll 0x88 pack8 4 unroll
|
|
3roll reg8 3unroll addressing-disp8-reg64
|
|
~ : 3 roll-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-reg8-indirect-reg64
|
|
~ : 1 # # mov-reg8-indirect-reg64
|
|
3roll 0x8A pack8 3unroll
|
|
reg8 swap addressing-indirect-reg64 ;
|
|
|
|
~ (output point, source register, source displacement value, target register
|
|
~ -- output point)
|
|
: mov-reg8-disp8-reg64
|
|
~ : 2 # # # mov-reg8-disp8-reg64
|
|
4 roll 0x8A pack8 4 unroll
|
|
reg8 3unroll addressing-disp8-reg64 ;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: mov-reg8-reg8
|
|
~ : 1 # # mov-reg8-reg8
|
|
3roll 0x88 pack8 3unroll
|
|
swap reg8 swap addressing-reg8
|
|
~ : swap-subitems
|
|
;
|
|
|
|
|
|
~ String instructions
|
|
~ ~~~~~~~~~~~~~~~~~~~
|
|
~
|
|
~ These are in their own section because there's an awful lot of
|
|
~ combinations, and fortunately they are very uniform in structure.
|
|
~
|
|
~ What makes these useful is that they take their parameters from certain
|
|
~ fixed registers, which are chosen such that the operations chain into each
|
|
~ other well. Thus you can use them to build various block-memory and string
|
|
~ operations, and even if you need unusual forms of loop unrolling or
|
|
~ alignment tweaking, the code will end up uniform in structure. On modern
|
|
~ processors, this is even the high-performance approach, due to highly
|
|
~ optimized microcode, though these operations were inefficient when they
|
|
~ were first invented.
|
|
~
|
|
~ We break with the Intel mnemonics, which follow the pattern
|
|
~ movsb/movsw/movsd/movsq, because this would otherwise be the only place we
|
|
~ use the b/w/d/q thing instead of 8/16/32/64. Tradition and pronounceability
|
|
~ are both nice things, but approachability to newcomers is important, too.
|
|
~
|
|
~ Some of these are repeatable; whether you view the repeatable variants
|
|
~ as different instructions is up to you. At any rate the machine code
|
|
~ representation of the repeatable variants is the same as for the regular
|
|
~ variants with an extra prefix, so we define them together.
|
|
~
|
|
~ This is a proper superset of the flatassembler implementations of string
|
|
~ instructions. The wisdom of that is questionable, but at least it's noted
|
|
~ here...
|
|
|
|
~ (output point -- output point)
|
|
: movs8
|
|
~ : 1 movs8
|
|
0xA4 pack8 ;
|
|
: movs16
|
|
~ : 2 movs16
|
|
0x66 pack8 0xA5 pack8 ;
|
|
: movs32
|
|
~ : 1 movs32
|
|
0xA5 pack8 ;
|
|
: movs64
|
|
~ : 1 movs64
|
|
rex-w 0xA5 pack8 ;
|
|
: rep-movs8
|
|
~ : 2 rep-movs8
|
|
0xF3 pack8 0xA4 pack8 ;
|
|
: rep-movs16
|
|
~ : 3 rep-movs16
|
|
0xF3 pack8 0x66 pack8 0xA5 pack8 ;
|
|
: rep-movs32
|
|
~ : 2 rep-movs32
|
|
0xF3 pack8 0xA5 pack8 ;
|
|
: rep-movs64
|
|
~ : 2 rep-movs64
|
|
0xF3 pack8 rex-w 0xA5 pack8 ;
|
|
|
|
~ (output point -- output point)
|
|
: lods8
|
|
~ : 1 lods8
|
|
0xAC pack8 ;
|
|
: lods16
|
|
~ : 2 lods16
|
|
0x66 pack8 0xAd pack8 ;
|
|
: lods32
|
|
~ : 1 lods32
|
|
0xAD pack8 ;
|
|
: lods64
|
|
~ : 1 lods64
|
|
rex-w 0xAD pack8 ;
|
|
: rep-lods8
|
|
~ : 2 rep-lods8
|
|
0xF3 pack8 0xAC pack8 ;
|
|
: rep-lods16
|
|
~ : 3 rep-lods16
|
|
0xF3 pack8 0x66 pack8 0xAD pack8 ;
|
|
: rep-lods32
|
|
~ : 2 rep-lods32
|
|
0xF3 pack8 0xAD pack8 ;
|
|
: rep-lods64
|
|
~ : 2 rep-lods64
|
|
0xF3 pack8 rex-w 0xAD pack8 ;
|
|
|
|
~ (output point -- output point)
|
|
: stos8
|
|
~ : 1 stos8
|
|
0xAA pack8 ;
|
|
: stos16
|
|
~ : 2 stos16
|
|
0x66 pack8 0xAB pack8 ;
|
|
: stos32
|
|
~ : 1 stos32
|
|
0xAB pack8 ;
|
|
: stos64
|
|
~ : 1 stos64
|
|
rex-w 0xAB pack8 ;
|
|
: rep-stos8
|
|
~ : 2 rep-stos8
|
|
0xF3 pack8 0xAA pack8 ;
|
|
: rep-stos16
|
|
~ : 3 rep-stos16
|
|
0xF3 pack8 0x66 pack8 0xAB pack8 ;
|
|
: rep-stos32
|
|
~ : 2 rep-stos32
|
|
0xF3 pack8 0xAB pack8 ;
|
|
: rep-stos64
|
|
~ : 2 rep-stos64
|
|
0xF3 pack8 rex-w 0xAB pack8 ;
|
|
|
|
~ (output point -- output point)
|
|
: cmps8
|
|
~ : 1 cmps8
|
|
0xA6 pack8 ;
|
|
: cmps16
|
|
~ : 2 cmps16
|
|
0x66 pack8 0xA7 pack8 ;
|
|
: cmps32
|
|
~ : 1 cmps32
|
|
0xA7 pack8 ;
|
|
: cmps64
|
|
~ : 1 cmps64
|
|
rex-w 0xA7 pack8 ;
|
|
: repz-cmps8
|
|
~ : 2 repz-cmps8
|
|
0xF3 pack8 0xA6 pack8 ;
|
|
: repz-cmps16
|
|
~ : 3 repz-cmps16
|
|
0xF3 pack8 0x66 pack8 0xA7 pack8 ;
|
|
: repz-cmps32
|
|
~ : 2 repz-cmps32
|
|
0xF3 pack8 0xA7 pack8 ;
|
|
: repz-cmps64
|
|
~ : 2 repz-cmps64
|
|
0xF3 pack8 rex-w 0xA7 pack8 ;
|
|
: repnz-cmps8
|
|
~ : 2 repnz-cmps8
|
|
0xF2 pack8 0xA6 pack8 ;
|
|
: repnz-cmps16
|
|
~ : 3 repnz-cmps16
|
|
0xF2 pack8 0x66 pack8 0xA7 pack8 ;
|
|
: repnz-cmps32
|
|
~ : 2 repnz-cmps32
|
|
0xF2 pack8 0xA7 pack8 ;
|
|
: repnz-cmps64
|
|
~ : 2 repnz-cmps64
|
|
0xF2 pack8 rex-w 0xA7 pack8 ;
|
|
|
|
~ (output point -- output point)
|
|
: scas8
|
|
~ : 1 scas8
|
|
0xA8 pack8 ;
|
|
: scas16
|
|
~ : 2 scas16
|
|
0x66 pack8 0xAF pack8 ;
|
|
: scas32
|
|
~ : 1 scas32
|
|
0xAF pack8 ;
|
|
: scas64
|
|
~ : 1 scas64
|
|
rex-w 0xAF pack8 ;
|
|
: repz-scas8
|
|
~ : 2 repz-scas8
|
|
0xF3 pack8 0xAE pack8 ;
|
|
: repz-scas16
|
|
~ : 3 repz-scas16
|
|
0xF3 pack8 0x66 pack8 0xAF pack8 ;
|
|
: repz-scas32
|
|
~ : 2 repz-scas32
|
|
0xF3 pack8 0xAF pack8 ;
|
|
: repz-scas64
|
|
~ : 2 repz-scas64
|
|
0xF3 pack8 rex-w 0xAF pack8 ;
|
|
: repnz-scas8
|
|
~ : 2 repnz-scas8
|
|
0xF2 pack8 0xAE pack8 ;
|
|
: repnz-scas16
|
|
~ : 3 repnz-scas16
|
|
0xF2 pack8 0x66 pack8 0xAF pack8 ;
|
|
: repnz-scas32
|
|
~ : 2 repnz-scas32
|
|
0xF2 pack8 0xAF pack8 ;
|
|
: repnz-scas64
|
|
~ : 2 repnz-scas64
|
|
0xF2 pack8 rex-w 0xAF pack8 ;
|
|
|
|
|
|
~ Arithmetic instructions
|
|
~ ~~~~~~~~~~~~~~~~~~~~~~~
|
|
~
|
|
~ Operations between two registers always have the target register as the
|
|
~ last parameter. Operations between a register and an immediate ALSO have the
|
|
~ target register as the last parameter, which is obvious for add-reg64-imm8
|
|
~ but can be confusing for sub-reg64-imm8 or cmp-reg64-imm8. Also note what
|
|
~ this means for sub-reg64-reg64 and cmp-reg64-reg64.
|
|
~
|
|
~ Every possible convention here leads to counterintuitive behavior in some
|
|
~ cases. This one has proven to be more memorable than the other obvious
|
|
~ choices, and that'll have to be enough.
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: add-reg64-reg64
|
|
~ : 1 # # add-reg64-reg64
|
|
3roll rex-w 0x01 pack8 3unroll
|
|
swap reg64 swap addressing-reg64
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: add-indirect-reg64-reg64
|
|
~ : 1 # # add-indirect-reg64-reg64
|
|
3roll rex-w 0x01 pack8 3unroll
|
|
swap reg64 swap addressing-indirect-reg64
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: add-reg64-indirect-reg64
|
|
~ : 1 # # add-reg64-indirect-reg64
|
|
3roll rex-w 0x03 pack8 3unroll
|
|
reg64 swap addressing-indirect-reg64 ;
|
|
|
|
~ (output point, immediate value, target register -- output point)
|
|
: add-reg64-imm8
|
|
~ : 2 # # add-reg64-imm8
|
|
3roll rex-w 0x83 pack8 swap 0 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ See above re: parameter order.
|
|
~
|
|
~ (output point, source register, target register -- output point)
|
|
: sub-reg64-reg64
|
|
~ : 1 # # sub-reg64-reg64
|
|
3roll rex-w 0x2B pack8 3unroll
|
|
reg64 swap addressing-reg64 ;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: sub-indirect-reg64-reg64
|
|
~ : 1 # # sub-indirect-reg64-reg64
|
|
3roll rex-w 0x2B pack8 3unroll
|
|
swap reg64 swap addressing-indirect-reg64
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ See above re: parameter order.
|
|
~
|
|
~ (output point, immediate value, target register -- output point)
|
|
: sub-reg64-imm8
|
|
~ : 2 # # sub-reg64-imm8
|
|
3roll rex-w 0x83 pack8 swap 5 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ See above re: parameter order.
|
|
~
|
|
~ (output point, source register, target register -- output point)
|
|
: sbb-reg64-imm8
|
|
~ : 2 # # sbb-reg64-imm8
|
|
3roll rex-w 0x83 pack8 swap 3 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ (output point, target register -- output point)
|
|
: neg-reg64
|
|
~ : 1 # neg-reg64
|
|
swap rex-w 0xF7 pack8
|
|
swap 3 swap addressing-reg64 ;
|
|
|
|
~ The target register is always rax.
|
|
~
|
|
~ (output point, source register -- output point)
|
|
: mul-reg64
|
|
~ : 1 # mul-reg64
|
|
swap rex-w 0xF7 pack8 swap
|
|
4 swap addressing-reg64 ;
|
|
|
|
~ The dividend is 128 bits, and is formed from rdx as the high half and rax
|
|
~ as the low half. The divisor is a specified register. The quotient is
|
|
~ returned in rax, truncated towards zero. The remainder is in rdx. This
|
|
~ entire process is unsigned.
|
|
~
|
|
~ The official mnemonic for this is "div", but divmod is what it does.
|
|
~
|
|
~ (output point, divisor register -- output point)
|
|
: divmod-reg64
|
|
~ : 1 # divmod-reg64
|
|
swap rex-w 0xF7 pack8 swap
|
|
6 swap addressing-reg64 ;
|
|
|
|
~ Same as divmod, but signed.
|
|
~
|
|
~ (output point, divisor register -- output point)
|
|
: idivmod-reg64
|
|
~ : 1 # idivmod-reg64
|
|
swap rex-w 0xF7 pack8 swap
|
|
7 swap addressing-reg64 ;
|
|
|
|
~ (output point, target register -- output point)
|
|
: inc-reg64
|
|
~ : 1 # inc-reg64
|
|
swap rex-w 0xFF pack8 swap 0 swap addressing-reg64 ;
|
|
|
|
~ (output point, target register -- output point)
|
|
: dec-reg64
|
|
~ : 1 # dec-reg64
|
|
swap rex-w 0xFF pack8 swap 1 swap addressing-reg64 ;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: and-reg64-reg64
|
|
~ : 1 # # and-reg64-reg64
|
|
3roll rex-w 0x23 pack8 3unroll
|
|
reg64 swap addressing-reg64 ;
|
|
|
|
~ (output point, source value, target register -- output point)
|
|
: and-reg64-imm8
|
|
~ : 2 # # and-reg64-imm8
|
|
3roll rex-w 0x83 pack8 swap
|
|
4 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: or-reg64-reg64
|
|
~ : 1 # # or-reg64-reg64
|
|
3roll rex-w 0x0B pack8 3unroll
|
|
reg64 swap addressing-reg64 ;
|
|
|
|
~ (output point, source value, target register -- output point)
|
|
: or-reg64-imm8
|
|
~ : 2 # # or-reg64-imm8
|
|
3roll rex-w 0x83 pack8 swap
|
|
1 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ (output point, source register, target register -- output point)
|
|
: xor-reg64-reg64
|
|
~ : 1 # # xor-reg64-reg64
|
|
3roll rex-w 0x33 pack8 3unroll
|
|
reg64 swap addressing-reg64 ;
|
|
|
|
~ (output point, target register -- output point)
|
|
: not-reg64
|
|
~ : 1 # not-reg64
|
|
swap rex-w 0xF7 pack8
|
|
swap 2 swap addressing-reg64 ;
|
|
|
|
~ (output point, bit count, target register -- output point)
|
|
: rol-reg64-imm8
|
|
~ : 2 # # rol-reg64-imm8
|
|
3roll rex-w 0xC1 pack8 swap
|
|
0 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ (output point, bit count, target register -- output point)
|
|
: rol-reg8-imm8
|
|
~ : 2 # # rol-reg8-imm8
|
|
3roll 0xC0 pack8 swap
|
|
0 swap addressing-reg8
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ (output point, bit count, target register -- output point)
|
|
: ror-reg64-imm8
|
|
~ : 1 # # ror-reg64-imm8
|
|
3roll rex-w 0xC1 pack8 swap
|
|
1 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ (output point, bit count, target register -- output point)
|
|
: ror-reg8-imm8
|
|
~ : 2 # # ror-reg8-imm8
|
|
3roll 0xC0 pack8 swap
|
|
1 swap addressing-reg64
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
|
|
~ Control flow instructions
|
|
~ ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
~
|
|
~ The comparator instructions cmp and test follow the same argument-order
|
|
~ conventions as the arithmetic instructions: The target register always comes
|
|
~ last. See the arithmetic section for more discussion.
|
|
|
|
~ Pretend to subtract right from left, and set the flags the same way as if
|
|
~ we actually had.
|
|
~
|
|
~ (output point, left register, right register -- output point)
|
|
: cmp-reg64-reg64
|
|
~ : 1 # # cmp-reg64-reg64
|
|
3roll rex-w 0x3B pack8 3unroll
|
|
reg64 swap addressing-reg64 ;
|
|
|
|
~ See above re: parameter order.
|
|
~
|
|
~ (output point, immediate value, target register -- output point)
|
|
: cmp-reg64-imm8
|
|
~ : 2 # # cmp-reg64-imm8
|
|
3roll rex-w 0x83 pack8
|
|
~ (immediate value, register, output point)
|
|
swap 7 swap addressing-reg64
|
|
~ ( output point, immediate value)
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ Pretend to bitwise-and left with right, and set the flags the same way as
|
|
~ if we actually had.
|
|
~
|
|
~ The names of the condition codes can be a little confusing when using them
|
|
~ after "test", because they're really premised on the idea that you did
|
|
~ "cmp".
|
|
~
|
|
~ (output point, left register, right register -- output point)
|
|
: test-reg64-reg64
|
|
~ : 1 # # test-reg64-reg64
|
|
3roll rex-w 0x85 pack8 3unroll
|
|
swap reg64 swap addressing-reg64
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ (output point, condition code, target register -- output point)
|
|
: set-reg8-cc
|
|
~ : 1 # # set-reg8-cc
|
|
3roll 0x0F pack8
|
|
3roll condition-code 0x90 opcodecc
|
|
swap reg8 3 0 3roll modrm
|
|
~ : swap-subitems
|
|
;
|
|
|
|
~ (output point, address offset value, condition code -- output point)
|
|
: jmp-cc-rel-imm8
|
|
~ : 1 # # jmp-cc-rel-imm8
|
|
3roll swap condition-code 0x70 opcodecc
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ (output point, address offset value, condition code -- output point)
|
|
: jmp-cc-rel-imm32
|
|
~ : 5 # # jmp-cc-rel-imm32
|
|
3roll 0x0F pack8
|
|
swap condition-code 0x70 opcodecc
|
|
swap
|
|
~ : provide-hex
|
|
pack32 ;
|
|
|
|
~ (output point, register -- output point)
|
|
: jmp-abs-indirect-reg64
|
|
~ : 1 # jmp-abs-indirect-reg64
|
|
swap 0xFF pack8 swap
|
|
4 swap addressing-indirect-reg64 ;
|
|
|
|
~ (output point, address offset value -- output point)
|
|
: jmp-rel-imm8
|
|
~ : 2 # jmp-rel-imm8
|
|
swap 0xEB pack8
|
|
swap
|
|
~ : provide-hex
|
|
pack8 ;
|
|
|
|
~ (output point, address offset value -- output point)
|
|
: jmp-rel-imm32
|
|
~ : 5 # jmp-rel-imm32
|
|
swap 0xE9 pack8
|
|
swap
|
|
~ : provide-hex
|
|
pack32 ;
|
|
|
|
~ This is technically a "near" call. The name intentionally doesn't say so.
|
|
~
|
|
~ (output point, address offset value -- output point)
|
|
: call-rel-imm32
|
|
~ : 5 # call-rel-imm32
|
|
swap 0xE8 pack8
|
|
swap
|
|
~ : provide-hex
|
|
pack32 ;
|
|
|
|
~ Similarly, this is a "near" return.
|
|
~
|
|
~ (output point)
|
|
: ret
|
|
~ : 1 ret
|
|
0xC3 pack8 ;
|
|
|