Conditionals

March 12, 2025 ยท View on GitHub

All "conditional" operations described here work with a condition value provided in a register with value -1 (0xFFFFFFFF) for "true", or 0 (0x00000000) for "false". The macros assume that no other value is used; if a condition register has a value distinct from either, then what is obtained is unspecified.

The carry flag (C) can be converted into a condition value (0 or -1) in any register rx with sbcs rx, rx (this is compatible with ARMv6-M). If C is cleared, then this sets rx to -1; otherwise (C is set), rx is set to 0. Note that C is set when an addition operation triggers an output carry, or when a subtraction operation does not trigger an output borrow (in ARM CPUs, the C flag operates as the opposite of a borrow flag in subtraction; this is unlike x86 CPUs, where a borrow sets C).

Conditional Move

A conditional move sets an output register rx to the value of another register ry if a condition value rc is -1, or leaves rx unchanged otherwise.

Compatible ARMv6-M

A conditional move can be done in 3 cycles; the operand ry is consumed:

@ rc \in { 0, -1 }
@ rx <- ry if rc == -1; rx preserved if rc == 0
@ ry is consumed
@ rc is preserved
.macro COND_MOVE_1  rx, ry, rc
        bics    \rx, \rc
        ands    \ry, \rc
        orrs    \rx, \ry
.endm

Another method also costs 3 cycles, and preserves ry, but it needs an extra scratch register (if the same register as ry is used for the scratch, then this is equivalent to the previous method).

@ rc \in { 0, -1 }
@ rx <- ry if rc == -1; rx preserved if rc == 0
@ ry is preserved
@ rc is preserved
@ rt is scratch
@ VARIANT: rt and ry can be the same register (consumed)
.macro COND_MOVE_3  rx, ry, rc, rt
        subs    \rt, \rx, \ry
        ands    \rt, \rc
        subs    \rx, \rt
.endm

Both methods can also work with an inverted condition, i.e. do the data move only if rc is 0, and not do it if rc is -1:

@ rc \in { 0, -1 }
@ rx <- ry if rc == 0; rx preserved if rc == -1
@ ry is consumed
@ rc is preserved
.macro CONDNOT_MOVE_1  rx, ry, rc
        ands    \rx, \rc
        bics    \ry, \rc
        orrs    \rx, \ry
.endm
@ rc \in { 0, -1 }
@ rx <- ry if rc == 0; rx preserved if rc == -1
@ ry is preserved
@ rc is preserved
@ rt is scratch
@ VARIANT: rt and ry can be the same register (consumed)
.macro CONDNOT_MOVE_3  rx, ry, rc, rt
        subs    \rt, \rx, \ry
        bics    \rt, \rc
        subs    \rx, \rt
.endm

Specific to ARMv7-M

On ARMv7-M, we can do the move in 2 cycles. Moreover, the alteration to ry is predictable: ry is preserved if no move happens (rc == 0), or cleared otherwise (rc == -1):

@ rc \in { 0, -1 }
@ rx <- ry if rc == -1; rx preserved if rc == 0
@ ry <- 0 if rc == -1; ry preserved if rc == 0
@ rc is preserved
.macro COND_MOVE_4  rx, ry, rc
        bics    \rx, \rc            @ can be skipped if rx is known to be zero
        umlal   \ry, \rx, \rc, \ry
.endm

A variant preserves ry in all cases:

@ rc \in { 0, -1 }
@ rx <- ry if rc == -1; rx preserved if rc == 0
@ ry is preserved
@ rc is preserved
.macro COND_MOVE_5  rx, ry, rc
        bics    \rx, \rc            @ can be skipped if rx is known to be zero
        mls     \rx, \ry, \rc, \rx
.endm

In both methods above, if rx is zero on input, then the first instruction (the bics opcode) can be removed.

A 2-cycle move with an inverted condition consumes ry:

@ rc \in { 0, -1 }
@ rx <- ry if rc == 0; rx preserved if rc == -1
@ ry is consumed
@ rc is preserved
.macro CONDNOT_MOVE_4  rx, ry, rc
        subs    \ry, \rx
        umaal   \rx, \ry, \rc, \ry
.endm

A 2-cycle move with an inverted condition and preserving ry can also be done, but it requires an extra scratch register:

