[llvm-exegesis][NFC] Refactor snippet file reading out of tool main.

Summary: Add unit tests.

Reviewers: gchatelet

Subscribers: mgorny, tschuett, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D68212

llvm-svn: 373202
This commit is contained in:
Clement Courbet 2019-09-30 12:50:25 +00:00
parent dab6f0746d
commit 3e13816be2
6 changed files with 334 additions and 144 deletions

View File

@ -30,6 +30,7 @@ add_library(LLVMExegesis
RegisterAliasing.cpp
RegisterValue.cpp
SchedClassResolution.cpp
SnippetFile.cpp
SnippetGenerator.cpp
SnippetRepetitor.cpp
Target.cpp

View File

@ -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 <string>
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 <reg> <hex_value>
RegisterValue RegVal;
SmallVector<StringRef, 2> 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 <REG> <HEX_VALUE>\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 <reg>
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<std::vector<BenchmarkCode>> readSnippets(const LLVMState &State,
StringRef Filename) {
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferPtr =
MemoryBuffer::getFileOrSTDIN(Filename);
if (std::error_code EC = BufferPtr.getError()) {
return make_error<BenchmarkFailure>("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<MCAsmParser> AsmParser(
createMCAsmParser(SM, Context, Streamer, *TM.getMCAsmInfo()));
if (!AsmParser)
return make_error<BenchmarkFailure>("cannot create asm parser");
AsmParser->getLexer().setCommentConsumer(&Streamer);
const std::unique_ptr<MCTargetAsmParser> TargetAsmParser(
TM.getTarget().createMCAsmParser(*TM.getMCSubtargetInfo(), *AsmParser,
*TM.getMCInstrInfo(),
MCTargetOptions()));
if (!TargetAsmParser)
return make_error<BenchmarkFailure>("cannot create target asm parser");
AsmParser->setTargetParser(*TargetAsmParser);
if (AsmParser->Run(false))
return make_error<BenchmarkFailure>("cannot parse asm file");
if (Streamer.numInvalidComments())
return make_error<BenchmarkFailure>(
Twine("found ")
.concat(Twine(Streamer.numInvalidComments()))
.concat(" invalid LLVM-EXEGESIS comments"));
return std::vector<BenchmarkCode>{std::move(Result)};
}
} // namespace exegesis
} // namespace llvm

View File

@ -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 <vector>
namespace llvm {
namespace exegesis {
// Reads code snippets from file `Filename`.
Expected<std::vector<BenchmarkCode>> readSnippets(const LLVMState &State,
StringRef Filename);
} // namespace exegesis
} // namespace llvm
#endif

View File

@ -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 @@ generateSnippets(const LLVMState &State, unsigned Opcode,
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 <reg> <hex_value>
RegisterValue RegVal;
llvm::SmallVector<llvm::StringRef, 2> 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 <reg>
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<std::vector<BenchmarkCode>>
readSnippets(const LLVMState &State, llvm::StringRef Filename) {
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferPtr =
llvm::MemoryBuffer::getFileOrSTDIN(Filename);
if (std::error_code EC = BufferPtr.getError()) {
return llvm::make_error<BenchmarkFailure>(
"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<llvm::MCAsmParser> AsmParser(
llvm::createMCAsmParser(SM, Context, Streamer, *TM.getMCAsmInfo()));
if (!AsmParser)
return llvm::make_error<BenchmarkFailure>("cannot create asm parser");
AsmParser->getLexer().setCommentConsumer(&Streamer);
const std::unique_ptr<llvm::MCTargetAsmParser> TargetAsmParser(
TM.getTarget().createMCAsmParser(*TM.getMCSubtargetInfo(), *AsmParser,
*TM.getMCInstrInfo(),
llvm::MCTargetOptions()));
if (!TargetAsmParser)
return llvm::make_error<BenchmarkFailure>(
"cannot create target asm parser");
AsmParser->setTargetParser(*TargetAsmParser);
if (AsmParser->Run(false))
return llvm::make_error<BenchmarkFailure>("cannot parse asm file");
if (Streamer.numInvalidComments())
return llvm::make_error<BenchmarkFailure>(
llvm::Twine("found ")
.concat(llvm::Twine(Streamer.numInvalidComments()))
.concat(" invalid LLVM-EXEGESIS comments"));
return std::vector<BenchmarkCode>{std::move(Result)};
}
void benchmarkMain() {
#ifndef HAVE_LIBPFM
llvm::report_fatal_error(

View File

@ -18,6 +18,7 @@ add_llvm_unittest(LLVMExegesisX86Tests
BenchmarkResultTest.cpp
RegisterAliasingTest.cpp
SchedClassResolutionTest.cpp
SnippetFileTest.cpp
SnippetGeneratorTest.cpp
SnippetRepetitorTest.cpp
TargetTest.cpp

View File

@ -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<std::vector<BenchmarkCode>> 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<uint64_t>(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