Reputation: 31
CONTEXT:
I am currently learning Assembly for a Linux distribution with an Intel IA32 x86 processor and I've been struggling to understand how to deal with the overflow issue.
I believe I understand the theory around it but I have not managed to implement a solution for the overflow cases.
I've searched around the web and the pages that I found explained the flag system and how/when they activate. That is not what I am interested in. I want to know how to "solve" it.
Actually, while I was writing this and I was thinking thoroughly and slowly, I believe I understood the solution. So now I actually just want to know if I'm thinking straight or is there a better way to do it.
PROBLEM:
An overflow may occur when:
For simplicity, lets consider two signed chars (1 byte). I understand that the result will need more bits so lets consider the result to be a short int (2 bytes).
Data:
op1 = 0x7F # Bit representation: 0111 1111 Decimal representation: +127
op2 = 0x01 # Bit representation: 0000 0001 Decimal representation: + 1
op3 = 0x80 # Bit representation: 1000 0000 Decimal representation: -128
op4 = 0xff # Bit representation: 1111 1111 Decimal representation: - 1
Example A:
movl $0, %eax # Set all bits to 0
movb op1, %al # Move op1 to the al register
addb op2, %al # Adding two positives that activates the Overflow flag
# Deactivates the Carry Flag
# Current bit representation in ax: 0000 0000 1000 0000
# Expected bit representation in ax: 0000 0000 1000 0000
# The simple fact of consider the short int (which contains 0s) fixes the issue. || (+127) + (+1) = (+128)
ret # Returns ax
Example B:
movl $0, %eax # Set all bits to 0
movb op3, %al # Move op3 to the al register
addb op4, %al # Adding two negative numbers that activates the Overflow flag
# Activates Carry Flag
# Current bit representation in ax: 0000 0000 0111 1111
# Expected bit representation in ax: 1111 1111 0111 1111
notb %ah # The "not" instruction on the most significative byte resolves the issue since it inverts all bits. || (-128) + (-1) = (-129)
ret # Returns ax
Example C:
movl $0, %eax # Set all bits to 0
movb op1, %al # Move op1 to the al register
subb op4, %al # Subtracting a negative number to a positive number that activates the Overflow flag
# Activates the Carry Flag
# Current bit representation in ax: 0000 0000 1000 0000
# Expected bit representation in ax: 0000 0000 1000 0000
# The simple fact of consider the short int (which contains 0s) fixes the issue. || (+127) - (-1) = (+128)
ret # Returns ax
Example D:
movl $0, %eax # Set all bits to 0
movb op3, %al # Move op3 to the al register
subb op2, %al # Subtracting a positive number to a negative number that activates the Overflow flag
# Deactivates Carry Flag
# Current bit representation in ax: 0000 0000 0111 1111
# Expected bit representation in ax: 1111 1111 0111 1111
notb %ah # The "not" instruction on the most significative byte resolves the issue since it inverts all bits. || (-128) - (+1) = (-129)
ret # Returns ax
For everything that I stated above, I conclude the two following functions:
For additions:
addition:
movl $0, %eax # Set all bits to 0
movb opX, %al # Move opX to the al register
addb opY, %al # Adding two numbers
jc overflow # Jumps if Carry Flag is on
end:
ret # Returns ax
overflow:
notb %ah # Inverts most significative bits
jmp end # Jumps back to the end of the function
For subtractions:
subtraction:
movl $0, %eax # Set all bits to 0
movb opX, %al # Move opX to the al register
subb opY, %al # Subtracting two numbers
jc end # Jumps if Carry Flag is on
notb %ah # Inverts most significative bits
end:
ret # Returns ax
I believe these functions will work correctly even with numbers that do not cause an overflow.
QUESTIONS:
1st: Have I made any mistake on what I've stated above?
2nd: If I did, where and how to solve it? If I didn't, is there any better way to solve this?
3rd: Will this interfere with other additions and subtractions that do not cause an overflow?
4th: Why am I not using the Overflow flag anywhere?
Upvotes: 1
Views: 2227
Reputation: 16586
You are probably sort of missing the point that overflow generally is wanted behaviour (due to simple+cheap HW implementation plus high performance).
Ie. it's up to a programmer to use 16bit arithmetic if they expect numbers like 150 + 150, and to stay with 8 bit arithmetic only when they know the result will be inside 0..255 or -128..+127, or if they don't mind truncated results in case of overflow (valid usage pattern in some cases).
Your 4->8 extension doesn't make much sense, usually having 32b integers is enough to not overflow at all. That assumption did hurt when 2+GiB RAM and files appeared, making some installers to report -2GB of RAM available and asking for more :), or copying only part of 3GiB file.
So the second example should be instead implemented as (my AT&T syntax isn't great, so sorry for possible syntax errors), counting on the fact, that your data will not overflow with valid input data, as valid data fit into 16b results:
movsbw op3, %ax # Move op3 to the ax register, sign extend it from 8b to 16b
movsbw op4, %bx # sign-extend op4 to 16b as well
addw %bx, %ax # Adding two negative numbers, not caring about OF/CF, expecting valid result
# Current bit representation in ax: 1111 1111 0111 1111
# note the eax is: ???? ???? ???? ???? 1111 1111 0111 1111
# high 16b are undefined, and if you would clear eax, they would be 0 anyway
ret
Taking care of CF/OF would degrade the performance a lot, and is used only in special scenarios where you really must to validate the calculation. Most of the SW happily lives with "garbage in, garbage out" premise (and usually that works well), and produces valid/expected output only for valid/expected input.
Making all calculations "safe/correct" is sort of pointless (or better to say "expensive"), as the computer has very limited resources, and you can't work with big numbers with computers anyway. Natively 64b CPUs can do arithmetic with 64 or 128b integers, beyond that you can implement non-native operations working with integers of size of available memory, but sooner or later (probably very very late, considering 32GiB of operational memory) you will hit a point, where you will overflow for large enough integer (28e32G in this example). Natively you are done already at 264/128/256+ value or even sooner, which is not even that much for an integer, although for common human usage even mere 232 is usually enough.
Upvotes: 3