Assembler

This chapter covers the assembly programming support in the Cpu0 backend.

When it comes to assembly language programming, there are two types of usage in C/C++ as follows:

ordinary assembly

asm("ld       $2, 8($sp)");

inline assembly

int foo = 10;
const int bar = 15;

__asm__ __volatile__("addu %0,%1,%2"
                     :"=r"(foo) // 5
                     :"r"(foo), "r"(bar)
                     );

In LLVM, the first method is supported by the LLVM AsmParser, and the second by the inline assembly handler.

With AsmParser and inline assembly support in the Cpu0 backend, we can hand-code assembly language in a C/C++ file and translate it into an object file (in ELF format).

AsmParser support

This section lists all the AsmParser code for the Cpu0 backend, with only brief explanations. Please refer to [1] for a more detailed explanation of AsmParser.

Running Chapter10_1/ with ch11_1.cpp will produce the following error message.

Run Chapter10_1/ with ch11_1.cpp will get the following error message.

lbdex/input/ch11_1.cpp

asm("ld	$2, 8($sp)");
asm("st	$0, 4($sp)");
asm("addiu $3,	$ZERO, 0");
asm("add $v1, $at, $v0");
asm("sub $3, $2, $3");
asm("mul $2, $1, $3");
asm("div $3, $2");
asm("divu $2, $3");
asm("and $2, $1, $3");
asm("or $3, $1, $2");
asm("xor $1, $2, $3");
asm("mult $4, $3");
asm("multu $3, $2");
asm("mfhi $3");
asm("mflo $2");
asm("mthi $2");
asm("mtlo $2");
asm("sra $2, $2, 2");
asm("rol $2, $1, 3");
asm("ror $3, $3, 4");
asm("shl $2, $2, 2");
asm("shr $2, $3, 5");
asm("cmp $sw, $2, $3");
asm("jeq $sw, 20");
asm("jne $sw, 16");
asm("jlt $sw, -20");
asm("jle $sw, -16");
asm("jgt $sw, -4");
asm("jge $sw, -12");
asm("jsub 0x000010000");
asm("jr $4");
asm("ret $lr");
asm("jalr $t9");
asm("li $3, 0x00700000");
asm("la $3, 0x00800000($6)");
asm("la $3, 0x00900000");
JonathantekiiMac:input Jonathan$ clang -c ch11_1.cpp -emit-llvm -o
ch11_1.bc
JonathantekiiMac:input Jonathan$ /Users/Jonathan/llvm/test/build/bin/llc
-march=cpu0 -relocation-model=pic -filetype=obj ch11_1.bc
-o ch11_1.cpu0.o
LLVM ERROR: Inline asm not supported by this streamer because we don't have
an asm parser for this target

Since we haven’t implemented the Cpu0 assembler, the error message shown above occurs. Cpu0 can translate LLVM IR into assembly and object files directly, but it cannot translate hand-written assembly instructions into an object file.

Chapter11_1/ includes the AsmParser implementation as follows:

lbdex/chapters/Chapter11_1/AsmParser/Cpu0AsmParser.cpp

//===-- Cpu0AsmParser.cpp - Parse Cpu0 assembly to MCInst instructions ----===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "Cpu0.h"
#if CH >= CH11_1

#include "MCTargetDesc/Cpu0MCExpr.h"
#include "MCTargetDesc/Cpu0MCTargetDesc.h"
#include "Cpu0RegisterInfo.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstBuilder.h"
#include "llvm/MC/MCParser/MCAsmLexer.h"
#include "llvm/MC/MCParser/MCParsedAsmOperand.h"
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/MCParser/MCAsmLexer.h"
#include "llvm/MC/MCParser/MCParsedAsmOperand.h"
#include "llvm/MC/MCValue.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/TargetRegistry.h"

using namespace llvm;

#define DEBUG_TYPE "cpu0-asm-parser"

namespace {
class Cpu0AssemblerOptions {
public:
  Cpu0AssemblerOptions():
    reorder(true), macro(true) {
  }

  bool isReorder() {return reorder;}
  void setReorder() {reorder = true;}
  void setNoreorder() {reorder = false;}

  bool isMacro() {return macro;}
  void setMacro() {macro = true;}
  void setNomacro() {macro = false;}

private:
  bool reorder;
  bool macro;
};
}

namespace {
class Cpu0AsmParser : public MCTargetAsmParser {
  MCAsmParser &Parser;
  Cpu0AssemblerOptions Options;


#define GET_ASSEMBLER_HEADER
#include "Cpu0GenAsmMatcher.inc"

  bool MatchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode,
                               OperandVector &Operands, MCStreamer &Out,
                               uint64_t &ErrorInfo,
                               bool MatchingInlineAsm) override;

  bool ParseRegister(unsigned &RegNo, SMLoc &StartLoc, SMLoc &EndLoc) override;

  OperandMatchResultTy tryParseRegister(unsigned &RegNo, SMLoc &StartLoc,
                                        SMLoc &EndLoc) override;

  bool ParseInstruction(ParseInstructionInfo &Info, StringRef Name,
                        SMLoc NameLoc, OperandVector &Operands) override;

  bool ParseDirective(AsmToken DirectiveID) override;

  OperandMatchResultTy parseMemOperand(OperandVector &);

  bool ParseOperand(OperandVector &Operands, StringRef Mnemonic);

  int tryParseRegister(StringRef Mnemonic);

  bool tryParseRegisterOperand(OperandVector &Operands,
                               StringRef Mnemonic);

  bool needsExpansion(MCInst &Inst);

  void expandInstruction(MCInst &Inst, SMLoc IDLoc,
                         SmallVectorImpl<MCInst> &Instructions);
  void expandLoadImm(MCInst &Inst, SMLoc IDLoc,
                     SmallVectorImpl<MCInst> &Instructions);
  void expandLoadAddressImm(MCInst &Inst, SMLoc IDLoc,
                            SmallVectorImpl<MCInst> &Instructions);
  void expandLoadAddressReg(MCInst &Inst, SMLoc IDLoc,
                            SmallVectorImpl<MCInst> &Instructions);
  bool reportParseError(StringRef ErrorMsg);

  bool parseMemOffset(const MCExpr *&Res);
  bool parseRelocOperand(const MCExpr *&Res);

  const MCExpr *evaluateRelocExpr(const MCExpr *Expr, StringRef RelocStr);

  bool parseDirectiveSet();

  bool parseSetAtDirective();
  bool parseSetNoAtDirective();
  bool parseSetMacroDirective();
  bool parseSetNoMacroDirective();
  bool parseSetReorderDirective();
  bool parseSetNoReorderDirective();

  int matchRegisterName(StringRef Symbol);

  int matchRegisterByNumber(unsigned RegNum, StringRef Mnemonic);

  unsigned getReg(int RC,int RegNo);

public:
  Cpu0AsmParser(const MCSubtargetInfo &sti, MCAsmParser &parser,
                const MCInstrInfo &MII, const MCTargetOptions &Options)
    : MCTargetAsmParser(Options, sti, MII), Parser(parser) {
    // Initialize the set of available features.
    setAvailableFeatures(ComputeAvailableFeatures(getSTI().getFeatureBits()));
  }

  MCAsmParser &getParser() const { return Parser; }
  MCAsmLexer &getLexer() const { return Parser.getLexer(); }

};
}

namespace {

/// Cpu0Operand - Instances of this class represent a parsed Cpu0 machine
/// instruction.
class Cpu0Operand : public MCParsedAsmOperand {

  enum KindTy {
    k_Immediate,
    k_Memory,
    k_Register,
    k_Token
  } Kind;

public:
  Cpu0Operand(KindTy K) : MCParsedAsmOperand(), Kind(K) {}

  struct Token {
    const char *Data;
    unsigned Length;
  };
  struct PhysRegOp {
    unsigned RegNum; /// Register Number
  };
  struct ImmOp {
    const MCExpr *Val;
  };
  struct MemOp {
    unsigned Base;
    const MCExpr *Off;
  };

  union {
    struct Token Tok;
    struct PhysRegOp Reg;
    struct ImmOp Imm;
    struct MemOp Mem;
  };

  SMLoc StartLoc, EndLoc;

public:
  void addRegOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "Invalid number of operands!");
    Inst.addOperand(MCOperand::createReg(getReg()));
  }

  void addExpr(MCInst &Inst, const MCExpr *Expr) const{
    // Add as immediate when possible.  Null MCExpr = 0.
    if (Expr == 0)
      Inst.addOperand(MCOperand::createImm(0));
    else if (const MCConstantExpr *CE = dyn_cast<MCConstantExpr>(Expr))
      Inst.addOperand(MCOperand::createImm(CE->getValue()));
    else
      Inst.addOperand(MCOperand::createExpr(Expr));
  }

  void addImmOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "Invalid number of operands!");
    const MCExpr *Expr = getImm();
    addExpr(Inst,Expr);
  }

  void addMemOperands(MCInst &Inst, unsigned N) const {
    assert(N == 2 && "Invalid number of operands!");

    Inst.addOperand(MCOperand::createReg(getMemBase()));

    const MCExpr *Expr = getMemOff();
    addExpr(Inst,Expr);
  }

  bool isReg() const override { return Kind == k_Register; }
  bool isImm() const override { return Kind == k_Immediate; }
  bool isToken() const override { return Kind == k_Token; }
  bool isMem() const override { return Kind == k_Memory; }

  StringRef getToken() const {
    assert(Kind == k_Token && "Invalid access!");
    return StringRef(Tok.Data, Tok.Length);
  }

  unsigned getReg() const override {
    assert((Kind == k_Register) && "Invalid access!");
    return Reg.RegNum;
  }

  const MCExpr *getImm() const {
    assert((Kind == k_Immediate) && "Invalid access!");
    return Imm.Val;
  }

  unsigned getMemBase() const {
    assert((Kind == k_Memory) && "Invalid access!");
    return Mem.Base;
  }

  const MCExpr *getMemOff() const {
    assert((Kind == k_Memory) && "Invalid access!");
    return Mem.Off;
  }

  static std::unique_ptr<Cpu0Operand> CreateToken(StringRef Str, SMLoc S) {
    auto Op = std::make_unique<Cpu0Operand>(k_Token);
    Op->Tok.Data = Str.data();
    Op->Tok.Length = Str.size();
    Op->StartLoc = S;
    Op->EndLoc = S;
    return Op;
  }

  /// Internal constructor for register kinds
  static std::unique_ptr<Cpu0Operand> CreateReg(unsigned RegNum, SMLoc S, 
                                                SMLoc E) {
    auto Op = std::make_unique<Cpu0Operand>(k_Register);
    Op->Reg.RegNum = RegNum;
    Op->StartLoc = S;
    Op->EndLoc = E;
    return Op;
  }

  static std::unique_ptr<Cpu0Operand> CreateImm(const MCExpr *Val, SMLoc S, SMLoc E) {
    auto Op = std::make_unique<Cpu0Operand>(k_Immediate);
    Op->Imm.Val = Val;
    Op->StartLoc = S;
    Op->EndLoc = E;
    return Op;
  }

  static std::unique_ptr<Cpu0Operand> CreateMem(unsigned Base, const MCExpr *Off,
                                 SMLoc S, SMLoc E) {
    auto Op = std::make_unique<Cpu0Operand>(k_Memory);
    Op->Mem.Base = Base;
    Op->Mem.Off = Off;
    Op->StartLoc = S;
    Op->EndLoc = E;
    return Op;
  }

  /// getStartLoc - Get the location of the first token of this operand.
  SMLoc getStartLoc() const override { return StartLoc; }
  /// getEndLoc - Get the location of the last token of this operand.
  SMLoc getEndLoc() const override { return EndLoc; }

  void print(raw_ostream &OS) const override {
    switch (Kind) {
    case k_Immediate:
      OS << "Imm<";
      OS << *Imm.Val;
      OS << ">";
      break;
    case k_Memory:
      OS << "Mem<";
      OS << Mem.Base;
      OS << ", ";
      OS << *Mem.Off;
      OS << ">";
      break;
    case k_Register:
      OS << "Register<" << Reg.RegNum << ">";
      break;
    case k_Token:
      OS << Tok.Data;
      break;
    }
  }
};
}

void printCpu0Operands(OperandVector &Operands) {
  for (size_t i = 0; i < Operands.size(); i++) {
    Cpu0Operand* op = static_cast<Cpu0Operand*>(&*Operands[i]);
    assert(op != nullptr);
    LLVM_DEBUG(dbgs() << "<" << *op << ">");
  }
  LLVM_DEBUG(dbgs() << "\n");
}

//@1 {
bool Cpu0AsmParser::needsExpansion(MCInst &Inst) {

  switch(Inst.getOpcode()) {
    case Cpu0::LoadImm32Reg:
    case Cpu0::LoadAddr32Imm:
    case Cpu0::LoadAddr32Reg:
      return true;
    default:
      return false;
  }
}

void Cpu0AsmParser::expandInstruction(MCInst &Inst, SMLoc IDLoc,
                        SmallVectorImpl<MCInst> &Instructions){
  switch(Inst.getOpcode()) {
    case Cpu0::LoadImm32Reg:
      return expandLoadImm(Inst, IDLoc, Instructions);
    case Cpu0::LoadAddr32Imm:
      return expandLoadAddressImm(Inst,IDLoc,Instructions);
    case Cpu0::LoadAddr32Reg:
      return expandLoadAddressReg(Inst,IDLoc,Instructions);
    }
}
//@1 }

