← Back to Modules
module
5

Jumping Without Saying Where

Introduction

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

As in S3, some of your numbers may differ, and that is fine. A should read 52, the R.

Now look at what it assembled to:

# File jr.asm
0000                            ORG 256
0100
0100 3e 4a                      LD A,'J'
0102 cf                         RST 8
0103 9e                         DEFB 158       ; ZOUTC
0104
0104 18 01                      JR done         ; step over the data
0106
0106 76                         DEFB 118        ; skipped
0107
0107 3e 52              done:   LD A,'R'
0109 cf                         RST 8
010a 9e                         DEFB 158       ; ZOUTC
010b c9                         RET
# End of file jr.asm
010c

The # File lines, the ORG line and the address-only lines are the assembler's own, as S4 explains.

JR done is two bytes: 18 then a one-byte distance. Here that distance is 01, and it is the whole difference between this and JP.

The 01 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
 4A 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. Read the distance out of the error message rather than counting your own DEFS bytes - where you put the DEFS shifts the count, and the error names the real distance. 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. Loops That Stop. Every loop you can write so far runs forever. Next the program counts, and jumps only while the count has not run out, 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.

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.