Search code examples
arm

ARM Multiply Negates MNEG


Signed - SMNEGL, Unsigned - UMNEGL, ?? - MNEG

Ok so what is the third multiply-negate (MNEG)? There's a few operations like this one. The descriptions are the exact same. Is it just the same as unsigned or signed? I noticed MNEG has a 32-bit version while UMNEGL doesn't.


Solution

  • Let's break this down a bit.

    First of all, multiplication by itself. It comes in four flavors:

    • 32 x 32 = 32 (i.e. multiply two 32-bit inputs and get a 32-bit output, with high bits truncated): e.g. mul w0, w1, w2. There is no signed/unsigned version, because non-widening multiplication is bitwise the same operation for either signed or unsigned operands - a fact from modular arithmetic.

    • 64 x 64 = 64, e.g. mul x0, x1, x2. Same deal, no signed/unsigned distinction.

    • 32 x 32 = 64 "long multiply", e.g. smull x0, w1, w2. Here it does make a difference whether the operation is to work for signed or unsigned values, so we have separate smull and umull instructions. For instance, 0xffffffff * 0xffffffff should be 0x1 if treated as signed, or 0xfffffffe00000001 if treated as unsigned. (But as noted above, the low 32 bits are the same for both.)

    • 64 * 64 = 128. This uses mul / smulh or mul / umulh depending on whether it should be signed or unsigned. It's not directly relevant to your question.

    Now, mul and smull/umull are also available as multiply-add and multiply-subtract operations, where for instance madd w0, w1, w2, w3 computes w0 = (w1 * w2) + w3. Likewise smaddl x0, w1, w2, x3 computes x0 = (w1 * w2) + x3, with the multiplication done as signed 32x32=64, and umaddl x0, w1, w2, x3 similar for unsigned. Indeed, mul w0, w1, w2 is just an alias of madd w0, w1, w2, wzr, and ditto for smull and umull.

    As for the multiply-subtract versions, for instance msub w0, w1, w2, w3 computes w0 = w3 - (w1 * w2). If the fourth operand is the zero register, then msub w0, w1, w2, wzr would compute w0 = -(w1*w2). For convenience, this instruction is aliased as mneg w0, w1, w2. As above, it makes no difference whether you think of it as signed or unsigned, because it's the same binary operation either way.

    The long versions likewise have a multiply-subtract, which becomes multiply-negate with the zero register. smnegl x0, w1, w2 computes x0 = -(w1 * w2), signed widening, alias of smsubl x0, w1, w2, xzr. Likewise umnegl x0, w1, w2 computes x0 = -(w1 * w2), unsigned widening. You may think negation looks odd on an unsigned value, but it's all mod 2^64 so you can also think of it as x0 = 2^64 - (w1 * w2).

    There may not be much particular value in the multiply-negate instructions, but they come for free in the hardware as special cases of multiply-subtract when you use the zero register, so the ARM folks figured they might as well provide an alias in the assembler, just in case somebody found it useful.