Assembler¶
This chapter include the assembly programming support in Cpu0 backend.
When it comes to assembly language programming, there are two type of writting 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 is supported by LLVM AsmParser, and the second is inline assembly handler. With AsmParser and inline assembly support in Cpu0 backend, we can hand-code the assembly language in C/C++ file and translate it into obj (elf format).
AsmParser support¶
This section lists all the AsmParser code for Cpu0 backend with only a few explanation. Please refer here [1] for more AsmParser explanation.
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 havn’t implemented Cpu0 assembler, it has the error message as above. The Cpu0 can translate LLVM IR into assembly and obj directly, but it cannot translate hand-code assembly instructions into obj.
The Chapter11_1/ include 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 contains one thousand lines of code which do the assembly language parsing. You can understand it with a little patience only. To let files in directory of AsmParser be built, 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 adds code as above to generate Cpu0GenAsmMatcher.inc used by Cpu0AsmParser.cpp. Cpu0Asm.td include Cpu0RegisterInfoGPROutForAsm.td which define GPROut to CPURegs while Cpu0Other.td include Cpu0RegisterInfoGPROutForOther.td which define GPROut to CPURegs exclude SW. Cpu0Other.td is used when translating llvm IR to Cpu0 instruction. In latter case, the register SW is reserved for keeping the CPU status and not allowed to be allocated as a general purpose register. For example, if setting GPROut to include SW, when compile with C statement “a = (b & c);”, it may generate instruction “and $sw, $1, $2”, as a result that interrupt status in $sw will be destroyed. When programming in assembly, instruction “andi $sw, $sw, 0xffdf” is allowed. This assembly program is accepted and Cpu0 backend treats it safe since assembler programmer can disable trace debug message by “andi $sw, $sw, 0xffdf” and enable debug message by “ori $sw, $sw, 0x0020”. In addition, the interrupt bits can also be enabled or disabled by “ori” and “andi” instructions.
The EPC must set to CPURegs as follows, otherwise, MatchInstructionImpl() of MatchAndEmitInstruction() will return fail for “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>;
}
Above Cpu0InstrInfo.td declare the let ParserMethod = “parseMemOperand” and implement the parseMemOperand() in Cpu0AsmParser.cpp to handle the “mem” operand which used in Cpu0 instructions ld and st. For example, ld $2, 4($sp), the mem operand is 4($sp). Accompany with “let ParserMatchClass = Cpu0MemAsmOperand;”, LLVM will call parseMemOperand() of Cpu0AsmParser.cpp when it meets the assembly mem operand 4($sp). With above “let” assignment, TableGen will generate the following 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'
...
};
Above 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, remind that the CPURegs as below must follow the order of register number because AsmParser uses them when do register number encoding.
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 follows,
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 printed with explicit $sw displayed in assembly and disassembly. You can change the code in AsmParser and Dissassembly (the last chapter) to hide the $sw printed in these instructions (such as “jeq 20” rather than “jeq $sw, 20”).
Both AsmParser and Cpu0AsmParser inherited from MCAsmParser as follows,
llvm/lib/MC/MCParser/AsmParser.cpp
class AsmParser : public MCAsmParser {
...
}
...
AsmParser will call functions ParseInstruction() and MatchAndEmitInstruction() 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¶
Directory AsmParser handle the assembly to obj translation. The assembling Data Flow Diagram (DFD) as Fig. 52 and Fig. 53.
Given an example of assembly instruction “add $v1, $v0, $at”, llvm AsmParser kernel call backend ParseInstruction() of Cpu0AsmParser.cpp when it parses and recognises that the first token at the beginning of line is identifier. ParseInstruction() parses one assembly instruction, creates Operands and return to llvm AsmParser. Then AsmParser calls backend MatchAndEmitInstruction() to set Opcode and Operands to MCInst, then encoder can encode binary instruction from MCInst with the information come from Cpu0InstrInfo.td which includes binary value for Opcode ID and Operand IDs of the instruction.
List the key functions and data structure of MatchAndEmitInstruction() and encodeInstruction(), explaining in comments which begin 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;
}
MatchTable0 include all the possibile combinations of opcode and operands type. Even the assembly instruction of user input may pass Cpu0AsmParser in syntax check, the MatchAndEmitInstruction() still can be fail. For example, instruction “asm(“move $3, $2);” can pass but “asm(“move $3, $2, $1”);” will fail.
List flow of calling functions for Cpu0AsmParser as Fig. 56.
After ParseInstruction() and MatchAndEmitInstruction() will produce class MCInst.
In MatchAndEmitInstruction(), assembly will call MCObjectStreamer::emitInstruction() for encoding to binary, reference Fig. 32.
Run llc with option “-debug” or “-debug-only=asm-matcher” will show debug message to check the flow of assembler as follows,
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 ch11_1.bc -o ch11_1.cpu0.o
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 called as follows,
ParseDirective() -> parseDirectiveSet() -> parseSetReorderDirective(), parseSetNoReorderDirective(), parseSetMacroDirective(), parseSetNoMacroDirective() -> reportParseError()
ParseInstruction() -> ParseOperand() -> MatchOperandParserImpl() of Cpu0GenAsmMatcher.inc -> tryCustomParseOperand() of Cpu0GenAsmMatcher.inc -> parseMemOperand() -> parseMemOffset(), tryParseRegisterOperand()
MatchAndEmitInstruction() -> MatchInstructionImpl() of Cpu0GenAsmMatcher.inc, needsExpansion(), expandInstruction()
parseMemOffset() -> parseRelocOperand() -> getVariantKind()
tryParseRegisterOperand() -> tryParseRegister() -> matchRegisterName() -> getReg()), matchRegisterByNumber()
expandInstruction() -> expandLoadImm(), expandLoadAddressImm(), expandLoadAddressReg() -> EmitInstruction() of 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 is a inline assembly example. The clang supports inline assembly like gcc. The inline assembly used in C/C++ when program need to access the specific allocated register or memory for the C/C++ variable. For example, the variable foo of ch11_2.cpp may be allocated by compiler to register $2, $3 or any other register. The inline assembly fills the gap between high level language and assembly language. Reference here [2]. Chapter11_2 supports 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;
}
Same with backend structure, the structure of inline assembly can be divided by file name as Table: the structure of inline assembly.
File |
Function |
---|---|
Cpu0ISelLowering.cpp |
inline asm DAG node create |
Cpu0ISelDAGToDAG.cpp |
save OP code |
Cpu0AsmPrinter.cpp, |
inline asm instructions printing |
Cpu0InstrInfo.cpp |
Except Cpu0ISelDAGToDAG.cpp, the other functions are same with backend’s compile code. The Cpu0ISelLowering.cpp inline asm is explained after the result of running with ch11_2.cpp. Cpu0ISelDAGToDAG.cpp just save OP code in SelectInlineAsmMemoryOperand(). Since the the OP code is Cpu0 inline assembly instruction, no llvm IR DAG translation needed further, just save OP directly and return false to notify llvm system that Cpu0 backend has finished processing this inline assembly instruction.
Run Chapter11_2 with ch11_2.cpp will 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 translates gcc style inline assembly __asm__ into llvm IR Inline Assembler Expressions first [3], then replace the variable registers of SSA form to physical registers during llc register allocation stage. From above example, functions LowerAsmOperandForConstraint() and getSingleConstraintMatchWeight() of Cpu0ISelLowering.cpp will create different range of const operand by I, J, K, L, N, O, or P, and register operand by r . For instance, the following __asm__ will create the llvm asm 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 in __asm__ will generate register, %1, in llvm IR asm while I in __asm__ will generate const operand, -5, in llvm IR asm. Remind, the LowerAsmOperandForConstraint() limit the range of positive or negative const operand value to 16 bits since FL type immediate operand is 16 bits in Cpu0 instruction. So, the range of N is -65535 to -1 and the range of P is 65535 to 1. For any value out of the range, the code in LowerAsmOperandForConstraint() will treat it as error since FL instruction format has limitation of 16 bits.