void Cpu0AsmParser::expandLoadImm(MCInst &Inst, SMLoc IDLoc,
                                  SmallVectorImpl<MCInst> &Instructions){
  MCInst tmpInst;
  const MCOperand &ImmOp = Inst.getOperand(1);
  assert(ImmOp.isImm() && "expected immediate operand kind");
  const MCOperand &RegOp = Inst.getOperand(0);
  assert(RegOp.isReg() && "expected register operand kind");

  int ImmValue = ImmOp.getImm();
  tmpInst.setLoc(IDLoc);
  if ( 0 <= ImmValue && ImmValue <= 65535) {
    // for 0 <= j <= 65535.
    // li d,j => ori d,$zero,j
    tmpInst.setOpcode(Cpu0::ORi);
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(
              MCOperand::createReg(Cpu0::ZERO));
    tmpInst.addOperand(MCOperand::createImm(ImmValue));
    Instructions.push_back(tmpInst);
  } else if ( ImmValue < 0 && ImmValue >= -32768) {
    // for -32768 <= j < 0.
    // li d,j => addiu d,$zero,j
    tmpInst.setOpcode(Cpu0::ADDiu); //TODO:no ADDiu64 in td files?
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(
              MCOperand::createReg(Cpu0::ZERO));
    tmpInst.addOperand(MCOperand::createImm(ImmValue));
    Instructions.push_back(tmpInst);
  } else {
    // for any other value of j that is representable as a 32-bit integer.
    // li d,j => lui d,hi16(j)
    //           ori d,d,lo16(j)
    tmpInst.setOpcode(Cpu0::LUi);
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(MCOperand::createImm((ImmValue & 0xffff0000) >> 16));
    Instructions.push_back(tmpInst);
    tmpInst.clear();
    tmpInst.setOpcode(Cpu0::ORi);
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(MCOperand::createImm(ImmValue & 0xffff));
    tmpInst.setLoc(IDLoc);
    Instructions.push_back(tmpInst);
  }
}

void Cpu0AsmParser::expandLoadAddressReg(MCInst &Inst, SMLoc IDLoc,
                                         SmallVectorImpl<MCInst> &Instructions){
  MCInst tmpInst;
  const MCOperand &ImmOp = Inst.getOperand(2);
  assert(ImmOp.isImm() && "expected immediate operand kind");
  const MCOperand &SrcRegOp = Inst.getOperand(1);
  assert(SrcRegOp.isReg() && "expected register operand kind");
  const MCOperand &DstRegOp = Inst.getOperand(0);
  assert(DstRegOp.isReg() && "expected register operand kind");
  int ImmValue = ImmOp.getImm();
  if ( -32768 <= ImmValue && ImmValue <= 32767) {
    // for -32768 <= j < 32767.
    //la d,j(s) => addiu d,s,j
    tmpInst.setOpcode(Cpu0::ADDiu); //TODO:no ADDiu64 in td files?
    tmpInst.addOperand(MCOperand::createReg(DstRegOp.getReg()));
    tmpInst.addOperand(MCOperand::createReg(SrcRegOp.getReg()));
    tmpInst.addOperand(MCOperand::createImm(ImmValue));
    Instructions.push_back(tmpInst);
  } else {
    // for any other value of j that is representable as a 32-bit integer.
    // la d,j(s) => lui d,hi16(j)
    //              ori d,d,lo16(j)
    //              add d,d,s
    tmpInst.setOpcode(Cpu0::LUi);
    tmpInst.addOperand(MCOperand::createReg(DstRegOp.getReg()));
    tmpInst.addOperand(MCOperand::createImm((ImmValue & 0xffff0000) >> 16));
    Instructions.push_back(tmpInst);
    tmpInst.clear();
    tmpInst.setOpcode(Cpu0::ORi);
    tmpInst.addOperand(MCOperand::createReg(DstRegOp.getReg()));
    tmpInst.addOperand(MCOperand::createReg(DstRegOp.getReg()));
    tmpInst.addOperand(MCOperand::createImm(ImmValue & 0xffff));
    Instructions.push_back(tmpInst);
    tmpInst.clear();
    tmpInst.setOpcode(Cpu0::ADD);
    tmpInst.addOperand(MCOperand::createReg(DstRegOp.getReg()));
    tmpInst.addOperand(MCOperand::createReg(DstRegOp.getReg()));
    tmpInst.addOperand(MCOperand::createReg(SrcRegOp.getReg()));
    Instructions.push_back(tmpInst);
  }
}

void Cpu0AsmParser::expandLoadAddressImm(MCInst &Inst, SMLoc IDLoc,
                                         SmallVectorImpl<MCInst> &Instructions){
  MCInst tmpInst;
  const MCOperand &ImmOp = Inst.getOperand(1);
  assert(ImmOp.isImm() && "expected immediate operand kind");
  const MCOperand &RegOp = Inst.getOperand(0);
  assert(RegOp.isReg() && "expected register operand kind");
  int ImmValue = ImmOp.getImm();
  if ( -32768 <= ImmValue && ImmValue <= 32767) {
    // for -32768 <= j < 32767.
    //la d,j => addiu d,$zero,j
    tmpInst.setOpcode(Cpu0::ADDiu);
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(
              MCOperand::createReg(Cpu0::ZERO));
    tmpInst.addOperand(MCOperand::createImm(ImmValue));
    Instructions.push_back(tmpInst);
  } else {
    // for any other value of j that is representable as a 32-bit integer.
    // la d,j => lui d,hi16(j)
    //           ori d,d,lo16(j)
    tmpInst.setOpcode(Cpu0::LUi);
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(MCOperand::createImm((ImmValue & 0xffff0000) >> 16));
    Instructions.push_back(tmpInst);
    tmpInst.clear();
    tmpInst.setOpcode(Cpu0::ORi);
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(MCOperand::createReg(RegOp.getReg()));
    tmpInst.addOperand(MCOperand::createImm(ImmValue & 0xffff));
    Instructions.push_back(tmpInst);
  }
}

//@2 {
bool Cpu0AsmParser::MatchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode,
                                            OperandVector &Operands,
                                            MCStreamer &Out,
                                            uint64_t &ErrorInfo,
                                            bool MatchingInlineAsm) {
  printCpu0Operands(Operands);
  MCInst Inst;
  unsigned MatchResult = MatchInstructionImpl(Operands, Inst, ErrorInfo,
                                              MatchingInlineAsm);
  switch (MatchResult) {
  default: break;
  case Match_Success: {
    if (needsExpansion(Inst)) {
      SmallVector<MCInst, 4> Instructions;
      expandInstruction(Inst, IDLoc, Instructions);
      for(unsigned i =0; i < Instructions.size(); i++){
        Out.emitInstruction(Instructions[i], getSTI());
      }
    } else {
        Inst.setLoc(IDLoc);
        Out.emitInstruction(Inst, getSTI());
      }
    return false;
  }
//@2 }
  case Match_MissingFeature:
    Error(IDLoc, "instruction requires a CPU feature not currently enabled");
    return true;
  case Match_InvalidOperand: {
    SMLoc ErrorLoc = IDLoc;
    if (ErrorInfo != ~0U) {
      if (ErrorInfo >= Operands.size())
        return Error(IDLoc, "too few operands for instruction");

      ErrorLoc = ((Cpu0Operand &)*Operands[ErrorInfo]).getStartLoc();
      if (ErrorLoc == SMLoc()) ErrorLoc = IDLoc;
    }

    return Error(ErrorLoc, "invalid operand for instruction");
  }
  case Match_MnemonicFail:
    return Error(IDLoc, "invalid instruction");
  }
  return true;
}

int Cpu0AsmParser::matchRegisterName(StringRef Name) {

   int CC;
    CC = StringSwitch<unsigned>(Name)
      .Case("zero",  Cpu0::ZERO)
      .Case("at",  Cpu0::AT)
      .Case("v0",  Cpu0::V0)
      .Case("v1",  Cpu0::V1)
      .Case("a0",  Cpu0::A0)
      .Case("a1",  Cpu0::A1)
      .Case("t9",  Cpu0::T9)
      .Case("t0",  Cpu0::T0)
      .Case("t1",  Cpu0::T1)
      .Case("s0",  Cpu0::S0)
      .Case("s1",  Cpu0::S1)
      .Case("sw",  Cpu0::SW)
      .Case("gp",  Cpu0::GP)
      .Case("fp",  Cpu0::FP)
      .Case("sp",  Cpu0::SP)
      .Case("lr",  Cpu0::LR)
      .Case("pc",  Cpu0::PC)
      .Case("hi",  Cpu0::HI)
      .Case("lo",  Cpu0::LO)
      .Case("epc", Cpu0::EPC)
      .Default(-1);

  if (CC != -1)
    return CC;

  return -1;
}

unsigned Cpu0AsmParser::getReg(int RC,int RegNo) {
  return *(getContext().getRegisterInfo()->getRegClass(RC).begin() + RegNo);
}

int Cpu0AsmParser::matchRegisterByNumber(unsigned RegNum, StringRef Mnemonic) {
  if (RegNum > 15)
    return -1;

  return getReg(Cpu0::CPURegsRegClassID, RegNum);
}

int Cpu0AsmParser::tryParseRegister(StringRef Mnemonic) {
  const AsmToken &Tok = Parser.getTok();
  int RegNum = -1;

  if (Tok.is(AsmToken::Identifier)) {
    std::string lowerCase = Tok.getString().lower();
    RegNum = matchRegisterName(lowerCase);
  } else if (Tok.is(AsmToken::Integer))
    RegNum = matchRegisterByNumber(static_cast<unsigned>(Tok.getIntVal()),
                                   Mnemonic.lower());
    else
      return RegNum;  //error
  return RegNum;
}

bool Cpu0AsmParser::
  tryParseRegisterOperand(OperandVector &Operands,
                          StringRef Mnemonic){

  SMLoc S = Parser.getTok().getLoc();
  int RegNo = -1;

    RegNo = tryParseRegister(Mnemonic);
  if (RegNo == -1)
    return true;

  Operands.push_back(Cpu0Operand::CreateReg(RegNo, S,
      Parser.getTok().getLoc()));
  Parser.Lex(); // Eat register token.
  return false;
}

bool Cpu0AsmParser::ParseOperand(OperandVector &Operands,
                                 StringRef Mnemonic) {
  LLVM_DEBUG(dbgs() << "ParseOperand\n");
  // Check if the current operand has a custom associated parser, if so, try to
  // custom parse the operand, or fallback to the general approach.
  OperandMatchResultTy ResTy = MatchOperandParserImpl(Operands, Mnemonic);
  if (ResTy == MatchOperand_Success)
    return false;
  // If there wasn't a custom match, try the generic matcher below. Otherwise,
  // there was a match, but an error occurred, in which case, just return that
  // the operand parsing failed.
  if (ResTy == MatchOperand_ParseFail)
    return true;

  LLVM_DEBUG(dbgs() << ".. Generic Parser\n");

  switch (getLexer().getKind()) {
  default:
    Error(Parser.getTok().getLoc(), "unexpected token in operand");
    return true;
  case AsmToken::Dollar: {
    // parse register
    SMLoc S = Parser.getTok().getLoc();
    Parser.Lex(); // Eat dollar token.
    // parse register operand
    if (!tryParseRegisterOperand(Operands, Mnemonic)) {
      if (getLexer().is(AsmToken::LParen)) {
        // check if it is indexed addressing operand
        Operands.push_back(Cpu0Operand::CreateToken("(", S));
        Parser.Lex(); // eat parenthesis
        if (getLexer().isNot(AsmToken::Dollar))
          return true;

        Parser.Lex(); // eat dollar
        if (tryParseRegisterOperand(Operands, Mnemonic))
          return true;

        if (!getLexer().is(AsmToken::RParen))
          return true;

        S = Parser.getTok().getLoc();
        Operands.push_back(Cpu0Operand::CreateToken(")", S));
        Parser.Lex();
      }
      return false;
    }
    // maybe it is a symbol reference
    StringRef Identifier;
    if (Parser.parseIdentifier(Identifier))
      return true;

    SMLoc E = SMLoc::getFromPointer(Parser.getTok().getLoc().getPointer() - 1);

    MCSymbol *Sym = getContext().getOrCreateSymbol("$" + Identifier);

    // Otherwise create a symbol ref.
    const MCExpr *Res = MCSymbolRefExpr::create(Sym, MCSymbolRefExpr::VK_None,
                                                getContext());

    Operands.push_back(Cpu0Operand::CreateImm(Res, S, E));
    return false;
  }
  case AsmToken::Identifier:
  case AsmToken::LParen:
  case AsmToken::Minus:
  case AsmToken::Plus:
  case AsmToken::Integer:
  case AsmToken::String: {
     // quoted label names
    const MCExpr *IdVal;
    SMLoc S = Parser.getTok().getLoc();
    if (getParser().parseExpression(IdVal))
      return true;
    SMLoc E = SMLoc::getFromPointer(Parser.getTok().getLoc().getPointer() - 1);
    Operands.push_back(Cpu0Operand::CreateImm(IdVal, S, E));
    return false;
  }
  case AsmToken::Percent: {
    // it is a symbol reference or constant expression
    const MCExpr *IdVal;
    SMLoc S = Parser.getTok().getLoc(); // start location of the operand
    if (parseRelocOperand(IdVal))
      return true;

    SMLoc E = SMLoc::getFromPointer(Parser.getTok().getLoc().getPointer() - 1);

    Operands.push_back(Cpu0Operand::CreateImm(IdVal, S, E));
    return false;
  } // case AsmToken::Percent
  } // switch(getLexer().getKind())
  return true;
}

const MCExpr *Cpu0AsmParser::evaluateRelocExpr(const MCExpr *Expr,
                                               StringRef RelocStr) {
  Cpu0MCExpr::Cpu0ExprKind Kind =
      StringSwitch<Cpu0MCExpr::Cpu0ExprKind>(RelocStr)
          .Case("call16", Cpu0MCExpr::CEK_GOT_CALL)
          .Case("call_hi", Cpu0MCExpr::CEK_CALL_HI16)
          .Case("call_lo", Cpu0MCExpr::CEK_CALL_LO16)
          .Case("dtp_hi", Cpu0MCExpr::CEK_DTP_HI)
          .Case("dtp_lo", Cpu0MCExpr::CEK_DTP_LO)
          .Case("got", Cpu0MCExpr::CEK_GOT)
          .Case("got_hi", Cpu0MCExpr::CEK_GOT_HI16)
          .Case("got_lo", Cpu0MCExpr::CEK_GOT_LO16)
          .Case("gottprel", Cpu0MCExpr::CEK_GOTTPREL)
          .Case("gp_rel", Cpu0MCExpr::CEK_GPREL)
          .Case("hi", Cpu0MCExpr::CEK_ABS_HI)
          .Case("lo", Cpu0MCExpr::CEK_ABS_LO)
          .Case("tlsgd", Cpu0MCExpr::CEK_TLSGD)
          .Case("tlsldm", Cpu0MCExpr::CEK_TLSLDM)
          .Case("tp_hi", Cpu0MCExpr::CEK_TP_HI)
          .Case("tp_lo", Cpu0MCExpr::CEK_TP_LO)
          .Default(Cpu0MCExpr::CEK_None);

  assert(Kind != Cpu0MCExpr::CEK_None);
  return Cpu0MCExpr::create(Kind, Expr, getContext());
}