@ rc \in { 0, -1 }
@ rx <- ry if rc == 0; rx preserved if rc == -1
@ ry is preserved
@ rc is preserved
@ rt is scratch
@ VARIANT: rt and ry can be the same register (consumed)
.macro CONDNOT_MOVE_5  rx, ry, rc, rt
        subs    \rt, \ry, \rx
        umaal   \rx, \rt, \rc, \rt
.endm

Selection

In a selection operation, an output register rd is set to the value of either input register rx or input register ry, depending on whether condition rc is 0 or -1, respectively.

Compatible ARMv6-M

A 3-cycle method preserves all the inputs:

@ rc \in { 0, -1 }
@ rd <- rx if rc == 0; rd <- ry if rc == -1
@ rx is preserved
@ ry is preserved
@ rc is preserved
@ VARIANT: rd can be the same register as rx
.macro SELECT_1  rd, rx, ry, rc
        subs    \rd, \rx, \ry
        bics    \rd, \rc
        adds    \rd, \ry
.endm

In the method above, output rd can be the same register as input rx (the input whose value is used for the output if rc == 0). An alternate implementation allows instead rd to be the same register as input ry (the input whose value is used for the output if rc == -1):

@ rc \in { 0, -1 }
@ rd <- rx if rc == 0; rd <- ry if rc == -1
@ rx is preserved
@ ry is preserved
@ rc is preserved
@ VARIANT: rd can be the same register as ry
.macro SELECT_2  rd, rx, ry, rc
        subs    \rd, \ry, \rx
        ands    \rd, \rc
        adds    \rd, \rx
.endm

Both methods can work with an inverted condition by simply exchanging the rx and ry parameters.

Specific to ARMv7-M

On ARMv7-M, a 2-cycle method for selection exists; it preserves all inputs, and allows the output register to be the same register as input rx:

@ rc \in { 0, -1 }
@ rd <- rx if rc == 0; rd <- ry if rc == -1
@ rx is preserved
@ ry is preserved
@ rc is preserved
@ VARIANT: rd can be the same register as rx
.macro SELECT_3  rd, rx, ry, rc
        bics    \rd, \rx, \rc
        mls     \rd, \ry, \rc, \rd
.endm

Another method uses the ASPR.GE flags, which are set by a few instructions in the SIMD unit, and can be used for selection of values with the sel instruction:

@ rc \in { 0, -1 }
@ rd <- rx if rc == 0; rd <- ry if rc == -1
@ rx is preserved
@ ry is preserved
@ rc is preserved
@ rt is scratch
@ VARIANT: rd may be the same register as rx, ry or rt. Moreover, rt
@ may be the same register as rc (which is then consumed).
.macro SELECT_4  rd, rx, ry, rc, rt
        uadd16  \rt, \rc, \rc
        sel     \rd, \ry, \rx
.endm

That method actually uses only bits 15 and 31 of rc. An extra scratch register is used, but destination register rd can be the same register as rx, ry or rt (rt must still be distinct from rx and ry).

Since the ASPR.GE flags are set by uadd16 but not modified by sel, the SELECT_4 macro can be expanded to perform multiple selection operations at a marginal cost of 1 cycle per word; for instance:

        @ Use condition r14 (0 or -1) to set r0:r1:r2:r3 to either
        @ r4:r5:r6:r7 (if r14 == -1) or r8:r10:r11:r12 (if r14 == 0).
        uadd16  r0, r14, r14
        sel     r0, r4, r8
        sel     r1, r5, r10
        sel     r2, r6, r11
        sel     r3, r7, r12

Conditional Addition

In conditional addition, value ry is added to rx if condition rc is true (-1); if the condition is false (0) then rx is unmodified.

Compatible ARMv6-M

On ARMv6-M, this can be done in 2 cycles with the straightforward code:

@ rc \in { 0, -1 }
@ rx <- rx + ry if rc == -1; rx preserved if rc == 0
@ ry preserved if rc == -1; ry <- 0 if rc == 0
@ rc is preserved
.macro COND_ADD_1  rx, ry, rc
        ands    \ry, \rc
        adds    \rx, \ry
.endm

Note that operand ry is cleared if the addition is not done, but preserved if the addition is performed.

