0

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Sammy
  • 79
  • 6

2 Answers2

3

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 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.

Community
  • 1
  • 1
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
1

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.

OPSXCQ
  • 526
  • 5
  • 6
  • If you use EBX, you need to save/restore the caller's value. That's why the OP used ECX. But you're right that EAX holds garbage to start with, and that's one of the problems in the OP's code. – Peter Cordes Sep 23 '16 at 03:07