Search code examples
optimizationllvmtail-call-optimization

Why is this a tail call?


Here is a simple hello world:

#include <stdio.h>

int main() {
    printf("hello world\n");
    return 0;
}

Here it is compiled to LLVM IR:

will@ox:~$ clang -S -O3 -emit-llvm ~/test_apps/hello1.c -o -
; ModuleID = '/home/will/test_apps/hello1.c'
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

@str = private unnamed_addr constant [12 x i8] c"hello world\00"

; Function Attrs: nounwind uwtable
define i32 @main() #0 {
  %puts = tail call i32 @puts(i8* getelementptr inbounds ([12 x i8]* @str, i64 0, i64 0))
  ret i32 0
}

; Function Attrs: nounwind
declare i32 @puts(i8* nocapture readonly) #1

attributes #0 = { nounwind uwtable "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nounwind }

!llvm.ident = !{!0}

!0 = !{!"Ubuntu clang version 3.6.0-2ubuntu1 (tags/RELEASE_360/final) (based on LLVM 3.6.0)"}

The description of tail-call optimisation says that the following conditions must be met:

The call is a tail call - in tail position (ret immediately follows call and ret uses value of call or is void).

Yet in this example the value returned by puts() should not be used as the return value of the function.

Is this a legal tail-call optimisation? What does main() return?


Solution

  • The tail flag in LLVM is a bit strange. It just means that the call to puts is a candidate for tail call optimization, in particular it is not allowed to access any variable on the stack of the caller. The code generator still has to make sure that the call is in a position suitable for tail call optimization before it actually turns the call into a jump, and that's not the case here.

    If you look at the assembly emitted by LLVM you'll see that there is no tail call optimization happening:

    $ clang -O -S -o - bug.c
            [...]
    main:                                   # @main
            .cfi_startproc
    # BB#0:                                 # %entry
            pushq   %rax
    .Ltmp0:
            .cfi_def_cfa_offset 16
            movl    $.Lstr, %edi
            callq   puts
            xorl    %eax, %eax
            popq    %rdx
            retq