This specification describes the Intel Pentium [cite intel:pentium]. At the instruction-set level, this specification could almost be used to describe the 486; the Pentium supports just a few instructions not found on the 486. This specification has not been used in an application, which means that it is probably full of bugs. [We have plans for a test harness that should generate instructions at random, making sure that we get the same object code no matter whether we emit binary or assembly language, but as of July 1994 that harness is not in place.]
Specifying the x86 is very different from specifying a RISC machine. In particular, it is difficult to know how to factor this architecture, or indeed whether to try to factor it at all. We've ended by trying to factor the opcodes into tables, but not to factor the instructions. Handling the x86 opcode tables is painful, but we prefer it to specifying the opcodes individually, because we believe the tables reduce the likelihood of error. Factoring the instructions turned out to be a hopeless exercise, with one exception: there is a family of 8 groups of arithmetic instructions that factor nicely.
The x86 is not quite sure whether it is an 8-bit, a 16-bit, or a 32-bit machine. We don't know the exact history, but we believe that the machine started life as an 8-bit machine, and that new opcodes were added when the architecture was extended to 16 bits. New opcodes were not added when it was extended again to 32 bits; instead, the presence or absence of a prefix is used to distinguish 16- from 32-bit instructions. Unfortunately, the encoding isn't completely specified at assembly time; the meaning of the prefix depends on the setting of a bit in the ``executable-segment descriptor.'' We have chosen to specify encodings in which instructions without prefixes operate on 32-bit quantities and the prefix selects 16-bit operation, but the encoding can be changed by changing just two lines in Section [->]. An application that wanted to be able to switch between encodings dynamically would have to use the toolkit to generate two sets of encoding procedures, one each for the 16- and 32-bit defaults.
One thing we do is provide a way to generate just the 32-bit subset:
<pentium32.spec>= (U-> U->) keep <32-bit constructors> <floating-point constructors>
<pentium-int.spec>= discard <floating-point constructors>
Intel uses some naming conventions to try to tame the confusion surrounding operands. ``Operand specifiers'' describe the locations and sizes of operands. The specifiers are (mostly) composed from the following pieces:
For example, the specifier ``Eb,Gb'' describes an 8-bit memory-to-register instruction. ``Ev,Gv'' describes a similar instruction that operates on 16 or 32 bits, depending on the presence or absence of a prefix. Most of the x86 instructions are overloaded, but the opcode tables often use operand specifiers as suffixes to distinguish them. In some cases, where the machine specification doesn't give distinguishing suffixes, we have invented some.
Operand Width E effective address b 8-bit bytes (memory or register) w 16-bit words G general-purpose register d 32-bit doublewords I immediate v variable (w or d)
Many of the instructions support all three sizes, and we specify them
in two variants: a b variant with no prefix, and v
variants with and without prefixes. When the two v variants
differ only in the presence or absence of a prefix, we can specify
them simultaneously using the ov
pattern, which we define in
Section [->] to mean ``optional prefix.''
Sometimes, however, we have to specify all three variants explicitly,
as when there is an immediate operand---in that case, we have to give
three different output patterns because the token holding the
immediate operand may be 8, 16, or 32 bits wide.
Here is the overall structure of the Intel specification:
<pentium-core.spec>= <field specs> patterns <patterns for integer opcodes> <pattern specs for other patterns> <prefix assignments> <placeholders> relocatable reloc <constructors for displacements> <constructors for effective addresses> <arithmetic constructors> constructors <alphabetical constructors>
We want to take advantage of factoring when possible, but it works well only for the ``arithemtic group,'' shown in the upper left corners of Figures [<-] and [<-], which represent the ``one-byte opcode map'' from pages A-5 and A-6 of the Intel manual [cite intel:pentium]. This corner of the opcode table clearly represents an outer product of 8 opcodes with 6 suffixes, and we treat it as such.
Most of the rest of the opcode table we treat in purely geometric fashion, using row, column, and page numbers as Cartesian coordinates to determine opcodes.
<field specs>= (<-U U->) [D->]
fields of opcodet (8) row 4:7 col 0:2 page 3:3
<more fields of opcode
>
<placeholders>= (<-U U->) [D->] placeholder for opcodet is HLT
Because the map is so chaotic, we break it into rows, for the most part specifying one or two rows at a time, but sometimes breaking rows into pieces. The first rows are among the most interesting; rows 0--3 contain the outer product of arithmetic operators with operand specifiers:
<patterns for integer opcodes>= (<-U U->) [D->] arith is any of [ ADD OR ADC SBB AND SUB XOR CMP ], which is row = {0 to 3} & page = [0 1] [ Eb.Gb Ev.Gv Gb.Eb Gv.Ev AL.Ib eAX.Iv ] is col = {0 to 5}
The other columns in rows 0--3 follow a less discernible pattern.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ PUSH.ES POP.ES PUSH.CS esc2 PUSH.SS POP.SS PUSH.DS POP.DS SEG.ES DAA SEG.CS DAS SEG.SS AAA SEG.DS AAS ] is row = {0 to 3} & page = [0 1] & col = [6 7]
Rows 4 and 5 are the general-register opcodes, formed by an outer product of operation
and register specifier.[*]
Although the register specifier is actually part of the opcode, we
treat it as an operand below.
We create the field r32
as an alias for col
, so we can use
special register names for the values.
<patterns for integer opcodes>+= (<-U U->) [<-D->] regops is any of [ INC DEC PUSH POP ], which is row = [4 5] & page = [0 1]
<more fields of opcode
>= (<-U) [D->]
r32 0:2
<field specs>+= (<-U U->) [<-D->] fieldinfo r32 is [names [ eAX eCX eDX eBX eSP eBP eSI eDI ]]
<more fields of opcode
>+= (<-U) [<-D->]
sr16 0:2
<field specs>+= (<-U U->) [<-D->] fieldinfo sr16 is [sparse [ cs=1, ss=2, ds=3, es=4, fs=5, gs=6 ] ]
<more fields of opcode
>+= (<-U) [<-D->]
r16 0:2
<field specs>+= (<-U U->) [<-D->] fieldinfo r16 is [names [ AX CX DX BX SP BP SI DI ]]
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ PUSHA POPA BOUND ARPL SEG.FS SEF.GS OpPrefix AddrPrefix PUSH.Iv IMUL.Iv PUSH.Ib IMUL.Ib INSB INSv OUTSB OUTSv ] is page = [0 1] & row = 6 & col = {0 to 7}
Row 7 is factored so the jump codes can be re-used in the two-byte opcode map. Again, the row contains an outer product, but this time the columns determine jump conditions, not operand specifiers.
<patterns for integer opcodes>+= (<-U U->) [<-D->] Jb is row = 7 cond is any of [ .O .NO .B .NB .Z .NZ .BE .NBE .S .NS .P .NP .L .NL .LE .NLE ], which is page = [0 1] & col = {0 to 7}
Row 8 is a bit hard to follow because it contains a seemingly random
mix of opcodes and operand specifiers.
On page 0, we've left the ``immediate Group1'' implicit, giving only the
operand specifiers.
On page 1, MOV
shares the operand specifiers we gave with the
arithmetic instructions in rows 0--3.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ Eb.Ib Ev.Iv MOVB Ev.Ib TEST.Eb.Gb TEST.Ev.Gv XCHG.Eb.Gb XCHG.Ev.Gv ] is row = 8 & page = 0 & col = {0 to 7} MOV is row = 8 & page = 1 [ MOV.Ew.Sw LEA MOV.Sw.Ew POP.Ev ] is row = 8 & page = 1 & col = {4 to 7}
On page 0, row 9 is XCHG
(or NOP
).
Again, the register operand is actually part of the opcode.
Page 1 has several opcodes.
<patterns for integer opcodes>+= (<-U U->) [<-D->] XCHG is row = 9 & page = 0 NOP is XCHG & col = 0 [ CBW CWDQ CALL.aP WAIT PUSHF POPF SAHF LAHF ] is row = 9 & page = 1 & col = {0 to 7}
Although there is an outer product or two lurking in row 10 (A), we don't try to specify it, because the operand specifiers used there aren't widely useful.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ MOV.AL.Ob MOV.eAX.Ov MOV.Ob.AL MOV.Ov.eAX MOVSB MOVSv CMPSB CMPSv TEST.AL.Ib TEST.eAX.Iv STOSB STOSv LODSB LODSv SCASB SCASv ] is row = 10 & page = [0 1] & col = {0 to 7}
Row 11 (B) is another row in which the register operand is implicit in the opcode, but we need to define a new field to denote 8-bit registers.
<patterns for integer opcodes>+= (<-U U->) [<-D->] MOVib is row = 11 & page = 0 MOViv is row = 11 & page = 1
<more fields of opcode
>+= (<-U) [<-D]
r8 0:2
<field specs>+= (<-U U->) [<-D->] fieldinfo r8 is [names [ AL CL DL BL AH CH DH BH ]]
Rows 12 and 13 contain the bit operators; the others are easy.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ B.Eb.Ib B.Ev.Ib RET.Iw RET LES LDS MOV.Eb.Ib MOV.Ev.Iv B.Eb.1 B.Ev.1 B.Eb.CL B.Ev.CL AAM AAD _ XLAT ] is row = [12 13] & page = 0 & col = {0 to 7} [ ENTER LEAVE RET.far.Iw RET.far INT3 INT.Ib INTO IRET ] is row = 12 & page = 1 & col = {0 to 7} ESC is row = 13 & page = 1
Neither of the last two rows can use factoring.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ LOOPNE LOOPE LOOP JCXZ IN.AL.Ib IN.eAX.Ib OUT.Ib.AL OUT.Ib.eAX LOCK _ REPNE REP HLT CMC grp3.Eb grp3.Ev CALL.Jv JMP.Jv JMP.Ap JMP.Jb IN.AL.DX IN.eAX.DX OUT.DX.AL OUT.DX.eAX CLC STC CLI STI CLD STD grp4 grp5 ] is page = [0 1] & row = [14 15] & col = {0 to 7}
esc2
opcode.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ grp6 grp7 LAR LSL MOV.Eb.Gb MOV.Gv.Ev MOV.Gb.Eb MOV.Ev.Gv ] is esc2; page = 0 & row = [0 1] & col = {0 to 3} CLTS is esc2; page = 0 & row = 0 & col = 6
Row 3 is a block of MOV instructions.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ MOV.Rd.Cd MOV.Rd.Dd MOV.Cd.Rd MOV.Dd.Rd MOV.Rd.Td _ MOV.Td.Rd ] is esc2; page = 0 & row = 3 & col = {0 to 6}
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ WRMSR RDTSC RDMSR ] is esc2; page = 0 & row = 4 & col = {0 to 2}
The jumps in row 8 and sets in row 9 span two pages. They are outer products of opcodes and the conditions defined in row 7 of the one-byte opcode map.
<patterns for integer opcodes>+= (<-U U->) [<-D->] Jv is esc2; row = 8 SETb is esc2; row = 9
Rows 10 and 11 are more madness:
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ PUSH.FS POP.FS CPUID BT SHLD.Ib SHLD.CL _ _ CMPXCHG.Eb.Gb CMPXCHG.Ev.Gv LSS BTR LFS LGS MOVZX.Gv.Eb MOVZX.Gv.Ew ] is esc2; page = 0 & row = [10 11] & col = {0 to 7}
Row 12 is the only remaining non-empty row on page 0.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ XADD.Eb.Gb XADD.Ev.Gv grp9 ] is esc2; page = 0 & row = 12 & col = [0 1 7]
There are even fewer opcodes on page 1.
<patterns for integer opcodes>+= (<-U U->) [<-D->] [INVD WBINVD] is esc2; row = 0 & page = 1 & col = [0 1]
Rows 1--7 are empty, and 8 and 9 were covered on the previous page. Row 10 is:
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ PUSH.GS POP.GS RSM BTS SHRD.Ib SHRD.CL _ IMUL.Gv.Ev] is esc2; row = 10 & page = 1 & col = {0 to 7}
<patterns for integer opcodes>+= (<-U U->) [<-D->] [ grp8 BTC BSF BSR MOVSX.Gv.Eb MOVSX.Gv.Ew] is esc2; page = 1 & row = 11 & col = {2 to 7}
And the single opcode on row 12:
<patterns for integer opcodes>+= (<-U U->) [<-D] BSWAP is esc2; row = 12 & page = 1
mod
field of which
determines the addressing mode.
The Mod R/M byte also holds some bits that denote either a
register operand or some extra parts of the opcode (as with the groupx instructions).
Indexed addressing modes use an additional byte, called ``SIB,'' which
holds a scale factor and index and base registers.
<field specs>+= (<-U U->) [<-D->] fields of modrm (8) mod 6:7 reg_opcode 3:5 r_m 0:2 fields of sib (8) ss 6:7 index 3:5 base 0:2
<field specs>+= (<-U U->) [<-D->] fieldinfo [ base index ] is [ names [ eAX eCX eDX eBX eSP eBP eSI eDI ] ] fieldinfo ss is [ sparse [ "1" = 0, "2" = 1, "4" = 2, "8" = 3 ] ]
<placeholders>+= (<-U U->) [<-D->] placeholder for modrm is HLT placeholder for sib is HLT
We're faced with a specification problem because some Intel
instructions accept only effective addresses that refer to operands in
memory; register modes are not permitted.
Most instructions, however, accept any kind of effective address.
A good way to express this restriction would be with subtyping, but
the toolkit doesn't implement subtyping, so instead we define two
different constructor types: Mem
to refer to effective addresses
of operands in memory, and Eaddr
to refer to any effective
address.
This separation requires the use of an identity constructor E
to
map Mem
s into Eaddr
s.
Such a thing is bad enough in a specification, but these E
's have
to be used in application programs, too.
The ugliness is justified because it confers protection against
inadvertantly using a register operand with an instruction that
doesn't permit one.
<constructors for effective addresses>= (<-U U->) relocatable d a constructors Indir [reg] : Mem { reg != 4, reg != 5 } is mod = 0 & r_m = reg Disp8 d[reg] : Mem { reg != 4 } is mod = 1 & r_m = reg; i8 = d Disp32 d[reg] : Mem { reg != 4 } is mod = 2 & r_m = reg; i32 = d Abs32 a : Eaddr is mod = 0 & r_m = 5; i32 = a Reg reg : Eaddr is mod = 3 & r_m = reg Index [base][index * ss] : Mem { index != 4, base != 5 } is mod = 0 & r_m = 4; index & base & ss Index8 d[base][index * ss] : Mem { index != 4 } is mod = 1 & r_m = 4; index & base & ss; i8 = d Index32 d[base][index * ss] : Mem { index != 4 } is mod = 2 & r_m = 4; index & base & ss; i32 = d ShortIndex d[index * ss] : Mem { index != 4 } is mod = 0 & r_m = 4; index & base = 5 & ss; i32 = d E Mem : Eaddr is Mem
We'll eventually want to be able to keep only 32-bit constructors, to cut down on the time needed to generate encoding procedures.
<32-bit constructors>= (<-U) [D->] Indir Disp32 Reg Index Index32 E
Now, this is good as far as it goes, but there are a couple of problems: no support for conditional assembly, and lots of constructors, which increases generation time. So let's get a bit clever:
<proposed constructors for effective addresses>= constructors Indir0 [reg] { reg != 4, reg != 5 } is mod = 0 & r_m = reg Indir8 d[reg] { reg != 4 } is mod = 1 & r_m = reg; i8 = d Indir32 d[reg] { reg != 4 } is mod = 2 & r_m = reg; i32 = d Index0 [base][index*ss] { index != 4, base != 5 } is mod = 0 & r_m = 4; index & base & ss Index8 d[base][index*ss] { index != 4 } is mod = 1 & r_m = 4; index & base & ss; i8 = d Index32 d[base][index*ss] { index != 4 } is mod = 2 & r_m = 4; index & base & ss; i32 = d relocatable d constructors Indir d[reg] : Mem when { d = 0 } is Indir0 ( reg) when { } is Indir8 (d, reg) otherwise is Indir32(d, reg) Index d[base][index*ss] : Mem when { d = 0 } is Index0 ( base, index, ss) when { } is Index8 (d, base, index, ss) otherwise is Index32(d, base, index, ss) ShortIndex d[index*ss] : Mem { index != 4 } is mod = 0 & r_m = 4; index & base = 5 & ss; i32 = d E Mem : Eaddr is Mem Reg reg : Eaddr is mod = 3 & r_m = reg discard Indir0 Indir8 Indir32 Index0 Index8 Index32
The only problem now is that it's no longer possible to split out the 32-bit constructors.
Immediate operands occupy whole tokens and have their own classes.
<field specs>+= (<-U U->) [<-D] fields of I8 (8) i8 0:7 fields of I16 (16) i16 0:15 fields of I32 (32) i32 0:31
<placeholders>+= (<-U U->) [<-D] placeholder for I8 is HLT placeholder for I16 is HLT; HLT placeholder for I32 is HLT; HLT; HLT; HLT
reg_opcode
field of the Mod R/M byte specify different opcodes,
depending on the value of the opcode preceding the effective address.
Most of these opcodes are notated by ``Groupx'' in the manual.
We use different sets of names for the values depending on what opcode
precedes the effective-address specification.
To make sure we don't mistakenly use a name like INC.Eb
for
reg_opcode = 0
when the actual denotation is INC.Ev
, we include
in the specifications the opcode that must precede the Mod R/M
byte---in this case, either grp4
or grp5
.
This kind of specification is conjoined below with specifications of opcodes and effective addresses. If
INC.Eb is grp4; reg_opcode = 0
then we might conjoin it with a grp4
opcode followed by an
effective address, writing:
INC.Eb Eaddr is (grp4; Eaddr) & INC.Eb
The conjunction of the explicit grp4
with the grp4
in the
definition of INC.Eb
ensures that INC.Eb
is used correctly,
since grp4 & grp4
===grp4
.
If we incorrectly used grp5
when defining the INC.Eb
constructor, grp4 & grp5
would evaluate to a pattern that never
matches anything, and the toolkit would complain.
We've glossed over an important detail in the definition of this
constructor.
Because conjunction distributes over concatentation, the right-hand
side of the INC.Eb
constructor winds up being equivalent to
grp4; (Eaddr & reg_opcode = 0)
.
This conjunction, unfortunately, breaks the rules of conjunction,
which requires both patterns conjoined to have the same shape,
or sequence of token classes.
The conjunction is legal when Eaddr
is an instance of Indir
or Reg
,
because those effective addresses consist solely of one Mod R/M
token, but the other modes contain more tokens, and their shapes don't
match reg_opcode = 0
.
The solution is to relax the shape constraint by using the ellipsis
operator. ``p ...
'' creates a pattern that is equivalent to
p
, except it is permissible to write p ... & q
whenever
p
's shape is a prefix of q
's shape.
[
The ellipsis may also be used as a prefix operator on patterns, in
which case ... p & q
is permissible whenever p
's shape is a
suffix of q
's shape.
We haven't had occasion to use such patterns in machine descriptions,
because most hardware decodes complex instructions from left to right.]
In the case at hand, every Eaddr
begins with a Mod R/M token, so
we can always write
Eaddr & reg_opcode = 0 ...
We've now covered enough detail to specify the Mod R/M or ``Groupx'' opcodes.
Just to add some extra complexity, groups 1-3 include opcodes
that denote different operand specifiers.
For example, ADDi
can denote an integer add of bytes (Eb.Ib
),
16-byte or 32-byte words (Ev.Iv
), or words and bytes (Ev.Ib
),
depending on the suffix added to the opcode.
For each constructor created from the ADDi
pattern, only one operand
specifier is conjoined into the output pattern;
the resulting pattern has just one non-contradictory disjunct,
so the bits to be emitted are uniquely determined.
The constructors for these patterns are defined in Section [->].
<pattern specs for other patterns>= (<-U U->) [D->] patterns arithI is any of [ ADDi ORi ADCi SBBi ANDi SUBi XORi CMPi ], # group 1 which is (Eb.Ib | Ev.Iv | Ev.Ib); reg_opcode = {0 to 7} ... bshifts is B.Eb.1 | B.Eb.CL # D0 D2 vshifts is B.Ev.1 | B.Ev.CL # D1 D3 immshifts is B.Eb.Ib | B.Ev.Ib # C0 C1 rot is any of [ ROL ROR RCL RCR SHLSAL SHR _ SAR], which is (bshifts | vshifts | immshifts); reg_opcode = {0 to 7} ... grp3ops is any of [ TEST.Ib.Iv _ NOT NEG MUL.AL.eAX IMUL.AL.eAX DIV.AL.eAX IDIV.AL.eAX ], which is (grp3.Eb | grp3.Ev); reg_opcode = {0 to 7} ... grp4ops is any of [ INC.Eb DEC.Eb ], which is grp4; reg_opcode = [0 1] ... grp5ops is any of [ INC.Ev DEC.Ev CALL.Ev CALL.Ep JMP.Ev JMP.Ep PUSH.Ev _ ], which is grp5; reg_opcode = {0 to 7} ... grp6ops is any of [ SLDT STR LLDT LTR VERR VERW _ _ ], which is grp6; reg_opcode = {0 to 7} ... grp7ops is any of [ SGDT SIDT LGDT LIDT SMSW _ LMSW INVLPG ], which is grp7; reg_opcode = {0 to 7} ... bittestI is any of [ BTi BTSi BTRi BTCi ], which is grp8; reg_opcode = {4 to 7} ... CMPXCHG8B is grp9; reg_opcode = 1 ...
ow
and od
given here.
An application that wanted to be able to use both encodings would have
to generate two sets of encoding procedures, perhaps using function
pointers to switch back and forth.
In specifications, instructions with b
suffixes (e.g., ``Eb,Gb'')
use no prefix.
Instructions with v
suffixes (e.g., ``Ev,Gv'')
begin with ov
, which is followed by the rest of the instruction.
When ov
is used to build an opcode, this technique automatically
creates two variants: od
, a 32-bit variant with no prefix (epsilon
)
and ow
, a 16-bit variant with prefix OpPrefix
.
<prefix assignments>= (<-U U->) [D->] patterns ow is OpPrefix od is epsilon ov is ow | od
The address prefix is similar, but we haven't figured out how it's to be used.
<prefix assignments>+= (<-U U->) [<-D] patterns aw is AddrPrefix ad is epsilon av is aw | ad
D9
though DF
, which express opcode
values in hex; these are used in the specifications of the opcodes, so
it seemed expedient to make them patterns, rather than continually
writing something like opcode = 0xd8
.
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns [ D8 D9 DA DB DC DD DE DF ] is ESC & col = {0 to 7} [ FADD FMUL FCOM FCOMP FSUB FSUBR FDIV FDIVR ] is reg_opcode = {0 to 7} [ FLD _ FST FSTP FLDENV FLDCW FSTENV FSTCW ] is reg_opcode = {0 to 7} ... [ FNOP ] is D9; mod = 3 & reg_opcode = 2 & r_m = [0] [ FCHS FABS _ _ FTST FXAM _ _ ] is D9; mod = 3 & reg_opcode = 4 & r_m = {0 to 7} [ F2XM1 FYL2X FPTAN FPATAN FXTRACT FPREM1 FDECSTP FINCSTP ] is D9; mod = 3 & reg_opcode = 6 & r_m = {0 to 7} FXCH is D9; mod = 3 & reg_opcode = 1 Fconstants is any of [ FLD1 FLDL2T FLDL2E FLDPI FLDLG2 FLDLN2 FLDZ _ ], which is D9; mod = 3 & reg_opcode = 5 & r_m = {0 to 7} [ FPREM FYL2XP1 FSQRT FSINCOS FRNDINT FSCALE FSIN FCOS ] is D9; mod = 3 & reg_opcode = 7 & r_m = {0 to 7} [ FIADD FIMUL FICOM FICOMP FISUB FISUBR FIDIV FIDIVR ] is reg_opcode = {0 to 7} ... FUCOMPP is DA; mod = 3 & reg_opcode = 5 & r_m = 1 [ FILD _ FIST FISTP FBLD FLD.ext FBSTP FSTP.ext ] is reg_opcode = {0 to 7} ... [ FCLEX FINIT ] is DB; mod = 3 & reg_opcode = 4 & r_m = [2 3] [ FRSTOR _ FSAVE FSTSW ] is reg_opcode = {4 to 7} ... [ FFREE _ FST.st FSTP.st FUCOM FUCOMP _ _ ] is mod = 3 & reg_opcode = {0 to 7} [ FADDP _ FUBSRP FDIVRP FMULP _ FSUBP FDIVP ] is mod = 3 & reg_opcode = {0 to 7} FCOMPP is DE; mod = 3 & reg_opcode = 3 & r_m = 1 FSTSW.AX is DF; mod = 3 & reg_opcode = 4 & r_m = 0
This next group of floating-point patterns define suffixes that we use on other opcodes, not actual opcodes.
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns .STi is DD; mod = 3 Fstack is any of [ .ST.STi .STi.St P.STi.ST ], which is [ D8 DC DE ]; mod = 3 Fint is any of [.I32 .I16], which is [DA DE] Fmem is any of [.R32 .R64], which is [D8 DC] FlsI is any of [.lsI16 .lsI32], which is [DF DB] FlsR is any of [.lsR32 .lsR64], which is [D9 DD]
arithI
).
This is the only part of the Intel specification we were able to
factor very well, but it does give us 112 constructors in just a dozen
lines, so it is worth doing.
<arithmetic constructors>= (<-U U->) constructors arith^"iAL" i8! is arith & AL.Ib ; i8 arith^"iAX" i16! is ow; arith & eAX.Iv; i16 arith^"iEAX" i32! is od; arith & eAX.Iv; i32 arithI^"b" Eaddr, i8! is (Eb.Ib; Eaddr) & arithI; i8 arithI^"w" Eaddr, i16! is ow; (Ev.Iv; Eaddr) & arithI; i16 arithI^"d" Eaddr, i32! is od; (Ev.Iv; Eaddr) & arithI; i32 arithI^ov^"b" Eaddr, i8! is ov; (Ev.Ib; Eaddr) & arithI; i8 arith^"mrb" Eaddr, reg8 is arith & Eb.Gb; Eaddr & reg_opcode = reg8 ... arith^"mr"^ov Eaddr, reg is ov; arith & Ev.Gv; Eaddr & reg_opcode = reg ... arith^"rmb" reg8, Eaddr is arith & Gb.Eb; Eaddr & reg_opcode = reg8 ... arith^"rm"^ov reg, Eaddr is ov; arith & Gv.Ev; Eaddr & reg_opcode = reg ...
<32-bit constructors>+= (<-U) [<-D->] arith^"iEAX" arithI^"d" arith^"mr"^od arith^"rm"^od
Trying to factor the non-arithmetic instructions proved a thankless task, so we've given almost all instructions merely in alphabetical order as they appear in Chapter 25 of the Pentium manual. There's a little bit of local factoring, as with some bit operations.
It's not really appropriate to try to explain this part of the specification; the best way to read this section by comparing with the alphabetical section of the Pentium architecture manual [cite intel:pentium]. To generate a checker by the end of the millenium, we divide the spec into four parts and check each individually.
<alphabetical constructors>= (<-U) <alphabetical constructors A-F> <alphabetical constructors G-K> <alphabetical constructors L-Q> <alphabetical constructors R> <alphabetical constructors S-Z>
<alphabetical constructors A-F>= (<-U U->) [D->] AAA AAD is AAD; i8 = 10 AAM is AAM; i8 = 10 AAS # ADC, ADD, AND are in arith group ARPL Eaddr, reg16 is ARPL; Eaddr & reg_opcode = reg16 ...
<32-bit constructors>+= (<-U) [<-D->] AAA AAD AAM AAS
<names of pentium constructors>= (U->) [D->] AAA AAD AAM AAS ARPL
Note that ARPL
requires a 16-bit register,
i.e. %ax, %bx
, for its second operand.
The ``short'' variant of BOUND
(boundw
) requires a 16-bit
register for its first operand.
<alphabetical constructors A-F>+= (<-U U->) [<-D->] constructors BOUND^ov reg, Mem is ov; BOUND; Mem & reg_opcode = reg ... BSF^ov reg, Eaddr is ov; BSF; Eaddr & reg_opcode = reg ... BSR^ov reg, Eaddr is ov; BSR; Eaddr & reg_opcode = reg ... BSWAP r32 is BSWAP & ... r32 BT^ov Eaddr, reg is ov; BT; Eaddr & reg_opcode = reg ... BTi^ov Eaddr, i8! is ov; (grp8; Eaddr) & BTi; i8 BTC^ov Eaddr, reg is ov; BTC; Eaddr & reg_opcode = reg ... BTCi^ov Eaddr, i8! is ov; (grp8; Eaddr) & BTCi; i8 BTR^ov Eaddr, reg is ov; BTR; Eaddr & reg_opcode = reg ... BTRi^ov Eaddr, i8! is ov; (grp8; Eaddr) & BTRi; i8 BTS^ov Eaddr, reg is ov; BTS; Eaddr & reg_opcode = reg ... BTSi^ov Eaddr, i8! is ov; (grp8; Eaddr) & BTSi; i8
<names of pentium constructors>+= (U->) [<-D->] BOUND^ov BSF^ov BSR^ov BSWAP BT^ov BTi^ov BTC^ov BTCi^ov BTR^ov BTRi^ov BTS^ov BTSi^ov
<32-bit constructors>+= (<-U) [<-D->] BOUND^od BSF^od BSR^od BSWAP BT^od BTi^od BTC^od BTCi^od BTR^od BTRi^od BTS^od BTSi^od
To deal with relative displacements, we set up constructors to compute them. The displacements are relative to the end of the word.
<constructors for displacements>= (<-U U->) constructors rel8 reloc : Rel8 { reloc = L + i8! } is i8; L: epsilon rel16 reloc : Rel16 { reloc = L + i16! } is i16; L: epsilon rel32 reloc : Rel32 { reloc = L + i32! } is i32; L: epsilon
<32-bit constructors>+= (<-U) [<-D->] rel32
<alphabetical constructors A-F>+= (<-U U->) [<-D->] CALL.Jv^ow reloc is ow; CALL.Jv; rel16(reloc) CALL.Jv^od reloc is od; CALL.Jv; rel32(reloc) CALL.Ep^ov Mem is ov; (grp5; Mem) & CALL.Ep CALL.aP^ow CS":" IP is ow; CALL.aP; i16 = CS; i16 = IP CALL.aP^od CS":" IP is od; CALL.aP; i16 = CS; i32 = IP CALL.Ev^ov Mem is ov; (grp5; Mem) & CALL.Ev CBW is ow; CBW CWDE is od; CBW CLC CLD CLI CLTS CMC
The Linux assembler doesn't support multiple segments, so the
CALL
opcodes that take a code segment and offset are discarded
when generating assembly code for a Linux assembler.
<pentium-linux.spec>= (U-> U->) [D->] discard CALL.aP^ow CALL.aP^od
<alphabetical constructors A-F>+= (<-U U->) [<-D->] # CMP is in the arith group CMPSB^av is av; CMPSB CMPSv^ov^av is (av; ov | ov; av); CMPSv CMPXCHG.Eb.Gb Eaddr, reg is CMPXCHG.Eb.Gb; Eaddr & reg_opcode = reg ... CMPXCHG.Ev.Gv^ov Eaddr, reg is ov; CMPXCHG.Ev.Gv; Eaddr & reg_opcode = reg ... CMPXCHG8B Mem is (grp9; Mem) & CMPXCHG8B CPUID CWD is ow; CWDQ CDQ is od; CWDQ
<32-bit constructors>+= (<-U) [<-D->] CALL.Jv^od CALL.Ep^od CALL.Ev^od CALL.aP^od CBW CWDE CLC CLD CLI CLTS CMC CMPSv^od^av CMPXCHG.Ev.Gv^od CWD CDQ
CMPXCHG8B
and CPUID
are Pentium instructions so we can't check
them on a 486.
<names of pentium constructors>+= (U->) [<-D->] CMPXCHG8B CPUID CWD
<alphabetical constructors A-F>+= (<-U U->) [<-D->] DAA DAS DEC.Eb Eaddr is (grp4; Eaddr) & DEC.Eb DEC.Ev^ov Eaddr is ov; (grp5; Eaddr) & DEC.Ev DEC^ov r32 is ov; DEC & r32 DIV^"AL" Eaddr is (grp3.Eb; Eaddr) & DIV.AL.eAX DIV^"AX" Eaddr is ow; (grp3.Ev; Eaddr) & DIV.AL.eAX DIV^"eAX" Eaddr is od; (grp3.Ev; Eaddr) & DIV.AL.eAX
<32-bit constructors>+= (<-U) [<-D->] DAA DAS DEC.Ev^od DEC^od DIV^"eAX"
<names of pentium constructors>+= (U->) [<-D->] DAA DAS DEC.Eb
<alphabetical constructors A-F>+= (<-U U->) [<-D->] ENTER i16, i8! is ENTER; i16; i8 F2XM1
<32-bit constructors>+= (<-U) [<-D->] ENTER F2XM1
<names of pentium constructors>+= (U->) [<-D->] ENTER F2XM1
The first use of the left-hand side ellipsis is used
in the definition of FADD^Fstack
.
The left ellipsis denotes that
the pattern (FADD & r_m = idx)
must be a legal
suffix of the pattern Fstack
.
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FABS FADD^Fmem Mem is Fmem; Mem & FADD ... FADD^Fstack idx is Fstack & ... (FADD & r_m = idx)
<floating-point constructors>= (<-U <-U U->) [D->] FABS FADD^Fmem FADD^Fstack
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FIADD^Fint Mem is Fint; Mem & FIADD ... FBLD Mem is DF; Mem & FBLD FBSTP Mem is DF; Mem & FBSTP
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FCHS
<floating-point constructors>+= (<-U <-U U->) [<-D->] FCHS
<synthetics with WAIT
>= (U->) [D->]
FCLEX is WAIT; FCLEX
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FNCLEX is FCLEX
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns FCOMs is FCOM | FCOMP
<alphabetical constructors A-F>+= (<-U U->) [<-D->] constructors FCOMs^Fmem Mem is Fmem; Mem & FCOMs ... # includes FICOM, FICOMP FCOMs^.ST.STi idx is .ST.STi & ... (FCOMs & r_m = idx) FCOMPP FCOS
<floating-point constructors>+= (<-U <-U U->) [<-D->] FCOMs^Fmem FCOMs^.ST.STi FCOMPP FCOS
<pentium-linux.spec>+= (U-> U->) [<-D->] discard FCOMs^.ST.STi
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FDECSTP FDIV^Fmem Mem is Fmem; Mem & FDIV ... FDIV^Fstack idx is Fstack & ... (FDIV & r_m = idx) FDIVR^Fmem Mem is Fmem; Mem & FDIVR ... FDIVR^Fstack idx is Fstack & ... (FDIVR & r_m = idx)
<floating-point constructors>+= (<-U <-U U->) [<-D->] FDECSTP FDIV^Fmem FDIV^Fstack FDIVR^Fmem FDIVR^Fstack
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FFREE idx is DD; FFREE & r_m = idx
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FICOM^Fint Mem is Fint; Mem & FICOM FICOMP^Fint Mem is Fint; Mem & FICOMP FILD^FlsI Mem is FlsI; Mem & FILD FILD64 Mem is DF; Mem & FLD.ext ... FINIT
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns FISTs is FIST | FISTP
<alphabetical constructors A-F>+= (<-U U->) [<-D->] constructors FISTs^FlsI Mem is FlsI; Mem & FISTs FISTP64 Mem is DF; Mem & FSTP.ext
<floating-point constructors>+= (<-U <-U U->) [<-D->] FICOM^Fint FICOMP^Fint FICOM16 FICOM32 FICOMP16 FICOMP32 FILD^FlsI FILD64 FINIT FISTs^FlsI FISTP64
EDIT ME?
There is no obvious way to reference indirectly the stack pointer,
i.e., (%esp)
, because the encoding of this addressing mode is an
escape that indicates an sib
byte follows the mod-rm
byte.
The only way to construct this address is to build a sib
byte
that ignores its index
field. Page 26-7 specifies how to do this.
Luckily, we only need (%esp)
for a few floating-point instructions
that use the address on the top of the stack and then pops it off.
Although this addressing mode is technically an Eaddr
, we specify it
individually because virutally no other instruction uses it.
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FLD^FlsR Mem is FlsR; Mem & FLD FLD80 Mem is DB; Mem & FLD.ext ... FLD.STi idx is D9; mod = 3 & FLD & r_m = idx Fconstants FLDCW Mem is D9; Mem & FLDCW FLDENV Mem is D9; Mem & FLDENV
<floating-point constructors>+= (<-U <-U U->) [<-D->] FLD^FlsR FLD80 FLD.STi Fconstants FLDCW FLDENV
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FMUL^Fmem Mem is Fmem; Mem & FMUL ... FMUL^Fstack idx is Fstack & ... (FMUL & r_m = idx)
<floating-point constructors>+= (<-U <-U U->) [<-D->] FMUL^Fmem FMUL^Fstack
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FNOP
<floating-point constructors>+= (<-U <-U U->) [<-D->] FNOP
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FPATAN FPREM FPREM1 FPTAN
<floating-point constructors>+= (<-U <-U U->) [<-D->] FPATAN FPREM FPREM1 FPTAN
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FRNDINT FRSTOR Mem is DD; Mem & FRSTOR
<floating-point constructors>+= (<-U <-U U->) [<-D->]
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FNSAVE Mem is DD; Mem & FSAVE
<floating-point constructors>+= (<-U <-U U->) [<-D->]
<synthetics with WAIT
>+= (U->) [<-D->]
FSAVE Mem is WAIT; FNSAVE(Mem)
<floating-point constructors>+= (<-U <-U U->) [<-D->]
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FSCALE FSIN FSINCOS FSQRT
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns FSTs is FST | FSTP FSTs.st is FST.st | FSTP.st
<alphabetical constructors A-F>+= (<-U U->) [<-D->] constructors FSTs^FlsR Mem is FlsR; Mem & FSTs FSTP80 Mem is DB; Mem & FSTP.ext FSTs.st^.STi idx is .STi & ... (FSTs.st & r_m = idx) FSTCW Mem is D9; Mem & FSTCW
<synthetics with WAIT
>+= (U->) [<-D->]
FNSTCW Mem is WAIT; FSTCW(Mem)
<floating-point constructors>+= (<-U <-U U->) [<-D->] FSCALE FSIN FSINCOS FSQRT FSTs^FlsR FSTP80 FSTs.st^.STi FSTCW FNSTCW
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FSTENV Mem is D9; Mem & FSTENV
<synthetics with WAIT
>+= (U->) [<-D->]
FNSTENV Mem is WAIT; FSTENV(Mem)
<floating-point constructors>+= (<-U <-U U->) [<-D->] FSTENV FNSTENV
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FSTSW Mem is DD; Mem & FSTSW FSTSW.AX
<synthetics with WAIT
>+= (U->) [<-D->]
FNSTSW Mem is WAIT; FSTSW(Mem)
FNSTSW.AX is WAIT; FSTSW.AX()
<floating-point constructors>+= (<-U <-U U->) [<-D->] FSTSW FSTSW.AX FNSTSW FNSTSW.AX
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FSUB^Fmem Mem is Fmem; Mem & FSUB ... FSUB^Fstack idx is Fstack & ... (FSUB & r_m = idx) FSUBR^Fmem Mem is Fmem; Mem & FSUBR ... FSUBR^Fstack idx is Fstack & ... (FSUBR & r_m = idx)
<floating-point constructors>+= (<-U <-U U->) [<-D->] FSUB^Fmem FSUB^Fstack FSUBR^Fmem FSUBR^Fstack
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FTST
<floating-point constructors>+= (<-U <-U U->) [<-D->] FTST
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns FUCOMs is FUCOM | FUCOMP
<alphabetical constructors A-F>+= (<-U U->) [<-D->] constructors FUCOMs idx is DD; FUCOMs & r_m = idx FUCOMPP
<floating-point constructors>+= (<-U <-U U->) [<-D->] FUCOMs FUCOMPP
<synthetics with WAIT
>+= (U->) [<-D]
FWAIT is WAIT
<floating-point constructors>+= (<-U <-U U->) [<-D->] FWAIT
<alphabetical constructors A-F>+= (<-U U->) [<-D->] FXAM FXCH idx is FXCH & ... r_m = idx FXTRACT
<floating-point constructors>+= (<-U <-U U->) [<-D->] FXAM FXCH FXTRACT
<alphabetical constructors A-F>+= (<-U U->) [<-D] FYL2X FYL2XP1
<floating-point constructors>+= (<-U <-U U->) [<-D] FYL2X FYL2XP1
<names of pentium constructors>+= (U->) [<-D] FIADD^Fint FBLD FBSTP FNCLEX FCOS FDECSTP FDIVR^Fmem FIDIV^Fint FIDIVR^Fint FICOM^Fint FICOMP^Fint FILD64 FINCSTP FINIT FISTP64 FLD80 Fconstants FLDCW FLDENV FIMUL^Fint FNOP FPATAN FPREM FPREM1 FPTAN FRNDINT FRSTOR FSCALE FSIN FSINCOS FSQRT FSUBR^Fmem FISUB^Fint FISUBR^Fint FTST FUCOMs FUCOMPP FXAM FXCH FXTRACT FYL2X FYL2XP1
<alphabetical constructors G-K>= (<-U U->) [D->] HLT
<32-bit constructors>+= (<-U) [<-D->] HLT
<alphabetical constructors G-K>+= (<-U U->) [<-D->] IDIV Eaddr is (grp3.Eb; Eaddr) & IDIV.AL.eAX IDIV^"AX" Eaddr is ow; (grp3.Ev; Eaddr) & IDIV.AL.eAX IDIV^"eAX" Eaddr is od; (grp3.Ev; Eaddr) & IDIV.AL.eAX IMULb Eaddr is (grp3.Eb; Eaddr) & IMUL.AL.eAX IMUL^ov Eaddr is ov; (grp3.Ev; Eaddr) & IMUL.AL.eAX IMULrm^ov reg, Eaddr is ov; IMUL.Gv.Ev; Eaddr & reg_opcode = reg ... IMUL.Ib^ov reg, Eaddr, i8! is ov; IMUL.Ib; Eaddr & reg_opcode = reg ... ; i8 IMUL.Iv^"w" reg, Eaddr, i16! is ow; IMUL.Iv; Eaddr & reg_opcode = reg ... ; i16 IMUL.Iv^"d" reg, Eaddr, i32! is od; IMUL.Iv; Eaddr & reg_opcode = reg ... ; i32 IN.AL.Ib i8! is IN.AL.Ib; i8 IN.eAX.Ib^ov i8! is ov; IN.eAX.Ib; i8 IN.AL.DX IN.eAX.DX^ov is ov; IN.eAX.DX INC.Eb Eaddr is (grp4; Eaddr) & INC.Eb INC.Ev^ov Eaddr is ov; (grp5; Eaddr) & INC.Ev INC^ov r32 is ov; INC & r32 INSB INSv^ov is ov; INSv INT3 INT.Ib i8! is INT.Ib; i8 INTO INVD INVLPG Mem is (grp7; Mem) & INVLPG IRET
<32-bit constructors>+= (<-U) [<-D->] IDIV^"eAX" IMUL^od IMULrm^od IMUL.Iv^"d" IN.eAX.DX^od INC.Ev^od INC^od INSv^od INT3 INT.Ib INTO INVD INVLPG IRET
<alphabetical constructors G-K>+= (<-U U->) [<-D->] Jb^cond reloc is Jb & cond; rel8(reloc) Jv^cond^ow reloc is ow; Jv & ... cond; rel16(reloc) Jv^cond^od reloc is od; Jv & ... cond; rel32(reloc) JMP.Jb reloc is JMP.Jb; rel8(reloc) JMP.Jv^ow reloc is ow; JMP.Jv; rel16(reloc) JMP.Jv^od reloc is od; JMP.Jv; rel32(reloc) JMP.Ap^ow CS, IP is ow; JMP.Ap; i16 = CS; i16 = IP JMP.Ap^od CS, IP is od; JMP.Ap; i16 = CS; i32 = IP JMP.Ev^ov Eaddr is ov; (grp5; Eaddr) & JMP.Ev JMP.Ep^ov Mem is ov; (grp5; Mem ) & JMP.Ep
<32-bit constructors>+= (<-U) [<-D->] Jv^cond^od JMP.Jv^od JMP.Ap^od JMP.Ev^od JMP.Ep^od
<pentium-linux.spec>+= (U-> U->) [<-D->] discard JMP.Ap^ow JMP.Ap^ov
<alphabetical constructors L-Q>= (<-U U->) [D->] LAHF LAR^ov reg, Eaddr is ov; LAR; Eaddr & reg_opcode = reg ...
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns lfp is LDS | LES | LFS | LGS | LSS
<alphabetical constructors G-K>+= (<-U U->) [<-D] constructors lfp^ov reg, Mem is ov; lfp; Mem & reg_opcode = reg ... LEA^ov reg, Mem is ov; LEA; Mem & reg_opcode = reg ... LEAVE LGDT Mem is (grp7; Mem) & LGDT LIDT Mem is (grp7; Mem) & LIDT LLDT Eaddr is (grp6; Eaddr) & LLDT LMSW Eaddr is (grp7; Eaddr) & LMSW LOCK LODSB LODSv^ov is ov; LODSv
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns LOOPs is LOOP | LOOPE | LOOPNE
<alphabetical constructors L-Q>+= (<-U U->) [<-D->] constructors LOOPs^ov reloc is ov; LOOPs; rel8(reloc) LSL^ov reg, Eaddr is ov; LSL; Eaddr & reg_opcode = reg ... LTR Eaddr is (grp6; Eaddr) & LTR
<32-bit constructors>+= (<-U) [<-D->] LAHF LAR^ov lfp^ov LEA^ov LEAVE LGDT LIDT LLDT LMSW LOCK LODSB LODSv^ov
<alphabetical constructors L-Q>+= (<-U U->) [<-D->] MOV^"mrb" Eaddr, reg is MOV & Eb.Gb; Eaddr & reg_opcode = reg ... MOV^"mr"^ov Eaddr, reg is ov; MOV & Ev.Gv; Eaddr & reg_opcode = reg ... MOV^"rmb" reg, Eaddr is MOV & Gb.Eb; Eaddr & reg_opcode = reg ... MOV^"rm"^ov reg, Eaddr is ov; MOV & Gv.Ev; Eaddr & reg_opcode = reg ... MOV.Ew.Sw Mem, sr16 is ow; MOV.Ew.Sw; Mem & reg_opcode = sr16 ... MOV.Sw.Ew Mem, sr16 is MOV.Sw.Ew; Mem & reg_opcode = sr16 ... # assume 32-bit address mode MOV.AL.Ob offset is MOV.AL.Ob; i32 = offset MOV.eAX.Ov^ov offset is ov; MOV.eAX.Ov; i32 = offset MOV.Ob.AL offset is MOV.Ob.AL; i32 = offset MOV.Ov.eAX^ov offset is ov; MOV.Ov.eAX; i32 = offset MOVib r8, i8! is MOVib & r8; i8 MOViw r32, i16! is ow; MOViv & r32; i16 MOVid r32, i32! is od; MOViv & r32; i32 MOV.Eb.Ib Eaddr, i8! is MOV.Eb.Ib; Eaddr & reg_opcode = 0 ...; i8 MOV.Ev.Iv^ow Eaddr, i16! is ow; MOV.Ev.Iv; Eaddr & reg_opcode = 0 ...; i16 MOV.Ev.Iv^od Eaddr, i32! is od; MOV.Ev.Iv; Eaddr & reg_opcode = 0 ...; i32 MOV.Cd.Rd cr, reg is MOV.Cd.Rd; mod = 3 & r_m = reg & reg_opcode = cr MOV.Rd.Cd reg, cr is MOV.Rd.Cd; mod = 3 & r_m = reg & reg_opcode = cr MOV.Dd.Rd dr, reg is MOV.Dd.Rd; mod = 3 & r_m = reg & reg_opcode = dr MOV.Rd.Dd reg, dr is MOV.Rd.Dd; mod = 3 & r_m = reg & reg_opcode = dr MOVSB MOVSv^ov is ov; MOVSv MOVSX.Gv.Eb^ov r32, Eaddr is ov; MOVSX.Gv.Eb; Eaddr & reg_opcode = r32 ... MOVSX.Gv.Ew r16, Eaddr is MOVSX.Gv.Ew; Eaddr & reg_opcode = r16 ... MOVZX.Gv.Eb^ov r32, Eaddr is ov; MOVZX.Gv.Eb; Eaddr & reg_opcode = r32 ... MOVZX.Gv.Ew r16, Eaddr is MOVZX.Gv.Ew; Eaddr & reg_opcode = r16 ... MUL.AL Eaddr is (grp3.Eb; Eaddr) & MUL.AL.eAX MUL.AX^ov Eaddr is ov; (grp3.Ev; Eaddr) & MUL.AL.eAX
<32-bit constructors>+= (<-U) [<-D->] MOV^"mrb" MOV^"mr"^ov MOV^"rmb" MOV^"rm"^ov MOV.Ew.Sw MOV.Sw.Ew MOV.AL.Ob MOV.eAX.Ov^ov MOV.Ob.AL MOV.Ov.eAX^ov MOVib MOViw MOVid MOV.Eb.Ib MOV.Ev.Iv^ow MOV.Ev.Iv^od MOVSB MOVSv^ov MOVSX.Gv.Eb^od MOVSX.Gv.Ew MOVZX.Gv.Eb^od MOVZX.Gv.Ew MUL.AL MUL.AX^ov
<pentium-linux.spec>+= (U-> U->) [<-D->] discard MOVSX.Gv.Ew MOVZX.Gv.Eb^ov MOVZX.Gv.Ew MOV.Ew.Sw
<alphabetical constructors L-Q>+= (<-U U->) [<-D->] NEGb Eaddr is (grp3.Eb; Eaddr) & NEG NEG^ov Eaddr is ov; (grp3.Ev; Eaddr) & NEG NOP NOTb Eaddr is (grp3.Eb; Eaddr) & NOT NOT^ov Eaddr is ov; (grp3.Ev; Eaddr) & NOT
<32-bit constructors>+= (<-U) [<-D->] NEGb NEG^ov NOP NOTb NOT^ov
<alphabetical constructors L-Q>+= (<-U U->) [<-D->] # OR is in the arith group OUT.Ib.AL i8! is OUT.Ib.AL; i8 OUT.Ib.eAX^ov i8! is ov; OUT.Ib.eAX; i8 OUT.DX.AL OUT.DX.eAX^ov is ov; OUT.DX.eAX OUTSB OUTSv^ov is ov; OUTSv
<32-bit constructors>+= (<-U) [<-D->] OUT.Ib.AL OUT.Ib.eAX^ov OUT.DX.AL OUT.DX.eAX^ov OUTSB OUTSv^ov
<alphabetical constructors L-Q>+= (<-U U->) [<-D->] POP.Ev^ov Mem is ov; POP.Ev; Mem & reg_opcode = 0 ... POP^ov r32 is ov; POP & r32
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns POPs is POP.ES | POP.SS | POP.DS | POP.FS | POP.GS POPv is POPA | POPF
<alphabetical constructors L-Q>+= (<-U U->) [<-D->] constructors POPs POPv^ov is ov; POPv PUSH.Ev^ov Eaddr is ov; (grp5; Eaddr) & PUSH.Ev PUSH^ov r32 is ov; PUSH & r32 PUSH.Ib i8! is PUSH.Ib; i8 PUSH.Iv^ow i16! is ow; PUSH.Iv; i16 PUSH.Iv^od i32! is od; PUSH.Iv; i32
<pattern specs for other patterns>+= (<-U U->) [<-D->] patterns PUSHs is PUSH.CS | PUSH.SS | PUSH.DS | PUSH.ES | PUSH.FS | PUSH.GS PUSHv is PUSHA | PUSHF
<alphabetical constructors L-Q>+= (<-U U->) [<-D] constructors PUSHs PUSHv^ov is ov; PUSHv
<32-bit constructors>+= (<-U) [<-D->] POPs POPv^ov PUSH.Ev^ov PUSH^ov PUSH.Ib PUSH.Iv^ow PUSH.Iv^od PUSHs PUSHv^ov
<alphabetical constructors R>= (<-U U->) [D->] # ROL ROR RCL RCR SHLSAL SHR SAR rot^bshifts Eaddr is (bshifts; Eaddr) & rot rot^vshifts^ov Eaddr is ov; (vshifts; Eaddr) & rot rot^B.Eb.Ib Eaddr, i8! is (B.Eb.Ib; Eaddr) & rot; i8 rot^B.Ev.Ib^ov Eaddr, i8! is ov; (B.Ev.Ib; Eaddr) & rot; i8
<32-bit constructors>+= (<-U) [<-D->] rot^bshifts rot^vshifts^ov rot^B.Ev.Ib^ov
<alphabetical constructors R>+= (<-U U->) [<-D] RDMSR REP REPNE RET RET.far RET.Iw i16 is RET.Iw; i16 RET.far.Iw i16 is RET.far.Iw; i16 RSM
<32-bit constructors>+= (<-U) [<-D->] RDMSR REP REPNE RET RET.far RET.Iw RET.far.Iw RSM
<pentium-linux.spec>+= (U-> U->) [<-D->] discard RDMSR
<alphabetical constructors S-Z>= (<-U U->) [D->] SAHF # SAL SAR SHL SR above with rot # SBB is in the arith group SCASB SCASv^ov is ov; SCASv ## SETb^cond Mem is SETb & ... cond; Mem SETb^cond Eaddr is SETb & ... cond; Eaddr SGDT Mem is (grp7; Mem) & SGDT SIDT Mem is (grp7; Mem) & SIDT
<32-bit constructors>+= (<-U) [<-D->] SCASB SCASv^ov SETb^cond SGDT SIDT
<pattern specs for other patterns>+= (<-U U->) [<-D] patterns shdIb is SHRD.Ib | SHLD.Ib shdCL is SHRD.CL | SHLD.CL
<alphabetical constructors S-Z>+= (<-U U->) [<-D->] constructors shdIb^ov Eaddr, reg, count is ov; shdIb; Eaddr & reg_opcode = reg ... ; i8 = count shdCL^ov Eaddr, reg, "CL" is ov; shdCL; Eaddr & reg_opcode = reg ... SLDT Eaddr is (grp6; Eaddr) & SLDT SMSW Eaddr is (grp7; Eaddr) & SMSW STC STD STI STOSB STOSv^ov is ov; STOSv STR Mem is (grp6; Mem) & STR # SUB is in the arith group
<32-bit constructors>+= (<-U) [<-D->] shdIb^ov shdCL^ov SLDT SMSW STC STD STI STOSB STOSv^ov STR
<alphabetical constructors S-Z>+= (<-U U->) [<-D->] TEST.AL.Ib i8 is TEST.AL.Ib; i8 TEST.eAX.Iv^ow i16 is ow; TEST.eAX.Iv; i16 TEST.eAX.Iv^od i32 is od; TEST.eAX.Iv; i32 TEST.Eb.Ib Eaddr, i8 is (grp3.Eb; Eaddr) & TEST.Ib.Iv; i8 TEST.Ew.Iw Eaddr, i16 is ow; (grp3.Ev; Eaddr) & TEST.Ib.Iv; i16 TEST.Ed.Id Eaddr, i32 is od; (grp3.Ev; Eaddr) & TEST.Ib.Iv; i32 TEST.Eb.Gb Eaddr, reg is TEST.Eb.Gb; Eaddr & reg_opcode = reg ... TEST.Ev.Gv^ov Eaddr, reg is ov; TEST.Ev.Gv; Eaddr & reg_opcode = reg ...
<32-bit constructors>+= (<-U) [<-D->] TEST.AL.Ib TEST.eAX.Iv^ow TEST.eAX.Iv^od TEST.Eb.Ib TEST.Ew.Iw TEST.Ed.Id TEST.Eb.Gb TEST.Ev.Gv^ov
<alphabetical constructors S-Z>+= (<-U U->) [<-D->] VERR Eaddr is (grp6; Eaddr) & VERR VERW Eaddr is (grp6; Eaddr) & VERW
<32-bit constructors>+= (<-U) [<-D->] VERR VERW
<alphabetical constructors S-Z>+= (<-U U->) [<-D->] WAIT WBINVD WRMSR
<32-bit constructors>+= (<-U) [<-D->] WAIT WBINVD WRMSR
<pentium-linux.spec>+= (U-> U->) [<-D] discard WRMSR
<alphabetical constructors S-Z>+= (<-U U->) [<-D] XADD.Eb.Gb Eaddr, reg is XADD.Eb.Gb; Eaddr & reg_opcode = reg ... XADD.Ev.Gv^ov Eaddr, reg is ov; XADD.Ev.Gv; Eaddr & reg_opcode = reg ... XCHG^"eAX"^ov r32 is ov; XCHG & r32 XCHG.Eb.Gb Eaddr, reg is XCHG.Eb.Gb; Eaddr & reg_opcode = reg ... XCHG.Ev.Gv^ov Eaddr, reg is ov; XCHG.Ev.Gv; Eaddr & reg_opcode = reg ... XLATB is XLAT # XOR is in the arith group
<32-bit constructors>+= (<-U) [<-D] XADD.Eb.Gb XADD.Ev.Gv^ov XCHG^"eAX"^ov XCHG.Eb.Gb XCHG.Ev.Gv^ov XLATB
WAIT
instruction, which we've included in the alphabetical list
above, since they're all given together in Chapter 25.
The Gnu-Linux assembler does not recognize instructions prefixed
by WAIT
as synthetics.
For example, wait; fclex
disassembles as fclex
;
fclex
disassembles as fnclex
.
<pentium-synth.spec>=
constructors
<synthetics with WAIT
>
mld
needs some synthetics to help it deal with overloaded
operators.
This is a hack but better than writing them all out by hand.
<mld-pentium.spec>= [D->] constructors _call_l reloc : Function is call_jvod(reloc) _call_rm Mem : Function is call_evod(Mem) callfn Function is Function
The following constructor is used to emit relocatable addresses. It is the same as that defined for the MIPS and SPARC.
<mld-pentium.spec>+= [<-D] fields of addrtoken (32) addr32 0:31 placeholder for addrtoken is addr32 = 7 constructors emit_raddr reloc is addr32 = reloc
add
can mean any of five different instructions, depending on
the sizes and locations of the operands. The toolkit cannot do
this kind of overloading; it must use different names for
different instructions (constructors). The reason is that the
toolkit must generate a different encoding procedure for each
instruction, and in most programming languages, different
procedures must have different names.
Even in languages that do
support overloading, we might not be able to use the same name,
because the name-resolution mechanisms used in programming
languages are typically type-based and quite different from
what an assembler uses (LR parsing).
We solve this problem by requiring each constructor to have a
different name. Typically, the specification writer distinguishes
variants by adding suffixes to the base name of the constructor.
For example, the Pentium add
instructions include constructors
called addb
, addib
, addiowb
, addmrb
, and
addrmb
. To get from these names back to assembly language,
we have to define an appropriate mapping. These mappings are
defined separately from the main specification, because different
vendors use different syntaxes for their assembly languages.
An assembly specification includes three parts: a mapping of constructor names to assembly opcodes, the assembly format for each constructor operand, and the assembly syntax for each constructor. We use the specification for the assembly language supported by the Gnu-Linux assembler to illustrate assembly specifications. First, we describe assembly name mappings, then operand format and constructor assembly syntax.
add^"mrb"
contains two parts,
the first derived from the pattern add
and the second from the
string "mrb"
. The assembly name for this constructor is
addb
, so the pattern add
contributes its name and the suffix
"mrb"
is mapped to the string "b"
.
This example illustrates how the constructor addmrb
has a more specific name to
disambiguate it from other overloaded variants of addb
.
assembly opcode
introduces mappings from complete
constructor names (strings) to their assembly names (strings).
assembly component
introduces mappings from parts
of constructor names to their assembly names.
We provide a component-wise mapping, because it improves
factoring of assembly names among constructors that
share common suffixes and prefixes. For example, the suffix
B.Eb.Ib
always maps to b
in every constructor name
where it appears.
There is redundancy in the mapping of constructor names, so we
use a regular expression syntax to group related names.
The regular expression syntax is the same as the syntax for C-shell
``globbing'' expressions [cite joy:c-shell].
If a complete name mapping exists for a constructor name, it is
applied first.
If no complete mapping exists,
mappings are applied individually to each part of a
constructor's name and the resulting strings are concatenated into the
complete assembly name.
For example, the mappings applied to the parts of the constructor
name add^"mrb"
are:
<example mappings>= assembly component add is add {"mrb","rmb"} is b
The first rule maps add
to itself
and the second maps any string that matches mrb
or rmb
to b
.
The least general rule that matches a string is applied.
It is often useful to define a default mapping,
i.e., for the pattern ``*
''.
On the Pentium, for example, most constructor names do not map
directly to assembly names, so the default maps a name to the empty
string. This is wrong.
<pentium-linux-default.spec>= (U->) assembly component {Indir,{Disp*},Abs32,Reg,{*Index*},E,rel{8,16,32}} is "" {*} is $1
On the MIPS and SPARC, however, most constructor names map are
assembly names, so the default maps a name to itself, i.e.,
assembly component {*} is $1
.
The remaining rules specify all the assembly names for the Pentium constructors and illustrate use of the C-shell globbing expressions. In globbing expressions, ``*'' matches any string; any character and ``.'' matches itself. The concatenation operator is implicit, so adjacent characters are concatenated. Alternatives are comma-separated lists of strings delimited by ``'' and ``''.
We provide one extension to the C-shell syntax:
expressions in curly braces may be referenced on the right-hand side
by $n, where n is the n-th braced expression on the
left-hand side.
For example, the first rule specifies that the
suffixes ow
and aw
map to the assembly name w
, and od
and ad
map to l
.
The rest of these rules map all the suffixes used in
constructor names to their assembly names.
<pentium-linux-names.spec>= (U->) [D->] assembly component {iAL,AL} is b {iAX,AX} is w {iEAX,eAX} is l {o,a}d is l {o,a}w is w {.I32,.R64,.lsI32,.lsR64} is l {.I16,.R32,.lsR32} is s .lsI16 is w b.* is b {b,w} is $1 d is l B.{Eb.1,Eb.CL,Eb.Ib,Ev.Ib} is b B.{Ev.1,Ev.CL} is w {.STi,.ST.STi,.STi.St} is "" P.STi.ST is P .{O,NO,B,NB,Z,NZ,BE,NBE,S,NS,P,NP,L,NL,LE,NLE} is $1
Many constructor names contain suffixes that are mneumonics
for the opcodes they represent. These suffixes are often eliminated in
the assembly name.
For example, the assembly names for the immediate opcodes ADD.i
and
OR.i
are ADD
and OR
, respectively.
The following rules truncate these suffixes.
<pentium-linux-names.spec>+= (U->) [<-D->] assembly component {CALL}.* is $1 {CALL}l is $1 CMPXCHG8B is CMPXCHG {CMPXCHG,XADD,XCHG,TEST}.Eb.Gb is $1b CMPSv is CMPS {CMP*}.* is $1 {DEC,INC}.* is $1 {DIV}.* is $1 {*}.st is $1 {FLD,FSTP}80 is $1t {FLD,FSTP}.* is $1 {FILD,FISTP}.* is $1 {FICOM*}16 is $1s {FICOM*}32 is $1l {FILD,FISTP*}64 is $1ll {IDIV,IMUL}.* is $1 {IN,INT,J}.* is $1 JMP.Ep is lJMP {JMP}.* is $1 MOV{.Eb.Ib,.AL.Ob,.Ob.AL} is MOVb {MOViv,MOV.Ev.Iv} is MOV MOVSX.Gv.Ew is MOVSwl {MOV.Ew.Sw,MOV.Sw.Ew} is MOVw {MOVS,MOVZ}X.Gv.Eb is $1b {MOVSv,MOVSX.*} is MOVS {MOV,MOVS}.* is $1 {MOVSX,MOVZX}.* is $1 MOVi{b,w} is MOV$1 MOVid is MOVl {*}.AX is $1 {OUT.Ib.AL,OUT.DX.AL} is OUTb {OUT,OUTS}.* is $1 {RET.far}* is lRET {POP,PUSH,RET}.* is $1 {SCAS,STOS}v is $1 {SHRD,SHLD}.* is $1 SHRSAL is SHR TEST{.*.Ib,.Eb.*} is TESTb TEST.*.Iw is TESTw TEST.*.Id is TESTl TEST.* is TEST {XADD*}.* is $1 {XCHG*}.* is $1 {*}i is $1
The remaining rules are for constructors with special assembly names.
<pentium-linux-names.spec>+= (U->) [<-D->] assembly component {"mr","rm"} is "" {"mrb","rmb"} is b {*}64 is $1 {IDIV,DIV}"AL" is $1 {IDIV,DIV}"AX" is $1 {IDIV,DIV}"eAX" is $1 {IMULrm} is IMUL INT3 is INT FLD.ext is FLDLL {Jv,Jb} is J {INC}.Eb is INCb {INS,LODS}v is $1 MUL.AL is MULb OUTSv is OUTS SHLSAL is SHL TEST.Ew.Iw is TESTw TEST.Ed.Id is TESTl SETb is SET
Component-wise mapping doesn't work for all names.
<pentium-linux-names.spec>+= (U->) [<-D->] assembly opcode CALL.{Ev}{od} is CALL CALL.{Jv,Ep}{od,ow} is lCALL CALL.aP{od} is CALL CMPSv{od,ow}ad is CMPSl CMPSv{od,ow}aw is CMPSw JMP.Epod is lJMP MOVSX.Gv.Ebod is MOVSbl MOVSX.Gv.Ebow is MOVSbw {ROL,ROR,RCL,RCR,SHR,SAR}{B.Ev.*}od is $1l {ROL,ROR,RCL,RCR,SHR,SAR}{B.Ev.*}ow is $1w SHLSAL{B.Ev.*}od is SHLl SHLSAL{B.Ev.*}ow is SHLw XCHGeAXow is XCHGw XCHGeAXod is XCHGl
assembly operand
introduces mappings from operands to formatted strings
that specify how to print the operands in assembly code.
Operands may be fields, integer inputs, relocatable addresses, or
constant strings.
We use printf
-style syntax for formatted strings.
The first assembly operand
rule below specifies that immediate operands
are prefixed by ``$'' and are printed as integers.
The second rule specifies that the listed field inputs are prefixed by
``%'' and printed as strings, using the names provided in their
fieldinfo
declarations.
<pentium-linux-names.spec>+= (U->) [<-D->] assembly operand [count i8 i16 i32] is "$%d" [r32 sr16 r16 r8 base index] is "%%%s"
Some inputs are not declared as fields but should be printed in the
same format as fields. For example, the input reg
should be
printed using the names associated with the field base
.
The following rule uses the optional using
field
clause to specify that reg
, reg8
, etc. should
be printed using the fieldinfo associated with base
.
<pentium-linux-names.spec>+= (U->) [<-D->] assembly operand [reg reg8 sreg cr dr] is "%%%s" using field base
Some operands are used implicitly in a constructor.
For example, the constructor OUT.DX.AL "dx","al",
implicitly uses the dx
and al
registers as its operands.
Like all other operands, their format is assembler dependent so we
provide mappings for printing them in the Gnu-Linux format.
<pentium-linux-names.spec>+= (U->) [<-D] assembly operand dx is "%%dx" ax is "%%ax"
assembly syntax
.
Providing assembly syntax in a constructor specification can help a
specification writer or user read and identify a constructor, and it
is concise when only one assembly syntax is required.
An alternate syntax may be needed, however, if more than one assembly language
is used on the target.
assembly syntax
uses the same syntax as the constructors
directive:
a constructor name followed by a list of operands.
The assembly-syntax specification must use
the same set of operands that the constructor
uses, but the operands may appear in any order and with any
syntactic sugar.
To reduce redundancy, we define new patterns to
group constructors that share the same assembly syntax.
The Gnu-Linux assembly language reverses the order of all operands. The following directives reverse the order.
<pentium-linux-syntax.spec>= (U->) [D->] assembly syntax arith^"iAL" i8!, "%al" arith^"iAX" i16!, "%ax" arith^"iEAX" i32!, "%eax" DIV^"AL" Eaddr, "%al" DIV^"AX" Eaddr, "%ax" DIV^"eAX" Eaddr, "%eax" arithI^"b" i8!, Eaddr arithI^"w" i16!, Eaddr arithI^"d" i32!, Eaddr arithI^ov^"b" i8!, Eaddr MOV.Eb.Ib i8!, Eaddr MOV.Ev.Iv^ow i16!, Eaddr MOV.Ev.Iv^od i32!, Eaddr
<pentium-linux-syntax.spec>+= (U->) [<-D->] assembly syntax arith^"rmb" Eaddr, reg8 arith^"rm"^ov Eaddr, reg IMULrm^ov Eaddr, reg MOV^"rmb" Eaddr, reg MOV^"rm"^ov Eaddr, reg MOVZX.Gv.Ew Eaddr, r16 MOVSX.Gv.Ew Eaddr, r16 MOVZX.Gv.Eb^ov Eaddr, r32 MOVSX.Gv.Eb^ov Eaddr, r32 BSF^ov Eaddr, reg BSR^ov Eaddr, reg LAR^ov Eaddr, reg arith^"mrb" reg8, Eaddr arith^"mr"^ov reg, Eaddr MOV^"mr"^ov reg, Eaddr MOV^"mrb" reg, Eaddr TEST.Ev.Gv^ov reg, Eaddr BT^ov reg, Eaddr BTi^ov i8!, Eaddr BTC^ov reg, Eaddr BTCi^ov i8!, Eaddr BTR^ov reg, Eaddr BTRi^ov i8!, Eaddr BTS^ov reg, Eaddr BTSi^ov i8!, Eaddr CMPXCHG.Eb.Gb reg, Eaddr CMPXCHG.Ev.Gv^ov reg, Eaddr
<pentium-linux-syntax.spec>+= (U->) [<-D->] patterns fstack is FADD | FDIV | FDIVR | FMUL | FSUB | FSUBR fsti is fstack | FCOMs stidx is FFREE | FUCOMs | FXCH Sstack is P.STi.ST | .STi.St assembly syntax fstack^Sstack "%st", "%st"(idx) fstack^.ST.STi "%st"(idx), "%st" FCOMs^.ST.STi "%st"(idx), "%st" FSTs.st^.STi "%st"(idx) FLD.STi "%st"(idx) stidx "%st"(idx) FNSTSW.AX "%ax" FSTSW.AX "%ax" IDIV^"AX" Eaddr, "%ax" IDIV^"eAX" Eaddr, "%eax" IN.AL.Ib i8!, "%al", i8! IN.eAX.Ib^ov i8!, "%eax", i8! IN.AL.DX "%dx, %al" IN.eAX.DX^ov "%dx, %eax" IMUL.Iv^"d" i32!, Eaddr, reg INT3 "$3" LEA^ov Mem, reg MOVib i8!, r8 MOViw i16!, r32 MOVid i32!, r32 MOV.AL.Ob offset, "%al" MOV.eAX.Ov^ov offset, "%eax" MOV.Ob.AL "%al", offset MOV.Ov.eAX^ov "%eax", offset OUT.Ib.AL "%al", i8! OUT.Ib.eAX^ov "%eax", i8! OUT.DX.AL "%al", "%dx" OUT.DX.eAX^ow "%al", "%dx" OUT.DX.eAX^od "%eax", "%dx" patterns pES is POP.ES | PUSH.ES pSS is POP.SS | PUSH.SS pDS is POP.DS | PUSH.DS pFS is POP.FS | PUSH.FS pGS is POP.GS | PUSH.GS assembly syntax pES "%ES" pSS "%SS" pDS "%DS" pFS "%FS" pGS "%GS" PUSH.CS "%CS" rot^B.Eb.1 "$1", Eaddr rot^B.Ev.1^ov "$1", Eaddr rot^B.Eb.CL "%cl", Eaddr rot^B.Ev.CL^ov "%cl", Eaddr rot^B.Eb.Ib i8!, Eaddr rot^B.Ev.Ib^ov i8!, Eaddr shdIb^ov count, reg, Eaddr shdCL^ov "%cl", reg, Eaddr
<pentium-linux-syntax.spec>+= (U->) [<-D->] TEST.AL.Ib i8, "%al" TEST.eAX.Iv^ow i16, "%ax" TEST.eAX.Iv^od i32, "%ax" TEST.Eb.Ib i8, Eaddr TEST.Ew.Iw i16, Eaddr TEST.Ed.Id i32, Eaddr TEST.Eb.Gb reg, Eaddr TEST.Ev.Gv^ov reg, Eaddr XADD.Eb.Gb reg, Eaddr XADD.Ev.Gv^ov reg, Eaddr XCHG.Eb.Gb reg, Eaddr XCHG^"eAX"^ov "%eax", r32 XCHG.Ev.Gv^ov reg, Eaddr
Effective addresses also use a different syntax under Gnu-Linux.
<pentium-linux-syntax.spec>+= (U->) [<-D] assembly syntax Indir (reg) Disp32 d(reg) Index (base,index,ss) Index32 d(base,index,ss) ShortIndex d(,index,ss)
<i486-linux.spec>= <pentium32.spec> discard <names of pentium constructors> <pentium-linux.spec>
Names for generating assembly emitters are specified in several chunks.
<pentium-names.spec>= <pentium-linux-names.spec> <pentium-linux-default.spec> <pentium-linux-syntax.spec>
<header.spec>= (U-> U-> U-> U-> U-> U-> U->) <field specs> patterns <patterns for integer opcodes> <pattern specs for other patterns> <prefix assignments> <placeholders> <constructors for displacements> <constructors for effective addresses>
Check the arithmetic constructors. Substitute other chunks for this one to check each chunk.
<pentium-AF.spec>= <header.spec> <alphabetical constructors A-F>
<pentium-GK.spec>= <header.spec> <alphabetical constructors G-K>
<pentium-LQ.spec>= <header.spec> <alphabetical constructors L-Q>
<pentium-R.spec>= <header.spec> <alphabetical constructors R>
<pentium-SZ.spec>= <header.spec> <alphabetical constructors S-Z>
<pentium-arith.spec>= <header.spec> <arithmetic constructors>
<pentium-float.spec>= <header.spec> <floating-point constructors>
The Pentium checker checks all 32-bit instructions
accepted by the Gnu-Linux assembler. We omit some
variants of the rot
instructions, because exhaustively
checking each one seemed unnecessary.
<pentium-check.spec>= <pentium32.spec> <pentium-linux.spec> discard rot^B.Eb.1 rot^B.Ev.CL ROL^B.Eb.Ib RCL^B.Eb.Ib SHLSAL^B.Eb.Ib SAR^B.Eb.Ib ROR^B.Ev.Ib^ov RCR^B.Ev.Ib^ov SHR^B.Ev.Ib^ov
<pentium-checker.s>= .align 16
opcode
>: U1, D2, D3, D4, D5
WAIT
>: D1, D2, D3, D4, D5, D6, U7