← Back to Courses
module
5

Jumping without saying where

Why this matters

JP works. It has two costs, though, and both matter in a game.

It takes three bytes every time you use one, and a game loop is nothing but jumps. And it names an exact address, which means the program only works if it is sitting exactly where the assembler thought it would be.

JR avoids both. For most of the jumps you will ever write, it is the one to reach for.

What JR does differently

JR stands for jump relative. Rather than naming the address to go to, it says how far to move:

        JR done

You write it exactly like JP, with a label, and the assembler works out the distance. The difference is in what ends up in your program.

Here is the section's example. Type it in as jr.asm:

        ORG 256

        LD A,'J'
        RST 8
        DEFB 158       ; ZOUTC

        JR done         ; step over the data

        DEFB 118        ; skipped

done:   LD A,'R'
        RST 8
        DEFB 158       ; ZOUTC
        RET

What you should see

>JR
 A  BC   DE   HL   PC   SZ-H-PNC
 52 3D41 1EFF FB3E 0769 10010010

Now look at what it assembled to:

0100 3e 4a          LD A,'J'
0102 cf             RST 8
0103 9e             DEFB 158  ; ZOUTC
0104 18 00          JR done
0106 76             DEFB 118
0107 3e 52  done:   LD A,'R'
0109 cf             RST 8
010a 9e             DEFB 158  ; ZOUTC
010b c9             RET
010c
 01

JR done is two bytes: 18 then a one-byte distance. The listing shows 18 00 for the same reason a forward JP shows c3 00 00 - the distance was not yet known when that line was printed. The correction, 01, appears after the end of the file, and that is the byte the finished program actually holds: 18 01.

The 01 is the distance. It is counted from 0106, the address of the instruction after the jump, so 0106 + 1 = 0107, which is done:. That is worth remembering when you read a listing: the offset is measured from where the processor will be once it has finished reading the jump, not from the jump itself.

Jumping backwards gives a negative distance, stored the way the Z80 stores negative numbers - so a jump back four bytes assembles as 18 fc. You do not need to work those out. The assembler does, and it is the whole reason to use labels.

Labels are not the reason to prefer JR

It would be easy to conclude that labels are what JR buys you. They are not - JP done takes a label just as happily, and you used one in the last section.

The two real reasons are worth being clear about.

It is smaller

Two bytes against three. That sounds trivial, and for one jump it is. But you have 64 kilobytes for everything - code, graphics, sound, level data - and a game is full of short jumps. Replacing a hundred of them saves a hundred bytes, which is a sprite. Habits at this scale are what make a program fit.

It does not care where the program is

This is the more interesting one, and it is easiest to see by breaking something.

.COM programs load at address 256, which is why every program so far has started with ORG 256. Change that line to ORG 512 - lie to the assembler about where the program will be - and run it again.

The version above, with JR, still prints JR and returns perfectly.

Now change the JR done to JP done and run that. You get:

>J
 A  BC   DE   HL   PC   SZ-H-PNC
 41 3D41 1EFF FB3E 0208 10010010

Only the J. The R never printed, and PC reads 0208.

That number is the whole story. done: really sits at 0108. The assembler, told the program would be at 512, worked out 0208 instead - 256 too high, exactly the size of the lie. JP went where it was told, which was into memory that has nothing to do with your program.

The JR version did not care, because "move forward one byte" is true no matter where the program is loaded.

You will not often lie about ORG on purpose. But code does get moved, copied to a different address, or reused somewhere it was not written for, and relative jumps carry on working when absolute ones quietly do not.

The one limit

A relative jump has to fit its distance into a single byte, and that byte is signed - so it can reach 127 bytes forward or 128 back, and no further.

Go beyond that and the assembler stops you:

        JR far
        DEFS 200        ; reserve 200 bytes
far:    RET
error: relative jump out of range (200)

DEFS 200 reserves 200 bytes of space, which is a useful thing in its own right and here is a convenient way to push a label out of reach. The error names the distance it could not encode, so you always know how far over you are.

When that happens, use JP. That is what it is for: the long jumps, and the rare occasions when you genuinely mean one specific address.

In practice: reach for JR first. If the assembler complains, change that one to JP. You will find almost every jump in a loop is comfortably in range, because loops are small.

Change one thing

  • Change ORG 256 to ORG 512 in the JR version. It still works. Now change the JR to a JP and check PC in the register line. Where did it go, and can you predict that number before you run it?
  • Change JR done to JR 999. What does the assembler say?
  • Put DEFS 150 between the JR and done:. Read the error, then change the JR to a JP and watch the same program assemble.
  • Take the endless loop from the last section and change its JP loop to JR loop. Does anything about the behaviour change? Should it?

Exercises

5.1 - Every jump relative. Rewrite exercise 4.1, the three arrows, using JR for every jump. Assemble with -l and check that each one came out as two bytes.

5.2 - Find the boundary. Using DEFS, find the largest gap a forward JR will cross before the assembler refuses. Then do the same backwards. Do the two numbers agree with the limit given above?

5.3 - Read the offsets. Write a program with three JRs - one forwards, one backwards, one to the very next instruction. Work out each offset by hand from the listing addresses, then check them against the bytes the assembler produced.

5.4 - Move it. Write a short program with one JR and one JP, both jumping to labels. Assemble it at ORG 256 and confirm it works. Then change only the ORG and predict which of the two jumps breaks and what PC will read when it does.

When it goes wrong

What you see What it means
error: relative jump out of range (200) The target is further than a JR can reach. Use JP for that one. The number is the distance it could not encode.
error: unable to resolve reference: done The label in the jump does not match the label you defined.
Part of the output appears, then the program stops early A jump landed somewhere unintended. Check PC in the register line - it is the address it stopped at, and comparing it with the listing usually names the problem instantly.
The program works, but PC reads an address nowhere near your program An absolute jump built for the wrong ORG. Check the top line of your source.

Summary

  • JR jumps by a distance rather than to an address. Two bytes against three.
  • The distance is measured from the instruction after the jump, and the assembler works it out from the label.
  • JR reaches 127 bytes forward and 128 back. Beyond that the assembler refuses and names the distance, and you use JP instead.
  • A relative jump keeps working if the program is loaded somewhere else. An absolute one goes where it was told, which may be nowhere useful.
  • Reach for JR first, and let the assembler tell you when it will not fit.

Next

S6. Asking questions. Every loop you can write so far runs forever. Next the program compares two values and jumps only if the answer comes out a particular way, which turns a loop into something that finishes - and finally explains that last column of the register line.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.