10.4 Encoding 80x86 Instructions


10.4 Encoding 80x86 Instructions

The Y86 processor is simple to understand; it is easy to encode instructions by hand for this processor, and it is a great vehicle for learning how to assign opcodes. It's also a purely hypothetical device intended only as a teaching tool. It's time to take a look at the machine instruction format for a real CPU- the 80x86. After all, the programs you're going to write will run on areal CPU, like the 80x86, so to fully appreciate what your compilers are doing with your code (so you can choose the best statements and data structures when writing that code), you need to understand how real instructions are encoded. Even if you're using a different CPU, studying the 80x86 instruction encoding is a good exercise, because it is one of the more complex instruction sets in common use today and will provide a good insight into the operation of other real-world CPUs.

They don't call the 80x86 a Complex Instruction Set Computer (CISC) chip for nothing. Although more complex instruction encodings do exist, no one is going to challenge the assertion that the 80x86 has a complex instruction encoding. The generic 80x86 instruction takes the form shown in Figure 10-14. Although this diagram seems to imply that instructions can be up to 16 bytes long, instructions cannot actually be greater than 15 bytes in length.

click to expand
Figure 10-14: 80x86 instruction encoding

The prefix bytes are not the same as the opcode expansion prefix byte that we discussed in the previous section. Instead, the 80x86 prefix bytes modify the behavior of existing instructions. An instruction may have a maximum of four prefix bytes attached to it, but the 80x86 certainly supports more than four different prefix values. Also note that the behaviors of many prefix bytes are mutually exclusive, and the instruction's results will be undefined if you put a pair of mutually exclusive prefix bytes in front of an instruction. We'll take a look at a couple of these prefix bytes in a moment.

The 80x86 supports two basic opcode sizes: a standard 1-byte opcode and a 2-byte opcode consisting of a $0F opcode expansion prefix byte and a second byte specifying the actual instruction. One way to think of this opcode expansion prefix byte is as an 8-bit extension of the iii field in the Y86 encoding. This enables the encoding of up to 512 different instruction classes, although the 80x86 does not yet use them all. In reality, various instruction classes use certain bits in this opcode expansion prefix byte for decidedly non-instruction-class purposes. For example, consider the add instruction opcode. It takes the form shown in Figure 10-15.

click to expand
Figure 10-15: 80x86 add opcode

Bit 1 (d) specifies the direction of the transfer. If this bit is zero, then the destination operand is a memory location, such as in add(al,[ebx]); . If this bitis one, the destination operand is a register, as in add([ebx],al); .

Note that bit 0 (s) specifies the size of the operands the add instruction operates upon. There is a problem here, however. The 80x86 family supports up to three different operand sizes: 8-bit operands, 16-bit operands, and 32-bit operands. With a single size bit, the instruction can only encode two of these three different sizes. In modern (32-bit) operating systems, the vast majority of operands are either 8 bits or 32 bits, so the 80x86 CPU uses the size bit in the opcode to encode 8-bit or 32-bit sizes. For 16-bit operands, which occur less frequently than 8-bit or 32-bit operands, Intel uses a special opcode prefix byte to specify the size. As long as instructions that have 16-bit operands occur less than one out of every eight instructions (and this is generally true), this is more compact than adding another bit to the instruction's size. Using a size prefix byte allowed Intel's designers to extend the number of operand sizes without having to change the instruction encoding inherited from the original 16-bit processors in this CPU family.

10.4.1 Encoding Instruction Operands

The mod-reg-r/m byte (see Figure 10-14) provides the encoding for instruction operands by specifying the base addressing mode used to access the operands and the instruction operand size. This byte contains the fields shown in Figure 10-16.

click to expand
Figure 10-16: mod-reg-r/m byte

The reg field almost always specifies an 80x86 register. However, depending on the instruction, the register specified by reg can be either the source or the destination operand. To distinguish between the two possibilities, many instructions have the d (direction) field in their opcode that contains a value of zero when reg is the source and a value of one when reg is the destination operand.

This field uses the 3-bit register encodings found in Table 10-1. As you just learned, the size bit in the instruction's opcode specifies whether the reg field specifies an 8- or 32-bit register (when operating under a modern, 32-bit operating system). To make the reg field specify a 16-bit register requires setting the size bit in the opcode to one, as well as adding an additional prefix byte.

