Computer Architecture

The Ultimate 2025 Guide to ARM64 ADD Opcodes (4 Rules)

Unlock the power of ARM64 assembly in 2025 with our ultimate guide to ADD opcodes. Learn 4 core rules covering ADD/ADDS, 32/64-bit ops, and more.

D

Dr. Alistair Finch

Expert in low-level systems, compiler design, and ARM architecture optimization.

7 min read4 views

Introduction: Why ADD Matters in 2025

Welcome to 2025, where the ARM64 architecture, also known as AArch64, is no longer just for mobile phones. It's a dominant force in data centers, high-performance computing, and even the latest generation of desktop and laptop computers. To truly harness the power and efficiency of this architecture, developers and reverse engineers must have a deep understanding of its instruction set. At the very heart of this instruction set lies one of the most fundamental operations: addition. The ADD opcode is more than just simple arithmetic; it's a versatile tool with a surprising amount of depth. This guide will demystify the ARM64 ADD instruction by breaking it down into four simple, memorable rules, empowering you to read, write, and optimize low-level code with confidence.

What Are ARM64 Opcodes? A Quick Refresher

Before we dive into the rules, let's have a quick refresher. An opcode (operation code) is the portion of a machine language instruction that specifies the operation to be performed. In the ARM64 instruction set architecture (ISA), all instructions are a fixed length of 32 bits. These 32 bits are cleverly encoded to define the operation, the source registers, the destination register, and any immediate values or options.

The ADD instruction is a cornerstone of this system, performing the essential task of adding two values together. But unlike simpler architectures, ARM64 provides a rich family of addition instructions, each tailored for specific scenarios. Understanding the nuances between them is the key to unlocking efficient, high-performance code.

The 4 Core Rules for Mastering ARM64 ADD

To navigate the world of ARM64 addition, you only need to master four fundamental rules. Let's break them down one by one.

Rule 1: Understand the Core ADD and ADDS Variants

The first and most critical distinction to make is between ADD and ADDS. On the surface, they both perform addition, but their impact on the system's state is profoundly different.

  • ADD (Add): This is the standard addition instruction. It calculates the sum of two source operands and places the result in a destination register. It does not affect the condition flags.
  • ADDS (Add and Set Flags): This variant performs the same addition but also updates the four condition flags (N, Z, C, V) in the PSTATE register.

The condition flags are:

  • N (Negative): Set if the result is negative.
  • Z (Zero): Set if the result is zero.
  • C (Carry): Set if the addition resulted in an unsigned overflow (a carry-out).
  • V (Overflow): Set if the addition resulted in a signed overflow.

Using ADDS is essential for implementing conditional logic. For example, you might use ADDS to check if adding two numbers results in zero, then use a conditional branch instruction like B.EQ (Branch if Equal) to alter the program's flow.

Example:


// Basic addition, no flags affected
// X0 = X1 + 10
ADD  X0, X1, #10

// Addition that sets flags
// W0 = W1 + W2, and PSTATE flags are updated
ADDS W0, W1, W2
// Now we can branch based on the result
B.EQ a_label_if_result_was_zero

Rule 2: Differentiate Between 32-bit (W) and 64-bit (X) Operations

ARM64 is a 64-bit architecture, but it maintains full support for 32-bit operations for efficiency and compatibility. The distinction is made through the register names you use in the instruction.

  • W registers (e.g., W0, W1, WSP): These refer to the lower 32 bits of the general-purpose registers. Operations using W registers are 32-bit operations.
  • X registers (e.g., X0, X1, SP): These refer to the full 64 bits of the general-purpose registers. Operations using X registers are 64-bit operations.

An important rule to remember is that when you perform a 32-bit operation that writes to a W register, the upper 32 bits of the corresponding X register are zeroed out. This design choice prevents stale data from remaining in the upper bits and simplifies the architecture.

Example:


// 64-bit addition
// X0 = X1 + X2
ADD  X0, X1, X2

// 32-bit addition
// W0 = W1 + W2. The upper 32 bits of X0 are set to zero.
ADD  W0, W1, W2

Rule 3: Leverage Shifted Register Operands for Efficiency

One of the most powerful features of the ARM ISA is its ability to combine arithmetic and shift operations into a single instruction. The ADD instruction can take a register operand that has been shifted before the addition occurs. This is a hallmark of the RISC philosophy, enabling you to do more work with fewer instructions, reducing code size and improving performance.

The syntax is: ADD Rd, Rn, Rm, <shift> #amount

