How does ASM knows an arithmetic operation is signed or unsigned?
Asked Answered
B

2

5

I am assembling using MASM 14.0, and i am confused with the output of the below code.

TITLE Exercise 4 from chapter 4

; Author : Saad Ahmed

INCLUDE Irvine32.inc

.code
main PROC

mov eax, 0
mov al, 255
add al, 1
call DumpRegs       ; Display registers

mov al, 127
add al, 1
call DumpRegs       ; Display registers

exit
main ENDP
END main

Both arithmetic operations are done on unsigned integers 255 and 127.

However, the CPU is treating the first operation 255, as an unsigned integers and setting the carry flag, which would happen when you add 1 to an unsigned 255.

The complete status flags are CF=1 SF=0 ZF=1 OF=0 AF=1 PF=1 with eax as 0

But, the second operation considers the 127 to be a signed integer as it is setting the overflow flag, which would happen if you add 1 to a +127.

The complete status flags are to CF=0 SF=1 ZF=0 OF=1 AF=1 PF=0 with eax as 0.

The question is how does the CPU decides that the first operation was done on an unsigned 255, while the other one was on a signed integer?

Barcelona answered 5/7, 2016 at 2:41 Comment(1)
add/sub are the same for unsigned vs. 2's complement. See Understanding Carry vs. Overflow conditions/flags. That's from the x86 tag wiki.Bridgman
I
10

For addition (and subtraction) using twos complement there is no notion of signed or unsigned as far as the logic is concerned. Multiply and divide, yes, due to the sign extension required.

Take all combinations of 3 bit numbers from 000 to 111 and add them to all the combinations of 3 bit numbers, something manageable. Maybe write a program if you want or do it by hand. Or just do the corner cases. For each examine each operand as signed or unsigned using twos complement. You will note that the same addition works. 1 + 110 = 111. Now is that 1 + (-2) = -1 or was that 1 + 6 = 7. Both worked.

The carry flag IS the UNSIGNED overflow, the V flag is the SIGNED overflow, that is why we compute both so the user, who knows whether or not those are signed or unsigned numbers can choose the right conditional. And that is why you have signed jump if greater than or equal vs an unsigned jump if greater than or equal.

It is the beauty of twos complement that makes it all work.

Multiply (and divide) is different, because you have to sign extend. Multiply in binary has a beautiful feature, in that if you think about it

   abcd
*  0011
=======
   abcd
  abcd
 0000
0000
=======

A bit is either 1 or 0, so you are multiplying the top number by either one or zero, you either add it shifted or you dont. But also notice that very very quickly you are going to overflow. We know from grade school that n^x * n^y = n^(x+y). If your registers are Z bits wide the most significant bit positions of the operands cannot be larger than Z when added otherwise it overflows. Four bits 0010*0010 should work but 0010*1000 will overflow. The right way is the result is twice as wide as the operands.

So what if I want to multiply 1111 * 0010?

That is basically

    0000
   1111
  0000
+0000
========
 0011110

Wait was that a 15 (0b1111) or a -1 (0b1111)? -1 * 2 = -2 which is not what we got above, we did an unsigned multiply to do a signed multiply we have to sign extend and toss bits off the left

 11..1111
*00..0010
=========    
00000000
1111111
000000
00000
=========
11111110

and that gives the right answer for a signed multiply of two four bit registers 1111 and 0010.

Addition works because you only care about one column. Each column has a carry in, two operands a result and a carry out. And then you can cascade that as wide as you want. With a single bit you have 0 and 1. The zero is just zero not plus or minus zero, the 1 can either be a +1 or a -1. I find it easier to work through the combinations with more than one column, but it could be done. For addition the carry in is a 0 so I dont need to represent it operand a, operand b, carry out, and result

00 00  0 + 0 = 0
01 01  0 + 1 = 1;  0 + (-1) = -1
10 01  1 + 0 = 1;  (-1) + 0 = -1
11 10  1 + 1 = 0 unsigned overflow.  -1 + 1 = 0, 1 + -1 = 0, -1 + -1 = 0 signed overflow

Technically all the signed ones were a signed overflow in that last case, and that is the special case you deal with for any number of bits, take a three bit register 100 + 100 = 000 + carry out of 1 is that a 4 + 4 = 0 unsigned overflow or is that a -4 + -4 = 0 with a signed overflow? Which is why it is easier to see when you use a few bits and go through the combinations tossing the problem case of 1 and then all zeros.

A signed overflow is when the carry in of the most significant column does not match the carry out. an unsigned overflow is when the carry out of the msbit is a one.

Subtraction in logic is done with addition, we know from grade school that a - b = a + (-b) and we know from programming classes that to take the negative using twos complement you invert and add one. Well that works great we can just invert the second operand and invert the carry in of the lsbit making it a one or invert and add one. And that is how that works. The carry out is sometimes inverted coming out of the alu to indicate a borrow. You have to look at the flag combinations to figure this out, some processors invert the carry out some dont. Can sometimes tell from the subtract with borrow if your ISA has that instruction.

I know TL:DR...covered more than you asked.

Irv answered 5/7, 2016 at 3:28 Comment(1)
if the multiply is N bits in N bits out, then you dont need to sign extend right? so it is not sign specific. 1111 * 0010 = 1110 it is an overflow if unsigned but the right answer if signed (dumb luck).Irv
K
5

What's nice about two's complement is that the CPU doesn't need to know.

It just does addition on the unsigned bytes.

The carry flag is set because 255 + 1 doesn't fit in 8 bits (obviously).

The overflow flag is set because the MSB of both operands is 0, and the MSB of the ouput is 1 - indicating that if you consider this to be signed arithmetic, you have overflowed. If you consider it unsigned arithmetic, then you should just ignore the flag. See this wikipedia article.

Kirovabad answered 5/7, 2016 at 2:58 Comment(6)
True for addition and subtraction... for multiplication and division, there are separate instructions for signed and unsigned.Egregious
Thank you! Yes i researched and i guess CPU set all status flags after an arithmetic operation using a set of rules. We have to decide which flags are relevant, and which to ignore. So as you said, i was considering the above operation unsigned arithmetic, so i should just ignore the flag.Barcelona
@AdamD.Ruppe : In the case of IMUL and MUL both can operate on signed and unsigned values. IMUL is generally seen as the multioperand form of MUL.Depth
Huh, you know, I never actually realized MUL works on signed with the expected result, but indeed, it does.... well, wait a sec, it does if you truncate it, I don't think it does on the whole two word result. But, regardless, DIV and IDIV are importantly distinct though - very different results there.Egregious
@AdamD.Ruppe: The upper half of a multiply result depends on the choice of mul vs. imul, but the lower half doesn't. That's why Intel only added multi-operand and immediate forms of one of the multiply instructions, and they happened to pick imul. (Presumably because sign-extended imm8 was more useful than zero-extended).Bridgman
Another very nice link: Understanding Carry vs. Overflow conditions/flags. That's from the x86 tag wiki.Bridgman

© 2022 - 2024 — McMap. All rights reserved.