diff --git a/llvm/tools/llvm-exegesis/lib/CMakeLists.txt b/llvm/tools/llvm-exegesis/lib/CMakeLists.txt --- a/llvm/tools/llvm-exegesis/lib/CMakeLists.txt +++ b/llvm/tools/llvm-exegesis/lib/CMakeLists.txt @@ -30,6 +30,7 @@ RegisterAliasing.cpp RegisterValue.cpp SchedClassResolution.cpp + SnippetFile.cpp SnippetGenerator.cpp SnippetRepetitor.cpp Target.cpp diff --git a/llvm/tools/llvm-exegesis/lib/SnippetFile.h b/llvm/tools/llvm-exegesis/lib/SnippetFile.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/SnippetFile.h @@ -0,0 +1,35 @@ +//===-- SnippetFile.cpp -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Utilities to read a snippet file. +/// Snippet files are just asm files with additional comments to specify which +/// registers should be defined or are live on entry. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVM_EXEGESIS_SNIPPETFILE_H +#define LLVM_TOOLS_LLVM_EXEGESIS_SNIPPETFILE_H + +#include "BenchmarkCode.h" +#include "LlvmState.h" +#include "llvm/Support/Error.h" + +#include + +namespace llvm { +namespace exegesis { + +// Reads code snippets from file `Filename`. +Expected> readSnippets(const LLVMState &State, + StringRef Filename); + +} // namespace exegesis +} // namespace llvm + +#endif \ No newline at end of file diff --git a/llvm/tools/llvm-exegesis/lib/SnippetFile.cpp b/llvm/tools/llvm-exegesis/lib/SnippetFile.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/SnippetFile.cpp @@ -0,0 +1,164 @@ +//===-- SnippetFile.cpp -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "SnippetFile.h" +#include "BenchmarkRunner.h" // FIXME: Pull BenchmarkFailure out of there. +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCObjectFileInfo.h" +#include "llvm/MC/MCParser/MCAsmParser.h" +#include "llvm/MC/MCParser/MCTargetAsmParser.h" +#include "llvm/MC/MCRegisterInfo.h" +#include "llvm/MC/MCStreamer.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TargetRegistry.h" +#include + +namespace llvm { +namespace exegesis { +namespace { + +// An MCStreamer that reads a BenchmarkCode definition from a file. +class BenchmarkCodeStreamer : public MCStreamer, public AsmCommentConsumer { +public: + explicit BenchmarkCodeStreamer(MCContext *Context, + const MCRegisterInfo *TheRegInfo, + BenchmarkCode *Result) + : MCStreamer(*Context), RegInfo(TheRegInfo), Result(Result) {} + + // Implementation of the MCStreamer interface. We only care about + // instructions. + void EmitInstruction(const MCInst &Instruction, + const MCSubtargetInfo &STI) override { + Result->Instructions.push_back(Instruction); + } + + // Implementation of the AsmCommentConsumer. + void HandleComment(SMLoc Loc, StringRef CommentText) override { + CommentText = CommentText.trim(); + if (!CommentText.consume_front("LLVM-EXEGESIS-")) + return; + if (CommentText.consume_front("DEFREG")) { + // LLVM-EXEGESIS-DEFREF + RegisterValue RegVal; + SmallVector Parts; + CommentText.split(Parts, ' ', /*unlimited splits*/ -1, + /*do not keep empty strings*/ false); + if (Parts.size() != 2) { + errs() << "invalid comment 'LLVM-EXEGESIS-DEFREG " << CommentText + << "', expected two parameters \n"; + ++InvalidComments; + return; + } + if (!(RegVal.Register = findRegisterByName(Parts[0].trim()))) { + errs() << "unknown register '" << Parts[0] + << "' in 'LLVM-EXEGESIS-DEFREG " << CommentText << "'\n"; + ++InvalidComments; + return; + } + const StringRef HexValue = Parts[1].trim(); + RegVal.Value = APInt( + /* each hex digit is 4 bits */ HexValue.size() * 4, HexValue, 16); + Result->RegisterInitialValues.push_back(std::move(RegVal)); + return; + } + if (CommentText.consume_front("LIVEIN")) { + // LLVM-EXEGESIS-LIVEIN + const auto RegName = CommentText.ltrim(); + if (unsigned Reg = findRegisterByName(RegName)) + Result->LiveIns.push_back(Reg); + else { + errs() << "unknown register '" << RegName + << "' in 'LLVM-EXEGESIS-LIVEIN " << CommentText << "'\n"; + ++InvalidComments; + } + return; + } + } + + unsigned numInvalidComments() const { return InvalidComments; } + +private: + // We only care about instructions, we don't implement this part of the API. + void EmitCommonSymbol(MCSymbol *Symbol, uint64_t Size, + unsigned ByteAlignment) override {} + bool EmitSymbolAttribute(MCSymbol *Symbol, MCSymbolAttr Attribute) override { + return false; + } + void EmitValueToAlignment(unsigned ByteAlignment, int64_t Value, + unsigned ValueSize, + unsigned MaxBytesToEmit) override {} + void EmitZerofill(MCSection *Section, MCSymbol *Symbol, uint64_t Size, + unsigned ByteAlignment, SMLoc Loc) override {} + + unsigned findRegisterByName(const StringRef RegName) const { + // FIXME: Can we do better than this ? + for (unsigned I = 0, E = RegInfo->getNumRegs(); I < E; ++I) { + if (RegName == RegInfo->getName(I)) + return I; + } + errs() << "'" << RegName + << "' is not a valid register name for the target\n"; + return 0; + } + + const MCRegisterInfo *const RegInfo; + BenchmarkCode *const Result; + unsigned InvalidComments = 0; +}; + +} // namespace + +// Reads code snippets from file `Filename`. +Expected> readSnippets(const LLVMState &State, + StringRef Filename) { + ErrorOr> BufferPtr = + MemoryBuffer::getFileOrSTDIN(Filename); + if (std::error_code EC = BufferPtr.getError()) { + return make_error("cannot read snippet: " + Filename + + ": " + EC.message()); + } + SourceMgr SM; + SM.AddNewSourceBuffer(std::move(BufferPtr.get()), SMLoc()); + + BenchmarkCode Result; + + MCObjectFileInfo ObjectFileInfo; + const TargetMachine &TM = State.getTargetMachine(); + MCContext Context(TM.getMCAsmInfo(), TM.getMCRegisterInfo(), &ObjectFileInfo); + ObjectFileInfo.InitMCObjectFileInfo(TM.getTargetTriple(), /*PIC*/ false, + Context); + BenchmarkCodeStreamer Streamer(&Context, TM.getMCRegisterInfo(), &Result); + const std::unique_ptr AsmParser( + createMCAsmParser(SM, Context, Streamer, *TM.getMCAsmInfo())); + if (!AsmParser) + return make_error("cannot create asm parser"); + AsmParser->getLexer().setCommentConsumer(&Streamer); + + const std::unique_ptr TargetAsmParser( + TM.getTarget().createMCAsmParser(*TM.getMCSubtargetInfo(), *AsmParser, + *TM.getMCInstrInfo(), + MCTargetOptions())); + + if (!TargetAsmParser) + return make_error("cannot create target asm parser"); + AsmParser->setTargetParser(*TargetAsmParser); + + if (AsmParser->Run(false)) + return make_error("cannot parse asm file"); + if (Streamer.numInvalidComments()) + return make_error( + Twine("found ") + .concat(Twine(Streamer.numInvalidComments())) + .concat(" invalid LLVM-EXEGESIS comments")); + return std::vector{std::move(Result)}; +} + +} // namespace exegesis +} // namespace llvm diff --git a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp --- a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp +++ b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp @@ -17,6 +17,7 @@ #include "lib/Clustering.h" #include "lib/LlvmState.h" #include "lib/PerfHelper.h" +#include "lib/SnippetFile.h" #include "lib/SnippetRepetitor.h" #include "lib/Target.h" #include "lib/TargetSelect.h" @@ -27,7 +28,6 @@ #include "llvm/MC/MCParser/MCAsmParser.h" #include "llvm/MC/MCParser/MCTargetAsmParser.h" #include "llvm/MC/MCRegisterInfo.h" -#include "llvm/MC/MCStreamer.h" #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/CommandLine.h" @@ -222,149 +222,6 @@ return Generator->generateConfigurations(Instr, ForbiddenRegs); } -namespace { - -// An MCStreamer that reads a BenchmarkCode definition from a file. -// The BenchmarkCode definition is just an asm file, with additional comments to -// specify which registers should be defined or are live on entry. -class BenchmarkCodeStreamer : public llvm::MCStreamer, - public llvm::AsmCommentConsumer { -public: - explicit BenchmarkCodeStreamer(llvm::MCContext *Context, - const llvm::MCRegisterInfo *TheRegInfo, - BenchmarkCode *Result) - : llvm::MCStreamer(*Context), RegInfo(TheRegInfo), Result(Result) {} - - // Implementation of the llvm::MCStreamer interface. We only care about - // instructions. - void EmitInstruction(const llvm::MCInst &Instruction, - const llvm::MCSubtargetInfo &STI) override { - Result->Instructions.push_back(Instruction); - } - - // Implementation of the llvm::AsmCommentConsumer. - void HandleComment(llvm::SMLoc Loc, llvm::StringRef CommentText) override { - CommentText = CommentText.trim(); - if (!CommentText.consume_front("LLVM-EXEGESIS-")) - return; - if (CommentText.consume_front("DEFREG")) { - // LLVM-EXEGESIS-DEFREF - RegisterValue RegVal; - llvm::SmallVector Parts; - CommentText.split(Parts, ' ', /*unlimited splits*/ -1, - /*do not keep empty strings*/ false); - if (Parts.size() != 2) { - llvm::errs() << "invalid comment 'LLVM-EXEGESIS-DEFREG " << CommentText - << "\n"; - ++InvalidComments; - } - if (!(RegVal.Register = findRegisterByName(Parts[0].trim()))) { - llvm::errs() << "unknown register in 'LLVM-EXEGESIS-DEFREG " - << CommentText << "\n"; - ++InvalidComments; - return; - } - const llvm::StringRef HexValue = Parts[1].trim(); - RegVal.Value = llvm::APInt( - /* each hex digit is 4 bits */ HexValue.size() * 4, HexValue, 16); - Result->RegisterInitialValues.push_back(std::move(RegVal)); - return; - } - if (CommentText.consume_front("LIVEIN")) { - // LLVM-EXEGESIS-LIVEIN - if (unsigned Reg = findRegisterByName(CommentText.ltrim())) - Result->LiveIns.push_back(Reg); - else { - llvm::errs() << "unknown register in 'LLVM-EXEGESIS-LIVEIN " - << CommentText << "\n"; - ++InvalidComments; - } - return; - } - } - - unsigned numInvalidComments() const { return InvalidComments; } - -private: - // We only care about instructions, we don't implement this part of the API. - void EmitCommonSymbol(llvm::MCSymbol *Symbol, uint64_t Size, - unsigned ByteAlignment) override {} - bool EmitSymbolAttribute(llvm::MCSymbol *Symbol, - llvm::MCSymbolAttr Attribute) override { - return false; - } - void EmitValueToAlignment(unsigned ByteAlignment, int64_t Value, - unsigned ValueSize, - unsigned MaxBytesToEmit) override {} - void EmitZerofill(llvm::MCSection *Section, llvm::MCSymbol *Symbol, - uint64_t Size, unsigned ByteAlignment, - llvm::SMLoc Loc) override {} - - unsigned findRegisterByName(const llvm::StringRef RegName) const { - // FIXME: Can we do better than this ? - for (unsigned I = 0, E = RegInfo->getNumRegs(); I < E; ++I) { - if (RegName == RegInfo->getName(I)) - return I; - } - llvm::errs() << "'" << RegName - << "' is not a valid register name for the target\n"; - return 0; - } - - const llvm::MCRegisterInfo *const RegInfo; - BenchmarkCode *const Result; - unsigned InvalidComments = 0; -}; - -} // namespace - -// Reads code snippets from file `Filename`. -static llvm::Expected> -readSnippets(const LLVMState &State, llvm::StringRef Filename) { - llvm::ErrorOr> BufferPtr = - llvm::MemoryBuffer::getFileOrSTDIN(Filename); - if (std::error_code EC = BufferPtr.getError()) { - return llvm::make_error( - "cannot read snippet: " + Filename + ": " + EC.message()); - } - llvm::SourceMgr SM; - SM.AddNewSourceBuffer(std::move(BufferPtr.get()), llvm::SMLoc()); - - BenchmarkCode Result; - - llvm::MCObjectFileInfo ObjectFileInfo; - const llvm::TargetMachine &TM = State.getTargetMachine(); - llvm::MCContext Context(TM.getMCAsmInfo(), TM.getMCRegisterInfo(), - &ObjectFileInfo); - ObjectFileInfo.InitMCObjectFileInfo(TM.getTargetTriple(), /*PIC*/ false, - Context); - BenchmarkCodeStreamer Streamer(&Context, TM.getMCRegisterInfo(), &Result); - const std::unique_ptr AsmParser( - llvm::createMCAsmParser(SM, Context, Streamer, *TM.getMCAsmInfo())); - if (!AsmParser) - return llvm::make_error("cannot create asm parser"); - AsmParser->getLexer().setCommentConsumer(&Streamer); - - const std::unique_ptr TargetAsmParser( - TM.getTarget().createMCAsmParser(*TM.getMCSubtargetInfo(), *AsmParser, - *TM.getMCInstrInfo(), - llvm::MCTargetOptions())); - - if (!TargetAsmParser) - return llvm::make_error( - "cannot create target asm parser"); - AsmParser->setTargetParser(*TargetAsmParser); - - if (AsmParser->Run(false)) - return llvm::make_error("cannot parse asm file"); - if (Streamer.numInvalidComments()) - return llvm::make_error( - llvm::Twine("found ") - .concat(llvm::Twine(Streamer.numInvalidComments())) - .concat(" invalid LLVM-EXEGESIS comments")); - return std::vector{std::move(Result)}; -} - void benchmarkMain() { #ifndef HAVE_LIBPFM llvm::report_fatal_error( diff --git a/llvm/unittests/tools/llvm-exegesis/X86/CMakeLists.txt b/llvm/unittests/tools/llvm-exegesis/X86/CMakeLists.txt --- a/llvm/unittests/tools/llvm-exegesis/X86/CMakeLists.txt +++ b/llvm/unittests/tools/llvm-exegesis/X86/CMakeLists.txt @@ -18,6 +18,7 @@ BenchmarkResultTest.cpp RegisterAliasingTest.cpp SchedClassResolutionTest.cpp + SnippetFileTest.cpp SnippetGeneratorTest.cpp SnippetRepetitorTest.cpp TargetTest.cpp diff --git a/llvm/unittests/tools/llvm-exegesis/X86/SnippetFileTest.cpp b/llvm/unittests/tools/llvm-exegesis/X86/SnippetFileTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/tools/llvm-exegesis/X86/SnippetFileTest.cpp @@ -0,0 +1,132 @@ +//===-- SnippetFileTest.cpp -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "SnippetFile.h" + +#include "LlvmState.h" +#include "X86InstrInfo.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/raw_ostream.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace llvm { +namespace exegesis { + +void InitializeX86ExegesisTarget(); + +namespace { + +using testing::AllOf; +using testing::ElementsAre; +using testing::Eq; +using testing::Field; +using testing::Property; +using testing::SizeIs; + +class X86SnippetFileTest : public ::testing::Test { +protected: + X86SnippetFileTest() : State("x86_64-unknown-linux", "haswell") {} + + static void SetUpTestCase() { + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86TargetMC(); + LLVMInitializeX86Target(); + LLVMInitializeX86AsmPrinter(); + LLVMInitializeX86AsmParser(); + InitializeX86ExegesisTarget(); + } + + Expected> TestCommon(StringRef Contents) { + SmallString<64> Filename; + std::error_code EC; + EC = sys::fs::createUniqueDirectory("SnippetFileTestDir", Filename); + EXPECT_FALSE(EC); + sys::path::append(Filename, "snippet.s"); + errs() << Filename << "-------\n"; + { + raw_fd_ostream FOS(Filename, EC); + FOS << Contents; + EXPECT_FALSE(EC); + } + return readSnippets(State, Filename); + } + + const LLVMState State; +}; + +// FIXME: Refactor these to ../Common/Matchers.h +static auto HasOpcode = [](unsigned Opcode) { + return Property(&MCInst::getOpcode, Eq(Opcode)); +}; + +MATCHER_P2(RegisterInitialValueIs, Reg, Val, "") { + if (arg.Register == Reg && + arg.Value.getLimitedValue() == static_cast(Val)) + return true; + *result_listener << "expected: {" << Reg << ", " << Val << "} "; + *result_listener << "actual: {" << arg.Register << ", " + << arg.Value.getLimitedValue() << "}"; + return false; +} + +TEST_F(X86SnippetFileTest, Works) { + auto Snippets = TestCommon(R"( + # LLVM-EXEGESIS-DEFREG RAX 0f + # LLVM-EXEGESIS-DEFREG SIL 0 + # LLVM-EXEGESIS-LIVEIN RDI + # LLVM-EXEGESIS-LIVEIN DL + incq %rax + )"); + EXPECT_FALSE((bool)Snippets.takeError()); + ASSERT_THAT(*Snippets, SizeIs(1)); + const auto &Snippet = (*Snippets)[0]; + ASSERT_THAT(Snippet.Instructions, ElementsAre(HasOpcode(X86::INC64r))); + ASSERT_THAT(Snippet.RegisterInitialValues, + ElementsAre(RegisterInitialValueIs(X86::RAX, 15), + RegisterInitialValueIs(X86::SIL, 0))); + ASSERT_THAT(Snippet.LiveIns, ElementsAre(X86::RDI, X86::DL)); +} + +TEST_F(X86SnippetFileTest, BadDefregParam) { + auto Error = TestCommon(R"( + # LLVM-EXEGESIS-DEFREG DOESNOEXIST 0 + incq %rax + )") + .takeError(); + EXPECT_TRUE((bool)Error); + consumeError(std::move(Error)); +} + +TEST_F(X86SnippetFileTest, NoDefregValue) { + auto Error = TestCommon(R"( + # LLVM-EXEGESIS-DEFREG RAX + incq %rax + )") + .takeError(); + EXPECT_TRUE((bool)Error); + consumeError(std::move(Error)); +} + +TEST_F(X86SnippetFileTest, MissingParam) { + auto Error = TestCommon(R"( + # LLVM-EXEGESIS-LIVEIN + incq %rax + )") + .takeError(); + EXPECT_TRUE((bool)Error); + consumeError(std::move(Error)); +} + +} // namespace +} // namespace exegesis +} // namespace llvm