LLVM IR to Asm Part 2

811 阅读6分钟

7 Step One: Load a.ll file and parse it into Module

After done some intitializations for LLVM backend, the main function calls compileModule to compile the input file into assembly. Fisrtly, it calls llvm::parseIRFile() to load input file into MemoryBuffer, see How to use LLVM to read files into Memory in details, then llvm::parseAssembly() into Module because the input file is not in Bitcode mode but in IR Assembly mode.

/// llvm-project/llvm/include/llvm/IR/Module.h
/// A Module instance is used to store all the information related to an
/// LLVM module. Modules are the top level container of all other LLVM
/// Intermediate Representation (IR) objects. Each module directly contains a
/// list of globals variables, a list of functions, a list of libraries (or
/// other modules) this module depends on, a symbol table, and various data
/// about the target's characteristics.
/// A module maintains a GlobalValRefMap object that is used to hold all
/// constant references to global variables in the module.  When a global
/// variable is destroyed, it should have no entries in the GlobalValueRefMap.
/// The main container class for the LLVM Intermediate Representation.
class Module {
  LLVMContext &Context;           ///< The LLVMContext from which types and
                                  ///< constants are allocated.
  GlobalListType GlobalList;      ///< The Global Variables in the module
  FunctionListType FunctionList;  ///< The Functions in the module
  AliasListType AliasList;        ///< The Aliases in the module
  IFuncListType IFuncList;        ///< The IFuncs in the module
  NamedMDListType NamedMDList;    ///< The named metadata in the module
  std::string GlobalScopeAsm;     ///< Inline Asm at global scope.
  std::unique_ptr<ValueSymbolTable> ValSymTab; ///< Symbol table for values
  ComdatSymTabType ComdatSymTab;  ///< Symbol table for COMDATs
  std::unique_ptr<MemoryBuffer>
  OwnedMemoryBuffer;              ///< Memory buffer directly owned by this
                                  ///< module, for legacy clients only.
  std::unique_ptr<GVMaterializer>
  Materializer;                   ///< Used to materialize GlobalValues
  std::string ModuleID;           ///< Human readable identifier for the module
  std::string SourceFileName;     ///< Original source file name for module,
                                  ///< recorded in bitcode.
  std::string TargetTriple;       ///< Platform target triple Module compiled on
                                  ///< Format: (arch)(sub)-(vendor)-(sys0-(abi)
  NamedMDSymTabType NamedMDSymTab;  ///< NamedMDNode names.
  DataLayout DL;                  ///< DataLayout associated with the module
}

According to LLVM IR BNF, it LLParser::ParseTargetDefinitions(),

bool LLParser::ParseTargetDefinitions() {
  while (true) {
    switch (Lex.getKind()) {
    case lltok::kw_target: if (ParseTargetDefinition()) return true; break;
    case lltok::kw_source_filename:if (ParseSourceFileName()) return true; break;
    default: return false;}}}
///   ::= 'target' 'triple' '=' STRINGCONSTANT
///   ::= 'target' 'datalayout' '=' STRINGCONSTANT
bool LLParser::ParseTargetDefinition();
///   ::= 'source_filename' '=' STRINGCONSTANT
bool LLParser::ParseSourceFileName();

then LLParser::ParseTopLevelEntities(),

bool LLParser::ParseTopLevelEntities() {
  while (true) {
    switch (Lex.getKind()) {
    default:         return TokError("expected top-level entity");
    case lltok::Eof: return false;
    case lltok::kw_declare: if (ParseDeclare()) return true; break;
    case lltok::kw_define:  if (ParseDefine()) return true; break;
    case lltok::kw_module:  if (ParseModuleAsm()) return true; break;
    case lltok::kw_deplibs: if (ParseDepLibs()) return true; break;
    case lltok::LocalVarID: if (ParseUnnamedType()) return true; break;
    case lltok::LocalVar:   if (ParseNamedType()) return true; break;
    case lltok::GlobalID:   if (ParseUnnamedGlobal()) return true; break;
    case lltok::GlobalVar:  if (ParseNamedGlobal()) return true; break;
    case lltok::ComdatVar:  if (parseComdat()) return true; break;
    case lltok::exclaim:    if (ParseStandaloneMetadata()) return true; break;
    case lltok::SummaryID:  if (ParseSummaryEntry())  return true; break;
    case lltok::MetadataVar:if (ParseNamedMetadata()) return true; break;
    case lltok::kw_attributes: if (ParseUnnamedAttrGrp()) return true; break;
    case lltok::kw_uselistorder: if (ParseUseListOrder()) return true; break;
    case lltok::kw_uselistorder_bb: if (ParseUseListOrderBB()) return true; break;}}}
