いけむランド

はてダからやってきました

LLVM の言語バックエンドが生成するソースコードを読んでみる

LLVM のバックエンドにはソースコードを生成できるものがある。

% llc --help
  :
(snip)
  :
  -march                       - Architecture to generate code for:
    =x86                       -   32-bit X86: Pentium-Pro and above
    =x86-64                    -   64-bit X86: EM64T and AMD64
    =sparc                     -   SPARC
    =ppc32                     -   PowerPC 32
    =ppc64                     -   PowerPC 64
    =alpha                     -   Alpha (incomplete)
    =ia64                      -   IA-64 (Itanium)
    =arm                       -   ARM
    =thumb                     -   Thumb
    =mips                      -   Mips
    =cellspu                   -   STI CBEA Cell SPU
    =c                         -   C backend
    =msil                      -   MSIL backend
    =cpp                       -   C++ backend
  :


そこで単純な C のソースコードをビットコードに変換した後で C 言語バックエンドで C のソースコードに戻してみる。

#include <stdio.h>

int main(int argc, char** argv)
{
  printf("Hello, World!\n");
  return 0;
}


これを printf.c というファイルに保存して、llvm でごにょごにょする。

% llvm-gcc -emit-llvm printf.c -c # printf.o (bitcode) が生成される。
% llc -march=c printf.o -f        # printf.o.cbe.c     が生成される。


生成された printf.o.cbe.c は以下のとおり。*1

/* Provide Declarations */
#include <stdarg.h>
#include <setjmp.h>
/* get a declaration for alloca */
#if defined(__CYGWIN__) || defined(__MINGW32__)
#define  alloca(x) __builtin_alloca((x))
#define _alloca(x) __builtin_alloca((x))
#elif defined(__APPLE__)
extern void *__builtin_alloca(unsigned long);
#define alloca(x) __builtin_alloca(x)
#define longjmp _longjmp
#define setjmp _setjmp
#elif defined(__sun__)
#if defined(__sparcv9)
extern void *__builtin_alloca(unsigned long);
#else
extern void *__builtin_alloca(unsigned int);
#endif
#define alloca(x) __builtin_alloca(x)
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#define alloca(x) __builtin_alloca(x)
#elif defined(_MSC_VER)
#define inline _inline
#define alloca(x) _alloca(x)
#else
#include <alloca.h>
#endif

#ifndef __GNUC__  /* Can only support "linkonce" vars with GCC */
#define __attribute__(X)
#endif

#if defined(__GNUC__) && defined(__APPLE_CC__)
#define __EXTERNAL_WEAK__ __attribute__((weak_import))
#elif defined(__GNUC__)
#define __EXTERNAL_WEAK__ __attribute__((weak))
#else
#define __EXTERNAL_WEAK__
#endif

#if defined(__GNUC__) && defined(__APPLE_CC__)
#define __ATTRIBUTE_WEAK__
#elif defined(__GNUC__)
#define __ATTRIBUTE_WEAK__ __attribute__((weak))
#else
#define __ATTRIBUTE_WEAK__
#endif

#if defined(__GNUC__)
#define __HIDDEN__ __attribute__((visibility("hidden")))
#endif

#ifdef __GNUC__
#define LLVM_NAN(NanStr)   __builtin_nan(NanStr)   /* Double */
#define LLVM_NANF(NanStr)  __builtin_nanf(NanStr)  /* Float */
#define LLVM_NANS(NanStr)  __builtin_nans(NanStr)  /* Double */
#define LLVM_NANSF(NanStr) __builtin_nansf(NanStr) /* Float */
#define LLVM_INF           __builtin_inf()         /* Double */
#define LLVM_INFF          __builtin_inff()        /* Float */
#define LLVM_PREFETCH(addr,rw,locality) __builtin_prefetch(addr,rw,locality)
#define __ATTRIBUTE_CTOR__ __attribute__((constructor))
#define __ATTRIBUTE_DTOR__ __attribute__((destructor))
#define LLVM_ASM           __asm__
#else
#define LLVM_NAN(NanStr)   ((double)0.0)           /* Double */
#define LLVM_NANF(NanStr)  0.0F                    /* Float */
#define LLVM_NANS(NanStr)  ((double)0.0)           /* Double */
#define LLVM_NANSF(NanStr) 0.0F                    /* Float */
#define LLVM_INF           ((double)0.0)           /* Double */
#define LLVM_INFF          0.0F                    /* Float */
#define LLVM_PREFETCH(addr,rw,locality)            /* PREFETCH */
#define __ATTRIBUTE_CTOR__
#define __ATTRIBUTE_DTOR__
#define LLVM_ASM(X)
#endif

