[X86] support .nops directive

Add support of .nops on X86. This addresses llvm.org/PR45788.

Reviewed By: craig.topper

Differential Revision: https://reviews.llvm.org/D82826
This commit is contained in:
Jian Cai 2020-07-30 18:33:33 -07:00
parent 1e392fc445
commit c6334db577
13 changed files with 205 additions and 20 deletions

View File

@ -177,6 +177,10 @@ public:
///
virtual unsigned getMinimumNopSize() const { return 1; }
/// Returns the maximum size of a nop in bytes on this target.
///
virtual unsigned getMaximumNopSize() const { return 0; }
/// Write an (optimal) nop sequence of Count bytes to the given output. If the
/// target cannot generate such a sequence, it should return an error.
///

View File

@ -37,6 +37,7 @@ public:
FT_Data,
FT_CompactEncodedInst,
FT_Fill,
FT_Nops,
FT_Relaxable,
FT_Org,
FT_Dwarf,
@ -350,6 +351,31 @@ public:
}
};
class MCNopsFragment : public MCFragment {
/// The number of bytes to insert.
int64_t Size;
/// Maximum number of bytes allowed in each NOP instruction.
int64_t ControlledNopLength;
/// Source location of the directive that this fragment was created for.
SMLoc Loc;
public:
MCNopsFragment(int64_t NumBytes, int64_t ControlledNopLength, SMLoc L,
MCSection *Sec = nullptr)
: MCFragment(FT_Nops, false, Sec), Size(NumBytes),
ControlledNopLength(ControlledNopLength), Loc(L) {}
int64_t getNumBytes() const { return Size; }
int64_t getControlledNopLength() const { return ControlledNopLength; }
SMLoc getLoc() const { return Loc; }
static bool classof(const MCFragment *F) {
return F->getKind() == MCFragment::FT_Nops;
}
};
class MCOrgFragment : public MCFragment {
/// Value to use for filling bytes.
int8_t Value;

View File

@ -179,6 +179,8 @@ public:
SMLoc Loc = SMLoc()) override;
void emitFill(const MCExpr &NumValues, int64_t Size, int64_t Expr,
SMLoc Loc = SMLoc()) override;
void emitNops(int64_t NumBytes, int64_t ControlledNopLength,
SMLoc Loc) override;
void emitFileDirective(StringRef Filename) override;
void emitAddrsig() override;

View File

@ -777,6 +777,9 @@ public:
virtual void emitFill(const MCExpr &NumValues, int64_t Size, int64_t Expr,
SMLoc Loc = SMLoc());
virtual void emitNops(int64_t NumBytes, int64_t ControlledNopLength,
SMLoc Loc);
/// Emit NumBytes worth of zeros.
/// This function properly handles data in virtual sections.
void emitZeros(uint64_t NumBytes);

View File

