Printing one to ten in (GAS/AT&T x86/x64) assembly

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  • You can add to i with a single instruction addl$1, -20(%rbp) Aug 25 at 20:23
• 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: Aug 25 at 20:26

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.