bool Cpu0AsmParser::parseRelocOperand(const MCExpr *&Res) {

  Parser.Lex(); // eat % token
  const AsmToken &Tok = Parser.getTok(); // get next token, operation
  if (Tok.isNot(AsmToken::Identifier))
    return true;

  std::string Str = Tok.getIdentifier().str();

  Parser.Lex(); // eat identifier
  // now make expression from the rest of the operand
  const MCExpr *IdVal;
  SMLoc EndLoc;

  if (getLexer().getKind() == AsmToken::LParen) {
    while (1) {
      Parser.Lex(); // eat '(' token
      if (getLexer().getKind() == AsmToken::Percent) {
        Parser.Lex(); // eat % token
        const AsmToken &nextTok = Parser.getTok();
        if (nextTok.isNot(AsmToken::Identifier))
          return true;
        Str += "(%";
        Str += nextTok.getIdentifier();
        Parser.Lex(); // eat identifier
        if (getLexer().getKind() != AsmToken::LParen)
          return true;
      } else
        break;
    }
    if (getParser().parseParenExpression(IdVal,EndLoc))
      return true;

    while (getLexer().getKind() == AsmToken::RParen)
      Parser.Lex(); // eat ')' token

  } else
    return true; // parenthesis must follow reloc operand

  Res = evaluateRelocExpr(IdVal, Str);
  return false;
}

bool Cpu0AsmParser::ParseRegister(unsigned &RegNo, SMLoc &StartLoc,
                                  SMLoc &EndLoc) {

  StartLoc = Parser.getTok().getLoc();
  RegNo = tryParseRegister("");
  EndLoc = Parser.getTok().getLoc();
  return (RegNo == (unsigned)-1);
}

OperandMatchResultTy Cpu0AsmParser::tryParseRegister(unsigned &RegNo,
                                                     SMLoc &StartLoc,
                                                     SMLoc &EndLoc) {
  StartLoc = Parser.getTok().getLoc();
  RegNo = tryParseRegister("");
  EndLoc = Parser.getTok().getLoc();
  return (RegNo == (unsigned)-1) ? MatchOperand_NoMatch
                                 : MatchOperand_Success;
}

bool Cpu0AsmParser::parseMemOffset(const MCExpr *&Res) {
  switch(getLexer().getKind()) {
  default:
    return true;
  case AsmToken::Integer:
  case AsmToken::Minus:
  case AsmToken::Plus:
    return (getParser().parseExpression(Res));
  case AsmToken::Percent:
    return parseRelocOperand(Res);
  case AsmToken::LParen:
    return false;  // it's probably assuming 0
  }
  return true;
}

// eg, 12($sp) or 12(la)
OperandMatchResultTy Cpu0AsmParser::parseMemOperand(
               OperandVector &Operands) {

  const MCExpr *IdVal = 0;
  SMLoc S;
  // first operand is the offset
  S = Parser.getTok().getLoc();

  if (parseMemOffset(IdVal))
    return MatchOperand_ParseFail;

  const AsmToken &Tok = Parser.getTok(); // get next token
  if (Tok.isNot(AsmToken::LParen)) {
    Cpu0Operand &Mnemonic = static_cast<Cpu0Operand &>(*Operands[0]);
    if (Mnemonic.getToken() == "la") {
      SMLoc E = SMLoc::getFromPointer(Parser.getTok().getLoc().getPointer()-1);
      Operands.push_back(Cpu0Operand::CreateImm(IdVal, S, E));
      return MatchOperand_Success;
    }
    Error(Parser.getTok().getLoc(), "'(' expected");
    return MatchOperand_ParseFail;
  }

  Parser.Lex(); // Eat '(' token.

  const AsmToken &Tok1 = Parser.getTok(); // get next token
  if (Tok1.is(AsmToken::Dollar)) {
    Parser.Lex(); // Eat '$' token.
    if (tryParseRegisterOperand(Operands,"")) {
      Error(Parser.getTok().getLoc(), "unexpected token in operand");
      return MatchOperand_ParseFail;
    }

  } else {
    Error(Parser.getTok().getLoc(), "unexpected token in operand");
    return MatchOperand_ParseFail;
  }

  const AsmToken &Tok2 = Parser.getTok(); // get next token
  if (Tok2.isNot(AsmToken::RParen)) {
    Error(Parser.getTok().getLoc(), "')' expected");
    return MatchOperand_ParseFail;
  }

  SMLoc E = SMLoc::getFromPointer(Parser.getTok().getLoc().getPointer() - 1);

  Parser.Lex(); // Eat ')' token.

  if (!IdVal)
    IdVal = MCConstantExpr::create(0, getContext());

  // Replace the register operand with the memory operand.
  std::unique_ptr<Cpu0Operand> op(
      static_cast<Cpu0Operand *>(Operands.back().release()));
  int RegNo = op->getReg();
  // remove register from operands
  Operands.pop_back();
  // and add memory operand
  Operands.push_back(Cpu0Operand::CreateMem(RegNo, IdVal, S, E));
  return MatchOperand_Success;
}

bool Cpu0AsmParser::
ParseInstruction(ParseInstructionInfo &Info, StringRef Name, SMLoc NameLoc,
                 OperandVector &Operands) {

  // Create the leading tokens for the mnemonic, split by '.' characters.
  size_t Start = 0, Next = Name.find('.');
  StringRef Mnemonic = Name.slice(Start, Next);
  // Refer to the explanation in source code of function DecodeJumpFR(...) in 
  // Cpu0Disassembler.cpp
  if (Mnemonic == "ret")
    Mnemonic = "jr";

  Operands.push_back(Cpu0Operand::CreateToken(Mnemonic, NameLoc));

  // Read the remaining operands.
  if (getLexer().isNot(AsmToken::EndOfStatement)) {
    // Read the first operand.
    if (ParseOperand(Operands, Name)) {
      SMLoc Loc = getLexer().getLoc();
      Parser.eatToEndOfStatement();
      return Error(Loc, "unexpected token in argument list");
    }

    while (getLexer().is(AsmToken::Comma) ) {
      Parser.Lex();  // Eat the comma.

      // Parse and remember the operand.
      if (ParseOperand(Operands, Name)) {
        SMLoc Loc = getLexer().getLoc();
        Parser.eatToEndOfStatement();
        return Error(Loc, "unexpected token in argument list");
      }
    }
  }

  if (getLexer().isNot(AsmToken::EndOfStatement)) {
    SMLoc Loc = getLexer().getLoc();
    Parser.eatToEndOfStatement();
    return Error(Loc, "unexpected token in argument list");
  }

  Parser.Lex(); // Consume the EndOfStatement
  return false;
}

bool Cpu0AsmParser::reportParseError(StringRef ErrorMsg) {
   SMLoc Loc = getLexer().getLoc();
   Parser.eatToEndOfStatement();
   return Error(Loc, ErrorMsg);
}

bool Cpu0AsmParser::parseSetReorderDirective() {
  Parser.Lex();
  // if this is not the end of the statement, report error
  if (getLexer().isNot(AsmToken::EndOfStatement)) {
    reportParseError("unexpected token in statement");
    return false;
  }
  Options.setReorder();
  Parser.Lex(); // Consume the EndOfStatement
  return false;
}

bool Cpu0AsmParser::parseSetNoReorderDirective() {
    Parser.Lex();
    // if this is not the end of the statement, report error
    if (getLexer().isNot(AsmToken::EndOfStatement)) {
      reportParseError("unexpected token in statement");
      return false;
    }
    Options.setNoreorder();
    Parser.Lex(); // Consume the EndOfStatement
    return false;
}

bool Cpu0AsmParser::parseSetMacroDirective() {
  Parser.Lex();
  // if this is not the end of the statement, report error
  if (getLexer().isNot(AsmToken::EndOfStatement)) {
    reportParseError("unexpected token in statement");
    return false;
  }
  Options.setMacro();
  Parser.Lex(); // Consume the EndOfStatement
  return false;
}

bool Cpu0AsmParser::parseSetNoMacroDirective() {
  Parser.Lex();
  // if this is not the end of the statement, report error
  if (getLexer().isNot(AsmToken::EndOfStatement)) {
    reportParseError("`noreorder' must be set before `nomacro'");
    return false;
  }
  if (Options.isReorder()) {
    reportParseError("`noreorder' must be set before `nomacro'");
    return false;
  }
  Options.setNomacro();
  Parser.Lex(); // Consume the EndOfStatement
  return false;
}
bool Cpu0AsmParser::parseDirectiveSet() {

  // get next token
  const AsmToken &Tok = Parser.getTok();

  if (Tok.getString() == "reorder") {
    return parseSetReorderDirective();
  } else if (Tok.getString() == "noreorder") {
    return parseSetNoReorderDirective();
  } else if (Tok.getString() == "macro") {
    return parseSetMacroDirective();
  } else if (Tok.getString() == "nomacro") {
    return parseSetNoMacroDirective();
  }
  return true;
}

bool Cpu0AsmParser::ParseDirective(AsmToken DirectiveID) {

  if (DirectiveID.getString() == ".ent") {
    // ignore this directive for now
    Parser.Lex();
    return false;
  }

  if (DirectiveID.getString() == ".end") {
    // ignore this directive for now
    Parser.Lex();
    return false;
  }

  if (DirectiveID.getString() == ".frame") {
    // ignore this directive for now
    Parser.eatToEndOfStatement();
    return false;
  }

  if (DirectiveID.getString() == ".set") {
    return parseDirectiveSet();
  }

  if (DirectiveID.getString() == ".fmask") {
    // ignore this directive for now
    Parser.eatToEndOfStatement();
    return false;
  }

  if (DirectiveID.getString() == ".mask") {
    // ignore this directive for now
    Parser.eatToEndOfStatement();
    return false;
  }

  if (DirectiveID.getString() == ".gpword") {
    // ignore this directive for now
    Parser.eatToEndOfStatement();
    return false;
  }

  return true;
}

extern "C" void LLVMInitializeCpu0AsmParser() {
  RegisterMCAsmParser<Cpu0AsmParser> X(TheCpu0Target);
  RegisterMCAsmParser<Cpu0AsmParser> Y(TheCpu0elTarget);
}

#define GET_REGISTER_MATCHER
#define GET_MATCHER_IMPLEMENTATION
#include "Cpu0GenAsmMatcher.inc"

#else // #if CH >= CH11_1
extern "C" void LLVMInitializeCpu0AsmParser() {}
#endif

lbdex/chapters/Chapter11_1/AsmParser/CMakeLists.txt

add_llvm_component_library(LLVMCpu0AsmParser
  Cpu0AsmParser.cpp

  LINK_COMPONENTS
  MC
  MCParser
  Cpu0Desc
  Cpu0Info
  Support

  ADD_TO_COMPONENT
  Cpu0
  )

The Cpu0AsmParser.cpp file contains around one thousand lines of code that handle assembly language parsing. With a little patience, you can understand it.

To enable building the files in the AsmParser directory, modify CMakeLists.txt as follows:

lbdex/chapters/Chapter11_1/CMakeLists.txt

set(LLVM_TARGET_DEFINITIONS Cpu0Asm.td)
tablegen(LLVM Cpu0GenAsmMatcher.inc -gen-asm-matcher)
  Cpu0AsmParser
add_subdirectory(AsmParser)

lbdex/chapters/Chapter11_1/Cpu0Asm.td

//===-- Cpu0Asm.td - Describe the Cpu0 Target Machine ------*- tablegen -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// This is the top level entry point for the Cpu0 target.
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
// Target-independent interfaces
//===----------------------------------------------------------------------===//

include "llvm/Target/Target.td"

//===----------------------------------------------------------------------===//
// Target-dependent interfaces
//===----------------------------------------------------------------------===//

include "Cpu0RegisterInfo.td"
include "Cpu0RegisterInfoGPROutForAsm.td"
include "Cpu0.td"

lbdex/chapters/Chapter11_1/Cpu0RegisterInfoGPROutForAsm.td


//===----------------------------------------------------------------------===//
// Register Classes
//===----------------------------------------------------------------------===//

def GPROut : RegisterClass<"Cpu0", [i32], 32, (add CPURegs)>;

The CMakeLists.txt modification shown above generates Cpu0GenAsmMatcher.inc, which is used by Cpu0AsmParser.cpp.

Cpu0Asm.td includes Cpu0RegisterInfoGPROutForAsm.td, which defines GPROut as mapping to CPURegs. Meanwhile, Cpu0Other.td includes Cpu0RegisterInfoGPROutForOther.td, which defines GPROut to map to CPURegs excluding SW.

Cpu0Other.td is used when translating LLVM IR to Cpu0 instructions. In this case, the SW register is reserved for storing CPU status and must not be allocated as a general-purpose register.

For example, if GPROut includes SW, compiling the C statement a = (b & c); might generate the instruction and $sw, $1, $2. This would overwrite the interrupt status in $sw.

However, when programming in assembly, instructions like andi $sw, $sw, 0xffdf are allowed. This kind of assembly is accepted, and the Cpu0 backend considers it safe. An assembler programmer may use andi $sw, $sw, 0xffdf to disable trace debug messages, and ori $sw, $sw, 0x0020 to enable them.

Additionally, interrupt bits can also be enabled or disabled using ori and andi instructions.

The EPC must be set to CPURegs as shown below. Otherwise, MatchInstructionImpl() in MatchAndEmitInstruction() will fail for the instruction asm(“mfc0 $pc, $epc”);.