#if __GNUC__ < 4 /* Old GCC's, or compilers not GCC */ 
#define __builtin_stack_save() 0   /* not implemented */
#define __builtin_stack_restore(X) /* noop */
#endif

#define CODE_FOR_MAIN() /* Any target-specific code for main()*/

#ifndef __cplusplus
typedef unsigned char bool;
#endif


/* Support for floating point constants */
typedef unsigned long long ConstantDoubleTy;
typedef unsigned int        ConstantFloatTy;
typedef struct { unsigned long long f1; unsigned short f2; unsigned short pad[3]; } ConstantFP80Ty;
typedef struct { unsigned long long f1; unsigned long long f2; } ConstantFP128Ty;


/* Global Declarations */
/* Helper union for bitcasts */
typedef union {
  unsigned int Int32;
  unsigned long long Int64;
  float Float;
  double Double;
} llvmBitCastUnion;

/* External Global Variable Declarations */

/* Function Declarations */
double fmod(double, double);
float fmodf(float, float);
long double fmodl(long double, long double);
unsigned int main(unsigned int llvm_cbe_argc, unsigned char **llvm_cbe_argv);
unsigned int puts(unsigned char *);
unsigned char *malloc();
void free(unsigned char *);
void abort(void);


/* Global Variable Declarations */
static unsigned char _2E_str[14];


/* Global Variable Definitions and Initialization */
static unsigned char _2E_str[14] = "Hello, World!";


/* Function Bodies */
static inline int llvm_fcmp_ord(double X, double Y) { return X == X && Y == Y; }
static inline int llvm_fcmp_uno(double X, double Y) { return X != X || Y != Y; }
static inline int llvm_fcmp_ueq(double X, double Y) { return X == Y || llvm_fcmp_uno(X, Y); }
static inline int llvm_fcmp_une(double X, double Y) { return X != Y; }
static inline int llvm_fcmp_ult(double X, double Y) { return X <  Y || llvm_fcmp_uno(X, Y); }
static inline int llvm_fcmp_ugt(double X, double Y) { return X >  Y || llvm_fcmp_uno(X, Y); }
static inline int llvm_fcmp_ule(double X, double Y) { return X <= Y || llvm_fcmp_uno(X, Y); }
static inline int llvm_fcmp_uge(double X, double Y) { return X >= Y || llvm_fcmp_uno(X, Y); }
static inline int llvm_fcmp_oeq(double X, double Y) { return X == Y ; }
static inline int llvm_fcmp_one(double X, double Y) { return X != Y && llvm_fcmp_ord(X, Y); }
static inline int llvm_fcmp_olt(double X, double Y) { return X <  Y ; }
static inline int llvm_fcmp_ogt(double X, double Y) { return X >  Y ; }
static inline int llvm_fcmp_ole(double X, double Y) { return X <= Y ; }
static inline int llvm_fcmp_oge(double X, double Y) { return X >= Y ; }