Common shift types include:

  • LSL (Logical Shift Left): Shifts bits to the left, filling with zeros. Equivalent to multiplication by a power of 2.
  • LSR (Logical Shift Right): Shifts bits to the right, filling with zeros. Equivalent to unsigned division by a power of 2.
  • ASR (Arithmetic Shift Right): Shifts bits to the right, preserving the sign bit. Equivalent to signed division by a power of 2.

Example: Calculate y = x + (z * 4)


// Traditional approach (2 instructions)
LSL  X3, X2, #2   // X3 = X2 * 4
ADD  X0, X1, X3   // X0 = X1 + X3

// Efficient ARM64 approach (1 instruction)
// X0 = X1 + (X2 shifted left by 2)
ADD  X0, X1, X2, LSL #2

Rule 4: Know When to Use Extended Registers and ADDC

For more advanced scenarios, ARM64 provides two other powerful tools: the extended register variant of ADD and the ADDC instruction.

Extended Register Addition: This variant allows you to sign-extend or zero-extend a smaller register before adding it. This is incredibly useful when mixing 32-bit and 64-bit values.

Example: Add a 32-bit signed value to a 64-bit value.


// Add X1 and the sign-extended value of W2 to X0
// SXTW = Sign EXtend Word (32-bit to 64-bit)
ADD  X0, X1, W2, SXTW

ADDC (Add with Carry): This instruction is the key to performing multi-word arithmetic, such as 128-bit addition. It performs Rd = Rn + Rm + Carry Flag. You first use ADDS on the lower 64 bits to perform the initial addition and set the Carry flag. Then, you use ADDC on the upper 64 bits to add them together along with any carry-over from the first operation.

Example: 128-bit addition `(X1:X0) = (X3:X2) + (X5:X4)`


// Add the lower 64 bits, setting the carry flag
ADDS X0, X2, X4
// Add the upper 64 bits, including the carry from the first operation
ADDC X1, X3, X5

Similarly, ADCS exists to perform an Add with Carry operation while also setting the flags for the result of the second operation.

Comparison Table: ADD Family Opcodes

Quick comparison of the primary ARM64 addition opcodes.
Opcode Purpose Updates Flags? Common Use Case
ADD Result = Operand1 + Operand2 No General purpose arithmetic where flags are not needed.
ADDS Result = Operand1 + Operand2 Yes (N, Z, C, V) Arithmetic that is followed by a conditional branch.
ADDC Result = Operand1 + Operand2 + Carry Flag No The upper-word calculation in multi-word addition.
ADCS Result = Operand1 + Operand2 + Carry Flag Yes (N, Z, C, V) Multi-word addition where the final result's status is needed.

Practical Pitfalls and Best Practices

Understanding the rules is half the battle. Avoiding common mistakes is the other half.

Pitfall 1: Forgetting Flag Updates with ADD vs. ADDS

A frequent source of bugs is using ADD when ADDS was required. If you perform an addition and then immediately use a conditional branch, you must use the flag-setting variant. Otherwise, your branch will be based on the state of the flags from a previous instruction, leading to unpredictable behavior.

Pitfall 2: Mismatched Register Sizes

While the assembler will often catch blatant errors, it's possible to create subtle bugs by mixing W and X registers without a clear understanding of the zero-extension and sign-extension rules. Always be deliberate about your data sizes and use explicit extension (like SXTW) when your intent isn't perfectly captured by the default behavior.

Best Practice: Trust Your Compiler

For most application development, you won't be writing assembly by hand. The most practical skill is to write clean, efficient C, C++, or Rust code and then trust the compiler to generate optimized assembly. Use a tool like the Compiler Explorer to inspect the output of your code. You will see the compiler expertly using all the rules we've discussed—choosing between ADD/ADDS, using shifted operands, and performing multi-word arithmetic—far better than most humans can. Understanding these rules allows you to appreciate why the compiler made those choices and helps you write higher-level code that is easier to optimize.

Conclusion: Building on a Solid Foundation

The humble ADD instruction is a gateway to understanding the design philosophy of the ARM64 architecture. By mastering our four simple rules—distinguishing ADD/ADDS, managing 32/64-bit operations, leveraging shifted operands, and using ADDC for large arithmetic—you build a solid foundation. This knowledge not only empowers you to write and debug low-level code but also provides a deeper appreciation for the sophisticated work done by modern compilers. As ARM64 continues its expansion in 2025, this fundamental skill will remain an invaluable asset for any serious programmer or systems engineer.