lbdex/chapters/Chapter2/Cpu0RegisterInfo.td

def CPURegs : RegisterClass<"Cpu0", [i32], 32, (add
  ...
  , PC, EPC)>;

lbdex/chapters/Chapter11_1/Cpu0.td

def Cpu0AsmParser : AsmParser {
  let ShouldEmitMatchRegisterName = 0;
}

def Cpu0AsmParserVariant : AsmParserVariant {
  int Variant = 0;

  // Recognize hard coded registers.
  string RegisterPrefix = "$";
}
def Cpu0 : Target {
  ...
  let AssemblyParsers = [Cpu0AsmParser];
  let AssemblyParserVariants = [Cpu0AsmParserVariant];
}

lbdex/chapters/Chapter11_1/Cpu0InstrFormats.td

// Pseudo-instructions for alternate assembly syntax (never used by codegen).
// These are aliases that require C++ handling to convert to the target
// instruction, while InstAliases can be handled directly by tblgen.
class Cpu0AsmPseudoInst<dag outs, dag ins, string asmstr>:
  Cpu0Inst<outs, ins, asmstr, [], IIPseudo, Pseudo> {
  let isPseudo = 1;
  let Pattern = [];
}

lbdex/chapters/Chapter11_1/Cpu0InstrInfo.td

def Cpu0MemAsmOperand : AsmOperandClass {
  let Name = "Mem";
  let ParserMethod = "parseMemOperand";
}

// Address operand
def mem : Operand<i32> {
  ...
  let ParserMatchClass = Cpu0MemAsmOperand;
}
...
//===----------------------------------------------------------------------===//
// Pseudo Instruction definition
//===----------------------------------------------------------------------===//

let Predicates = [Ch11_1] in {
class LoadImm32< string instr_asm, Operand Od, RegisterClass RC> :
  Cpu0AsmPseudoInst<(outs RC:$ra), (ins Od:$imm32),
                     !strconcat(instr_asm, "\t$ra, $imm32")> ;
def LoadImm32Reg : LoadImm32<"li", shamt, GPROut>;

class LoadAddress<string instr_asm, Operand MemOpnd, RegisterClass RC> :
  Cpu0AsmPseudoInst<(outs RC:$ra), (ins MemOpnd:$addr),
                     !strconcat(instr_asm, "\t$ra, $addr")> ;
def LoadAddr32Reg : LoadAddress<"la", mem, GPROut>;

class LoadAddressImm<string instr_asm, Operand Od, RegisterClass RC> :
  Cpu0AsmPseudoInst<(outs RC:$ra), (ins Od:$imm32),
                     !strconcat(instr_asm, "\t$ra, $imm32")> ;
def LoadAddr32Imm : LoadAddressImm<"la", shamt, GPROut>;
}

In the Cpu0InstrInfo.td file, the directive let ParserMethod = “parseMemOperand” declares that the method parseMemOperand() will be used to handle the mem operand in Cpu0 instructions such as ld and st.

For example, in the instruction ld $2, 4($sp), the mem operand is 4($sp).

Together with the directive let ParserMatchClass = Cpu0MemAsmOperand;, LLVM will invoke the parseMemOperand() function in Cpu0AsmParser.cpp whenever it encounters a mem operand like 4($sp) in assembly code.

With the above let assignments, TableGen will generate the corresponding structure and functions in Cpu0GenAsmMatcher.inc.

build/lib/Target/Cpu0/Cpu0GenAsmMatcher.inc

  enum OperandMatchResultTy {
    MatchOperand_Success,    // operand matched successfully
    MatchOperand_NoMatch,    // operand did not match
    MatchOperand_ParseFail   // operand matched but had errors
  };
  OperandMatchResultTy MatchOperandParserImpl(
    OperandVector &Operands,
    StringRef Mnemonic);
  OperandMatchResultTy tryCustomParseOperand(
    OperandVector &Operands,
    unsigned MCK);
...
Cpu0AsmParser::OperandMatchResultTy Cpu0AsmParser::
tryCustomParseOperand(OperandVector &Operands,
            unsigned MCK) {

  switch(MCK) {
  case MCK_Mem:
    return parseMemOperand(Operands);
  default:
    return MatchOperand_NoMatch;
  }
  return MatchOperand_NoMatch;
}

Cpu0AsmParser::OperandMatchResultTy Cpu0AsmParser::
MatchOperandParserImpl(OperandVector &Operands,
             StringRef Mnemonic) {
  ...
}

/// MatchClassKind - The kinds of classes which participate in
/// instruction matching.
enum MatchClassKind {
  ...
  MCK_Mem, // user defined class 'Cpu0MemAsmOperand'
  ...
};

The three pseudo instruction definitions in Cpu0InstrInfo.td, such as LoadImm32Reg, are handled by Cpu0AsmParser.cpp as follows:

lbdex/chapters/Chapter11_1/AsmParser/Cpu0AsmParser.cpp

bool Cpu0AsmParser::needsExpansion(MCInst &Inst) {

  switch(Inst.getOpcode()) {
    case Cpu0::LoadImm32Reg:
    case Cpu0::LoadAddr32Imm:
    case Cpu0::LoadAddr32Reg:
      return true;
    default:
      return false;
  }
}

void Cpu0AsmParser::expandInstruction(MCInst &Inst, SMLoc IDLoc,
                        SmallVectorImpl<MCInst> &Instructions){
  switch(Inst.getOpcode()) {
    case Cpu0::LoadImm32Reg:
      return expandLoadImm(Inst, IDLoc, Instructions);
    case Cpu0::LoadAddr32Imm:
      return expandLoadAddressImm(Inst,IDLoc,Instructions);
    case Cpu0::LoadAddr32Reg:
      return expandLoadAddressReg(Inst,IDLoc,Instructions);
    }
}
bool Cpu0AsmParser::MatchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode,
                                            OperandVector &Operands,
                                            MCStreamer &Out,
                                            uint64_t &ErrorInfo,
                                            bool MatchingInlineAsm) {
  printCpu0Operands(Operands);
  MCInst Inst;
  unsigned MatchResult = MatchInstructionImpl(Operands, Inst, ErrorInfo,
                                              MatchingInlineAsm);
  switch (MatchResult) {
  default: break;
  case Match_Success: {
    if (needsExpansion(Inst)) {
      SmallVector<MCInst, 4> Instructions;
      expandInstruction(Inst, IDLoc, Instructions);
      for(unsigned i =0; i < Instructions.size(); i++){
        Out.emitInstruction(Instructions[i], getSTI());
      }
    } else {
        Inst.setLoc(IDLoc);
        Out.emitInstruction(Inst, getSTI());
      }
    return false;
  }
  ...
}

Finally, remember that the CPURegs list below must follow the order of register numbers, because the AsmParser uses this order when encoding register numbers.

lbdex/chapters/Chapter11_1/Cpu0RegisterInfo.td

//===----------------------------------------------------------------------===//
// The register string, such as "9" or "gp" will show on "llvm-objdump -d"
//@ All registers definition
let Namespace = "Cpu0" in {
  //@ General Purpose Registers
  def ZERO : Cpu0GPRReg<0,  "zero">, DwarfRegNum<[0]>;
  def AT   : Cpu0GPRReg<1,  "1">,    DwarfRegNum<[1]>;
  def V0   : Cpu0GPRReg<2,  "2">,    DwarfRegNum<[2]>;
  def V1   : Cpu0GPRReg<3,  "3">,    DwarfRegNum<[3]>;
  def A0   : Cpu0GPRReg<4,  "4">,    DwarfRegNum<[4]>;
  def A1   : Cpu0GPRReg<5,  "5">,    DwarfRegNum<[5]>;
  def T9   : Cpu0GPRReg<6,  "t9">,   DwarfRegNum<[6]>;
  def T0   : Cpu0GPRReg<7,  "7">,    DwarfRegNum<[7]>;
  def T1   : Cpu0GPRReg<8,  "8">,    DwarfRegNum<[8]>;
  def S0   : Cpu0GPRReg<9,  "9">,    DwarfRegNum<[9]>;
  def S1   : Cpu0GPRReg<10, "10">,   DwarfRegNum<[10]>;
  def GP   : Cpu0GPRReg<11, "gp">,   DwarfRegNum<[11]>;
  def FP   : Cpu0GPRReg<12, "fp">,   DwarfRegNum<[12]>;
  def SP   : Cpu0GPRReg<13, "sp">,   DwarfRegNum<[13]>;
  def LR   : Cpu0GPRReg<14, "lr">,   DwarfRegNum<[14]>;
  def SW   : Cpu0GPRReg<15, "sw">,   DwarfRegNum<[15]>;
//  def MAR  : Register< 16, "mar">,  DwarfRegNum<[16]>;
//  def MDR  : Register< 17, "mdr">,  DwarfRegNum<[17]>;

//#if CH >= CH4_1 1
  // Hi/Lo registers number and name
  def HI   : Cpu0Reg<0, "ac0">, DwarfRegNum<[18]>;
  def LO   : Cpu0Reg<0, "ac0">, DwarfRegNum<[19]>;
//#endif
  def PC   : Cpu0C0Reg<0, "pc">,  DwarfRegNum<[20]>;
  def EPC  : Cpu0C0Reg<1, "epc">, DwarfRegNum<[21]>;
}

//===----------------------------------------------------------------------===//
//@Register Classes
//===----------------------------------------------------------------------===//

def CPURegs : RegisterClass<"Cpu0", [i32], 32, (add
  // Reserved
  ZERO, AT, 
  // Return Values and Arguments
  V0, V1, A0, A1, 
  // Not preserved across procedure calls
  T9, T0, T1,
  // Callee save
  S0, S1,
  // Reserved
  GP, FP, 
  SP, LR, SW)>;

Run Chapter11_1/ with ch11_1.cpp to get the correct result as shown below:

JonathantekiiMac:input Jonathan$ /Users/Jonathan/llvm/test/build/bin/llc
-march=cpu0 -relocation-model=pic -filetype=obj ch11_1.bc -o
ch11_1.cpu0.o
JonathantekiiMac:input Jonathan$ /Users/Jonathan/llvm/test/build/bin/
llvm-objdump -d ch11_1.cpu0.o

ch11_1.cpu0.o:  file format ELF32-unknown

Disassembly of section .text:
.text:
       0:     01 2d 00 08                                     ld      $2, 8($sp)
       4:     02 0d 00 04                                     st      $zero, 4($sp)
       8:     09 30 00 00                                     addiu   $3, $zero, 0
       c:     13 31 20 00                                     add     $3, $1, $2
      10:     14 32 30 00                                     sub     $3, $2, $3
      ...

The instructions cmp and jeg display the $sw register explicitly in both assembly and disassembly. You can modify the code in the AsmParser and Disassembler (discussed in the last chapter) to hide $sw in these instructions —for example, displaying jeq 20 instead of jeq $sw, 20.

Both AsmParser and Cpu0AsmParser inherit from MCAsmParser as follows:

llvm/lib/MC/MCParser/AsmParser.cpp

class AsmParser : public MCAsmParser {
  ...
}
...

AsmParser will call the ParseInstruction() and MatchAndEmitInstruction() functions of Cpu0AsmParser as follows:

llvm/lib/MC/MCParser/AsmParser.cpp

bool AsmParser::parseStatement(ParseStatementInfo &Info) {
  ...
  // Directives start with "."
  if (IDVal[0] == '.' && IDVal != ".") {
    // First query the target-specific parser. It will return 'true' if it
    // isn't interested in this directive.
    if (!getTargetParser().ParseDirective(ID))
      return false;
    ...
  }
  ...
  bool HadError = getTargetParser().ParseInstruction(IInfo, OpcodeStr, IDLoc,
                                                     Info.ParsedOperands);
  ...
  // If parsing succeeded, match the instruction.
  if (!HadError) {
    unsigned ErrorInfo;
    getTargetParser().MatchAndEmitInstruction(IDLoc, Info.Opcode,
                                              Info.ParsedOperands, Out,
                                              ErrorInfo, ParsingInlineAsm);
  }
  ...
}

Assembler structure

Run llc with the option -debug-only=asm-matcher,cpu0-asm-parser to observe how the Cpu0 assembler works.

The AsmParser directory handles the translation from assembly to object files.

The assembling Data Flow Diagram (DFD) is shown in Fig. 52 and Fig. 53.

// Free usage license, author: Chung-Shu Chen 陳鍾樞
// dot -tPng asmDfd.gv -oasmDfd.png

digraph G {
  rankdir=LR;
  subgraph cluster_0 {
    style=filled;
//    label = "Assemble flow";
    node [style=filled,color=white]; user, asmParser, encoder, elfobj;
    user -> asmParser [ label = "cpu0 assembly" ];
    asmParser -> encoder [ label = "opcode ID & operand IDs" ];
    encoder -> elfobj [ label = "binary" ];
    color=lightgrey
  }
}

Fig. 52 Assembly flow

// Free usage license, author: Chung-Shu Chen 陳鍾樞
// dot -Tpng asmDfdEx.gv -oasmDfdEx.png

digraph G {
  rankdir=LR;
  subgraph cluster_2 {
    style=filled;
//    label = "Assemble flow, for instance: add $v1, $v0, $at";
    subgraph clusterA {
      label = "asmParser";
      node [style=filled,color=white]; ParseInstruction [label="ParseInstruction()"];
      node [style=filled,color=white]; MatchAndEmitInstruction [label="MatchAndEmitInstruction()"];
      ParseInstruction -> MatchAndEmitInstruction [ label = "Operands:\n (Cpu0::ADD, Cpu0::V1,\n Cpu0::AT, Cpu0::V0)" ];
    }
    subgraph clusterB {
      label = "encoder: Cpu0MCCodeEmitter.cpp";
      node [style=filled,color=white]; encodeInstruction [label="encodeInstruction()"];
    }
    MatchAndEmitInstruction -> encodeInstruction [ label = "Inst.Opcode=\nCpu0::ADD,\nInst.Operand[0] = V1,\nInst.Operand[1] = AT,\nInst.Operand[2] = V0" ];
    color=lightgrey
  }
}

Fig. 53 Assembly flow, for instance: add $v1, $v0, $at

Given an example assembly instruction add $v1, $v0, $at, the LLVM AsmParser core calls the backend ParseInstruction() function in Cpu0AsmParser.cpp when it detects that the first token at the beginning of the line is an identifier.

ParseInstruction() parses a single assembly instruction, creates the operand objects, and returns them to the LLVM AsmParser. Then, the AsmParser calls the backend MatchAndEmitInstruction() function to assign the opcode and operands to an MCInst. The encoder then encodes the binary instruction from the MCInst using information from Cpu0InstrInfo.td, which includes the binary values for the opcode ID and operand IDs of the instruction.

Below is a list of the key functions and data structures in MatchAndEmitInstruction() and encodeInstruction(), with explanations provided in comments beginning with ///.

llvm/build/lib/Target/Cpu0/Cpu0GenAsmMatcher.inc

enum InstructionConversionKind {
  Convert__Reg1_0__Reg1_1__Reg1_2,
  Convert__Reg1_0__Reg1_1__Imm1_2,
  ...
  CVT_NUM_SIGNATURES
};

} // end anonymous namespace

  struct MatchEntry {
    uint16_t Mnemonic;
    uint16_t Opcode;
    uint8_t ConvertFn;
    uint32_t RequiredFeatures;
    uint8_t Classes[3];
    StringRef getMnemonic() const {
      return StringRef(MnemonicTable + Mnemonic + 1,
                       MnemonicTable[Mnemonic]);
    }
  };

