#include "X86Counter.h"
#if defined(__linux__) && defined(HAVE_LIBPFM) && \
defined(LIBPFM_HAS_FIELD_CYCLES)
#include "llvm/Support/Endian.h"
#include "llvm/Support/Errc.h"
#include <perfmon/perf_event.h>
#include <perfmon/pfmlib.h>
#include <perfmon/pfmlib_perf_event.h>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <vector>
#include <poll.h>
#include <sys/mman.h>
#include <unistd.h>
namespace llvm {
namespace exegesis {
static constexpr int kLbrEntries = 16;
static constexpr size_t kBufferPages = 8;
static const size_t kDataBufferSize = kBufferPages * getpagesize();
static const size_t kMappedBufferSize = (kBufferPages + 1) * getpagesize();
static int pollLbrPerfEvent(const int FileDescriptor) {
struct pollfd PollFd;
PollFd.fd = FileDescriptor;
PollFd.events = POLLIN;
PollFd.revents = 0;
return poll(&PollFd, 1 , 10000 );
}
static void copyDataBuffer(void *MMappedBuffer, char *Buf, uint64_t Tail,
size_t DataSize) {
char *Start = reinterpret_cast<char *>(MMappedBuffer) + getpagesize();
uint64_t Offset = Tail % kDataBufferSize;
size_t CopySize = kDataBufferSize - Offset;
memcpy(Buf, Start + Offset, CopySize);
if (CopySize >= DataSize)
return;
memcpy(Buf + CopySize, Start, Offset);
return;
}
static Error parseDataBuffer(const char *DataBuf, size_t DataSize,
const void *From, const void *To,
SmallVector<int64_t, 4> *CycleArray) {
const char *DataPtr = DataBuf;
while (DataPtr < DataBuf + DataSize) {
struct perf_event_header Header;
memcpy(&Header, DataPtr, sizeof(struct perf_event_header));
if (Header.type != PERF_RECORD_SAMPLE) {
DataPtr += Header.size;
continue;
}
DataPtr += sizeof(Header);
uint64_t Count = support::endian::read64(DataPtr, endianness::native);
DataPtr += sizeof(Count);
struct perf_branch_entry Entry;
memcpy(&Entry, DataPtr, sizeof(struct perf_branch_entry));
for (uint64_t i = 0; i < Count; ++i) {
const uint64_t BlockStart = From == nullptr
? std::numeric_limits<uint64_t>::min()
: reinterpret_cast<uint64_t>(From);
const uint64_t BlockEnd = To == nullptr
? std::numeric_limits<uint64_t>::max()
: reinterpret_cast<uint64_t>(To);
if (BlockStart <= Entry.from && BlockEnd >= Entry.to)
CycleArray->push_back(Entry.cycles);
if (i == Count - 1)
return Error::success();
DataPtr += sizeof(Entry);
memcpy(&Entry, DataPtr, sizeof(struct perf_branch_entry));
}
}
return make_error<StringError>("Unable to parse databuffer.", errc::io_error);
}
X86LbrPerfEvent::X86LbrPerfEvent(unsigned SamplingPeriod) {
assert(SamplingPeriod > 0 && "SamplingPeriod must be positive");
EventString = "BR_INST_RETIRED.NEAR_TAKEN";
Attr = new perf_event_attr();
Attr->size = sizeof(*Attr);
Attr->type = PERF_TYPE_RAW;
Attr->config = 0x20c4;
Attr->sample_type = PERF_SAMPLE_BRANCH_STACK;
Attr->branch_sample_type = PERF_SAMPLE_BRANCH_ANY;
Attr->sample_period = SamplingPeriod;
Attr->wakeup_events = 1;
Attr->disabled = 1;
Attr->exclude_kernel = 1;
Attr->exclude_hv = 1;
Attr->read_format = PERF_FORMAT_GROUP;
FullQualifiedEventString = EventString;
}
X86LbrCounter::X86LbrCounter(pfm::PerfEvent &&NewEvent)
: CounterGroup(std::move(NewEvent), {}) {
MMappedBuffer = mmap(nullptr, kMappedBufferSize, PROT_READ | PROT_WRITE,
MAP_SHARED, getFileDescriptor(), 0);
if (MMappedBuffer == MAP_FAILED)
errs() << "Failed to mmap buffer.";
}
X86LbrCounter::~X86LbrCounter() {
if (0 != munmap(MMappedBuffer, kMappedBufferSize))
errs() << "Failed to munmap buffer.";
}
void X86LbrCounter::start() {
ioctl(getFileDescriptor(), PERF_EVENT_IOC_REFRESH, 1024 );
}
Error X86LbrCounter::checkLbrSupport() {
X86LbrCounter counter(X86LbrPerfEvent(123));
counter.start();
int Sum = 0;
int V = 1;
volatile int *P = &V;
auto TimeLimit =
std::chrono::high_resolution_clock::now() + std::chrono::microseconds(5);
for (int I = 0;
I < kLbrEntries || std::chrono::high_resolution_clock::now() < TimeLimit;
++I) {
Sum += *P;
}
counter.stop();
(void)Sum;
auto ResultOrError = counter.doReadCounter(nullptr, nullptr);
if (ResultOrError)
if (!ResultOrError.get().empty())
for (const int64_t &Value : ResultOrError.get())
if (Value != 0)
return Error::success();
return make_error<StringError>(
"LBR format with cycles is not suppported on the host.",
errc::not_supported);
}
Expected<SmallVector<int64_t, 4>>
X86LbrCounter::readOrError(StringRef FunctionBytes) const {
ioctl(getFileDescriptor(), PERF_EVENT_IOC_DISABLE, 0);
if (FunctionBytes.empty())
return make_error<StringError>("Empty function bytes",
errc::invalid_argument);
const void *From = reinterpret_cast<const void *>(FunctionBytes.data());
const void *To = reinterpret_cast<const void *>(FunctionBytes.data() +
FunctionBytes.size());
return doReadCounter(From, To);
}
Expected<SmallVector<int64_t, 4>>
X86LbrCounter::doReadCounter(const void *From, const void *To) const {
static constexpr int kMaxTimeouts = 160;
SmallVector<int64_t, 4> CycleArray;
auto DataBuf = std::make_unique<char[]>(kDataBufferSize);
int NumTimeouts = 0;
int PollResult = 0;
while (PollResult <= 0) {
PollResult = pollLbrPerfEvent(getFileDescriptor());
if (PollResult > 0)
break;
if (PollResult == -1)
return make_error<StringError>("Cannot poll LBR perf event.",
errc::io_error);
if (NumTimeouts++ >= kMaxTimeouts)
return make_error<StringError>(
"LBR polling still timed out after max number of attempts.",
errc::device_or_resource_busy);
}
struct perf_event_mmap_page Page;
memcpy(&Page, MMappedBuffer, sizeof(struct perf_event_mmap_page));
const uint64_t DataTail = Page.data_tail;
const uint64_t DataHead = Page.data_head;
std::atomic_thread_fence(std::memory_order_acq_rel);
const size_t DataSize = DataHead - DataTail;
if (DataSize > kDataBufferSize)
return make_error<StringError>("DataSize larger than buffer size.",
errc::invalid_argument);
copyDataBuffer(MMappedBuffer, DataBuf.get(), DataTail, DataSize);
Error error = parseDataBuffer(DataBuf.get(), DataSize, From, To, &CycleArray);
if (!error)
return CycleArray;
return std::move(error);
}
}
}
#endif