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
}
}](_images/graphviz-83981439aa0a8967cd12b525616450b7fd014f33.png)
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
}
}](_images/graphviz-4342827cb3d69f667c4384c52efb234b91270d15.png)
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
}
}](_images/graphviz-b8e184fba6bf28f4ebe4fd9c01b3b07dc4dc548d.png)
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
}
}](_images/graphviz-3f9ce164f96f1d09a1c4d5fe60e50c87dc9a85ad.png)
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()";
}
}](_images/graphviz-c118e4b7f2caa037ca1735b70e7d1ba6d2358591.png)
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”.
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.