static const MatchEntry MatchTable0[] = {
  { 0 /* add */, Cpu0::ADD, Convert__Reg1_0__Reg1_1__Reg1_2, 0, { MCK_CPURegs, MCK_CPURegs, MCK_CPURegs }, },
  { 4 /* addiu */, Cpu0::ADDiu, Convert__Reg1_0__Reg1_1__Imm1_2, 0, { MCK_CPURegs, MCK_CPURegs, MCK_Imm }, },
  ...
};

unsigned Cpu0AsmParser::
MatchInstructionImpl(const OperandVector &Operands,
                     MCInst &Inst, uint64_t &ErrorInfo,
                     bool matchingInlineAsm, unsigned VariantID) {
  ...
  // Find the appropriate table for this asm variant.
  const MatchEntry *Start, *End;
  switch (VariantID) {
  default: llvm_unreachable("invalid variant!");
  case 0: Start = std::begin(MatchTable0); End = std::end(MatchTable0); break;
  }
  // Search the table.
  auto MnemonicRange = std::equal_range(Start, End, Mnemonic, LessOpcode());
  ...
  for (const MatchEntry *it = MnemonicRange.first, *ie = MnemonicRange.second;
       it != ie; ++it) {
    ...
    // We have selected a definite instruction, convert the parsed
    // operands into the appropriate MCInst.

    /// For instance ADD , V1, AT, V0
    /// MnemonicRange.first = &MatchTable0[0]
    /// MnemonicRange.second = &MatchTable0[1]
    /// it.ConvertFn = Convert__Reg1_0__Reg1_1__Reg1_2

    convertToMCInst(it->ConvertFn, Inst, it->Opcode, Operands);
    ...
  }
  ...
}

static const uint8_t ConversionTable[CVT_NUM_SIGNATURES][9] = {
  // Convert__Reg1_0__Reg1_1__Reg1_2
  { CVT_95_Reg, 1, CVT_95_Reg, 2, CVT_95_Reg, 3, CVT_Done },
  // Convert__Reg1_0__Reg1_1__Imm1_2
  { CVT_95_Reg, 1, CVT_95_Reg, 2, CVT_95_addImmOperands, 3, CVT_Done },
  ...
};

/// When kind = Convert__Reg1_0__Reg1_1__Reg1_2, ConversionTable[Kind] is equal to CVT_95_Reg
/// For Operands[1], Operands[2], Operands[3] do the following:
///   static_cast<Cpu0Operand&>(*Operands[OpIdx]).addRegOperands(Inst, 1);
/// Since p = 0, 2, 4, then OpIdx = 1, 2, 3 when OpIdx=*(p+1).
/// Since, Operands[1] = V1, Operands[2] = AT, Operands[3] = V0,
///   for "ADD , V1, AT, V0" which created by ParseInstruction().
/// Inst.Opcode = ADD, Inst.Operand[0] = V1, Inst.Operand[1] = AT,
///   Inst.Operand[2] = V0.
void Cpu0AsmParser::
convertToMCInst(unsigned Kind, MCInst &Inst, unsigned Opcode,
                const OperandVector &Operands) {
  assert(Kind < CVT_NUM_SIGNATURES && "Invalid signature!");
  const uint8_t *Converter = ConversionTable[Kind];
  unsigned OpIdx;
  Inst.setOpcode(Opcode);
  for (const uint8_t *p = Converter; *p; p+= 2) {
    OpIdx = *(p + 1);
    switch (*p) {
    default: llvm_unreachable("invalid conversion entry!");
    case CVT_Reg:
      static_cast<Cpu0Operand&>(*Operands[OpIdx]).addRegOperands(Inst, 1);
      break;
    ...
    }
  }
}

lbdex/chapters/Chapter11_1/AsmParser/Cpu0AsmParser.cpp

/// For "ADD , V1, AT, V0", ParseInstruction() set Operands[1].Reg.RegNum = V1,
///   Operands[2].Reg.RegNum = AT, ..., by Cpu0Operand::CreateReg(RegNo, S,
///   Parser.getTok().getLoc()) in calling ParseOperand().
/// So, after (*Operands[1..3]).addRegOperands(Inst, 1),
///   Inst.Opcode = ADD, Inst.Operand[0] = V1, Inst.Operand[1] = AT,
///   Inst.Operand[2] = V0.
class Cpu0Operand : public MCParsedAsmOperand {
  ...
  void addRegOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "Invalid number of operands!");
    Inst.addOperand(MCOperand::createReg(getReg()));
  }
  ...
  unsigned getReg() const override {
    assert((Kind == k_Register) && "Invalid access!");
    return Reg.RegNum;
  }
  ...
}

lbdex/chapters/Chapter11_1/MCTargetDesc/Cpu0MCCodeEmitter.cpp

void Cpu0MCCodeEmitter::
encodeInstruction(const MCInst &MI, raw_ostream &OS,
                  SmallVectorImpl<MCFixup> &Fixups,
                  const MCSubtargetInfo &STI) const
{
  uint32_t Binary = getBinaryCodeForInstr(MI, Fixups, STI);
  ...
  EmitInstruction(Binary, Size, OS);
}

llvm/build/lib/Target/Cpu0/Cpu0GenMCCodeEmitter.inc

uint64_t Cpu0MCCodeEmitter::getBinaryCodeForInstr(const MCInst &MI,
    SmallVectorImpl<MCFixup> &Fixups,
    const MCSubtargetInfo &STI) const {
  static const uint64_t InstBits[] = {
    ...
    UINT64_C(318767104),      // ADD  /// 318767104=0x13000000
    ...
  };
  ...

  const unsigned opcode = MI.getOpcode();
  ...
  switch (opcode) {
    case Cpu0::ADD:
    ...
      // op: ra
      op = getMachineOpValue(MI, MI.getOperand(0), Fixups, STI);
      Value |= (op & UINT64_C(15)) << 20;
      // op: rb
      op = getMachineOpValue(MI, MI.getOperand(1), Fixups, STI);
      Value |= (op & UINT64_C(15)) << 16;
      // op: rc
      op = getMachineOpValue(MI, MI.getOperand(2), Fixups, STI);
      Value |= (op & UINT64_C(15)) << 12;
      break;
    }
    ...
  }
  return Value;
}
// Free usage license, author: Chung-Shu Chen 陳鍾樞
// dot -Tpng asmDfdEx2.gv -oasmDfdEx2.png

digraph G {
  rankdir=LR;
  subgraph cluster_2 {
    style=filled;
//    label = "Data flow in MatchAndEmitInstruction(), for instance: add $v1, $v0, $at";
    subgraph clusterA {
      label = "MatchAndEmitInstruction()";
      node [style=filled,color=white]; MatchTable0 [label="Start = std::\nbegin(MatchTable0);\nEnd = std::end\n(MatchTable0);"];
      node [style=filled,color=white]; equal_range [label="std::equal_range(Start, End, \nMnemonic, LessOpcode());"];
      node [style=filled,color=white]; convertToMCInst [label="convertToMCInst\n(Kind, ...)"];
      MatchTable0 -> equal_range [ label = "Start,\nEnd" ];
      equal_range -> convertToMCInst [ label = "Kind=\nConvert__Reg1_0__\nReg1_1__Reg1_2" ];
    }
    color=lightgrey
  }
}

Fig. 54 Data flow in MatchAndEmitInstruction(), for instance: add $v1, $v0, $at”

// Free usage license, author: Chung-Shu Chen 陳鍾樞
// dot -Tpng asmDfdEx3.gv -oasmDfdEx3.png

digraph G {
  rankdir=LR;
  subgraph cluster_2 {
    style=filled;
//    label = "Data flow in and between MatchAndEmitInstruction() and encodeInstruction(), for instance: add $v1, $v0, $at";
    subgraph clusterA {
      label = "MatchAndEmitInstruction()";
      node [style=filled,color=white]; convertToMCInst [label="convertToMCInst()"];
    }
    subgraph clusterB {
      label = "encodeInstruction()";
      node [style=filled,color=white]; getBinaryCodeForInstr [label="getBinaryCodeForInstr()"];
      node [style=filled,color=white]; EmitInstruction [label="EmitInstruction()"];
      getBinaryCodeForInstr -> EmitInstruction [ label = "Binary" ];
    }
    convertToMCInst -> getBinaryCodeForInstr [ label = "Inst.Opcode = ADD,\nInst.Operand[0] = V1,\nInst.Operand[1] = AT,\nInst.Operand[2] = V0" ];
    color=lightgrey
  }
}

Fig. 55 Data flow between MatchAndEmitInstruction() and encodeInstruction(), for instance: add $v1, $v0, $at

MatchTable0 includes all possible combinations of opcodes and operand types.

Even if a user’s assembly instruction passes the syntax check in Cpu0AsmParser, MatchAndEmitInstruction() can still fail. For example, the instruction asm(“move $3, $2”); may succeed, but asm(“move $3, $2, $1”); will fail.

The flow of function calls for Cpu0AsmParser is shown in Fig. 56.

digraph G {
  rankdir=TB;
  "parseStatement()" -> "ParseInstruction()" [label="1. OpcodeStr"];
  "ParseInstruction()" -> "parseStatement()" [label="Info.ParseOperands"];
  "parseStatement()" -> "MatchAndEmitInstruction()" [label="2. Info.ParsedOperands"];
  "MatchAndEmitInstruction()" -> "MatchInstructionImpl()";
  "ParseInstruction()" -> "ParseOperand()" [label="OpcodeStr"];
  "ParseOperand()" -> "ParseInstruction()" [label="Operands"];
  "ParseOperand()" -> "MatchOperandParserImpl()";
  "MatchAndEmitInstruction()" -> "MCObjectStreamer::emitInstruction()" [label="MCInst"];
  subgraph clusterAsm {
    label = "/lib/MC/MCParser/AsmParser.cpp";
    "parseStatement()";
  }
  subgraph clusterCpu0Asm {
    label = "Cpu0AsmParser.cpp";
    "MatchAndEmitInstruction()";
    "ParseOperand()";
    "ParseInstruction()";
  }
  subgraph clusterAsmParserInc {
    label = "Cpu0GenAsmMatcher.inc";
    "MatchInstructionImpl()";
    "MatchOperandParserImpl()";
    "convertToMapAndConstraints()";
    "tryCustomParseOperand()";
    "MatchInstructionImpl()" -> "convertToMapAndConstraints()";
    "MatchOperandParserImpl()" -> "tryCustomParseOperand()";
  }
  subgraph clusterObj {
    label = "lib/MC/MCObjectStreamer.cpp";
    "MCObjectStreamer::emitInstruction()";
  }
}

Fig. 56 Flow of calling functions for Cpu0AsmParser.

  • After ParseInstruction() and MatchAndEmitInstruction() are called, an MCInst object is produced.

    • In MatchAndEmitInstruction(), the assembler calls MCObjectStreamer::emitInstruction() to encode the instruction into binary. See Fig. 32 for reference.

  • Run llc with the option -debug or -debug-only=asm-matcher,cpu0-asm-parser to display debug messages and trace the assembler flow, as shown below.

  • For Cpu0, only memory operands (used in L-type or J-type instructions) will trigger a call to tryCustomParseOperand().

input % ~/llvm/test/build/bin/clang -target mips-unknown-linux-gnu -c
ch11_1.cpp -emit-llvm -o ch11_1.bc
input % ~/llvm/test/build/bin/llc -march=cpu0 -relocation-model=pic
-filetype=obj -debug-only=asm-matcher,cpu0-asm-parser ch11_1.bc -o
ch11_1.cpu0.o

ParseOperand
.. Generic Parser
ParseOperand
<ld><Register<19>><Mem<9, 8>>
AsmMatcher: found 1 encodings with mnemonic 'ld'
Trying to match opcode LD
  Matching formal operand class MCK_CPURegs against actual operand at index 1
  (Register<19>): match success using generic matcher
  Matching formal operand class MCK_Mem against actual operand at index 2
  (Mem<9, 8>): match success using generic matcher
  Matching formal operand class InvalidMatchClass against actual operand at
  index 3: actual operand index out of range Opcode result: complete match,
  selecting this opcode

