# Tips for Golfing in ARM

Because ARM is the July 2021 LoTM, I figured I'd create the tips thread.

ARM is probably the most successful architecture for the digital world. From mobile phones to supercomputers, ARM is everywhere! Consequently, let's have a thread where we can help each other shorten our ARM code.

One tip per answer, and no stupid tips like "oh remove comments and whitespace".

• Arm has comments and whitespace? – user Jul 1 at 1:50
• @user "oh remove comments and whitespace" is just an indicator of the types of tips that aren't exactly helpful – lyxal Jul 1 at 1:51
• There is quite a bit of variation between the different versions (Aarch64, Jazelle, Cortex-m) so it may be helpful for each answer to specify the exact execution environment. – ceilingcat Jul 1 at 2:28
• Finally, a chance to share my infinite wisdom. – EasyasPi Jul 3 at 2:02

# Use s instructions to your advantage.

### ARM

Unlike x86, ARM lets you choose whether to set the condition codes on its instructions, by suffixing the instructions with s. This is useful for do-while loops, where you can subtract, do some things, and keep that condition set until you want to branch or conditionally execute.

loop:
// Subtract from r0, set condition codes
subs    r0, r0, #1
// idk, do something silly
ldr     r1, [r0, r2]
add     r1, r3, r1, lsl #2
str     r1, [r0, r2]
// condition codes are still set from subs
// loop while r0 is not zero
bne     loop


## Thumb-2

This applies in a different way on Thumb-2.

In Thumb-2, most of the narrow instructions always set the condition codes*, while wide instructions give you the freedom of choice. However, the assembler won't warn you if it is using the wide instruction.

Take the following "Hello World" program:

        .syntax unified
.arch armv6t2
.thumb
.thumb_func
.globl _start
_start:
mov     r0, #1      // stdout
adr     r1, .Lhello // "Hello, World!"
mov     r2, #13     // string length
mov     r7, #4      // sys_write
svc     #0          // write(1, "Hello, World!", 13)
mov     r7, #1      // sys_exit
svc     #0          // exit(dontcare)
// UNREACHABLE
.Lhello:
.ascii "Hello, World!"


This is very inefficient, as mov Rd, #imm is a wide instruction:

00000000 <_start>:
0:   f04f 0001       mov.w   r0, #1
8:   f04f 020d       mov.w   r2, #13
c:   f04f 0704       mov.w   r7, #4
10:   df00            svc     #0
12:   f04f 0701       mov.w   r7, #1
16:   df00            svc     #0


Instead, if you use movs which sets the condition codes, you get a much smaller result:

00000000 <_start>:
0:   2001            movs    r0, #1
6:   220d            movs    r2, #13
8:   2704            movs    r7, #4
a:   df00            svc     0
c:   2701            movs    r7, #1
e:   df00            svc     0


Note: 2 bytes can be saved here by placing this code at an address that is not 4 byte aligned

*There is one exception. IT blocks.

IT Blocks will prevent condition codes from being set, even on narrow instructions (aside from ones like cmp and tst where it is pointless not to).

If you are in a situation where you want to chain up to 4 instructions together without setting the condition codes, you can use it al.

Note that this is only beneficial for two or more instructions, as IT blocks require an opcode themselves. For only one, it might just be better to use a wide instruction or to cmp again.

ldm, stm, push, and pop are pretty cool. They let you load/store multiple registers in order, optionally incrementing or decrementing before or after.

It can be useful for large literal pools instead of a chain of ldrs.

In Thumb-2, this also lets you postincrement without a wide instruction.

    // *r1++ = r0
str     r0, [r1], #4 // f841 0b04
stm     r1!, {r0}    // c101


# Thumb: Make sure your literal pools are aligned.

The PC-relative instructions adr and ldr =x will only be narrow instructions if they are accessing a 4 byte aligned pointer.

Simply reordering things or adding padding can make your literal pools aligned and prevent a wide instruction. This can often create a chain reaction when you have multiple constants, as the act of making one ldr/adr aligned will change the alignment of other literals, this can be for better or worse.

Another option is to load your code at an address that is not 4 byte aligned, so the literal is 4 byte aligned. You can typically just put a nop at the top of the code to simulate this (assemblers will align your code by default)