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".

  • 1
    \$\begingroup\$ Arm has comments and whitespace? \$\endgroup\$
    – user
    Jul 1, 2021 at 1:50
  • 2
    \$\begingroup\$ @user "oh remove comments and whitespace" is just an indicator of the types of tips that aren't exactly helpful \$\endgroup\$
    – lyxal
    Jul 1, 2021 at 1:51
  • 5
    \$\begingroup\$ 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. \$\endgroup\$
    – ceilingcat
    Jul 1, 2021 at 2:28
  • \$\begingroup\$ Finally, a chance to share my infinite wisdom. \$\endgroup\$
    – EasyasPi
    Jul 3, 2021 at 2:02

3 Answers 3


Use s instructions to your advantage.


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.

        // 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


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
        .globl _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
        .ascii "Hello, World!"

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

00000000 <_start>:
   0:   f04f 0001       mov.w   r0, #1
   4:   f20f 0110       addw    r1, pc, #16 // adr r1, .Lhello
   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
   2:   f20f 010c       addw    r1, pc, #12 // adr r1, .Lhello
   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.


ARM/Thumb: Use block loads/stores

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)


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.