The other functions in Cpu0AsmParser are called in the following flow:

  • ParseDirective()parseDirectiveSet()parseSetReorderDirective(), parseSetNoReorderDirective(), parseSetMacroDirective(), parseSetNoMacroDirective()reportParseError()

  • ParseInstruction()ParseOperand()MatchOperandParserImpl() (in Cpu0GenAsmMatcher.inc) → tryCustomParseOperand() (in Cpu0GenAsmMatcher.inc) → parseMemOperand()parseMemOffset(), tryParseRegisterOperand()

  • MatchAndEmitInstruction()MatchInstructionImpl() (in Cpu0GenAsmMatcher.inc) → needsExpansion()expandInstruction()

  • parseMemOffset()parseRelocOperand()getVariantKind()

  • tryParseRegisterOperand()tryParseRegister()matchRegisterName()getReg(), matchRegisterByNumber()

  • expandInstruction()expandLoadImm(), expandLoadAddressImm(), expandLoadAddressReg()EmitInstruction() (in Cpu0AsmPrint.cpp)

Inline assembly

Run Chapter11_1 with ch11_2 will get the following error.

lbdex/input/ch11_2.cpp

extern "C" int printf(const char *format, ...);
int inlineasm_addu(void)
{
  int foo = 10;
  const int bar = 15;

//  call i32 asm sideeffect "addu $0,$1,$2", "=r,r,r"(i32 %1, i32 %2) #1, !srcloc !1
  __asm__ __volatile__("addu %0,%1,%2"
                       :"=r"(foo) // 5
                       :"r"(foo), "r"(bar)
                       );

  return foo;
}

int inlineasm_longlong(void)
{
  int a, b;
  const long long bar = 0x0000000500000006;
  int* p = (int*)&bar;
//  int* q = (p+1); // Do not set q here.

//  call i32 asm sideeffect "ld $0,$1", "=r,*m"(i32* %2) #2, !srcloc !2
  __asm__ __volatile__("ld %0,%1"
                       :"=r"(a) // 0x700070007000700b
                       :"m"(*p)
                       );
  int* q = (p+1); // Set q just before inline asm refer to avoid register clobbered. 
//  call i32 asm sideeffect "ld $0,$1", "=r,*m"(i32* %6) #2, !srcloc !3
  __asm__ __volatile__("ld %0,%1"
                       :"=r"(b) // 11
                       :"m"(*q)
//              Or use :"m"(*(p+1)) to avoid register clobbered. 
                       );

  return (a+b);
}

int inlineasm_constraint(void)
{
  int foo = 10;
  const int n_5 = -5;
  const int n5 = 5;
  const int n0 = 0;
  const unsigned int un5 = 5;
  const int n65536 = 0x10000;
  const int n_65531 = -65531;

//   call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,I"(i32 %1, i32 15) #1, !srcloc !2
  __asm__ __volatile__("addiu %0,%1,%2"
                       :"=r"(foo) // 15
                       :"r"(foo), "I"(n_5)
                       );

  __asm__ __volatile__("addiu %0,%1,%2"
                       :"=r"(foo) // 15
                       :"r"(foo), "J"(n0)
                       );

  __asm__ __volatile__("addiu %0,%1,%2"
                       :"=r"(foo) // 10
                       :"r"(foo), "K"(n5)
                       );

  __asm__ __volatile__("ori %0,%1,%2"
                       :"=r"(foo) // 10
                       :"r"(foo), "L"(n65536) // 0x10000 = 65536
                       );

  __asm__ __volatile__("addiu %0,%1,%2"
                       :"=r"(foo) // 15
                       :"r"(foo), "N"(n_65531)
                       );

  __asm__ __volatile__("addiu %0,%1,%2"
                       :"=r"(foo) // 10
                       :"r"(foo), "O"(n_5)
                       );

  __asm__ __volatile__("addiu %0,%1,%2"
                       :"=r"(foo) // 15
                       :"r"(foo), "P"(un5)
                       );

  return foo;
}

int inlineasm_arg(int u, int v)
{
  int w;

  __asm__ __volatile__("subu %0,%1,%2"
                       :"=r"(w)
                       :"r"(u), "r"(v)
                       );

  return w;
}

int g[3] = {1,2,3};

int inlineasm_global()
{
  int c, d;
  __asm__ __volatile__("ld %0,%1"
                       :"=r"(c) // c=3
                       :"m"(g[2])
                       );

  __asm__ __volatile__("addiu %0,%1,1"
                       :"=r"(d) // d=4
                       :"r"(c)
                       );

  return d;
}

#ifdef TESTSOFTFLOATLIB
// test_float() will call soft float library
int inlineasm_float()
{
  float a = 2.2;
  float b = 3.3;
  
  int c = (int)(a + b);

  int d;
  __asm__ __volatile__("addiu %0,%1,1"
                       :"=r"(d)
                       :"r"(c)
                       );

  return d;
}
#endif

int test_inlineasm()
{
  int a, b, c, d, e, f;

  a = inlineasm_addu(); // 25
  b = inlineasm_longlong(); // 11
  c = inlineasm_constraint(); // 15
  d = inlineasm_arg(1, 10); // -9
  e = inlineasm_arg(6, 3); // 3
  __asm__ __volatile__("addiu %0,%1,1"
                       :"=r"(f) // e=4
                       :"r"(e)
                       );

  return (a+b+c+d+e+f); // 25+11+15-9+3+4=49
}

1-160-129-73:input Jonathan$ ~/llvm/test/build/bin/llc
-march=cpu0 -relocation-model=static -filetype=asm ch11_2.bc -o -
  .section .mdebug.abi32
  .previous
  .file "ch11_2.bc"
error: couldn't allocate output register for constraint 'r'

The ch11_2.cpp file is an inline assembly example. Clang supports inline assembly similarly to GCC.

Inline assembly is used in C/C++ when a program needs to access a specific allocated register or memory location for a C/C++ variable. For example, the variable foo in ch11_2.cpp may be allocated by the compiler to register $2, $3, or another register.

Inline assembly helps bridge the gap between high-level languages and assembly language. See reference [2].

Chapter11_2 adds support for inline assembly as follows:

lbdex/chapters/Chapter11_2/Cpu0AsmPrinter.h

  bool PrintAsmOperand(const MachineInstr *MI, unsigned OpNo,
                       const char *ExtraCode, raw_ostream &O) override;
  bool PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNum,
                             const char *ExtraCode, raw_ostream &O) override;
  void printOperand(const MachineInstr *MI, int opNum, raw_ostream &O);

lbdex/chapters/Chapter11_2/Cpu0AsmPrinter.cpp

// Print out an operand for an inline asm expression.
bool Cpu0AsmPrinter::PrintAsmOperand(const MachineInstr *MI, unsigned OpNum,
                                     const char *ExtraCode, raw_ostream &O) {
  // Does this asm operand have a single letter operand modifier?
  if (ExtraCode && ExtraCode[0]) {
    if (ExtraCode[1] != 0) return true; // Unknown modifier.

    const MachineOperand &MO = MI->getOperand(OpNum);
    switch (ExtraCode[0]) {
    default:
      // See if this is a generic print operand
      return AsmPrinter::PrintAsmOperand(MI,OpNum, ExtraCode,O);
    case 'X': // hex const int
      if ((MO.getType()) != MachineOperand::MO_Immediate)
        return true;
      O << "0x" << StringRef(utohexstr(MO.getImm())).lower();
      return false;
    case 'x': // hex const int (low 16 bits)
      if ((MO.getType()) != MachineOperand::MO_Immediate)
        return true;
      O << "0x" << StringRef(utohexstr(MO.getImm() & 0xffff)).lower();
      return false;
    case 'd': // decimal const int
      if ((MO.getType()) != MachineOperand::MO_Immediate)
        return true;
      O << MO.getImm();
      return false;
    case 'm': // decimal const int minus 1
      if ((MO.getType()) != MachineOperand::MO_Immediate)
        return true;
      O << MO.getImm() - 1;
      return false;
    case 'z': {
      // $0 if zero, regular printing otherwise
      if (MO.getType() != MachineOperand::MO_Immediate)
        return true;
      int64_t Val = MO.getImm();
      if (Val)
        O << Val;
      else
        O << "$0";
      return false;
    }
    }
  }

  printOperand(MI, OpNum, O);
  return false;
}

bool Cpu0AsmPrinter::PrintAsmMemoryOperand(const MachineInstr *MI,
                                           unsigned OpNum,
                                           const char *ExtraCode,
                                           raw_ostream &O) {
  int Offset = 0;
  // Currently we are expecting either no ExtraCode or 'D'
  if (ExtraCode) {
    return true; // Unknown modifier.
  }

  const MachineOperand &MO = MI->getOperand(OpNum);
  assert(MO.isReg() && "unexpected inline asm memory operand");
  O << Offset << "($" << Cpu0InstPrinter::getRegisterName(MO.getReg()) << ")";

  return false;
}

void Cpu0AsmPrinter::printOperand(const MachineInstr *MI, int opNum,
                                  raw_ostream &O) {
  const MachineOperand &MO = MI->getOperand(opNum);
  bool closeP = false;

  if (MO.getTargetFlags())
    closeP = true;

  switch(MO.getTargetFlags()) {
  case Cpu0II::MO_GPREL:    O << "%gp_rel("; break;
  case Cpu0II::MO_GOT_CALL: O << "%call16("; break;
  case Cpu0II::MO_GOT:      O << "%got(";    break;
  case Cpu0II::MO_ABS_HI:   O << "%hi(";     break;
  case Cpu0II::MO_ABS_LO:   O << "%lo(";     break;
  case Cpu0II::MO_GOT_HI16: O << "%got_hi16("; break;
  case Cpu0II::MO_GOT_LO16: O << "%got_lo16("; break;
  }

  switch (MO.getType()) {
    case MachineOperand::MO_Register:
      O << '$'
        << StringRef(Cpu0InstPrinter::getRegisterName(MO.getReg())).lower();
      break;

    case MachineOperand::MO_Immediate:
      O << MO.getImm();
      break;

    case MachineOperand::MO_MachineBasicBlock:
      O << *MO.getMBB()->getSymbol();
      return;

    case MachineOperand::MO_GlobalAddress:
      O << *getSymbol(MO.getGlobal());
      break;

    case MachineOperand::MO_BlockAddress: {
      MCSymbol *BA = GetBlockAddressSymbol(MO.getBlockAddress());
      O << BA->getName();
      break;
    }

    case MachineOperand::MO_ExternalSymbol:
      O << *GetExternalSymbolSymbol(MO.getSymbolName());
      break;

    case MachineOperand::MO_JumpTableIndex:
      O << MAI->getPrivateGlobalPrefix() << "JTI" << getFunctionNumber()
        << '_' << MO.getIndex();
      break;

    case MachineOperand::MO_ConstantPoolIndex:
      O << MAI->getPrivateGlobalPrefix() << "CPI"
        << getFunctionNumber() << "_" << MO.getIndex();
      if (MO.getOffset())
        O << "+" << MO.getOffset();
      break;

    default:
      llvm_unreachable("<unknown operand type>");
  }

  if (closeP) O << ")";
}

lbdex/chapters/Chapter11_2/Cpu0InstrInfo.cpp