///   ::= 'declare' FunctionHeader
bool LLParser::ParseDeclare();
///   ::= 'define' FunctionHeader (!dbg !56)* '{' ...
bool LLParser::ParseDefine();
///   ::= 'module' 'asm' STRINGCONSTANT
bool LLParser::ParseModuleAsm();
///   ::= 'deplibs' '=' '[' ']'
///   ::= 'deplibs' '=' '[' STRINGCONSTANT (',' STRINGCONSTANT)* ']'
/// FIXME: Remove in 4.0. Currently parse, but ignore.
bool LLParser::ParseDepLibs();
///   ::= LocalVarID '=' 'type' type
bool LLParser::ParseUnnamedType();
///   ::= LocalVar '=' 'type' type
bool LLParser::ParseNamedType();
///   OptionalVisibility (ALIAS | IFUNC) ...
///   OptionalLinkage OptionalPreemptionSpecifier OptionalVisibility
///   OptionalDLLStorageClass
///                                                     ...   -> global variable
///   GlobalID '=' OptionalVisibility (ALIAS | IFUNC) ...
///   GlobalID '=' OptionalLinkage OptionalPreemptionSpecifier OptionalVisibility
///                OptionalDLLStorageClass
///                                                     ...   -> global variable
bool LLParser::ParseUnnamedGlobal() ;
///   GlobalVar '=' OptionalVisibility (ALIAS | IFUNC) ...
///   GlobalVar '=' OptionalLinkage OptionalPreemptionSpecifier
///                 OptionalVisibility OptionalDLLStorageClass
///                                                     ...   -> global variable
bool LLParser::ParseNamedGlobal();
bool LLParser::parseComdat() ;
///   !42 = !{...}
bool LLParser::ParseStandaloneMetadata();
///   ::= SummaryID '=' GVEntry | ModuleEntry | TypeIdEntry
bool LLParser::ParseSummaryEntry() ;
///   !foo = !{ !1, !2 }
bool LLParser::ParseNamedMetadata() ;
///   ::= 'attributes' AttrGrpID '=' '{' AttrValPair+ '}'
bool LLParser::ParseUnnamedAttrGrp();
///   ::= 'uselistorder' Type Value ',' UseListOrderIndexes
bool LLParser::ParseUseListOrder(PerFunctionState *PFS) ;
///   ::= 'uselistorder_bb' @foo ',' %bar ',' UseListOrderIndexes
bool LLParser::ParseUseListOrderBB() ;

and do some validation. Afterward, we get Module like this, using M->dump() in lldb to see the parsed result.

(lldb) b llc.cpp:524
(lldb) p M->dump()
; ModuleID = '/Users/k/llvm-project/llvm/lib/Target/RISCV/LLDB/localvar.ll'
source_filename = "/Users/k/llvm-project/llvm/lib/Target/RISCV/LLDB/localvar.ll"
target datalayout = "e-m:e-p:32:32-i64:64-n32-S128"
define i32 @main(i32 signext %argc, i8** %argv) {
    %1 = alloca i32, align 4
    store i32 1, i32* %1, align 4
    ret i32 0
}

After we got the Module, llc builds up all of the passes that we want to do to the module. Those transformations are taken effect in its passes. More information about Pass and PassManager. Because we are to emit an assembly file from a.ll, llc will call LLVMTargetMachine::addPassesToEmitFile to add related passes. It calls

  1. addPassesToGenerateCode() add needed passes to codegen process which transform IR to MachineInstr,
  2. LLVMTargetMachine::addAsmPrinter() add needed passes to output Asm which transform MachineInstr to MCInstr then to Assembly,
  3. llvm::createFreeMachineFunctionPass() This pass frees the MachineFunction object associated with a Function, in turn.

