Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
760 views
in Technique[技术] by (71.8m points)

assembly - Why do x86 jump/call instructions use relative displacements instead of absolute destinations?

I am learning 8086 and there is one particular question which is bothering me and I have not been able to find any satisfactory answer yet.

I understand that CPU executes the code sequentially and if want to change the code flow we would like the IP to point to the new/old address where the code of our interest is sitting.

Now, my question is why we(I mean CPU) don't just go and update the IP with the address corresponding to the label when we encounter jump instruction?

What is the need to have a displacement which is added to IP when we encounter jump instruction?

In my opinion

  1. calculating the displacement(i.e the distance from the jump label to the next instruction after the jump) and
  2. then taking that displacements 2's compliment,
  3. which finally gets added to the IP so that IP points to the address/instruction pointed by label

To me this sounds like more work then just updating the IP with the address corresponding to label. But, I am sure there must be a reason for the way things are done, its just that I am not aware.

What was the reason for this design choice in 8086?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

You are vastly over-estimating the cost in CPU complexity of decoding a relative jump.

  1. calculating the displacement(i.e the distance from the jump label to the next instruction after the jump)
  2. then taking that displacements 2's compliment,

The machine code has to contain the result of step 2 (a signed integer relative displacement), so all of that is done at assemble time. And in the assembler, subtracting two integer addresses already gives you the signed 2's complement displacement you need.

There are real advantages to using relative displacements, so making the ISA worse just to simplify writing an assembler would not have made any sense. You only need to write the assembler once, but everything that runs on the machine benefits from more compact code, and position independence.

Relative branch displacements are completely normal, and used in most other architectures, too (e.g. ARM: https://community.arm.com/processors/b/blog/posts/branch-and-call-sequences-explained, where fixed-width instructions makes a direct absolute branch encoding impossible anyway). It would have made 8086 the odd one out to not use relative branch encoding.

update: Maybe not totally the odd one out. MIPS uses rel16 << 2 for beq / bne (MIPS instructions are fixed at 32-bits wide and always aligned). But for unconditional j (jump) instructions, it interestingly it uses a pseudo-direct encoding. It keeps the high 4 bits of PC, and directly replaces the PC[27:2] bits with the value encoded in the instruction. (Again, low 2 bits of the program counter are always 0.) So within the same 1/16th of address space, j instructions are direct jumps, and don't give you position-independent code. This applies to jal (jump-and-link = call), making function calls from PIC code less efficient :( Linux-MIPS used to require PIC binaries, but apparently now it doesn't (but shared libs still have to be PIC).


When the CPU runs eb fe, all it has to do is add the displacement to IP instead of replacing IP. Since non-jump instructions already update IP by adding the instruction length, the adder hardware already exists.

Note that sign-extending 8-bit displacements to 16-bit (or 32 or 64-bit) is trivial in hardware: 2's complement sign-extension is just copying the sign bit, which doesn't require any logic gates, just wires to connect one bit to the rest. (e.g. 0xfe becomes 0xfffe, while 0x05 becomes 0x0005.)


8086 put a big emphasis on code density, providing short forms of many common instructions. This makes sense, because code-fetch was one of the most important bottlenecks on 8086, so smaller code usually was faster code.

For example, two forms of relative jmp existed, one with rel8 (short) and one with rel16 (near). (In 32 and 64-bit mode introduced in later CPUs, the E9 opcode is a jmp rel32 instead of rel16, but EB is still jmp rel8 because jumps within a function are often within -128/+127).

But there's no special short for for call, because it wouldn't be much use most of the time. So why does it still bother with a relative displacement instead of absolute?

Well x86 does have absolute jumps, but only for indirect or far jumps. (To a different code segment). For example, the EA opcode is jmp ptr16:16: "Jump far, absolute, address given in operand".

To do an absolute near jump, simply mov ax, target_label / jmp ax. (Or in MASM syntax, mov ax, OFFSET target_label).


Relative displacements are position-independent

Comments on the question brought this up.

Consider a block of machine code (already assembled), with some jumps inside the block. If you copy that whole block to a different start address (or change the CS base address so the same block is accessible at a different offset with the segment), then only relative jumps will keep working.

For labels + absolute addresses to solve the same problem, the code would have to be re-assembled with a different ORG directive. Obviously that can't happen on the fly when you change CS with a far jmp!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...