Think In Geek

In geek we trust

Exploring AArch64 assembler – Chapter 3

In the last chapter we saw that instructions may have register operands and immediate operands. We also mentioned that mixing 32-bit and 64-bit register was not allowed. Today we will talk a bit more about register operands.

Operators for register operands

Many instructions that take a register as the second source operand of an instruction can also apply some extra operation to the value of that source register. This can be used as a way to increase density of computation by requiring less instructions and also to allow some common operations, e.g. conversions, in one of the operands.

We can distinguish two kinds of operators here: shifting operators and extending operators.

Shifting operators

There are three shifting operators in AArch64: LSL, LSR, ASR and ROR. Their syntax is as follows:

reg, LSL, #amount
reg, LSR, #amount
reg, ASR, #amount
reg, ROR, #amount

where reg can be a 64-bit register Xn or a 32-bit register Wn and amount is a number whose range depends on the register used and ranges from 0 to 31 for 32-bit registers and from 0 to 63 for 64-bit registers.

Operator LSL performs a logical shift left to the value in reg (it does not change the contents of reg though). Shifting n bits to the left means introducing n zeros as the least significant bits and discarding n most significant bits from the original value. Shifting left n-bits is equivalent to multiply to 2n.

add r1, r2, r3, LSL #4 /* r1 ← r2 + (r3 << 4)
                          this is the same as
                          r1 ← r2 + r3 * 16 */
add r0, r0, r0, LSL #2  /* r0  ← r0 + r0 << 2
                           this is the same as
                           r0 ← r0 + r0 * 4
                           which happens to be the same as
                           r0 ← r0 * 5
                           assuming no overflow happens
                         */

Operator LSR performs a logical shift right. This operation is the dual of LSL, but zeros are introduced in the n most significant bits and the n least significant bits are discarded. For unsigned arithmetic numbers, this operation is equivalent to division by 2n.

Operator ASR performs an arithmetic shift right. This is like LSL but instead of introducing zeros in the n most significant bits the most significant bit is replicated n times in the n most significant bits. As in LSL, the n least significant bits are discarded. If the most significant bit of the register is zero, ASR is equivalent to LSR. This shift operator is useful for two’s complement numbers as it propagates the sign bit (which would be the most significant bit in the register if interpreted as a binary number) and it can be used for dividing by 2n negative numbers as well. A LSR on a two’s complement negative number does not make sense for the purpose of a division.

Operator ROR performs a rotate right of the register. This is commonly used for cryptography and its usage is less usual than the other shifting operands. A rotation is similar to LSR but rather than dropping bits and introducing zeros, the least signficant bits that would be dropped are introduced as the most significant bits. There is no rotate left because a rotate right can be used for this: just rotate all bits minus the number of steps we want to rotate to the left.

In AArch64 only a few instructions (mainly logical ones) can use the ROR shifting operator.

mov w2, #0x1234            // w2 ← 0x1234
mov w1, wzr                // w1 ← 0
orr w0, w1, w2, ROR #4     // w0 ← BitwiseOr(w1, RotateRight(w2, 4))
                           // this sets w0 to 0x40000123
orr w0, w1, w2, ROR #28    // w0 ← BitwiseOr(w1, RotateRight(w2, 32-4))
                           // this is in practice like RotateLeft(w2, 4)
                           // so this sets w0 to 0x12340

Extending operators

Extending operators main purpose is to widen a narrower value found in a register to match the number of bits for the operation. An extending operator is of the form kxtw, where k is the kind of integer we want to widen and w is the width of the narrow value. For the former, the kind of integer can be U (unsigned) or S (signed, i.e. two’s complement). For the latter the width can be B, H or W which means respectively byte (least 8 significant bits of the register), half-word (least 16 significant bits of the register) or word (least significant 32 bits of the register).

This means that the extending operators are uxtb, sxtb, uxth, sxth, uxtw, sxtw.

These operators exist because sometimes we have to lift the range of the source value from a smaller bit width to a bigger one. In later chapters we will see many cases where this happens. For instance, it may happen that we need to add a 32-bit register to a 64-bit register. If both registers represent two’s complement integers then

add x0, x1, w2, sxtw  // x0 ← x1 + ExtendSigned32To64(w2)

There is some kind of context that has to be taken into account when using these extension operators. For instance, the two instructions below have slight different meanings:

add x0, x1, w2, sxtb // x0 ← x1 + ExtendSigned8To64(w2)
add w0, w1, w2, sxtb // w0 ← w1 + ExtendSigned8To32(w2)