Notes that, in addPassesToGenerateCode(), llc calls LLVMTargetMachine::createPassConfig() of LLVMTargetMachine, which RISCV has its derivation RISCVTargetMachine, and overrides the RISCVTargetMachine::createPassConfig(), to return an instance of RISCVPassConfig , to customize passes with TargetPassConfig::addPass() for

  1. RISCVPassConfig::addIRPass(), called in TargetPassConfig::addISelPasses(), to perform LLVM IR to IR transforms following machine independent optimization.
  2. RISCVPassConfig::addInstSelector() This method should install an instruction selector pass, which converts from LLVM code to machine instructions
  3. RISCVPassConfig::addIRTranslator() This method should install an IR translator pass, which converts from LLVM code to machine instructions with possibly generic opcodes.
  4. RISCVPassConfig::addLegalizeMachineIR() This method should install a legalize pass, which converts the instruction sequence into one that can be selected by the target.
  5. RISCVPassConfig::addRegBankSelect() This method should install a register bank selector pass, which assigns register banks to virtual registers without a register class or register banks.
  6. RISCVPassConfig::addGlobalInstructionSelect() This method should install a (global) instruction selector pass, which converts possibly generic instructions to fully target-specific instructions, thereby constraining all generic virtual registers to register classes.
  7. RISCVPassConfig::addPreEmitPass() This pass may be implemented by targets that want to run passes immediately before machine code is emitted.
  8. RISCVPassConfig::addPreEmitPass2() Targets may add passes immediately before machine code is emitted in this callback. This is called even later than `addPreEmitPass`.
  9. RISCVPassConfig::addPreSched2() This method may be implemented by targets that want to run passes after prolog-epilog insertion and before the second instruction scheduling pass.
  10. RISCVPassConfig::addPreRegAlloc() This method may be implemented by targets that want to run passes immediately before register allocation.

    After those passes set up, we can see what are those passes inside.

    (lldb) p PM.PM->dumpPasses()
    Target Library Information
    Target Pass Configuration
    Machine Module Information
    Target Transform Information
    Type-Based Alias Analysis
    Scoped NoAlias Alias Analysis
    Assumption Cache Tracker
    Profile summary info
    Create Garbage Collector Module Metadata
    Machine Branch Probability Analysis
    --ModulePass Manager
    ----Pre-ISel Intrinsic Lowering
    ----FunctionPass Manager
    ------Expand Atomic instructions
    ------Module Verifier
    ------Dominator Tree Construction
    ------Basic Alias Analysis (stateless AA impl)
    ------Natural Loop Information
    ------Canonicalize natural loops
    ------Scalar Evolution Analysis
    ------Loop Pass Manager
    --------Canonicalize Freeze Instructions in Loops
    --------Induction Variable Users
    --------Loop Strength Reduction
    ------Basic Alias Analysis (stateless AA impl)
    ------Function Alias Analysis Results
    ------Merge contiguous icmps into a memcmp
    ------Natural Loop Information
    ------Lazy Branch Probability Analysis
    ------Lazy Block Frequency Analysis
    ------Expand memcmp() to load/stores
    ------Lower Garbage Collection Instructions
    ------Shadow Stack GC Lowering
    ------Lower constant intrinsics
    ------Remove unreachable blocks from the CFG
    ------Dominator Tree Construction
    ------Natural Loop Information
    ------Post-Dominator Tree Construction
    ------Branch Probability Analysis
    ------Block Frequency Analysis
    ------Constant Hoisting
    ------Partially inline calls to library functions
    ------Instrument function entry/exit with calls to e.g. mcount() (post inlining)
    ------Scalarize Masked Memory Intrinsics
    ------Expand reduction intrinsics
    ------Dominator Tree Construction
    ------Natural Loop Information
    ------CodeGen Prepare
    ----Rewrite Symbols
    ----FunctionPass Manager
    ------Dominator Tree Construction
    ------Exception handling preparation
    ------Safe Stack instrumentation pass
    ------Insert stack protectors
    ------Module Verifier
    ------Dominator Tree Construction
    ------Basic Alias Analysis (stateless AA impl)
    ------Function Alias Analysis Results
    ------Natural Loop Information
    ------Post-Dominator Tree Construction
    ------Branch Probability Analysis
    ------Lazy Branch Probability Analysis
    ------Lazy Block Frequency Analysis
    ------RISCV DAG->DAG Pattern Instruction Selection
    ------Finalize ISel and expand pseudo-instructions
    ------Lazy Machine Block Frequency Analysis
    ------Early Tail Duplication
    ------Optimize machine instruction PHIs
    ------Slot index numbering
    ------Merge disjoint stack slots
    ------Local Stack Slot Allocation
    ------Remove dead machine instructions
    ------MachineDominator Tree Construction
    ------Machine Natural Loop Construction
    ------Early Machine Loop Invariant Code Motion
    ------MachineDominator Tree Construction
    ------Machine Block Frequency Analysis
    ------Machine Common Subexpression Elimination
    ------MachinePostDominator Tree Construction
    ------Machine code sinking
    ------Peephole Optimizations
    ------Remove dead machine instructions
    ------RISCV Merge Base Offset
    ------Detect Dead Lanes
    ------Process Implicit Definitions
    ------Remove unreachable machine basic blocks
    ------Live Variable Analysis
    ------MachineDominator Tree Construction
    ------Machine Natural Loop Construction
    ------Eliminate PHI nodes for register allocation
    ------Two-Address instruction pass
    ------Slot index numbering
    ------Live Interval Analysis
    ------Simple Register Coalescing
    ------Rename Disconnected Subregister Components
    ------Machine Instruction Scheduler
    ------Machine Block Frequency Analysis
    ------Debug Variable Analysis
    ------Live Stack Slot Analysis
    ------Virtual Register Map
    ------Live Register Matrix
    ------Bundle Machine CFG Edges
    ------Spill Code Placement Analysis
    ------Lazy Machine Block Frequency Analysis
    ------Machine Optimization Remark Emitter
    ------Greedy Register Allocator
    ------Virtual Register Rewriter
    ------Stack Slot Coloring
    ------Machine Copy Propagation Pass
    ------Machine Loop Invariant Code Motion
    ------Fixup Statepoint Caller Saved
    ------PostRA Machine Sink
    ------Machine Block Frequency Analysis
    ------MachineDominator Tree Construction
    ------MachinePostDominator Tree Construction
    ------Lazy Machine Block Frequency Analysis
    ------Machine Optimization Remark Emitter
    ------Shrink Wrapping analysis
    ------Prologue/Epilogue Insertion & Frame Finalization
    ------Control Flow Optimizer
    ------Lazy Machine Block Frequency Analysis
    ------Tail Duplication
    ------Machine Copy Propagation Pass
    ------Post-RA pseudo instruction expansion pass
    ------MachineDominator Tree Construction
    ------Machine Natural Loop Construction
    ------Post RA top-down list latency scheduler
    ------Analyze Machine Code For Garbage Collection
    ------Machine Block Frequency Analysis
    ------MachinePostDominator Tree Construction
    ------Branch Probability Basic Block Placement
    ------Insert fentry calls
    ------Insert XRay ops
    ------Implement the 'patchable-function' attribute
    ------Branch relaxation pass
    ------Contiguously Lay Out Funclets
    ------StackMap Liveness Analysis
    ------Live DEBUG_VALUE analysis
    ------RISCV pseudo instruction expansion pass
    ------RISCV atomic pseudo instruction expansion pass
    ------Lazy Machine Block Frequency Analysis
    ------Machine Optimization Remark Emitter
    ------RISCV Assembly Printer
    ------Free MachineFunction
    

    Then llc will calls PM.run(*M) to run all the added passed on Module to output Assembly if everything is OK.