@ -62,8 +62,8 @@ STATISTIC(EmittedAlignFragments,
"Number of emitted assembler fragments - align");
STATISTIC(EmittedFillFragments,
"Number of emitted assembler fragments - fill");
STATISTIC(EmittedOrgFragments,
"Number of emitted assembler fragments - org");
STATISTIC(EmittedNopsFragments, "Number of emitted assembler fragments - nops");
STATISTIC(EmittedOrgFragments, "Number of emitted assembler fragments - org");
STATISTIC(evaluateFixup, "Number of evaluated fixups");
STATISTIC(FragmentLayouts, "Number of fragment layouts");
STATISTIC(ObjectBytes, "Number of emitted object file bytes");
@ -312,6 +312,9 @@ uint64_t MCAssembler::computeFragmentSize(const MCAsmLayout &Layout,
return Size;
}
case MCFragment::FT_Nops:
return cast<MCNopsFragment>(F).getNumBytes();
case MCFragment::FT_LEB:
return cast<MCLEBFragment>(F).getContents().size();
@ -613,6 +616,45 @@ static void writeFragment(raw_ostream &OS, const MCAssembler &Asm,
break;
}
case MCFragment::FT_Nops: {
++stats::EmittedNopsFragments;
const MCNopsFragment &NF = cast<MCNopsFragment>(F);
int64_t NumBytes = NF.getNumBytes();
int64_t ControlledNopLength = NF.getControlledNopLength();
int64_t MaximumNopLength = Asm.getBackend().getMaximumNopSize();
assert(NumBytes > 0 && "Expected positive NOPs fragment size");
assert(ControlledNopLength >= 0 && "Expected non-negative NOP size");
if (ControlledNopLength > MaximumNopLength) {
Asm.getContext().reportError(NF.getLoc(),
"illegal NOP size " +
std::to_string(ControlledNopLength) +
". (expected within [0, " +
std::to_string(MaximumNopLength) + "])");
// Clamp the NOP length as reportError does not stop the execution
// immediately.
ControlledNopLength = MaximumNopLength;
}
// Use maximum value if the size of each NOP is not specified
if (!ControlledNopLength)
ControlledNopLength = MaximumNopLength;
while (NumBytes) {
uint64_t NumBytesToEmit =
(uint64_t)std::min(NumBytes, ControlledNopLength);
assert(NumBytesToEmit && "try to emit empty NOP instruction");
if (!Asm.getBackend().writeNopData(OS, NumBytesToEmit)) {
report_fatal_error("unable to write nop sequence of the remaining " +
Twine(NumBytesToEmit) + " bytes");
break;
}
NumBytes -= NumBytesToEmit;
}
break;
}
case MCFragment::FT_LEB: {
const MCLEBFragment &LF = cast<MCLEBFragment>(F);
OS << LF.getContents();

View File

@ -279,6 +279,9 @@ void MCFragment::destroy() {
case FT_Fill:
delete cast<MCFillFragment>(this);
return;
case FT_Nops:
delete cast<MCNopsFragment>(this);
return;
case FT_Relaxable:
delete cast<MCRelaxableFragment>(this);
return;
@ -336,6 +339,9 @@ LLVM_DUMP_METHOD void MCFragment::dump() const {
case MCFragment::FT_CompactEncodedInst:
OS << "MCCompactEncodedInstFragment"; break;
case MCFragment::FT_Fill: OS << "MCFillFragment"; break;
case MCFragment::FT_Nops:
OS << "MCFNopsFragment";
break;
case MCFragment::FT_Relaxable: OS << "MCRelaxableFragment"; break;
case MCFragment::FT_Org: OS << "MCOrgFragment"; break;
case MCFragment::FT_Dwarf: OS << "MCDwarfFragment"; break;
@ -408,6 +414,12 @@ LLVM_DUMP_METHOD void MCFragment::dump() const {
<< " NumValues:" << FF->getNumValues();
break;
}
case MCFragment::FT_Nops: {
const auto *NF = cast<MCNopsFragment>(this);
OS << " NumBytes:" << NF->getNumBytes()
<< " ControlledNopLength:" << NF->getControlledNopLength();
break;
}
case MCFragment::FT_Relaxable: {
const auto *F = cast<MCRelaxableFragment>(this);
OS << "\n ";

View File

@ -819,6 +819,16 @@ void MCObjectStreamer::emitFill(const MCExpr &NumValues, int64_t Size,
insert(new MCFillFragment(Expr, Size, NumValues, Loc));
}
void MCObjectStreamer::emitNops(int64_t NumBytes, int64_t ControlledNopLength,
SMLoc Loc) {
// Emit an NOP fragment.
MCDataFragment *DF = getOrCreateDataFragment();
flushPendingLabels(DF, DF->getContents().size());
assert(getCurrentSectionOnly() && "need a section");
insert(new MCNopsFragment(NumBytes, ControlledNopLength, Loc));
}
void MCObjectStreamer::emitFileDirective(StringRef Filename) {
getAssembler().addFileName(Filename);
}

View File

@ -202,6 +202,9 @@ void MCStreamer::emitFill(uint64_t NumBytes, uint8_t FillValue) {
emitFill(*MCConstantExpr::create(NumBytes, getContext()), FillValue);
}
void llvm::MCStreamer::emitNops(int64_t NumBytes, int64_t ControlledNopLen,
llvm::SMLoc) {}
/// The implementation in this class just redirects to emitFill.
void MCStreamer::emitZeros(uint64_t NumBytes) { emitFill(NumBytes, 0); }

View File

@ -934,6 +934,7 @@ private:
OperandVector &Operands);
bool parseDirectiveArch();
bool parseDirectiveNops(SMLoc L);
bool parseDirectiveEven(SMLoc L);
bool ParseDirectiveCode(StringRef IDVal, SMLoc L);
@ -4037,7 +4038,9 @@ bool X86AsmParser::ParseDirective(AsmToken DirectiveID) {
"a '%' prefix in .intel_syntax");
}
return false;
} else if (IDVal == ".even")
} else if (IDVal == ".nops")
return parseDirectiveNops(DirectiveID.getLoc());
else if (IDVal == ".even")
return parseDirectiveEven(DirectiveID.getLoc());
else if (IDVal == ".cv_fpo_proc")
return parseDirectiveFPOProc(DirectiveID.getLoc());
@ -4073,6 +4076,42 @@ bool X86AsmParser::parseDirectiveArch() {
return false;
}
/// parseDirectiveNops
/// ::= .nops size[, control]
bool X86AsmParser::parseDirectiveNops(SMLoc L) {
int64_t NumBytes = 0, Control = 0;
SMLoc NumBytesLoc, ControlLoc;
const MCSubtargetInfo STI = getSTI();
NumBytesLoc = getTok().getLoc();
if (getParser().checkForValidSection() ||
getParser().parseAbsoluteExpression(NumBytes))
return true;
if (parseOptionalToken(AsmToken::Comma)) {
ControlLoc = getTok().getLoc();
if (getParser().parseAbsoluteExpression(Control))
return true;
}
if (getParser().parseToken(AsmToken::EndOfStatement,
"unexpected token in '.nops' directive"))
return true;
if (NumBytes <= 0) {
Error(NumBytesLoc, "'.nops' directive with non-positive size");
return false;
}
if (Control < 0) {
Error(ControlLoc, "'.nops' directive with negative NOP size");
return false;
}
/// Emit nops
getParser().getStreamer().emitNops(NumBytes, Control, L);
return false;
}
/// parseDirectiveEven
/// ::= .even
bool X86AsmParser::parseDirectiveEven(SMLoc L) {

View File

@ -207,6 +207,8 @@ public:
void finishLayout(MCAssembler const &Asm, MCAsmLayout &Layout) const override;
unsigned getMaximumNopSize() const override;
bool writeNopData(raw_ostream &OS, uint64_t Count) const override;
};
} // end anonymous namespace
@ -1067,6 +1069,21 @@ void X86AsmBackend::finishLayout(MCAssembler const &Asm,
}
}
unsigned X86AsmBackend::getMaximumNopSize() const {
if (!STI.hasFeature(X86::FeatureNOPL) && !STI.hasFeature(X86::Mode64Bit))
return 1;
if (STI.getFeatureBits()[X86::FeatureFast7ByteNOP])
return 7;
if (STI.getFeatureBits()[X86::FeatureFast15ByteNOP])
return 15;
if (STI.getFeatureBits()[X86::FeatureFast11ByteNOP])
return 11;
// FIXME: handle 32-bit mode
// 15-bytes is the longest single NOP instruction, but 10-bytes is
// commonly the longest that can be efficiently decoded.
return 10;
}
/// Write a sequence of optimal nops to the output, covering \p Count
/// bytes.
/// \return - true on success, false on failure
@ -1094,23 +1111,7 @@ bool X86AsmBackend::writeNopData(raw_ostream &OS, uint64_t Count) const {
"\x66\x2e\x0f\x1f\x84\x00\x00\x00\x00\x00",
};
// This CPU doesn't support long nops. If needed add more.
// FIXME: We could generated something better than plain 0x90.
if (!STI.hasFeature(X86::FeatureNOPL) && !STI.hasFeature(X86::Mode64Bit)) {
for (uint64_t i = 0; i < Count; ++i)
OS << '\x90';
return true;
}
// 15-bytes is the longest single NOP instruction, but 10-bytes is
// commonly the longest that can be efficiently decoded.
uint64_t MaxNopLength = 10;
if (STI.getFeatureBits()[X86::FeatureFast7ByteNOP])
MaxNopLength = 7;
else if (STI.getFeatureBits()[X86::FeatureFast15ByteNOP])
MaxNopLength = 15;
else if (STI.getFeatureBits()[X86::FeatureFast11ByteNOP])
MaxNopLength = 11;
uint64_t MaxNopLength = (uint64_t)getMaximumNopSize();
// Emit as many MaxNopLength NOPs as needed, then emit a NOP of the remaining
// length.