Table 10-1: reg Field Encodings

reg Value

Register If Data Size Is 8 Bits

Register If Data Size Is 16 Bits

Register If Data Size Is 32 Bits

%000

al

ax

eax

%001

cl

cx

ecx

%010

dl

dx

edx

%011

bl

bx

ebx

%100

ah

sp

esp

%101

ch

bp

ebp

%110

dh

si

esi

%111

bh

di

edi

With the d bit in the opcode of a two-operand instruction determining whether the reg field contains the source operand or the destination operand, the mod and r/m fields combine to specify the other of the two operands. In the case of a single-operand instruction like not or neg , the reg field will contain an opcode extension and mod and r/m will combine to specify the only operand. The mod and r/m fields together specify the operand addressing modes listed in Tables 10-2 and 10-3.

Table 10-2: mod Field Encodings

mod

Description

%00

Specifies register indirect addressing mode (with two exceptions: scaled indexed [sib] addressing modes with no displacement operand when r/m= %100; and displacement-only addressing mode when r/m = %101).

%01

Specifies that a 1-byte signed displacement follows the addressing mode byte(s).

%10

Specifies that a 1-byte signed displacement follows the addressing mode byte(s).

%11

Specifies direct register access.

Table 10-3: mod-r/m Encodings

mod

r/m

Addressing Mode

%00

%000

[eax]

%01

%000

[eax+disp 8 ]

%10

%000

[eax+disp 32 ]

%11

%000

al, ax, or eax

%00

%001

[ecx]

%01

%001

[ecx+disp 8 ]

%10

%001

[ecx+disp 32 ]

%11

%001

cl, cx, or ecx

%00

%010

[edx]

%01

%010

[edx+disp 8 ]

%10

%010

[edx+disp 32 ]

%11

%010

dl, dx, or edx

%00

%011

[ebx]

%01

%011

[ebx+disp 8 ]

%10

%011

[ebx+disp 32 ]

%11

%011

bl, bx, or ebx

%00

%100

Scaled indexed (sib) mode

%01

%100

sib + disp 8 mode

%10

%100

sib + disp 32 mode

%11

%100

ah, sp, or esp

%00

%101

Displacement-only mode (32-bit displacement)

%01

%101

[ebp+disp 8 ]

%10

%101

[ebp+disp 32 ]

%11

%101

ch, bp, or ebp

%00

%110

[esi]

%01

%110

[esi+disp 8 ]

%10

%110

[esi+disp 32 ]

%11

%110

dh, si, or esi

%00

%111

[edi]

%01

%111

[edi+disp 8 ]

%10

%111

[edi+disp 32 ]

%11

%111

bh, di, or edi

There are a couple of interesting things to note about Tables 10-2 and 10-3. First, there are two different forms of the [ reg + disp ] addressing modes: one form with an 8-bit displacement and one form with a 32-bit displacement. Addressing modes whose displacement falls in the range ˆ’ 128..+127 require only a single byte after the opcode to encode the displacement. Instructions with a displacement that falls within this range will be shorter and sometimes faster than instructions whose displacement values are not within this range and thus require the addition of four bytes after the opcode.

The second thing to note is that there is no [ebp] addressing mode. If you look in Table 10-3 where this addressing mode logically belongs (where r/m is %101 and mod is %00), you'll find that its slot is occupied by the 32-bit displacement-only addressing mode. The basic encoding scheme for addressing modes didn't allow for a displacement-only addressing mode, so Intel ' stole ' the encoding for [ebp] and used that for the displacement-only mode. Fortunately, anything you can do with the [ebp] addressing mode you can also do with the [ebp+ disp 8 ] addressing mode by setting the 8-bit displacement to zero. True, such an instruction is a little bit longer than it would otherwise need to be if the [ebp] addressing mode existed, but the same capabilities are still there. Intel wisely chose to replace this particular register indirect addressing mode because they anticipated that programmers would use it less often than they would use the other register indirect addressing modes.

Another thing you'll notice missing from this table are addressing modes of the form [esp], [esp+disp 8 ], and [esp+ disp 32 ] . Intel's designers borrowed the encodings for these three addressing modes to support the scaled indexed addressing modes they added to their 32-bit processors in the 80x86 family.