/// Return the number of bytes of code the specified instruction may be.
unsigned Cpu0InstrInfo::GetInstSizeInBytes(const MachineInstr &MI) const {
  case  TargetOpcode::INLINEASM: {       // Inline Asm: Variable size.
    const MachineFunction *MF = MI.getParent()->getParent();
    const char *AsmStr = MI.getOperand(0).getSymbolName();
    return getInlineAsmLength(AsmStr, *MF->getTarget().getMCAsmInfo());
  }

lbdex/chapters/Chapter11_2/Cpu0ISelDAGToDAG.h

  bool SelectInlineAsmMemoryOperand(const SDValue &Op,
                                    unsigned ConstraintID,
                                    std::vector<SDValue> &OutOps) override;

lbdex/chapters/Chapter11_2/Cpu0ISelDAGToDAG.cpp

// inlineasm begin
bool Cpu0DAGToDAGISel::
SelectInlineAsmMemoryOperand(const SDValue &Op, unsigned ConstraintID,
                             std::vector<SDValue> &OutOps) {
  // All memory constraints can at least accept raw pointers.
  switch(ConstraintID) {
  default:
    llvm_unreachable("Unexpected asm memory constraint");
  case InlineAsm::Constraint_m:
    OutOps.push_back(Op);
    return false;
  }
  return true;
}
// inlineasm end

lbdex/chapters/Chapter11_2/Cpu0ISelLowering.h

    // Inline asm support
    ConstraintType getConstraintType(StringRef Constraint) const override;

    /// Examine constraint string and operand type and determine a weight value.
    /// The operand object must already have been set up with the operand type.
    ConstraintWeight getSingleConstraintMatchWeight(
      AsmOperandInfo &info, const char *constraint) const override;

    /// This function parses registers that appear in inline-asm constraints.
    /// It returns pair (0, 0) on failure.
    std::pair<unsigned, const TargetRegisterClass *>
    parseRegForInlineAsmConstraint(const StringRef &C, MVT VT) const;

    std::pair<unsigned, const TargetRegisterClass *>
    getRegForInlineAsmConstraint(const TargetRegisterInfo *TRI,
                                 StringRef Constraint, MVT VT) const override;

    /// LowerAsmOperandForConstraint - Lower the specified operand into the Ops
    /// vector.  If it is invalid, don't add anything to Ops. If hasMemory is
    /// true it means one of the asm constraint of the inline asm instruction
    /// being processed is 'm'.
    void LowerAsmOperandForConstraint(SDValue Op,
                                      std::string &Constraint,
                                      std::vector<SDValue> &Ops,
                                      SelectionDAG &DAG) const override;

    bool isLegalAddressingMode(const DataLayout &DL, const AddrMode &AM,
                               Type *Ty, unsigned AS,
                               Instruction *I = nullptr) const override;

lbdex/chapters/Chapter11_2/Cpu0ISelLowering.cpp

//===----------------------------------------------------------------------===//
//                           Cpu0 Inline Assembly Support
//===----------------------------------------------------------------------===//

/// getConstraintType - Given a constraint letter, return the type of
/// constraint it is for this target.
Cpu0TargetLowering::ConstraintType 
Cpu0TargetLowering::getConstraintType(StringRef Constraint) const
{
  // Cpu0 specific constraints
  // GCC config/mips/constraints.md
  // 'c' : A register suitable for use in an indirect
  //       jump. This will always be $t9 for -mabicalls.
  if (Constraint.size() == 1) {
    switch (Constraint[0]) {
      default : break;
      case 'c':
        return C_RegisterClass;
      case 'R':
        return C_Memory;
    }
  }
  return TargetLowering::getConstraintType(Constraint);
}

/// Examine constraint type and operand type and determine a weight value.
/// This object must already have been set up with the operand type
/// and the current alternative constraint selected.
TargetLowering::ConstraintWeight
Cpu0TargetLowering::getSingleConstraintMatchWeight(
    AsmOperandInfo &info, const char *constraint) const {
  ConstraintWeight weight = CW_Invalid;
  Value *CallOperandVal = info.CallOperandVal;
    // If we don't have a value, we can't do a match,
    // but allow it at the lowest weight.
  if (!CallOperandVal)
    return CW_Default;
  Type *type = CallOperandVal->getType();
  // Look at the constraint type.
  switch (*constraint) {
  default:
    weight = TargetLowering::getSingleConstraintMatchWeight(info, constraint);
    break;
  case 'c': // $t9 for indirect jumps
    if (type->isIntegerTy())
      weight = CW_SpecificReg;
    break;
  case 'I': // signed 16 bit immediate
  case 'J': // integer zero
  case 'K': // unsigned 16 bit immediate
  case 'L': // signed 32 bit immediate where lower 16 bits are 0
  case 'N': // immediate in the range of -65535 to -1 (inclusive)
  case 'O': // signed 15 bit immediate (+- 16383)
  case 'P': // immediate in the range of 65535 to 1 (inclusive)
    if (isa<ConstantInt>(CallOperandVal))
      weight = CW_Constant;
    break;
  case 'R':
    weight = CW_Memory;
    break;
  }
  return weight;
}

/// This is a helper function to parse a physical register string and split it
/// into non-numeric and numeric parts (Prefix and Reg). The first boolean flag
/// that is returned indicates whether parsing was successful. The second flag
/// is true if the numeric part exists.
static std::pair<bool, bool>
parsePhysicalReg(const StringRef &C, std::string &Prefix,
                 unsigned long long &Reg) {
  if (C.front() != '{' || C.back() != '}')
    return std::make_pair(false, false);

  // Search for the first numeric character.
  StringRef::const_iterator I, B = C.begin() + 1, E = C.end() - 1;
  I = std::find_if(B, E, isdigit);

  Prefix.assign(B, I - B);

  // The second flag is set to false if no numeric characters were found.
  if (I == E)
    return std::make_pair(true, false);

  // Parse the numeric characters.
  return std::make_pair(!getAsUnsignedInteger(StringRef(I, E - I), 10, Reg),
                        true);
}

std::pair<unsigned, const TargetRegisterClass *> Cpu0TargetLowering::
parseRegForInlineAsmConstraint(const StringRef &C, MVT VT) const {
  const TargetRegisterClass *RC;
  std::string Prefix;
  unsigned long long Reg;

  std::pair<bool, bool> R = parsePhysicalReg(C, Prefix, Reg);

  if (!R.first)
    return std::make_pair(0U, nullptr);
  if (!R.second)
    return std::make_pair(0U, nullptr);

 // Parse $0-$15.
  assert(Prefix == "$");
  RC = getRegClassFor((VT == MVT::Other) ? MVT::i32 : VT);

  assert(Reg < RC->getNumRegs());
  return std::make_pair(*(RC->begin() + Reg), RC);
}

/// Given a register class constraint, like 'r', if this corresponds directly
/// to an LLVM register class, return a register of 0 and the register class
/// pointer.
std::pair<unsigned, const TargetRegisterClass *>
Cpu0TargetLowering::getRegForInlineAsmConstraint(const TargetRegisterInfo *TRI,
                                                 StringRef Constraint,
                                                 MVT VT) const
{
  if (Constraint.size() == 1) {
    switch (Constraint[0]) {
    case 'r':
      if (VT == MVT::i32 || VT == MVT::i16 || VT == MVT::i8) {
        return std::make_pair(0U, &Cpu0::CPURegsRegClass);
      }
      if (VT == MVT::i64)
        return std::make_pair(0U, &Cpu0::CPURegsRegClass);
      // This will generate an error message
      return std::make_pair(0u, static_cast<const TargetRegisterClass*>(0));
    case 'c': // register suitable for indirect jump
      if (VT == MVT::i32)
        return std::make_pair((unsigned)Cpu0::T9, &Cpu0::CPURegsRegClass);
      assert(0 && "Unexpected type.");
    }
  }

  std::pair<unsigned, const TargetRegisterClass *> R;
  R = parseRegForInlineAsmConstraint(Constraint, VT);

  if (R.second)
    return R;

  return TargetLowering::getRegForInlineAsmConstraint(TRI, Constraint, VT);
}

/// LowerAsmOperandForConstraint - Lower the specified operand into the Ops
/// vector.  If it is invalid, don't add anything to Ops.
void Cpu0TargetLowering::LowerAsmOperandForConstraint(SDValue Op,
                                                     std::string &Constraint,
                                                     std::vector<SDValue>&Ops,
                                                     SelectionDAG &DAG) const {
  SDLoc DL(Op);
  SDValue Result;

  // Only support length 1 constraints for now.
  if (Constraint.length() > 1) return;

  char ConstraintLetter = Constraint[0];
  switch (ConstraintLetter) {
  default: break; // This will fall through to the generic implementation
  case 'I': // Signed 16 bit constant
    // If this fails, the parent routine will give an error
    if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(Op)) {
      EVT Type = Op.getValueType();
      int64_t Val = C->getSExtValue();
      if (isInt<16>(Val)) {
        Result = DAG.getTargetConstant(Val, DL, Type);
        break;
      }
    }
    return;
  case 'J': // integer zero
    if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(Op)) {
      EVT Type = Op.getValueType();
      int64_t Val = C->getZExtValue();
      if (Val == 0) {
        Result = DAG.getTargetConstant(0, DL, Type);
        break;
      }
    }
    return;
  case 'K': // unsigned 16 bit immediate
    if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(Op)) {
      EVT Type = Op.getValueType();
      uint64_t Val = (uint64_t)C->getZExtValue();
      if (isUInt<16>(Val)) {
        Result = DAG.getTargetConstant(Val, DL, Type);
        break;
      }
    }
    return;
  case 'L': // signed 32 bit immediate where lower 16 bits are 0
    if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(Op)) {
      EVT Type = Op.getValueType();
      int64_t Val = C->getSExtValue();
      if ((isInt<32>(Val)) && ((Val & 0xffff) == 0)){
        Result = DAG.getTargetConstant(Val, DL, Type);
        break;
      }
    }
    return;
  case 'N': // immediate in the range of -65535 to -1 (inclusive)
    if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(Op)) {
      EVT Type = Op.getValueType();
      int64_t Val = C->getSExtValue();
      if ((Val >= -65535) && (Val <= -1)) {
        Result = DAG.getTargetConstant(Val, DL, Type);
        break;
      }
    }
    return;
  case 'O': // signed 15 bit immediate
    if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(Op)) {
      EVT Type = Op.getValueType();
      int64_t Val = C->getSExtValue();
      if ((isInt<15>(Val))) {
        Result = DAG.getTargetConstant(Val, DL, Type);
        break;
      }
    }
    return;
  case 'P': // immediate in the range of 1 to 65535 (inclusive)
    if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(Op)) {
      EVT Type = Op.getValueType();
      int64_t Val = C->getSExtValue();
      if ((Val <= 65535) && (Val >= 1)) {
        Result = DAG.getTargetConstant(Val, DL, Type);
        break;
      }
    }
    return;
  }

  if (Result.getNode()) {
    Ops.push_back(Result);
    return;
  }

  TargetLowering::LowerAsmOperandForConstraint(Op, Constraint, Ops, DAG);
}

bool Cpu0TargetLowering::isLegalAddressingMode(const DataLayout &DL,
                                               const AddrMode &AM, Type *Ty,
                                               unsigned AS, Instruction *I) const {
  // No global is ever allowed as a base.
  if (AM.BaseGV)
    return false;

  switch (AM.Scale) {
  case 0: // "r+i" or just "i", depending on HasBaseReg.
    break;
  case 1:
    if (!AM.HasBaseReg) // allow "r+i".
      break;
    return false; // disallow "r+r" or "r+r+i".
  default:
    return false;
  }

  return true;
}

Similar to the backend structure, the structure of inline assembly can also be organized by file name, as shown in the table titled “The structure of inline assembly”.

Table 38 inline assembly functions

File

Function

Cpu0ISelLowering.cpp

inline asm DAG node create

Cpu0ISelDAGToDAG.cpp

save OP code

Cpu0AsmPrinter.cpp,

inline asm instructions printing

Cpu0InstrInfo.cpp

Except for Cpu0ISelDAGToDAG.cpp, the other functions are the same as those used in the backend’s normal code compilation.

The inline assembly handling in Cpu0ISelLowering.cpp is explained after showing the result of running with ch11_2.cpp.

Cpu0ISelDAGToDAG.cpp simply saves the opcode in SelectInlineAsmMemoryOperand(). Since the opcode represents a Cpu0 inline assembly instruction, no further LLVM IR DAG translation is needed. The function just saves the opcode directly and returns false to notify the LLVM system that the Cpu0 backend has finished processing this inline assembly instruction.

Run Chapter11_2 with ch11_2.cpp to get the following result:

1-160-129-73:input Jonathan$ clang -target mips-unknown-linux-gnu -c
ch11_2.cpp -emit-llvm -o ch11_2.bc

1-160-129-73:input Jonathan$ ~/llvm/test/build/bin/
llvm-dis ch11_2.bc -o -
...
target triple = "mips-unknown-linux-gnu"

@g = global [3 x i32] [i32 1, i32 2, i32 3], align 4

; Function Attrs: nounwind
define i32 @_Z14inlineasm_adduv() #0 {
  %foo = alloca i32, align 4
  %bar = alloca i32, align 4
  store i32 10, i32* %foo, align 4
  store i32 15, i32* %bar, align 4
  %1 = load i32* %foo, align 4
  %2 = call i32 asm sideeffect "addu $0,$1,$2", "=r,r,r"(i32 %1, i32 15) #1,
  !srcloc !1
  store i32 %2, i32* %foo, align 4
  %3 = load i32* %foo, align 4
  ret i32 %3
}

; Function Attrs: nounwind
define i32 @_Z18inlineasm_longlongv() #0 {
  %a = alloca i32, align 4
  %b = alloca i32, align 4
  %bar = alloca i64, align 8
  %p = alloca i32*, align 4
  %q = alloca i32*, align 4
  store i64 21474836486, i64* %bar, align 8
  %1 = bitcast i64* %bar to i32*
  store i32* %1, i32** %p, align 4
  %2 = load i32** %p, align 4
  %3 = call i32 asm sideeffect "ld $0,$1", "=r,*m"(i32* %2) #1, !srcloc !2
  store i32 %3, i32* %a, align 4
  %4 = load i32** %p, align 4
  %5 = getelementptr inbounds i32* %4, i32 1
  store i32* %5, i32** %q, align 4
  %6 = load i32** %q, align 4
  %7 = call i32 asm sideeffect "ld $0,$1", "=r,*m"(i32* %6) #1, !srcloc !3
  store i32 %7, i32* %b, align 4
  %8 = load i32* %a, align 4
  %9 = load i32* %b, align 4
  %10 = add nsw i32 %8, %9
  ret i32 %10
}

; Function Attrs: nounwind
define i32 @_Z20inlineasm_constraintv() #0 {
  %foo = alloca i32, align 4
  %n_5 = alloca i32, align 4
  %n5 = alloca i32, align 4
  %n0 = alloca i32, align 4
  %un5 = alloca i32, align 4
  %n65536 = alloca i32, align 4
  %n_65531 = alloca i32, align 4
  store i32 10, i32* %foo, align 4
  store i32 -5, i32* %n_5, align 4
  store i32 5, i32* %n5, align 4
  store i32 0, i32* %n0, align 4
  store i32 5, i32* %un5, align 4
  store i32 65536, i32* %n65536, align 4
  store i32 -65531, i32* %n_65531, align 4
  %1 = load i32* %foo, align 4
  %2 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,I"(i32 %1, i32 -5) #1,
  !srcloc !4
  store i32 %2, i32* %foo, align 4
  %3 = load i32* %foo, align 4
  %4 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,J"(i32 %3, i32 0) #1,
  !srcloc !5
  store i32 %4, i32* %foo, align 4
  %5 = load i32* %foo, align 4
  %6 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,K"(i32 %5, i32 5) #1,
  !srcloc !6
  store i32 %6, i32* %foo, align 4
  %7 = load i32* %foo, align 4
  %8 = call i32 asm sideeffect "ori $0,$1,$2", "=r,r,L"(i32 %7, i32 65536) #1,
  !srcloc !7
  store i32 %8, i32* %foo, align 4
  %9 = load i32* %foo, align 4
  %10 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,N"(i32 %9, i32 -65531)
  #1, !srcloc !8
  store i32 %10, i32* %foo, align 4
  %11 = load i32* %foo, align 4
  %12 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,O"(i32 %11, i32 -5) #1,
  !srcloc !9
  store i32 %12, i32* %foo, align 4
  %13 = load i32* %foo, align 4
  %14 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,P"(i32 %13, i32 5) #1,
  !srcloc !10
  store i32 %14, i32* %foo, align 4
  %15 = load i32* %foo, align 4
  ret i32 %15
}

; Function Attrs: nounwind
define i32 @_Z13inlineasm_argii(i32 %u, i32 %v) #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  %w = alloca i32, align 4
  store i32 %u, i32* %1, align 4
  store i32 %v, i32* %2, align 4
  %3 = load i32* %1, align 4
  %4 = load i32* %2, align 4
  %5 = call i32 asm sideeffect "subu $0,$1,$2", "=r,r,r"(i32 %3, i32 %4) #1,
  !srcloc !11
  store i32 %5, i32* %w, align 4
  %6 = load i32* %w, align 4
  ret i32 %6
}

