ASM Snippets
Here are some useful and common ASM snippets. You may encounter them in others' code, or use them to solve situations yourself. It's a good idea to read all of the snippets and pick the best one for your use case, depending on the use.
Contents
Sign extension
Assuming we have a value, we may want to sign-extend it. It's possible using as little as two instructions!
add a, a ; Shift MSB into carry sbc a, a
Bytes: 2; Cycles: 2
16-bit arithmetic
It's tempting to answer that all the work is already done by hl instructions, but unlike the z80, the GB CPU can only perform 16-bit additions. The rest must be implemented by hand as the following examples do:
ld a, l sub a, {low byte} ld l, a ld a, h sbc a, {high byte} ld h, a
ld a, l adc a, {low byte} ld l, a ld a, h adc a, {high byte} ld h, a
This pattern can also be used for additions that can't be carried out using `add hl, reg16`, such as adding a value to some memory address, or when hl is clobbered.
16-bit and 8-bit
Often, we find ourselves the need to add an 8-bit offset to a 16-bit position. Common examples include accessing fields in a struct, or adding an 8-bit vector to a 16-bit value. It's tempting to use the above pattern with the high byte as zero, but it's possible to do better:
; Assuming A already contains the value to be added... add a, l ld l, a jr nc, .noCarry inc h .noCarry
This, however, requires creating a label, which is tedious especially for compilers without anonymous label support such as RGBDS. There is however an alternative, slightly trickier to understand, perfectly equal in size and speed, that doesn't use any labels.
add a, l ; a = low + old_l ld l, a ; a = low + old_l = new_l adc a, h ; a = new_l + old_h + carry sub l ; a = old_h + carry ld h, a
Advancing to the next aligned struct
One of the largest issues when processing a struct is that we don't always find ourselves at a constant offset within the struct (for example, if you stop processing a NPC because it's actually far off-screen, you'll be in a different position than if you had read until the end. Thus, it's useful to be able to go to the beginning of the struct (or of the next one) at any point
Assuming we have a pointer to anywhere within a struct in any 16-bit register, advance to the next one. (Example given with the 16-bit register being de)
Requirements: The size of the struct must be a power of two.
Clobbers: A
ld a, e or {size of struct}-1 ld e, a inc de
Bytes: 5; Cycles: 6
Without page crossing
If the high byte of the pointer is constant across all structs, it's possible to save one cycle:
ld a, e or {size of struct}-1 inc a ld e, a
It also puts the low byte of the next struct's address in A, which is useful for termination comparisons:
ld a, e or 16-1 inc a ld e, a cp LOW(wArrayEnd) jr c, .processStruct
Comparisons
Set carry if two numbers match
Less optimized snippets
These are common snippets which are less optimized than the ones shown above, but are documented regardless to aid at reading them. Note that the snippet you are looking for might slightly differ. Look at each one, and consider if a 16-bit register couldn't be swapped for another, for instance.
Adding a 8-bit number to a 16-bit one
In hl:
ld c, a ld b, 0 add hl, bc
In another register:
ld l, a ld h, 0 add hl, de ld d, h ld e, l
Those clobber hl and another registers = 4 out of 7. They do, however, preserve a and the Z flag.
Advancing to the next aligned struct
ld a, l and -{size of struct} add a, {size of struct} ld l, a
This is a more naive implementation of the "advance to next struct" snippet, but it's bigger by 1 byte, and 1 cycle slower.
External links
- "Optimizing assembly code" from pret (archived copy)