0

The program gives a wrong calculation in assembly language compared to C++ and I can't find the error. Any ideas?

// (2 * c + b + a - 5) / (a / 4 - 1)

#include <iostream>

using namespace std;

void print_of() {
    cout << "Overflow error\n";
    exit(1);
}

void print_zf() {
    cout << "Division by zero error\n";
    exit(1);
}

int main() {
    long int a, b, c;
    long int asm_answer;
    cout << "(2 * c + b + a - 5) / (a / 4 - 1)\n";
    cout << "Enter a, b, c: ";
    cin >> a >> b >> c;
    // eax, ebx, ecx - general-purpose registers, its specific abilities don't matter in this laboratory work
    __asm {
        ; a / 4
        mov rax, a; rax = a
        mov rbx, 4; rbx = 4
        cqo; Convert Quad to Octa (rax -> rdx:rax)
        idiv rbx; division with a sign (dividend should be in rdx:rax -> quotient in rax, remainder in rdx)
        
        ; a / 4 - 1
        dec rax; decrement
        jo of_error; overflow error
        jz zf_error; if rax = 0, there will be division by zefo error
         
        ; reset
        push rax; push (a / 4 - 1) in stack
        
        ; 2 * c
        mov rax, c; rax = c
        imul rax, 2; rax = rax * 2
        jo of_error; overflow error
        
        ; 2 * c + b
        mov rdx, b; rdx = b
        add rax, rdx; rax = rax + rdx
        jo of_error; overflow error
        
        ; 2 * c + b + a
        mov rdx, a; rdx = a
        add rax, rdx; rax = rax + rdx
        jo of_error; overflow error
        
        ; 2 * c + b + a - 5
        sub rax, -5; rax = rax - 5
        jo of_error; overflow error
        
        ; rax = 2 * c + b - 5, rbx = a / 4 - 1
        pop rbx; rbx = a / 4 - 1
        
        ; (2 * c + b + a - 5) / (a / 4 - 1)
        cqo; Convert Quad to Octa (rax -> rdx:rax)
        idiv rbx; division with a sign (dividend should be in rdx:rax -> quotient in rax, remainder in rdx)
        mov asm_answer, rax
        jmp ext; "exit" is reserved
        
    of_error:
        call print_of; print overflow error (exit in function!)
    zf_error:
        call print_zf; print division by zero error (exit in function!)
    ext:
        
    }
    cout << "asm result: " << asm_answer << "\n";
    cout << "C++ result: " << (2 * c + b + a - 5) / (a / 4 - 1) << "\n";
    return 0;
}

Example of console output:

Enter a, b, c: 100 0 20 asm result: 6 C++ result: 5 Program ended with exit code: 0

Another example:

Enter a, b, c: 0 0 0 asm result: -5 C++ result: 5 Program ended with exit code: 0

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    Based on your debugging so far, at what point in the calculation does the assembly result start to disagree with the expected result? – Brian61354270 Apr 22 '21 at 19:57
  • 4
    You add "b" twice, and are adding 5 (by subtracting negative 5). – 1201ProgramAlarm Apr 22 '21 at 19:57
  • @1201ProgramAlarm, sorry for misinformation, I copied the code here incorrectly, in the source code the subtraction -5 occurs only once. – kernel_task Apr 22 '21 at 20:35
  • @Brian, I try to put a breakpoint inside the assembly code, but the program pauses only after exiting __asm block. – kernel_task Apr 22 '21 at 20:38
  • 1
    Do you understand that substraction -5 is addition 5, n -= (-5) is n += 5? The expected `2 * c + b + a - 5` actually is `2 * c + b + a - (-5)` in your code. – 273K Apr 22 '21 at 20:45
  • @S.M., yes, you are right, I didn't pay attention to it, thank you. – kernel_task Apr 22 '21 at 20:51
  • 1
    @kernel_task: Then use a better debugger, or switch your debugger into "disassembly" mode so you can single-step asm instructions, whether they were from the asm block or not. Figuring out how to use debugging tools should always be step #1, because seeing register values change as you single step is *incredibly* valuable when debugging asm. Without that, you're wasting your own (and everyone else's) time. – Peter Cordes Apr 23 '21 at 02:34
  • I assume this is ICC or `clang -fasm-blocks` compiling for non-Windows? Otherwise `long int` would be a 32-bit type and `mov rax, a` would load garbage. You don't need push/pop, just use another register like r8, r9, rdi or rsi. And you can skip checking for overflow after `dec rax`: `x/4` can't produce LONG_MIN. (Also, it would be much more efficient to divide by 4 using `sar`, but to round negative inputs correctly (toward 0, not toward -inf) you'd have to use an extra instruction to account for the sign bit. Check out 2 different ways compilers do it: https://godbolt.org/z/E8qc5sT5j) – Peter Cordes Apr 23 '21 at 02:45
  • `imul reg,2` is also inefficient. You *could* use `imul rax, c, 2` to load-and-multiply (since this clunky style of inline asm forces args to come from memory, although I guess they would be anyway after `cin`). Anyway, compilers would normally `add rax,rax` or `shl rax,1` to multiply by 2. (Both of those ways set OF appropriately, the same way imul does). Of course, C++ compilers don't normally check for overflow so they'd use `lea rax, [rbx + rax*2 - 5]` / `add rax, c` to get the top half done in 2 instructions, (apart from loading a and b into registers). – Peter Cordes Apr 23 '21 at 02:51
  • https://godbolt.org/z/Tffn4oohs uses GNU C [`__builtin_add_overflow`](https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html) to see GCC's and clang's code-gen for an overflow-checked version of the top half (denominator) of your expression, as well as plain versions without overflow-checking. I considered using Rust for its overflow-checking options, but decided not to. Or GCC `-fsanitize=undefined` would also do overflow-checking on all the signed operations in the plain version, but makes bulkier code. – Peter Cordes Apr 23 '21 at 03:06

0 Answers0