; Function Attrs: nounwind
define i32 @_Z16inlineasm_globalv() #0 {
  %c = alloca i32, align 4
  %d = alloca i32, align 4
  %1 = call i32 asm sideeffect "ld $0,$1", "=r,*m"(i32* getelementptr inbounds
  ([3 x i32]* @g, i32 0, i32 2)) #1, !srcloc !12
  store i32 %1, i32* %c, align 4
  %2 = load i32* %c, align 4
  %3 = call i32 asm sideeffect "addiu $0,$1,1", "=r,r"(i32 %2) #1, !srcloc !13
  store i32 %3, i32* %d, align 4
  %4 = load i32* %d, align 4
  ret i32 %4
}

; Function Attrs: nounwind
define i32 @_Z14test_inlineasmv() #0 {
  %a = alloca i32, align 4
  %b = alloca i32, align 4
  %c = alloca i32, align 4
  %d = alloca i32, align 4
  %e = alloca i32, align 4
  %f = alloca i32, align 4
  %g = alloca i32, align 4
  %1 = call i32 @_Z14inlineasm_adduv()
  store i32 %1, i32* %a, align 4
  %2 = call i32 @_Z18inlineasm_longlongv()
  store i32 %2, i32* %b, align 4
  %3 = call i32 @_Z20inlineasm_constraintv()
  store i32 %3, i32* %c, align 4
  %4 = call i32 @_Z13inlineasm_argii(i32 1, i32 10)
  store i32 %4, i32* %d, align 4
  %5 = call i32 @_Z13inlineasm_argii(i32 6, i32 3)
  store i32 %5, i32* %e, align 4
  %6 = load i32* %e, align 4
  %7 = call i32 asm sideeffect "addiu $0,$1,1", "=r,r"(i32 %6) #1, !srcloc !14
  store i32 %7, i32* %f, align 4
  %8 = call i32 @_Z16inlineasm_globalv()
  store i32 %8, i32* %g, align 4
  %9 = load i32* %a, align 4
  %10 = load i32* %b, align 4
  %11 = add nsw i32 %9, %10
  %12 = load i32* %c, align 4
  %13 = add nsw i32 %11, %12
  %14 = load i32* %d, align 4
  %15 = add nsw i32 %13, %14
  %16 = load i32* %e, align 4
  %17 = add nsw i32 %15, %16
  %18 = load i32* %f, align 4
  %19 = add nsw i32 %17, %18
  %20 = load i32* %g, align 4
  %21 = add nsw i32 %19, %20
  ret i32 %21
}
...
1-160-129-73:input Jonathan$ ~/llvm/test/build/bin/llc
  -march=cpu0 -relocation-model=static -filetype=asm ch11_2.bc -o -
  .section .mdebug.abi32
  .previous
  .file "ch11_2.bc"
  .text
  .globl  _Z14inlineasm_adduv
  .align  2
  .type _Z14inlineasm_adduv,@function
  .ent  _Z14inlineasm_adduv     # @_Z14inlineasm_adduv
_Z14inlineasm_adduv:
  .frame  $fp,16,$lr
  .mask   0x00001000,-4
  .set  noreorder
  .set  nomacro
# BB#0:
  addiu $sp, $sp, -16
  st  $fp, 12($sp)            # 4-byte Folded Spill
  addu  $fp, $sp, $zero
  addiu $2, $zero, 10
  st  $2, 8($fp)
  addiu $2, $zero, 15
  st  $2, 4($fp)
  ld  $3, 8($fp)
  #APP
  addu $2,$3,$2
  #NO_APP
  st  $2, 8($fp)
  addu  $sp, $fp, $zero
  ld  $fp, 12($sp)            # 4-byte Folded Reload
  addiu $sp, $sp, 16
  ret $lr
  nop
  .set  macro
  .set  reorder
  .end  _Z14inlineasm_adduv
$tmp3:
  .size _Z14inlineasm_adduv, ($tmp3)-_Z14inlineasm_adduv

  .globl  _Z18inlineasm_longlongv
  .align  2
  .type _Z18inlineasm_longlongv,@function
  .ent  _Z18inlineasm_longlongv # @_Z18inlineasm_longlongv
_Z18inlineasm_longlongv:
  .frame  $fp,32,$lr
  .mask   0x00001000,-4
  .set  noreorder
  .set  nomacro
# BB#0:
  addiu $sp, $sp, -32
  st  $fp, 28($sp)            # 4-byte Folded Spill
  addu  $fp, $sp, $zero
  addiu $2, $zero, 6
  st  $2, 12($fp)
  addiu $2, $zero, 5
  st  $2, 8($fp)
  addiu $2, $fp, 8
  st  $2, 4($fp)
  #APP
  ld $2,0($2)
  #NO_APP
  st  $2, 24($fp)
  ld  $2, 4($fp)
  addiu $2, $2, 4
  st  $2, 0($fp)
  #APP
  ld $2,0($2)
  #NO_APP
  st  $2, 20($fp)
  ld  $3, 24($fp)
  addu  $2, $3, $2
  addu  $sp, $fp, $zero
  ld  $fp, 28($sp)            # 4-byte Folded Reload
  addiu $sp, $sp, 32
  ret $lr
  .set  macro
  .set  reorder
  .end  _Z18inlineasm_longlongv
$tmp7:
  .size _Z18inlineasm_longlongv, ($tmp7)-_Z18inlineasm_longlongv

  .globl  _Z20inlineasm_constraintv
  .align  2
  .type _Z20inlineasm_constraintv,@function
  .ent  _Z20inlineasm_constraintv # @_Z20inlineasm_constraintv
_Z20inlineasm_constraintv:
  .frame  $fp,32,$lr
  .mask   0x00001000,-4
  .set  noreorder
  .set  nomacro
# BB#0:
  addiu $sp, $sp, -32
  st  $fp, 28($sp)            # 4-byte Folded Spill
  addu  $fp, $sp, $zero
  addiu $2, $zero, 10
  st  $2, 24($fp)
  addiu $2, $zero, -5
  st  $2, 20($fp)
  addiu $2, $zero, 5
  st  $2, 16($fp)
  addiu $3, $zero, 0
  st  $3, 12($fp)
  st  $2, 8($fp)
  lui $2, 1
  st  $2, 4($fp)
  lui $2, 65535
  ori $2, $2, 5
  st  $2, 0($fp)
  ld  $2, 24($fp)
  #APP
  addiu $2,$2,-5
  #NO_APP
  st  $2, 24($fp)
  #APP
  addiu $2,$2,0
  #NO_APP
  st  $2, 24($fp)
  #APP
  addiu $2,$2,5
  #NO_APP
  st  $2, 24($fp)
  #APP
  ori $2,$2,65536
  #NO_APP
  st  $2, 24($fp)
  #APP
  addiu $2,$2,-65531
  #NO_APP
  st  $2, 24($fp)
  #APP
  addiu $2,$2,-5
  #NO_APP
  st  $2, 24($fp)
  #APP
  addiu $2,$2,5
  #NO_APP
  st  $2, 24($fp)
  addu  $sp, $fp, $zero
  ld  $fp, 28($sp)            # 4-byte Folded Reload
  addiu $sp, $sp, 32
  ret $lr
  nop
  .set  macro
  .set  reorder
  .end  _Z20inlineasm_constraintv
$tmp11:
  .size _Z20inlineasm_constraintv, ($tmp11)-_Z20inlineasm_constraintv

  .globl  _Z13inlineasm_argii
  .align  2
  .type _Z13inlineasm_argii,@function
  .ent  _Z13inlineasm_argii     # @_Z13inlineasm_argii
_Z13inlineasm_argii:
  .frame  $fp,16,$lr
  .mask   0x00001000,-4
  .set  noreorder
  .set  nomacro
# BB#0:
  addiu $sp, $sp, -16
  st  $fp, 12($sp)            # 4-byte Folded Spill
  addu  $fp, $sp, $zero
  ld  $2, 16($fp)
  st  $2, 8($fp)
  ld  $2, 20($fp)
  st  $2, 4($fp)
  ld  $3, 8($fp)
  #APP
  subu $2,$3,$2
  #NO_APP
  st  $2, 0($fp)
  addu  $sp, $fp, $zero
  ld  $fp, 12($sp)            # 4-byte Folded Reload
  addiu $sp, $sp, 16
  ret $lr
  nop
  .set  macro
  .set  reorder
  .end  _Z13inlineasm_argii
$tmp15:
  .size _Z13inlineasm_argii, ($tmp15)-_Z13inlineasm_argii

  .globl  _Z16inlineasm_globalv
  .align  2
  .type _Z16inlineasm_globalv,@function
  .ent  _Z16inlineasm_globalv   # @_Z16inlineasm_globalv
_Z16inlineasm_globalv:
  .frame  $fp,16,$lr
  .mask   0x00001000,-4
  .set  noreorder
  .set  nomacro
# BB#0:
  addiu $sp, $sp, -16
  st  $fp, 12($sp)            # 4-byte Folded Spill
  addu  $fp, $sp, $zero
  lui $2, %hi(g)
  ori $2, $2, %lo(g)
  addiu $2, $2, 8
  #APP
  ld $2,0($2)
  #NO_APP
  st  $2, 8($fp)
  #APP
  addiu $2,$2,1
  #NO_APP
  st  $2, 4($fp)
  addu  $sp, $fp, $zero
  ld  $fp, 12($sp)            # 4-byte Folded Reload
  addiu $sp, $sp, 16
  ret $lr
  nop
  .set  macro
  .set  reorder
  .end  _Z16inlineasm_globalv
$tmp19:
  .size _Z16inlineasm_globalv, ($tmp19)-_Z16inlineasm_globalv

  .globl  _Z14test_inlineasmv
  .align  2
  .type _Z14test_inlineasmv,@function
  .ent  _Z14test_inlineasmv     # @_Z14test_inlineasmv
_Z14test_inlineasmv:
  .frame  $fp,48,$lr
  .mask   0x00005000,-4
  .set  noreorder
  .set  nomacro
# BB#0:
  addiu $sp, $sp, -48
  st  $lr, 44($sp)            # 4-byte Folded Spill
  st  $fp, 40($sp)            # 4-byte Folded Spill
  addu  $fp, $sp, $zero
  jsub  _Z14inlineasm_adduv
  nop
  st  $2, 36($fp)
  jsub  _Z18inlineasm_longlongv
  nop
  st  $2, 32($fp)
  jsub  _Z20inlineasm_constraintv
  nop
  st  $2, 28($fp)
  addiu $2, $zero, 10
  st  $2, 4($sp)
  addiu $2, $zero, 1
  st  $2, 0($sp)
  jsub  _Z13inlineasm_argii
  nop
  st  $2, 24($fp)
  addiu $2, $zero, 3
  st  $2, 4($sp)
  addiu $2, $zero, 6
  st  $2, 0($sp)
  jsub  _Z13inlineasm_argii
  nop
  st  $2, 20($fp)
  #APP
  addiu $2,$2,1
  #NO_APP
  st  $2, 16($fp)
  jsub  _Z16inlineasm_globalv
  nop
  st  $2, 12($fp)
  ld  $3, 32($fp)
  ld  $4, 36($fp)
  addu  $3, $4, $3
  ld  $4, 28($fp)
  addu  $3, $3, $4
  ld  $4, 24($fp)
  addu  $3, $3, $4
  ld  $4, 20($fp)
  addu  $3, $3, $4
  ld  $4, 16($fp)
  addu  $3, $3, $4
  addu  $2, $3, $2
  addu  $sp, $fp, $zero
  ld  $fp, 40($sp)            # 4-byte Folded Reload
  ld  $lr, 44($sp)            # 4-byte Folded Reload
  addiu $sp, $sp, 48
  ret $lr
  nop
  .set  macro
  .set  reorder
  .end  _Z14test_inlineasmv
$tmp23:
  .size _Z14test_inlineasmv, ($tmp23)-_Z14test_inlineasmv

  .type g,@object               # @g
  .data
  .globl  g
  .align  2
g:
  .4byte  1                       # 0x1
  .4byte  2                       # 0x2
  .4byte  3                       # 0x3
  .size g, 12

Clang first translates GCC-style inline assembly (__asm__) into LLVM IR Inline Assembler Expressions [3]. Then, during the llc register allocation stage, it replaces the SSA-form variable registers with physical registers.

In the above example, the functions LowerAsmOperandForConstraint() and getSingleConstraintMatchWeight() in Cpu0ISelLowering.cpp generate different ranges of constant operands based on constraint codes I, J, K, L, N, O, or P, and register operands based on r.

For instance, the following __asm__ generates the corresponding LLVM inline assembly immediately after it:

__asm__ __volatile__("addiu %0,%1,%2"
                     :"=r"(foo) // 15
                     :"r"(foo), "I"(n_5)
                     );
%2 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,I"(i32 %1, i32 -5) #0, !srcloc !1
__asm__ __volatile__("addiu %0,%1,%2"
                     :"=r"(foo) // 15
                     :"r"(foo), "N"(n_65531)
                     );
%10 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,N"(i32 %9, i32 -65531) #0, !srcloc !5
__asm__ __volatile__("addiu %0,%1,%2"
                     :"=r"(foo) // 15
                     :"r"(foo), "P"(un5)
                     );
%14 = call i32 asm sideeffect "addiu $0,$1,$2", "=r,r,P"(i32 %13, i32 5) #0, !srcloc !7

The r constraint in __asm__ will generate a register operand (e.g., %1) in LLVM IR inline assembly. The I constraint will generate a constant operand (e.g., -5) in LLVM IR inline assembly.

Note that LowerAsmOperandForConstraint() limits the range of positive and negative constant operand values to 16 bits, since the FL-type immediate operand in Cpu0 instructions is 16 bits.

As a result:

  • The range of N is from -65535 to -1.

  • The range of P is from 1 to 65535.

Any value outside of these ranges is treated as an error in LowerAsmOperandForConstraint(), due to the 16-bit limitation of the FL instruction format.