If r/m = %100 and mod = %00, then this specifies an addressing mode of the form [reg1 32 +reg2 32 *n] . This scaled index addressing mode computes the final address in memory as the sum of reg2 multiplied by n ( n = 1, 2, 4, or 8) and reg1 . Programs most often use this addressing mode when reg1 is a pointer holding the base address of an array of bytes ( n = 1), words ( n = 2), double words ( n = 4), or quad words ( n = 8) and reg2 holds the index into that array.

If r/m = %100 and mod = %01, then this specifies an addressing mode of the form [reg1 32 +reg2 32 *n + disp 8 ] . This scaled index addressing mode computes the final address in memory as the sum of reg2 multiplied by n ( n =1, 2, 4, or 8), reg1 , and the 8-bit signed displacement (sign extended to 32 bits). Programs most often use this addressing mode when reg1 is a pointer holding the base address of an array of records, reg2 holding the index into that array, and disp 8 providing the offset to a desired field in the record.

If r/m = %100 and mod = %10, then this specifies an addressing mode of the form [reg1 32 +reg2 32 *n + disp 32 ] . This scaled index addressing mode computes the final address in memory as the sum of reg2 multiplied by n ( n = 1, 2, 4, or 8), reg1 , and the 32-bit signed displacement. Programs most often use this addressing mode to index into static arrays of bytes, words, double words, or quad words.

If values corresponding to one of the sib modes appear in the mod and r/m fields, then the addressing mode is a scaled indexed addressing mode with a second byte (the sib ) following the mod-reg-r/m byte, though don't forget that the mod field still specifies a displacement size of zero, one, or four bytes. Figure 10-17 shows the layout of this extra sib, and Table 10-4, Table 10-5, and Table 10-6 explain the values for each of the sib fields.

click to expand
Figure 10-17: The sib (scaled index byte) layout
Table 10-4: Scale Values

Scale Value

Index*Scale Value

%00

Index*1

%01

Index*2

%10

Index*4

%11

Index*8

Table 10-5: Register Values for sib Encoding

Index Value

Register

%000

EAX

%001

ECX

%010

EDX

%011

EBX

%100

Illegal

%101

EBP

%110

ESI

%111

EDI

Table 10-6: Base Register Values for sib Encoding

Base

Value Register

%000

EAX

%001

ECX

%010

EDX

%011

EBX

%100

ESP

%101

Displacement only if mod = %00, EBP if mod = %01 or %10

%110

ESI

%111

EDI

The mod-reg-r/m and sib bytes are complex and convoluted, no question about that. The reason these addressing mode bytes are so convoluted is that Intel reused its 16-bit addressing circuitry when it switched to the 32-bit format rather than simply abandoning the 16-bit circuitry at that point. There were good hardware reasons for doing this, but the result is a complex scheme for specifying addressing modes.

Note that if the r/m field of the mod-reg-r/m byte contains %100 and mod does not contain %11 the addressing mode is a sib mode rather than the expected [esp], [esp+ disp 8 ] , or [ESP+ disp 32 ] mode. In this case the compiler or assembler will emit an extra sib byte immediately following the mod-reg-r/m byte. Table 10-7 lists the various combinations of legal scaled indexed addressing modes on the 80x86.

Table 10-7: The Scaled Indexed Addressing Modes

mod

Index

Legal Scaled Indexed Addressing Modes [1]

%00

%000

[base 32 +eax*n]

Base ‰  %101

%001

[base 32 +ecx*n]

 

%010

[base 32 +edx*n]

 

%011

[base 32 +ebx*n]

 

%100

n/a [2]

 

%101

[base 32 +ebp*n]

 

%110

[base 32 +esi*n]

 

%111

[base 32 +edi*n]

%00

%000

[disp 32 +eax*n]

Base = %101 [3]

%001

[disp 32 +ecx*n]

 

%010

[disp 32 +edx*n]

 

%011

[disp 32 +ebx*n]

 

%100

n/a

 

%101

[disp 32 +ebp*n]

 

%110

[disp 32 +esi*n]

 

%111

[disp 32 +edi*n]

%01

%000

[disp 8 +base 32 +eax*n]

 

%001

