I am trying to write a for loop that does multiplication by adding a number (var a) by another number (var b) times.
.globl times
times:
movl $0, %ecx # i = 0
cmpl %ecx, %esi #if b-i
jge end # if >= 0, jump to end
loop:
addl (%edi, %eax), %eax #sum += a
incl %ecx # i++
cmpl %esi, %ecx # compare (i-b)
jl loop # < 0? loop b times total
end:
ret
Where am I going wrong? I've run through the logic and I can't figure out what the problem is.
TL:DR: you didn't zero EAX, and your ADD instruction is using a memory operand.
You should have used a debugger. You'd easily have seen that EAX wasn't zero to start with. See the bottom of the x86 tag wiki for tips on using gdb to debug asm.
I guess you're using the x86-64 System V ABI, so your args (a and b) are in %edi and %esi.
At the start of a function, registers other than the ones holding your args should be assumed to contain garbage. Even the high parts of registers that are holding your args can contain garbage. (exception to this rule: unofficially, narrow args are sign or zero extended to 32-bit by the caller)
Neither arg is a pointer, so you shouldn't dereference them. add (%edi, %eax), %eax calculates a 32-bit address as EDI+EAX, and then loads 32 bits from there. It adds that dword to EAX (the destination operand).
I'm shocked that your program didn't segfault, since you're using your integer arg as a pointer.
For many x86 instructions (like ADD), the destination operand is not write-only. add %edi, %eax does EAX += EDI. I think you're getting mixed up with 3-operand RISC syntax, where you might have an instruction like add %src1, %src2, %dst.
x86 has some instructions like that, added as recent extensions, like BMI2 bzhi, but the usual instructions are all 2-operand with destructive destinations. (except for LEA, where instead of loading from the address, it stores the address in the destination. So lea (%edi, %eax), %eax would work. You could even put the result in a different register. LEA is great for saving MOV instructions by doing shift+add and a mov all in one instruction, using the addressing mode syntax and machine-code encoding.
You have a comment that says ie eax = sum + (a x 4bits). No clue what you're talking about there. a is 4 bytes (not bits), and you're not multiplying a (%edi) by anything.
Just for fun, here's how I'd write your function (if I had to avoid imul %edi, %esi / mov %esi, %eax). I'll assume both args are non-negative, to keep it simple. If your args are signed integers, and you have to loop -b times if b is negative, then you need some extra code.
# args: int a(%edi), int b(%esi) # comments are important for documenting inputs/outputs to blocks of code
# return value: product in %eax
# assumptions: b is non-negative.
times:
xor %eax, %eax # zero eax
test %esi, %esi # set flags from b
jz loop_end # early-out if it's zero
loop: # do{
add %edi, %eax # sum += a,
dec %esi # b-- (setting flags based on the result, except for CF so don't use ja or jb after it)
jge loop # }while(b>=0)
loop_end:
ret
Note the indenting style, so it's easy to find the branch targets. Some people like to indent extra for instructions inside loops.
Your way works fine (if you do it right), but my way illustrates that counting down is easier in asm (no need for an extra register or immediate to hold the upper bound). Also, avoiding redundant compares. But don't worry about optimizing until after you're comfortable writing code that at least works.
This is a pseudo code, keep that in mind.
mov X,ebx <- put into EBX your counter, your B
mov Y,edx <- put into EDX your value, your A
mov 0,eax <- Result
loop:
add eax,edx
dec ebx
jnz loop <- While EBX is not zero
The above implementation should result in your value into EAX. Your code looks like it's missing the eax initialisation.
Related
Consider this C code:
int f(void) {
int ret;
char carry;
__asm__(
"nop # do something that sets eax and CF"
: "=a"(ret), "=#ccc"(carry)
);
return carry ? -ret : ret;
}
When I compile it with gcc -O3, I get this:
f:
nop # do something that sets eax and CF
setc %cl
movl %eax, %edx
negl %edx
testb %cl, %cl
cmovne %edx, %eax
ret
If I change char carry to int carry, I instead get this:
f:
nop # do something that sets eax and CF
setc %cl
movl %eax, %edx
movzbl %cl, %ecx
negl %edx
testl %ecx, %ecx
cmovne %edx, %eax
ret
That change replaced testb %cl, %cl with movzbl %cl, %ecx and testl %ecx, %ecx. The program is actually equivalent, though, and GCC knows it. As evidence of this, if I compile with -Os instead of -O3, then both char carry and int carry result in the exact same assembly:
f:
nop # do something that sets eax and CF
jnc .L1
negl %eax
.L1:
ret
It seems like one of two things must be true, but I'm not sure which:
A testb is faster than a movzbl followed by a testl, so GCC's use of the latter with int is a missed optimization.
A testb is slower than a movzbl followed by a testl, so GCC's use of the former with char is a missed optimization.
My gut tells me that an extra instruction will be slower, but I also have a nagging doubt that it's preventing a partial register stall that I just don't see.
By the way, the usual recommended approach of xoring the register to zero before the setc doesn't work in my real example. You can't do it after the inline assembly runs, since xor will overwrite the carry flag, and you can't do it before the inline assembly runs, since in the real context of this code, every general-purpose call-clobbered register is already in use somehow.
There's no downside I'm aware of to reading a byte register with test vs. movzb.
If you are going to zero-extend, it's also a missed optimization not to xor-zero a reg ahead of the asm statement, and setc into that so the cost of zero-extension is off the critical path. (On CPUs other than Intel IvyBridge+ where movzx r32, r8 is not zero latency). Assuming there's a free register, of course. Recent GCC does sometimes find this zero/set-flags/setcc optimization for generating a 32-bit boolean from a flag-setting instruction, but often misses it when things get complex.
Fortunately for you, your real use-case couldn't do that optimization anyway (except with mov $0, %eax zeroing, which would be off the critical path for latency but cause a partial-register stall on Intel P6 family, and cost more code size.) But it's still a missed optimization for your test case.
This question is similar to another question I posted here. I am attempting to write the Assembly version of the following in c/c++:
int x[10];
for (int i = 0; i < 10; i++){
x[i] = i;
}
Essentially, creating an array storing the values 1 through 9.
My current logic is to create a label that loops up to 10 (calling itself until reaching the end value). In the label, I have placed the instructions to update the array at the current index of iteration. However, after compiling with gcc filename.s and running with ./a.out, the error Segmentation fault: 11 is printed to the console. My code is below:
.data
x:.fill 10, 4
index:.int 0
end:.int 10
.text
.globl _main
_main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
jmp outer_loop
leave
ret
outer_loop:
movl index(%rip), %eax;
cmpl end(%rip), %eax
jge end_loop
lea x(%rip), %rdi;
mov index(%rip), %rsi;
movl index(%rip), %eax;
movl %eax, (%rdi, %rsi, 4)
incl index(%rip)
jmp outer_loop
leave
ret
end_loop:
leave
ret
Oddly the code below
lea x(%rip), %rdi;
mov index(%rip), %rsi;
movl index(%rip), %eax;
movl %eax, (%rdi, %rsi, 4)
works only if it is not in a label that is called repetitively. Does anyone know how I can implement the code above in a loop, without Segmentation fault: 11 being raised? I am using x86 Assembly on MacOS with GNU GAS syntax compiled with gcc.
Please note that this question is not a duplicate of this question as different Assembly syntax is being used and the scope of the problem is different.
You're using a 64-bit instruction to access a 32-bit area of memory :
mov index(%rip), %rsi;
This results in %rsi being assigned the contents of memory starting from index and ending at end (I'm assuming no alignment, though I don't remember GAS's rules regarding it). Thus, %rsi effectively is assigned the value 0xa00000000 (assuming first iteration of the loop), and executing the following movl %eax, (%rdi, %rsi, 4) results in the CPU trying to access the address that's not mapped by your process.
The solution is to remove the assignment, and replace the line after it with movl index(%rip), %esi. 32-bit operations are guaranteed to always clear out the upper bits of 64-bit registers, so you can then safely use %rsi in the address calculation, as it's going to contain the current index and nothing more.
Your debugger would've told you this, so please do use it next time.
What is the meaning of a dollar sign in front of a gnu assembly label?
For example, what is the difference between mov msg, %si and mov $msg, %si
(For more context, I'm playing around with the x86 Bare Metal Examples: https://github.com/cirosantilli/x86-bare-metal-examples/blob/master/bios_hello_world.S)
#include "common.h"
BEGIN
mov $msg, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int $0x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
(What do the dollar ($) and percentage (%) signs represent in assembly intel x86? discusses the general use of % before registers and $ before constants; but, I don't think it lays out the use of $ with labels nearly as clearly as the answer below )
You use $(dollar) sign when addressing a constant, e.g.:
movl $1, %eax (put 1 to %eax register)
or when handling an address of some variable, e.g.: movl $var, %eax (this means take an address of var label and put it into %eax register).
If you don't use dollar sign that would mean "take the value from var label and put it to register".
I write a lot of vectorized loops, so 1 common idiom is
volatile int dummy[1<<10];
for (int64_t i = 0; i + 16 <= argc; i+= 16) // process all elements with whole vector
{
int x = dummy[i];
}
// handle remainder (hopefully with SIMD too)
But the resulting machine code has 1 more instruction than I would like (using gcc 4.9)
.L3:
leaq -16(%rax), %rdx
addq $16, %rax
cmpq %rcx, %rax
movl -120(%rsp,%rdx,4), %edx
jbe .L3
If I change the code to for (int64_t i = 0; i <= argc - 16; i+= 16), then the "extra"
instruction is gone:
.L2:
movl -120(%rsp,%rax,4), %ecx
addq $16, %rax
cmpq %rdx, %rax
jbe .L2
But why the difference? I was thinking maybe it was due to loop invariants, but too vaguely. Then I noticed in the 5 instruction case, the increment is done before the load, which would require an extra mov due to x86's destructive 2 operand instructions.
So another explanation could be that it's trading instruction parallelism for 1 extra instruction.
Although it seems there would hardly be any performance difference, can someone explain this mystery (preferably who knows about compiler transformations)?
Ideally I would like to keep the i + 16 <= size form since that has a more intuitive meaning (the last element of the vector doesn't go out of bounds)
If argc were below -2147483632, and i was below 2147483632, the expressions i+16 <= argc would be required to yield an arithmetically-correct result, while the expression and i<argc-16 would not. The need to give an arithmetically-correct result in that corner case prevents the compiler from optimizing the former expression to match the latter.
I have looked at a few dumps of assembler code and there is this section (found here and here) in the main function:
<main+0>: push %ebp
<main+1>: mov %esp, %ebp
<main+3>: sub $0x8, %esp
<main+6>: and $0xfffffff0, %esp
<main+9>: mov $0x0, %eax
<main+14>: add $0xf, %eax
<main+17>: add $0xf, %eax
<main+20>: shr $0x4, %eax
<main+23>: shl $0x4, %eax
<main+26>: sub %eax, %esp
Can you explain me what (main+9) to (main+26) is used for?
Why is this done so 'inefficient'?
So you want a full walk-through without doing any research yourself? Sounds legit.
main+9: mov $0x0, %eax
Loads the register eax with hex 0 (=dec 0).
main+14: add $0xf, %eax
Adds hex F (= dec 15) to the zero in eax.
main+17: add $0xf, %eax
Adds hex F (= dec 15) to eax again. These three instructions could have also been done by
movl $0x1e, %eax
but who's counting instructions... Anyway, at this point eax contains hex 1E which is dec 30.
main+20: shr $0x4, %eax
Shifts the contents of eax to the right by four bits.
main+23: shl $0x4, %eax
Shifts eax right back. Why? Because this clears the lowest four bits. Now eax contains hex 10 (= dec 16)
main+26: sub %eax, %esp
Substracts eax from esp (the stack pointer). Since
main+6: and $0xfffffff0, %esp
cleared the lower four bits in esp previously, the new esp will be sixteen byte aligned, as per ABI. Why not simply use esp after main+6? Because on x86, the stack grows downwards from the top of memory. Simply masking off the lower bits of esp risks clobbering local variables. Hence the subtraction to grow the stack down to the sixteen byte boundary.