I am using inline C assembly and I does not understand why does not work cmp command same for this 2 cases.
I have C function:
int array_max(const int input_array[], const int array_size);
In the main I created array and passed it in to the function
int input_array[] = {50,10,3,70,5};
printf("%d\n", array_max(input_array, 5));
Function definition is writed in assembly
__asm__
(
"array_max:;"
" xor %rdx, %rdx;"
" movq $70, %rax;"
" movq $50, %rbx;"
" cmp %rbx, %rax;"
" jge gr_eq;"
" movq %rbx, %rax;"
"gr_eq:;"
" ret"
);
In this case is return value 70
__asm__
(
"array_max:;"
" xor %rdx, %rdx;"
" movq $70, %rax;"
" movq (%rdi,%rdx,4), %rbx;" // input_array[0] == 50
" cmp %rbx, %rax;"
" jge gr_eq;"
" movq %rbx, %rax;"
"gr_eq:;"
" ret"
);
But it this case 50...
Can sameone help me how can I compare passed array values to value in the register? And explain me why this implementation does not work?
The problem is that you have an array of 32-bit signed int
s and you are reading 64-bit values from the array (when each element is only 4 bytes in size) and comparing as 64-bit signed integers. If you stepped through the code with a debugger like GDB you should see what is going on. Be forewarned that RBX is a non-volatile register in the AMD64 System V ABI so its value has to be preserved in a function. You can avoid this by not using any of RBX, RBP, or R12-R15 registers.
The simplest of changes would be to do this:
__asm__
(
"array_max:;"
" xor %rdx, %rdx;"
" mov $70, %eax;"
" mov (%rdi,%rdx,4), %ecx;"
" cmp %ecx, %eax;"
" jge gr_eq;"
" mov %ecx, %eax;"
"gr_eq:;"
" ret"
);
As you move forward you should realize that you are passing arr_size
as a const int
which is a 32-bit signed number. As the second parameter that will passed via RSI. When passing a 32-bit value, the upper 32-bits of the register (like RSI) are undefined. You will have to make sure you sign extend the 32-bit value to a 64-bit signed value. Even better is to change const int
to const size_t
which in 64-bit code will be a 64-bit unsigned value. That puts the responsibility on the caller to put the value into the full register.
A version of the code that continues to use const int arr_size
; ensures the array size is greater than 0; steps through the array from the last element to the first element could be done this way:
__asm__
(
"array_max:\n\t"
" movsx %esi, %rsi\n\t" # Sign extend arr_size(ESI) 32-bit value to 64-bit
" mov $0x80000000, %eax\n\t" # EAX starts as lowest signed integer
" jmp 2f\n"
"1:\n\t"
" mov (%rdi,%rsi,4), %ecx\n\t" # Read current 32-bit signed value from array
" cmp %eax, %ecx\n\t"
" jl 2f\n\t" # Is current element(ECX) < max value(EAX)?
" mov %ecx, %eax\n" # If not, update max value with current element
"2:\n\t"
" dec %rsi\n\t" # Work backwards through the array
" jge 1b\n\t" # Until we have finished processing 1st element
" ret" # Return max array value in EAX
);
The way this works is that it sets the initial maximum value in EAX to the smallest negative value (0x80000000) and starts from the end of the array and compares the current element to the maximum value. If the current element (ECX) is greater than the maximum value seen (EAX) then set the maximum value to the current element. This continues until the beginning of the array has been processed.
You can make some improvements like using one of the CMOVcc
instructions to set the maximum value with the current element without the branch. It would appear something like:
" cmp %eax, %ecx\n\t"
" cmovg %ecx, %eax\n" # Set max value to current element if ECX>EAX
Instead of:
" cmp %eax, %ecx\n\t"
" jl 2f\n\t" # Is current element(ECX) < max val(EAX)?
" mov %ecx, %eax\n" # If not, update max value with current element
I use the "\n\t"
(which is newline character followed by tab character) to improve formatting if someone used GCC/CLANG's -S
option to review generated assembly instructions. You can continue to use ;
instead.
I use numeric labels to ensure all the labels are unique and don't conflict with any other labels that may be generated by the C compiler. More information on this feature can be found here
Numeric Labels
A numeric label consists of a single digit in the range zero (0) through nine (9) followed by a colon (:). Numeric labels are used only for local reference and are not included in the object file's symbol table. Numeric labels have limited scope and can be redefined repeatedly.
When a numeric label is used as a reference (as an instruction operand, for example), the suffixes b (“backward”) or f (“forward”) should be added to the numeric label. For numeric label N, the reference Nb refers to the nearest label N defined before the reference, and the reference Nf refers to the nearest label N defined after the reference.