[disp 8 +base 32 +ecx*n]

 

%010

[disp 8 +base 32 +edx*n]

 

%011

[disp 8 +base 32 +ebx*n]

 

%100

n/a

 

%101

[disp 8 +base 32 +ebp*n]

 

%110

[disp 8 +base 32 +esi*n]

 

%111

[disp 8 +base 32 +edi*n]

%10

%000

[disp 32 +base 32 +eax*n]

 

%001

[disp 32 +base 32 +ecx*n]

 

%010

[disp 32 +base 32 +edx*n]

 

%011

[disp 32 +base 32 +ebx*n]

 

%100

n/a

 

%101

[disp 32 +base 32 +ebp*n]

 

%110

[disp 32 +base 32 +esi*n]

 

%111

[disp 32 +base 32 +edi*n]

[1] The base 32 register can be any of the 80x86 32-bit general-purpose registers, as specified by the base field.

[2] The 80x86 does not allow a program to use the ESP as an index register.

[3] The 80x86 doesn't support a [ base 32 +ebp* n ] addressing mode, but you can achieve the same effective address using [ base 32 +ebp* n + disp8 ] with an 8-bit displacement value of zero.

In each of the addressing modes appearing in Table 10-7, the mod field of the mod-reg-r/m byte specifies the size of the displacement (zero, one, or four bytes). The base and index fields of the sib specify the base and index registers, respectively. Note that this addressing mode does not allow the use of the ESP register as an index register. Presumably, Intel left this particular mode undefined to provide the ability to extend the addressing modes to three bytes in a future version of the CPU, although doing so seems a bit extreme.

Just as the mod-reg-r/m encoding replaced the [ebp] addressing mode with a displacement only mode, the sib addressing format replaces the [EBP+ index * scale ] mode with a displacement plus index mode (that is, no base register). If it turns out that you really need to use the [EBP+ index * scale ] addressing mode, you will have to use the [ disp8 +EBP+ index * scale ] mode instead, specifying a one-byte displacement value of zero.

10.4.2 Encoding the add Instruction - Some Examples

To figure out how to encode an instruction using this complex scheme, some examples may prove useful. Therefore, let's look at how to encode the 80x86 add instruction using various addressing modes. The add opcode is either $00,$01, $02, or $03, depending on the direction and size bits in the opcode (see Figure 10-15). Figures 10-18 through 10-25 on the next three pages show how to encode various forms of the add instruction using different addressing modes.

click to expand
Figure 10-18: Encoding the add( al, cl ); instruction
click to expand
Figure 10-19: Encoding the add( eax, ecx ); instruction
click to expand
Figure 10-20: Encoding the add( disp, edx ); instruction
click to expand
Figure 10-21: Encoding the add( [ebx], edi ); instruction
click to expand
Figure 10-22: Encoding the add( [esi+disp 8] , eax ); instruction
click to expand
Figure 10-23: Encoding the add( [ebp+disp 32 ], ebx ); instruction
click to expand
Figure 10-24: Encoding the add( [disp32+eax*1], ebp ); instruction
click to expand
Figure 10-25: Encoding the add( [ebx+edi*4], ecx ); instruction

There is an interesting side effect of the mod-reg-r/m organization and resulting from how the direction bit works: some instructions have two different legal opcodes. For example, we could also encode the add(al,cl); instruction shown in Figure 10-18 as $02, $C8 by reversing the positions of the AL and CL registers in the reg and r/m fields and then setting the d bit (bit 1) in the opcode to one. This applies to all instructions with two register operands and a direction bit, such as the add(eax,ecx); instruction in Figure 10-19, which can also be encoded as $03, $C8.

10.4.3 Encoding Immediate Operands

You may have noticed that the mod-reg-r/m and sib bytes don't contain any bit combinations you can use to specify that an instruction contains an immediate operand. The 80x86 uses a completely different opcode to specify an immediate operand. Figure 10-26 shows the basic encoding for an add immediate instruction.

click to expand
Figure 10-26: Encoding an add immediate instruction

There are three major differences between the encoding of the add immediate instruction and the standard add instruction. First, and most important, the opcode has a one in the HO bit position. This tells the CPU that the instruction has an immediate constant. This one change alone, however, does not tell the CPU that it must execute an add instruction, as you'll see shortly.