View File

@ -0,0 +1,12 @@
# RUN: not llvm-mc -triple i386 %s -filetype=obj -o /dev/null 2>&1 | FileCheck --check-prefix=X86 %s
# RUN: not llvm-mc -triple=x86_64 %s -filetype=obj -o /dev/null 2>&1 | FileCheck --check-prefix=X64 %s
.nops 4, 3
# X86: :[[@LINE-1]]:1: error: illegal NOP size 3.
.nops 4, 4
# X86: :[[@LINE-1]]:1: error: illegal NOP size 4.
.nops 4, 5
# X86: :[[@LINE-1]]:1: error: illegal NOP size 5.
.nops 16, 15
# X86: :[[@LINE-1]]:1: error: illegal NOP size 15.
# X64: :[[@LINE-2]]:1: error: illegal NOP size 15.

View File

@ -0,0 +1,12 @@
# RUN: llvm-mc -triple i386 %s -filetype=obj | llvm-objdump -d - | FileCheck %s
.nops 4
# CHECK: 0: 90 nop
# CHECK-NEXT: 1: 90 nop
# CHECK-NEXT: 2: 90 nop
# CHECK-NEXT: 3: 90 nop
.nops 4, 1
# CHECK: 4: 90 nop
# CHECK-NEXT: 5: 90 nop
# CHECK-NEXT: 6: 90 nop
# CHECK-NEXT: 7: 90 nop

View File

@ -0,0 +1,19 @@
# RUN: llvm-mc -triple=x86_64 %s -filetype=obj | llvm-objdump -d - | FileCheck %s
.nops 4, 1
# CHECK: 0: 90 nop
# CHECK-NEXT: 1: 90 nop
# CHECK-NEXT: 2: 90 nop
# CHECK-NEXT: 3: 90 nop
.nops 4, 2
# CHECK-NEXT: 4: 66 90 nop
# CHECK-NEXT: 6: 66 90 nop
.nops 4, 3
# CHECK-NEXT: 8: 0f 1f 00 nopl (%rax)
# CHECK-NEXT: b: 90 nop
.nops 4, 4
# CHECK-NEXT: c: 0f 1f 40 00 nopl (%rax)
.nops 4, 5
# CHECK-NEXT: 10: 0f 1f 40 00 nopl (%rax)
.nops 4
# CHECK-NEXT: 14: 0f 1f 40 00 nopl (%rax)