diff --git a/llvm/include/llvm/MC/MCAsmBackend.h b/llvm/include/llvm/MC/MCAsmBackend.h --- a/llvm/include/llvm/MC/MCAsmBackend.h +++ b/llvm/include/llvm/MC/MCAsmBackend.h @@ -177,6 +177,10 @@ /// 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. /// diff --git a/llvm/include/llvm/MC/MCFragment.h b/llvm/include/llvm/MC/MCFragment.h --- a/llvm/include/llvm/MC/MCFragment.h +++ b/llvm/include/llvm/MC/MCFragment.h @@ -37,6 +37,7 @@ FT_Data, FT_CompactEncodedInst, FT_Fill, + FT_Nops, FT_Relaxable, FT_Org, FT_Dwarf, @@ -350,6 +351,31 @@ } }; +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; diff --git a/llvm/include/llvm/MC/MCObjectStreamer.h b/llvm/include/llvm/MC/MCObjectStreamer.h --- a/llvm/include/llvm/MC/MCObjectStreamer.h +++ b/llvm/include/llvm/MC/MCObjectStreamer.h @@ -179,6 +179,8 @@ 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; diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h --- a/llvm/include/llvm/MC/MCStreamer.h +++ b/llvm/include/llvm/MC/MCStreamer.h @@ -777,6 +777,9 @@ 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); diff --git a/llvm/lib/MC/MCAssembler.cpp b/llvm/lib/MC/MCAssembler.cpp --- a/llvm/lib/MC/MCAssembler.cpp +++ b/llvm/lib/MC/MCAssembler.cpp @@ -62,8 +62,8 @@ "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 @@ return Size; } + case MCFragment::FT_Nops: + return cast(F).getNumBytes(); + case MCFragment::FT_LEB: return cast(F).getContents().size(); @@ -613,6 +616,45 @@ break; } + case MCFragment::FT_Nops: { + ++stats::EmittedNopsFragments; + const MCNopsFragment &NF = cast(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(F); OS << LF.getContents(); diff --git a/llvm/lib/MC/MCFragment.cpp b/llvm/lib/MC/MCFragment.cpp --- a/llvm/lib/MC/MCFragment.cpp +++ b/llvm/lib/MC/MCFragment.cpp @@ -279,6 +279,9 @@ case FT_Fill: delete cast(this); return; + case FT_Nops: + delete cast(this); + return; case FT_Relaxable: delete cast(this); return; @@ -336,6 +339,9 @@ 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 @@ << " NumValues:" << FF->getNumValues(); break; } + case MCFragment::FT_Nops: { + const auto *NF = cast(this); + OS << " NumBytes:" << NF->getNumBytes() + << " ControlledNopLength:" << NF->getControlledNopLength(); + break; + } case MCFragment::FT_Relaxable: { const auto *F = cast(this); OS << "\n "; diff --git a/llvm/lib/MC/MCObjectStreamer.cpp b/llvm/lib/MC/MCObjectStreamer.cpp --- a/llvm/lib/MC/MCObjectStreamer.cpp +++ b/llvm/lib/MC/MCObjectStreamer.cpp @@ -819,6 +819,16 @@ 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); } diff --git a/llvm/lib/MC/MCStreamer.cpp b/llvm/lib/MC/MCStreamer.cpp --- a/llvm/lib/MC/MCStreamer.cpp +++ b/llvm/lib/MC/MCStreamer.cpp @@ -202,6 +202,9 @@ 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); } diff --git a/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp b/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp --- a/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp +++ b/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp @@ -934,6 +934,7 @@ OperandVector &Operands); bool parseDirectiveArch(); + bool parseDirectiveNops(SMLoc L); bool parseDirectiveEven(SMLoc L); bool ParseDirectiveCode(StringRef IDVal, SMLoc L); @@ -4037,7 +4038,9 @@ "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 @@ 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) { diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp --- a/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp +++ b/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp @@ -207,6 +207,8 @@ 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 @@ } } +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 @@ "\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. diff --git a/llvm/test/MC/X86/x86-directive-nops-errors.s b/llvm/test/MC/X86/x86-directive-nops-errors.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/X86/x86-directive-nops-errors.s @@ -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. diff --git a/llvm/test/MC/X86/x86-directive-nops.s b/llvm/test/MC/X86/x86-directive-nops.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/X86/x86-directive-nops.s @@ -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 diff --git a/llvm/test/MC/X86/x86_64-directive-nops.s b/llvm/test/MC/X86/x86_64-directive-nops.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/X86/x86_64-directive-nops.s @@ -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)