The second difference is that there is no direction bit in the opcode. This makes sense because you cannot specify a constant as a destination operand. Therefore, the destination operand is always the location specified by the mod and r/m bits in the mod-reg-r/m field.

In place of the direction bit, the opcode has a sign extension (x) bit. For 8-bit operands, the CPU ignores the sign extension bit. For 16-bit and 32-bit operands, the sign-extension bit specifies the size of the constant following the add instruction. If the sign extension bit contains zero, the constant is already the same size as the operand (either 16 or 32 bits). If the sign-extension bit contains one, the constant is a signed 8-bit value, and the CPU sign extends this value to the appropriate size before adding it to the operand. This little trick often makes programs quite a bit shorter because one commonly adds small constants to 16- or 32-bit destination operands.

The third difference between the add immediate and the standard add instruction is the meaning of the reg field in the mod-reg-r/m byte. Because the instruction implies that the source operand is a constant and the mod-r/m fields specify the destination operand, the instruction does not need to use the reg field to specify an operand. Instead, the 80x86 CPU uses these three bits as an opcode extension. For the add immediate instruction, these three bits must contain zero, and another bit pattern would correspond to a different instruction.

Note that when adding a constant to a memory location, any displacement associated with the memory location immediately precedes the constant data in the instruction sequence.

10.4.4 Encoding 8-, 16-, and 32-Bit Operands

When Intel designed the 8086, they used one opcode bit (s) to specify whether the operand sizes were 8 or 16 bits. Later, when they extended the 80x86 architecture to 32 bits with the introduction of the 80386, they had a problem: with this single operand size bit they could only encode two sizes, but they needed to encode three (8, 16, and 32 bits). To solve this problem, they used an operand-size prefix byte .

Intel studied its instruction set and came to the conclusion that in a 32-bit environment, programs were likely to use 8-bit and 32-bit operands far more often than 16-bit operands. Therefore, Intel decided to let the size bit(s) in the opcode select between 8- and 32-bit operands, as the previous sections describe. Although modern 32-bit programs don't use 16-bit operands very often, they do need them now and then. To allow for 16-bit operands, Intel lets you prefix a 32-bit instruction with the operand size prefix byte, whose value is $66. This prefix byte tells the CPU that the operands contain 16-bit data rather than 32-bit data.

You do not have to explicitly put an operand-size prefix byte in front of your 16-bit instructions; the assembler or compiler will take care of this automatically for you. However, do keep in mind that whenever you use a 16-bit object in a 32-bit program, the instruction is one byte longer because of the prefix value. Therefore, you should be careful about using 16-bit instructions if size, and, to a lesser extent, speed are important.

10.4.5 Alternate Encodings for Instructions

As noted earlier in this chapter, one of Intel's primary design goals for the 80x86 was to create an instruction set to allow programmers to write very short programs in order to save memory, which was precious at the time. One way they did this was to create alternative encodings of some very commonly used instructions. These alternative instructions were shorter than their standard counterparts, and Intel hoped that programmers would make extensive use of the shorter versions, thus creating shorter programs.

A good example of these alternative instructions are the add(constant, accumulator ); instructions, where the accumulator is AL, AX, or EAX. The 80x86 provides 1-byte opcodes for add(constant,al); and add(constant,eax ); , which are $04 and $05, respectively. With a 1-byte opcode and no mod-reg-r/m byte, these instructions are one byte shorter than their standard add immediate counterparts. The add(constant,ax); instruction requires an operand size prefix, so its opcode is effectively two bytes if you count the prefix byte. This, however, is still one byte shorter than the corresponding standard add immediate.

You do not have to specify anything special to use these instructions. Any decent assembler or compiler will automatically choose the shortest possible instruction it can use when translating your source code into machine code. However, you should note that Intel only provides alternative encodings for the accumulator registers. Therefore, if you have a choice of several instructions to use and the accumulator registers are among these choices, the AL, AX, and EAX registers are often your best bet. However, this option is usually only available to assembly language programmers.




Write Great Code. Understanding the Machine, Vol. 1
The Art of Assembly Language
ISBN: 1593270038
EAN: 2147483647
Year: 2003
Pages: 144
Authors: Randall Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net