unsigned int main(unsigned int llvm_cbe_argc, unsigned char **llvm_cbe_argv) {
  unsigned int llvm_cbe_argc_addr;    /* Address-exposed local */
  unsigned char **llvm_cbe_argv_addr;    /* Address-exposed local */
  unsigned int llvm_cbe_retval;    /* Address-exposed local */
  unsigned int llvm_cbe_tmp;    /* Address-exposed local */
  unsigned int llvm_cbe_alloca_20_point;
  unsigned int llvm_cbe_tmp1;
  unsigned int llvm_cbe_tmp2;
  unsigned int llvm_cbe_retval3;

  CODE_FOR_MAIN();
  llvm_cbe_alloca_20_point = ((unsigned int )0u);
  *(&llvm_cbe_argc_addr) = llvm_cbe_argc;
  *(&llvm_cbe_argv_addr) = llvm_cbe_argv;
  llvm_cbe_tmp1 = puts(((&_2E_str[((signed int )0u)])));
  *(&llvm_cbe_tmp) = 0u;
  llvm_cbe_tmp2 = *(&llvm_cbe_tmp);
  *(&llvm_cbe_retval) = llvm_cbe_tmp2;
  llvm_cbe_retval3 = *(&llvm_cbe_retval);
  return llvm_cbe_retval3;
}


プラットフォーム毎のマクロの定義の後には LLVM_ 接頭辞を持つマクロの定義が続く。*2

  • #if defined(__CYGWIN__) という表記もあることから、cygwin のことも考えられていないわけではないらしい。
    • だけど __attribute__ は __GNUC__ なら必ず定義されてしまうみたいなところを考えると、とりあえずコンパイルできる程度の考えしかなさそう。
  • llvm_fcmp_* マクロで double 型の比較演算をラップしている。実際に元の C ソースコードにそのような演算がある場合はこのマクロが使用される。以下にその例を示す。
double func(double a, double b)
{
  return (a >= b) ? a + b : a - b;
}
double func(double llvm_cbe_a, double llvm_cbe_b) {
  double llvm_cbe_a_addr;    /* Address-exposed local */
  double llvm_cbe_b_addr;    /* Address-exposed local */
  double llvm_cbe_retval;    /* Address-exposed local */
  double llvm_cbe_iftmp_2e_0;    /* Address-exposed local */
  double llvm_cbe_tmp;    /* Address-exposed local */
  unsigned int llvm_cbe_alloca_20_point;
  double llvm_cbe_tmp1;
  double llvm_cbe_tmp2;
  double llvm_cbe_tmp5;
  double llvm_cbe_tmp6;
  double llvm_cbe_tmp13;
  double llvm_cbe_tmp14;
  double llvm_cbe_retval15;

  llvm_cbe_alloca_20_point = ((unsigned int )0u);
  *(&llvm_cbe_a_addr) = llvm_cbe_a;
  *(&llvm_cbe_b_addr) = llvm_cbe_b;
  llvm_cbe_tmp1 = *(&llvm_cbe_a_addr);
  llvm_cbe_tmp2 = *(&llvm_cbe_b_addr);
  llvm_cbe_tmp5 = *(&llvm_cbe_a_addr);
  llvm_cbe_tmp6 = *(&llvm_cbe_b_addr);
  if (((((unsigned char )(bool )(llvm_fcmp_oge(llvm_cbe_tmp1, llvm_cbe_tmp2)))) != ((unsigned char )0))) {
    goto llvm_cbe_bb;
  } else {
    goto llvm_cbe_bb8;
  }

llvm_cbe_bb:
  *(&llvm_cbe_iftmp_2e_0) = (llvm_cbe_tmp5 + llvm_cbe_tmp6);
  goto llvm_cbe_bb12;

llvm_cbe_bb8:
  *(&llvm_cbe_iftmp_2e_0) = (llvm_cbe_tmp5 - llvm_cbe_tmp6);
  goto llvm_cbe_bb12;

llvm_cbe_bb12:
  llvm_cbe_tmp13 = *(&llvm_cbe_iftmp_2e_0);
  *(&llvm_cbe_tmp) = llvm_cbe_tmp13;
  llvm_cbe_tmp14 = *(&llvm_cbe_tmp);
  *(&llvm_cbe_retval) = llvm_cbe_tmp14;
  llvm_cbe_retval15 = *(&llvm_cbe_retval);
  return llvm_cbe_retval15;
}


C++ と MSIL については気が向いたら掘り下げる。

*1:cbe はたぶん C BackEnd の略だろうけど、backend って一単語だよなあ?

*2:llvm/cbe.h くらいにまとめておいても良いと思うけど。