June 7th, 2021

The ARM processor (Thumb-2), part 6: The lie hiding inside the CMN instruction

Last time, we learned that the CMN instruction stands for compare negative, and it compares its first argument with the negative of the second argument:

    ; compare negative (compare Rn with -op2)
    cmn     Rn, op2             ; Set flags for Rn + op2

We noted that the N in the name is misleading, because it stands for negative, even though in the seemingly-analogous MVN instruction, the N stands for not.

But that’s not the most misleading part of the CMN instruction.

The big lie about the compare negative instruction is that it doesn’t even compare the negative.

You had one job!

The compare negative instruction is defined in terms of addition, rather than subtraction of the negative. Mathematically, the operations are identical: Subtracting a negative is the same as adding the positive.

But computers aren’t operating on mathematical integers.

Let’s look more closely at the difference between subtraction of the negative and addition of the positive. Recall that subtraction in a true-carry system is rewritten as addition, using the identity -x = ~x + 1. Therefore, a - b becomes a + ~b + 1. And the carry for this operation is the combined carry from both additions.

On the other hand, the CMN instruction is implemented as a straight addition, not a subtraction of the negative.

Expression Evaluated as
a - (-b) let c = ~b + 1
result = a + ~c + 1
a + b result = a + b

One difference between the two is that in the “subtract the negative” version, the carry that occurs in the calculation of ~c + 1 contributes to the final carry, whereas that extra carry disappears in the “add the positive” version.

When would ~c + 1 generate a carry?

Answer: When c is zero, because we have ~c + 1 = ~0 + 1 = 0xFFFFFFFF + 1, and that sum generates a carry. And c is zero when b is zero.

On the other hand, if b is zero, then a + b never generates a carry because you’re just adding zero, which does nothing.

Conclusion: If the second parameter to CMN is zero, then CMN results in no carry, even though “subtracting the negative of zero” would have produced a carry.

In other words, these two sequences do not generate the same flags:

    ; compare r0 against negative r1 using single instruction
    cmn     r0, r1          ; set flags for (r0 + r1)

    ; compare r0 against negative r1 using two instructions
    rsb     r12, r1, #0     ; r12 = negative r1
    cmp     r0, r12         ; set flags for (r0 - r12)

In the case where r1 is zero, the first version clears carry, but the second version sets carry. This means that if you follow the CMN with a conditional branch that consumes carry, you will get the wrong answer if the second parameter happens to be zero.

We haven’t learned about ARM conditionals yet, but when we do, you’ll discover that it is the unsigned relative comparison conditionals¹ that are based on the carry flag. Therefore, don’t use the CMN instruction to make unsigned relative comparisons against values which might be zero, because the carry flag may be set incorrectly.

Fortunately, this problem is relatively easy to avoid:

First rule: Don’t use

    cmn     Rd, #0

Don’t hard-code zero as the second parameter because the carry won’t be set properly.² Just write it as the positive comparison:

    cmp     Rd, #0

This is even easier to write, and it doesn’t have the carry flag problem.

Second rule: If you use a register or shifted register as the second parameter to CMN, don’t follow it with a condition that relies on the carry flag. In practice, this means that you should use signed conditions to test the result rather than unsigned conditions. (We haven’t learned about conditions yet.)

Fortunately, this second rule is not that much of a problem, because the fact that you are “comparing against the negative” strongly implies that you are interpreting the comparison as between two signed integers, so you are unlikely to follow up with an unsigned relative comparison conditional.

Okay, so that takes care of carry. The other troublesome bit is the overflow bit, which is just the carry from bit 30 into 31.

What are the cases in which a carry from bit 30 to bit 31 would be lost? We already identified zero as one of those cases. The other case is where the value is 0x80000000.

Ah, the curse of the most negative integer.

Ignoring the overflow from bit 30 to bit 31 means that the negative of 0x80000000 is treated as the positive number +0x80000000. Since every two’s complement 32-bit integer is less than +0x80000000 (viewed as a large positive number), the result of the comparison is pretty much foregone. It will always say “less than”.

In a way, though, the curse of the most negative integer isn’t so much of a problem here. After all, you did ask to compare against the negative of the most negative integer, and that’s a positive number that’s so positive it can’t even be represented!

Wow, what a complicated, messed-up instruction CMN turned out to be.

Next time, we’ll return to our exploration of the ARM Thumb-2 instruction set by looking at bitwise operations.

¹ The unsigned relative comparison conditionals are “unsigned less than”, “unsigned less than or equal”, “unsigned greater than”, and “unsigned greater than or equal”.

² Note that everything is fine if the second argument is not zero.

    ; compare against 0xFFFFFFFE.
    ; this works properly even for unsigned comparison.
    cmn     r1, #2

The problematic case for unsigned comparisons occurs only when the second argument is zero.

Topics
History

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

7 comments

Discussion is closed. Login to edit/delete existing comments.

  • Ferdinand Oeinck

    I read in "ARM Assembly language programming' by Peter Cockerell, 1987, about the CMN instruction:

    "The main use of CMN is in comparing a register with a small negative immediate number, which would not otherwise be possible without loading the number into a register (using MVN). For example, a comparison with -1 would require:

    <code>

    which uses two instructions and an auxiliary register, compared to this:

    <code>"

    I think programmers do not need to know more about it. RISC in...

    Read more
  • Neil Rashbrook

    … although how often do you need to perform an unsigned compare against zero, except occasionally as an equal to zero test, when you can’t arrange to instruction that created that zero value set the zero flag? Certainly I hope you weren’t hoping to find unsigned values less than zero…

  • Erik Fjeldstrom

    Sounds like the designers of the ARM architecture really wanted to make a sign-magnitude machine but were overruled. 🙂

    • Alex Martin

      Perhaps they got partway through implementing the arithmetic circuitry and then realized why everyone else uses two’s complement. I understand ARM was originally designed by a relatively small independent team, so they may have had to learn some lessons for themselves…

      • Fabian Giesen

        Nothing of the sort. CMN is just a mnemonic (despite it being somewhat misleading) and the reason it exists has to do with the immediate encoding (discussed in one of the earlier posts, the one about the barrel shifter). Namely, most RISCs have signed immediate operands for their arithmetic operations, but in the A32 (and Thumb-2/T32) encodings, all immediates are unsigned. So you need some way to compare against negative values that uses the same...

        Read more
      • Richard Russell

        A “relatively” small team? The original ARM 1 was designed by Sophie Wilson and Steve Furber, barely a “team” at all!

      • David N

        I did a little bit of assembler on that (may have been the ARM 2, whatever the Archimedes had). It was beautifully minimalist and elegant. The current instruction set has moved away from that, I think.