In both cases the least significant 8 bits of w2 are extended but in the first case they are extended to 64 bit and in the second case to 32-bit.

Extension and shift

It is possible to extend a value and then shift it left 1, 2, 3 or 4 bits by specifying an amount after the extension operator. For instance

mov x0, #0                // x0 ← 0
mov x1, #0x1234           // x0 ← 0x1234
add x2, x0, x1, sxtw #1   // x2 ← x0 + (ExtendSigned16To64(x1) << 1)
                          // this sets x2 to 0x2468
add x2, x0, x1, sxtw #2   // x2 ← x0 + (ExtendSigned16To64(x1) << 2)
                          // this sets x2 to 0x48d0
add x2, x0, x1, sxtw #3   // x2 ← x0 + (ExtendSigned16To64(x1) << 3)
                          // this sets x2 to 0x91a0
add x2, x0, x1, sxtw #4   // x2 ← x0 + (ExtendSigned16To64(x1) << 4)
                          // this sets x2 to 0x12340

This may seem a bit odd and arbitrary at this point but in later chapters we will see that this is actually useful in many cases.

This is all for today.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

7 thoughts on “Exploring AArch64 assembler – Chapter 3

  • David Todd says:

    I believe that for your second and third “add” instructions, you meant to have “sxtw #2” and “sxtw #3” as the extension operations (in order to get the results your describe in your comments).

  • David Todd says:

    Roger, I thought it odd that you moved 0 into x0 for your extension and shift examples. Why not just
    l2: mov x1, #0x1234 // x1 ← 0x1234
    l3: add x2, xzr, x1, sxtw #1 // x2 ← 0 + (ExtendSigned16To64(x1) << 1)

    That is, use the xzr register (always reads 0) as the first source operand. But the gcc compiler [(SUSE Linux) 6.2.1 20161209 ] gives an error message "extst2.s:6: Error: extending shift is not permitted at operand 3 — `add x2,xzr,x1,sxtw#1'" for each of those four lines.

    I can't think of any reason why that shouldn't be an allowed operation. Is that a compiler (assembler) error, or is there a reason why using "xzr" as the first source prevents use of extending shift of the second source operand?

    • Roger Ferrer Ibáñez says:

      Hi David,

      if you check the documentation of ADD you will see that it reads as Xn|SP or Wn|WSP. This syntax means that XZR or WZR cannot be used in these contexts.

      In fact, attempting to do so, like you do in the other comment actually means using the WSP, XSP which are other registers (whose meaning I have not explained but I can advance you that they mean the stack pointer 🙂 ).

      Kind regards,
      Roger

  • David Todd says:

    Roger (and anyone following this): it is an assembler error.
    If I code the instruction manually into a 32-bit .word,
    “add x2,xzr,x1,sxtw #1” executes as expected, storing into x2 the value in x1 shifted by the requested number of bits.

    Anyone wanting to try it, the instruction is “.word 0x8b21c7e2 // add x2,xzr,x1,sxtw #1”

    I would hope this would be fixed in a future release of the assembler.

    • Roger Ferrer Ibáñez says:

      Hi David,

      thanks for your comment. As I mentioned above, not all instructions allow the usage of XZR or WZR. If the syntax says Xn|XSP or Wn|WSP it means that XZR, WZR cannot be used in that operand. The instruction you have encoded is effectively the following one:

      8b21c7e2 	add	x2, sp, w1, sxtw #1
      

      This is clearly not what you wanted!

      Kind regards,
      Roger

      • David Todd says:

        Thanks, Roger! I hadn’t grasped the point that the “Xn|XSP” nomenclature was intended to exclude the XZR register, which I had been thinking of as just another general-purpose regiseter. That’s a helpful point to keep in mind (learning the hard way, I won’t forget!). You can use XZR as the second source operand but not as the first.

        And again, for those following this, my hand-assembled instruction “add x2,xzr,x1,sxtw #1” is, as Roger analyzed, actually the encoding for “add x2, sp, w1, sxtw #1”. As the reference manual points out (ARM DDI 0487A.k
        Copyright © 2013-2016 ARM Limited or its affiliates. All rights reserved. pg C6-437), the “0b1111” encoding of the register field, which I had assumed would reference X31 (the XZR register), references XSP in this context. What I thought was a correctly executing instruction when I ran it though gdb was actually an instruction that left the correct shifted value from an earlier instruction in X2, the destination register — and I thought it had executed correctly.

        Thanks again, Roger, for the tutorials and for the clarifications!

Leave a Reply

Your email address will not be published. Required fields are marked *