The low half of a multiply doesn't care about signedness, only the high half above the input widths.
In the general case with different input widths, min(m,n)
bits will be guaranteed the same for all inputs whether you treated inputs as signed or unsigned.
Yes, non-widening mul
is directly usable for i32 = i32 * i32
or u32 = u32 * u32
. You can see compilers use it that way on the Godbolt compiler explorer, even with clang -fwrapv
to make signed overflow well-defined behaviour in C (wrapping around).
Non-widening multiply is a binary operation that, like add/sub, is the same for unsigned or 2's complement integers. It's equivalent to conditionally adding left-shifted copies of one input (partial products), according to bits of the other.
Addition propagates carry from low to high only, and left shift only shifts in zeros, and multiplication is built from those. And the "condition" for whether to add a shifted copy or not doesn't depend on whether we interpret the MSB as -2^{n-1}
or +2^{n-1}
. (Related: Which 2's complement integer operations can be used without zeroing high bits in the inputs, if only the low part of the result is wanted?)
Widening multiply is equivalent to sign-extending or zero-extending the inputs to the desired output width, and then doing a non-widening multiply. CPUs internally do it more efficiently, but the final integer result must be bitwise identical. 2's complement sign-extension duplicates the sign-bit to higher positions, zero-extension makes them zero.
A signed x unsigned multiply can be done by sign-extending one input, zero-extending the other, then doing a non-widening multiply. (And software may have to do this in practice without fun RISC-V instructions like mulhsu
: high-half of signed x unsigned multiply.)
Any bit-position above min(m,n)
will be at a position where sign-extension or zero-extension of an input could have put a 0 vs. a 1 at this position. Thus affecting the output at this bit-position. (At least for some possible inputs, like where one has its highest bit set so is negative if treated as signed.)
For smull
and umull
, min(32,32) = 32
, so the low half is the same between those instructions, differing only in high half.
See also Why is imul used for multiplying unsigned numbers? for another take on the math, including a proof that of the statement: When multiplying two numbers a
and b
of length n
, the result is of length 2 n
and, most importantly, the k
-th digit only depends on the lowest k
digits (of the inputs).
And discussion of x86's later introduction of a non-widening form of imul reg, reg/mem
, as opposed to one-operand mul/imul where the other source, and the destination, are implicit. The reason for this x86 design decision is the same as with ARM and RISC-V and others only providing one non-widening multiply instruction, instead of signed and unsigned versions.