Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is GCC IR different from LLVM IR?

Tags:

Why do people prefer LLVM IR, and how exactly is it different from the GCC IR? Is target dependency a factor here?

I'm a complete newbie to compilers, and wasn't able to find anything relevant even after many hours of searching for an answer. Any insights would be helpful.

like image 666
lost_wanderer Avatar asked Nov 25 '16 07:11

lost_wanderer


People also ask

Does GCC have an IR?

Thus GIMPLE (GCC's IR) was never considered to be more than an implementation detail, in particular it doesn't provide a full description of compiled program (e.g. it lacks program's call graph, type definitions, stack offsets and alias information).

Is GCC faster than LLVM?

While LLVM's Clang C/C++ compiler was traditionally known for its faster build speeds than GCC, in recent releases of GCC the build speeds have improved and in some areas LLVM/Clang has slowed down with further optimization passes and other work added to its growing code-base.

What is LLVM IR used for?

You can think of LLVM IR as a platform-independent assembly language with an infinite number of function local registers. When developing compilers there are huge benefits with compiling your source language to an intermediate representation (IR) 1 instead of compiling directly to a target architecture (e.g. x86).

What is the difference between LLVM and Clang?

LLVM is a backend compiler meant to build compilers on top of it. It deals with optimizations and production of code adapted to the target architecture. CLang is a front end which parses C, C++ and Objective C code and translates it into a representation suitable for LLVM.


1 Answers

Firstly, as this answer touches on complex and sensitive topics I want to make few disclaimers:

  • I assume your question is about middle-end IRs of LLVM and GCC (as the term "LLVM IR" applies only to middle-end). Discussion of differences of back-end IRs (LLVM MachineIR and GCC RTL) and related codegen tools (LLVM Tablegen and GCC Machine Description) is an interesting and important topic but would make the answer several times larger.
  • I left out library-based design of LLVM vs monolithic design of GCC as this is separate from IR per se (although related).
  • I enjoy hacking on both GCC and LLVM and I do not put one ahead of other. LLVM is what it is because people could learn from things that GCC had wrong back in 2000-s (and which have been significantly improved since then).
  • I'm happy to improve this answer so please post comments if you think that something is imprecise or missing.

The most important fact is that LLVM IR and GCC IR (called GIMPLE) are not that different in their core - both are standard control-flow graphs of basic blocks, each block being a linear sequence of 2 inputs, 1 output instructions (so called "three-address code") which have been converted to SSA form. Most production compilers have been using this design since 1990-s.

Main advantages of LLVM IR are that it's less tightly bound to compiler implementation, more formally defined and has nicer C++ API. This allows for easier processing, transformation and analysis, which makes it IR of choice these days, both for compiler and for other related tools.

I expand on benefits of LLVM IR in subchapters below.

Standalone IR

LLVM IR originally designed to be fully reusable across arbitrary tools besides compiler itself. The original intent was to use it for multi-stage optimization: IR would be consequently optimized by ahead-of-time compiler, link-time optimizer and JIT compiler at runtime. This didn't work out but reusability had other important implications, most noticeably it allowed easy integration of other types of tools (static analyzers, instrumenters, etc.).

GCC community never had desire to enable any tools besides compiler (Richard Stallman resisted attempts to make IR more reusable to prevent third-party commercial tools from reusing GCC's frontends). Thus GIMPLE (GCC's IR) was never considered to be more than an implementation detail, in particular it doesn't provide a full description of compiled program (e.g. it lacks program's call graph, type definitions, stack offsets and alias information).

Flexible pipeline

The idea of reusability and making IR a standalone entity led to an important design consequence in LLVM: compilation passes can be run in any order which prevents complex inter-pass dependencies (all dependencies have to be made explicit via analysis passes) and enables easier experimentation with compilation pipeline e.g.

  • running strict IR verification checks after each pass
  • bisecting pipeline to find a minimal subset of passes which cause compiler crash
  • fuzzing order of passes

Better unit-testing support

Standalone IR allows LLVM to use IR-level unit tests which allows easy testing of optimization/analysis corner-cases. This is much harder to achieve through C/C++ snippets (as in GCC testsuite) and even when you manage, the generated IR will most likely change significantly in future versions of the compiler and the corner case that your test was intended for will no longer be covered.

Simple link-time optimization

Standalone IR enables easy combination of IR from separate translation units with a follow-up (whole program) optimization. This is not a complete replacement for link-time optimization (as it does not deal with scalability issues which arise in production software) but is often good enough for smaller programs (e.g. in embedded development or research projects).

Stricter IR definition

Although criticized by academia, LLVM IR has a much stricter semantics compared to GIMPLE. This simplifies implementation of various static analyzers e.g. IR Verifier.

No intermediate IRs

LLVM IR is generated directly by the frontend (Clang, llgo, etc.) and preserved throughout the whole middle-end. This means that all tools, optimizations and internal APIs only need to operate on single IR. The same is not true for GCC - even GIMPLE has three distinct variants:

  • high GIMPLE (includes lexical scopes, high-level control-flow constructs, etc.)
  • pre-SSA low GIMPLE
  • final SSA GIMPLE and also GCC frontends typically generate intermediate GENERIC IR instead of GIMPLE.

Simpler IR

Compared to GIMPLE, LLVM IR was deliberately made simpler by reducing number of cases which IR consumers need to consider. I've added several examples below.

Explicit control-flow

All basic blocks in LLVM IR program have to end with explicit control-flow opcode (branch, goto, etc.). Implicit control flow (i.e. fallthrough) is not allowed.

Explicit stack allocations

In LLVM IR virtual registers do not have memory. Stack allocations are represented by dedicated alloca operations. This simplifies working with stack variables e.g. equivalent of GCC's ADDR_EXPR is not needed.

Explicit indexing operations

Contrary to GIMPLE which has plethora of opcodes for memory references (INDIRECT_REF, MEM_REF, ARRAY_REF, COMPONENT_REF, etc.), LLVM IR has only plain load and store opcodes and all complex arithmetic is moved to dedicated structured indexing opcode, getelementptr.

Garbage collection support

LLVM IR provides dedicated pseudo-instructions for garbage-collected languages.

Higher-level implementation language

While C++ may not be the best programming language, it definitely allows to write much simpler (and in many case more functional) system code, especially with post-C++11 changes (LLVM aggressively adopts new Standards). Following LLVM, GCC has also adopted C++ but majority of the codebase is still written in C style.

There are too many instances where C++ enables a simpler code so I'll just name a few.

Explicit hierarchy

The hierarchy of operators in LLVM is implemented via standard inheritance and template-based custom RTTI. On the other hand GCC achieves the same via old-style inheritance-via-aggregation

// Base class which all operators aggregate struct GTY(()) tree_base {   ENUM_BITFIELD(tree_code) code : 16;    unsigned side_effects_flag : 1;   unsigned constant_flag : 1;   unsigned addressable_flag : 1;    ...  // Many more fields };  // Typed operators add type to base data struct GTY(()) tree_typed {   struct tree_base base;   tree type; };  // Constants add integer value to typed node data struct GTY(()) tree_int_cst {   struct tree_typed typed;   HOST_WIDE_INT val[1]; };  // Complex numbers add real and imaginary components to typed data struct GTY(()) tree_complex {   struct tree_typed typed;   tree real;   tree imag; };  // Many more operators follow ...  

and tagged union paradigms:

union GTY ((ptr_alias (union lang_tree_node),             desc ("tree_node_structure (&%h)"), variable_size)) tree_node {   struct tree_base GTY ((tag ("TS_BASE"))) base;   struct tree_typed GTY ((tag ("TS_TYPED"))) typed;   struct tree_int_cst GTY ((tag ("TS_INT_CST"))) int_cst;   struct tree_complex GTY ((tag ("TS_COMPLEX"))) complex; 

All GCC operator APIs use the base tree type which is accessed via fat macro interface (DECL_NAME, TREE_IMAGPART, etc.). Interface is only verified at runtime (and only if GCC was configured with --enable-checking) and does not allow static checking.

More concise APIs

LLVM generally provides simpler APIs for pattern matching IR in optimizers. For example checking that instruction is an addition with constant in GCC looks like

  if (gimple_assign_p (stmt)       && gimple_assign_rhs_code (stmt) == PLUS_EXPR       && TREE_CODE (gimple_assign_rhs2 (stmt)) == INTEGER_CST)     {       ... 

and in LLVM:

  if (auto BO = dyn_cast<BinaryOperator>(V))   if (BO->getOpcode() == Instruction::Add       && isa<ConstantInt>(BO->getOperand(1))     { 

Arbitrary-precision arithmetic

Due to C++ support for overloading, LLVM can uses arbitrary-precision ints for all computations whereas GCC still uses physical integers (HOST_WIDE_INT type, which is 32-bit on 32-bit hosts):

  if (!tree_fits_shwi_p (arg1))     return false;    *exponent = tree_to_shwi (arg1); 

As shown in the example this can lead to missed optimizations.

GCC has got an equivalent of APInts few years ago but the majority of the codebase still uses HOST_WIDE_INT.

like image 177
yugr Avatar answered Sep 22 '22 11:09

yugr