1
\$\begingroup\$

I've started my journey of learning assembly on my Mac. As a start, I've decided to write a program to print the numbers one through ten to the console, then a final string signifying the end of the program. I want to kick bad habits to the curb quick, and know if there's any optimizations I can make to this code. Any and all feedback is appreciated!

loop_10.s

# Author: Ben Antonellis
# Start of stack: offset=-20(%rbp)
# Short: Prints the numbers 1-10, then "End of program."

    .section  __TEXT,__text
    .globl _main
_main:
    # Stack construction
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $32, %rsp

    movl    $0, -20(%rbp)           # int i = 0;
Loop_Condition_Compare:
    cmpl    $10, -20(%rbp)          # Check if i equals 10
    jne     Loop_Inside             # True: Continue loop
    jmp     Loop_Exit               # False: Exit loop

Loop_Inside:
    # Add one to i
    movl    -20(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -20(%rbp)

    # Print i
    movl    -20(%rbp), %esi
    leaq    integer(%rip), %rdi
    movb    $0, %al
    callq   _printf

    # Move to compare i
    jmp     Loop_Condition_Compare

Loop_Exit:
    # Print "End of program."
    leaq    end_string(%rip), %rdi
    movb    $0, %al
    callq   _printf

    # Stack deconstruction
    xorl    %eax, %eax
    addq    $32, %rsp
    popq    %rbp
    retq

.data

    integer: .asciz "%d\n"
    end_string: .asciz "End of program.\n"

And here's how I run my code:

run.sh

FILE=loop_10

gcc -o $FILE -fno-pie $FILE.s
./$FILE
rm $FILE
\$\endgroup\$
2
  • 1
    \$\begingroup\$ You can add to i with a single instruction addl $1, -20(%rbp) \$\endgroup\$
    – Sep Roland
    Aug 25, 2021 at 20:23
  • 1
    \$\begingroup\$ Instead of the 2 jumps jne Loop_Inside jmp Loop_Exit Loop_Inside:, you should inverse the condition and allow falling through in the code beneath: je Loop_Exit Loop_Inside: \$\endgroup\$
    – Sep Roland
    Aug 25, 2021 at 20:26

1 Answer 1

2
\$\begingroup\$

I want to kick bad habits to the curb quick, and know if there's any optimizations I can make to this code

Sure thing. I'll look past the option to print all text at once without a loop though, that would be an optimization but it wouldn't be as instructive or interesting. The details that are left are all minor compared to the cost of printing anything, but so be it.

movb $0, %al

In general when possible, I recommend xorl %eax, %eax (which you used later). Writing to 8-bit registers has complicated issues. Taking out the high bits of eax as "collateral damage" isn't a problem here, so I'd go with the good old xorl %eax, %eax, which doesn't have complicated issues and is in general the recommended way to zero a register.

loop patterns

The loop pattern has a lot of jumps and branches, all of them in the loop. Looping a known non-zero number of times can be done with just one branch. Even in general, you can do it with one branch and a jump outside of the loop (so it is executed less often).

For example, for the general case:

    jmp Loop_Condition_Compare
Loop_Inside:
    ; stuff
Loop_Condition_Compare:
    ; some comparison
    jcc Loop_Inside
Loop_Exit:

And if you know that the loop condition will be true the first time it would be evaluated in that general pattern, you can leave out the jump that goes to the loop condition, leaving only the jcc. By the way jcc is a short-hand for a conditional jump with whichever condition code that you need.

Iterating to zero can help save an instruction, but involves either counting down, or counting up through negative numbers, so this technique is not always easy or good to apply. I probably wouldn't use it in a case like this, but it's possible.

You can also align the loop, but when that helps and by how much is not easy to predict.

Prefer registers over stack

Putting i on the stack is not necessary, it could be in a callee-save (aka non-volatile) register instead. For the x64 SysV ABI (used by x64 Mac and x64 Linux and generally almost anything that isn't Windows) that's rbx, rbp, r12, r13, r14 and r15. Saving one of those (in the function prologue) and using it for i will also mean that i survives the function calls, and saves on stores and loads.

Alternatively, you could keep i in a convenient register most of the time and only store it to the stack right before the call, and then load it back right after the call.

I don't know if maybe you're following a mental model of "local variables go on the stack" (which I absolutely wouldn't blame you for, that's a common oversimplification found in most textbooks and tutorial websites and so on), but that's a thing for compilers in -O0 mode. My general advice is: prefer to put values in registers. I intentionally write "values", because a variable doesn't need to be in the same place all the time, it could be in one place at one time and another place another time if that works out better in the code, so picking a location for a variable is not really the proper/complete way to look at it (it might lead you to miss optimization opportunities). Compilers also try to be a bit clever with this sort of thing, when not in -O0 mode.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.