Specific to ARMv7-M

On ARMv7-M, the conditional addition can be done in a single cycle, and preserving ry:

@ rc \in { 0, -1 }
@ rx <- rx + ry if rc == -1; rx preserved if rc == 0
@ ry is preserved
@ rc is preserved
.macro COND_ADD_2  rx, ry, rc
        mls     \rx, \ry, \rc, \rx
.endm

A variant clears ry when the addition is done, and preserves it when the addition is not performed (this is distinct from the COND_ADD_1 macro semantics):

@ rc \in { 0, -1 }
@ rx <- rx + ry if rc == -1; rx preserved if rc == 0
@ ry <- 0 if rc == -1; ry preserved if rc == 0
@ rc is preserved
.macro COND_ADD_3  rx, ry, rc
        umlal   \ry, \rx, \rc, \ry
.endm

Conditional Subtraction

Conditional subtraction is similar to conditional addition, except that a subtraction is done instead of an addition.

Compatible ARMv6-M

As in the case of conditional addition, a 2-cycle variant clears ry when the subtraction is not done:

@ rc \in { 0, -1 }
@ rx <- rx - ry if rc == -1; rx preserved if rc == 0
@ ry preserved if rc == -1; ry <- 0 if rc == 0
@ rc is preserved
.macro COND_SUB_1  rx, ry, rc
        ands    \ry, \rc
        subs    \rx, \ry
.endm

Specific to ARMv7-M

On ARMv7-M, a 1-cycle conditional subtraction preserves ry:

@ rc \in { 0, -1 }
@ rx <- rx - ry if rc == -1; rx preserved if rc == 0
@ ry is preserved
@ rc is preserved
.macro COND_SUB_2  rx, ry, rc
        mla     \rx, \ry, \rc, \rx
.endm

A variant consumes ry is rc == -1, which does not seem to provide any advantage over COND_SUB_2 (the actual replacement value is either 2*ry if rx >= ry, or 2*ry - 1 if rx < ry):

@ rc \in { 0, -1 }
@ rx <- rx - ry if rc == -1; rx preserved if rc == 0
@ ry consumed if rc == -1; ry preserved if rc == 0
@ rc is preserved
.macro COND_SUB_3  rx, ry, rc
        umlal   \rx, \ry, \rc, \ry
.endm

Conditional Swap

A conditional swap exchanges the contents of registers rx and ry if the condition rc is true (-1), but leaves them unchanged if the condition is false (0).

Compatible ARMv6-M

A 4-cycle conditional swap can be performed, using an extra scratch register:

@ rc \in { 0, -1 }
@ (rx, ry) <- (ry, rx) if rc == -1; rx and ry preserved if rc == 0
@ rc is preserved
@ rt is scratch
.macro COND_SWAP_1  rx, ry, rc, rt
        subs    \rt, \rx, \ry
        ands    \rt, \rc
        subs    \rx, \rt
        adds    \ry, \rt
.endm

The classic method is often expressed with XORs. Here, we use additions and subtractions because the ARMv6-M architecture offers a three-operand subtraction opcode, which saves one cycle over the classic method using eors (which only has a two-operand mode in ARMv6-M).

Specific to ARMv7-M

On ARMv7-M, a conditional swap can be done in 3 cycles, still using one extra register:

@ rc \in { 0, -1 }
@ (rx, ry) <- (ry, rx) if rc == -1; rx and ry preserved if rc == 0
@ rc is preserved
@ rt is scratch
.macro COND_SWAP_2  rx, ry, rc, rt
        subs    \rt, \rx, \ry
        mla     \rx, \rc, \rt, \rx
        umlal   \rt, \ry, \rc, \rt
.endm

A 2-cycle variant exists, but requires that ry is not greater than rx (considering both values as unsigned); otherwise, if ry > rx and rc == -1 then the output ry will be off by 1:

@ rc \in { 0, -1 }
@ rx >= ry (unsigned comparison)
@ (rx, ry) <- (ry, rx) if rc == -1; rx and ry preserved if rc == 0
@ rc is preserved
@ rt is scratch
.macro COND_SWAP_3  rx, ry, rc, rt
        subs    \rt, \rx, \ry
        umlal   \rx, \ry, \rc, \rt
.endm