purge all old debug hooks

I'm thinking of maybe starting fresh, and it'll help to have all this
out of my face.  We'll always have Git.

Change-Id: I838f2fc33e793cfb4a73655d74e9e990ca3be1fa
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/264747
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
This commit is contained in:
Mike Klein 2020-01-16 11:20:43 -06:00
parent b18e74dcbd
commit 9f64f4c377
3 changed files with 14 additions and 210 deletions

View File

@ -15,7 +15,6 @@
#include "src/core/SkCpu.h" #include "src/core/SkCpu.h"
#include "src/core/SkVM.h" #include "src/core/SkVM.h"
#include <functional> // std::hash #include <functional> // std::hash
#include <string.h>
// JIT code isn't MSAN-instrumented, so we won't see when it uses // JIT code isn't MSAN-instrumented, so we won't see when it uses
// uninitialized memory, and we'll not see the writes it makes as properly // uninitialized memory, and we'll not see the writes it makes as properly
@ -28,43 +27,9 @@
#endif #endif
#if defined(SKVM_JIT) #if defined(SKVM_JIT)
#include <sys/mman.h> #include <sys/mman.h> // mmap, mprotect
#if defined(SKVM_PERF_DUMPS)
#include <stdio.h>
#include <time.h>
#endif
#if defined(SKVM_JIT_VTUNE)
#include <jitprofiling.h>
static void notify_vtune(const char* name, void* addr, size_t len,
const std::vector<skvm::LineTableEntry>& lines) {
if (iJIT_IsProfilingActive() != iJIT_SAMPLING_ON) {
return;
}
std::vector<LineNumberInfo> table;
for (auto& entry : lines) {
table.push_back({ (unsigned)entry.offset, (unsigned)entry.line });
}
iJIT_Method_Load event;
memset(&event, 0, sizeof(event));
event.method_id = iJIT_GetNewMethodID();
event.method_name = const_cast<char*>(name); // wtf?
event.method_load_address = addr;
event.method_size = len;
event.line_number_table = table.data();
event.line_number_size = table.size();
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, &event);
}
#else
static void notify_vtune(const char* name, void* addr, size_t len,
const std::vector<skvm::LineTableEntry>& lines) {}
#endif
#endif #endif
namespace skvm { namespace skvm {
// Debugging tools, mostly for printing various data structures out to a stream. // Debugging tools, mostly for printing various data structures out to a stream.
@ -377,23 +342,6 @@ namespace skvm {
} }
} }
void Program::dumpJIT() const {
#if !defined(SK_BUILD_FOR_WIN)
// Disassemble up through vzeroupper, retq exit. No need for constants or zero padding.
const uint8_t vzeroupper_retq[] = { 0xc5, 0xf8, 0x77, 0xc3 };
auto end = (const uint8_t*)memmem(fJITBuf, fJITSize,
vzeroupper_retq, SK_ARRAY_COUNT(vzeroupper_retq));
SkASSERT(end);
end += SK_ARRAY_COUNT(vzeroupper_retq);
FILE* p = popen("llvm-mc --disassemble --no-warn", "w");
for (auto buf = (const uint8_t*)fJITBuf; buf != end; buf++) {
fprintf(p, "0x%02x\n", *buf);
}
pclose(p);
#endif
}
// Builder -> Program, with liveness and loop hoisting analysis. // Builder -> Program, with liveness and loop hoisting analysis.
Program Builder::done(const char* debug_name) { Program Builder::done(const char* debug_name) {
@ -2099,7 +2047,6 @@ namespace skvm {
bool Program::jit(const std::vector<Builder::Instruction>& instructions, bool Program::jit(const std::vector<Builder::Instruction>& instructions,
const bool try_hoisting, const bool try_hoisting,
std::vector<LineTableEntry>* line_table,
Assembler* a) const { Assembler* a) const {
using A = Assembler; using A = Assembler;
@ -2153,12 +2100,6 @@ namespace skvm {
bytes_masks; // These vary per-lane. bytes_masks; // These vary per-lane.
LabelAndReg iota; // Exists _only_ to vary per-lane. LabelAndReg iota; // Exists _only_ to vary per-lane.
auto mark_line = [&](int line) {
if (line_table) {
line_table->push_back({line, a->size()});
}
};
auto warmup = [&](Val id) { auto warmup = [&](Val id) {
const Builder::Instruction& inst = instructions[id]; const Builder::Instruction& inst = instructions[id];
@ -2572,9 +2513,6 @@ namespace skvm {
#endif #endif
} }
// Leave plenty of room for loop overhead and constant "line" markers.
mark_line(id+1000);
// Calls to tmp() or dst() might have flipped this false from its default true state. // Calls to tmp() or dst() might have flipped this false from its default true state.
return ok; return ok;
}; };
@ -2613,12 +2551,10 @@ namespace skvm {
} }
} }
int line = 1; // All loop overhead is marked as "line 1".
a->label(&body); a->label(&body);
{ {
a->cmp(N, K); a->cmp(N, K);
jump_if_less(&tail); jump_if_less(&tail);
mark_line(line);
for (Val id = 0; id < (Val)instructions.size(); id++) { for (Val id = 0; id < (Val)instructions.size(); id++) {
if (!hoisted(id) && !emit(id, /*scalar=*/false)) { if (!hoisted(id) && !emit(id, /*scalar=*/false)) {
return false; return false;
@ -2631,14 +2567,12 @@ namespace skvm {
} }
sub(N, K); sub(N, K);
jump(&body); jump(&body);
mark_line(line);
} }
a->label(&tail); a->label(&tail);
{ {
a->cmp(N, 1); a->cmp(N, 1);
jump_if_less(&done); jump_if_less(&done);
mark_line(line);
for (Val id = 0; id < (Val)instructions.size(); id++) { for (Val id = 0; id < (Val)instructions.size(); id++) {
if (!hoisted(id) && !emit(id, /*scalar=*/true)) { if (!hoisted(id) && !emit(id, /*scalar=*/true)) {
return false; return false;
@ -2651,13 +2585,11 @@ namespace skvm {
} }
sub(N, 1); sub(N, 1);
jump(&tail); jump(&tail);
mark_line(line);
} }
a->label(&done); a->label(&done);
{ {
exit(); exit();
mark_line(line);
} }
// Except for explicit aligned load and store instructions, AVX allows // Except for explicit aligned load and store instructions, AVX allows
@ -2671,7 +2603,6 @@ namespace skvm {
for (int i = 0; i < K; i++) { for (int i = 0; i < K; i++) {
a->word(imm); a->word(imm);
} }
mark_line(++line); // 2,3,4...
}); });
bytes_masks.foreach([&](int imm, LabelAndReg* entry) { bytes_masks.foreach([&](int imm, LabelAndReg* entry) {
@ -2684,7 +2615,6 @@ namespace skvm {
#if defined(__x86_64__) #if defined(__x86_64__)
a->bytes(mask, sizeof(mask)); a->bytes(mask, sizeof(mask));
#endif #endif
mark_line(++line);
}); });
if (!iota.label.references.empty()) { if (!iota.label.references.empty()) {
@ -2693,7 +2623,6 @@ namespace skvm {
for (int i = 0; i < K; i++) { for (int i = 0; i < K; i++) {
a->word(i); a->word(i);
} }
mark_line(++line);
} }
return true; return true;
@ -2707,9 +2636,9 @@ namespace skvm {
// First try allowing code hoisting (faster code) // First try allowing code hoisting (faster code)
// then again without if that fails (lower register pressure). // then again without if that fails (lower register pressure).
bool try_hoisting = true; bool try_hoisting = true;
if (!this->jit(instructions, try_hoisting, nullptr, &a)) { if (!this->jit(instructions, try_hoisting, &a)) {
try_hoisting = false; try_hoisting = false;
if (!this->jit(instructions, try_hoisting, nullptr, &a)) { if (!this->jit(instructions, try_hoisting, &a)) {
return; return;
} }
} }
@ -2720,130 +2649,15 @@ namespace skvm {
fJITBuf = mmap(nullptr,fJITSize, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1,0); fJITBuf = mmap(nullptr,fJITSize, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1,0);
// Assemble the program for real. // Assemble the program for real.
std::vector<LineTableEntry> line_table;
a = Assembler{fJITBuf}; a = Assembler{fJITBuf};
SkAssertResult(this->jit(instructions, try_hoisting, &line_table, &a)); SkAssertResult(this->jit(instructions, try_hoisting, &a));
SkASSERT(a.size() <= fJITSize); SkASSERT(a.size() <= fJITSize);
// Remap as executable, and flush caches on platforms that need that. // Remap as executable, and flush caches on platforms that need that.
mprotect(fJITBuf, fJITSize, PROT_READ|PROT_EXEC); mprotect(fJITBuf, fJITSize, PROT_READ|PROT_EXEC);
__builtin___clear_cache((char*)fJITBuf, __builtin___clear_cache((char*)fJITBuf,
(char*)fJITBuf + fJITSize); (char*)fJITBuf + fJITSize);
// Hook into profilers and such for debugging and development.
notify_vtune(debug_name, fJITBuf, a.size(), line_table);
#if defined(SKVM_PERF_DUMPS)
this->dumpJIT(debug_name, a.size());
#endif
} }
#endif #endif
#if defined(SKVM_PERF_DUMPS)
void Program::dumpJIT(const char* debug_name, size_t size) const {
SkASSERT(debug_name);
#if 0 && defined(__aarch64__)
SkDebugf("\n%s:", debug_name);
// cat | llvm-mc -arch aarch64 -disassemble
auto cur = (const uint8_t*)fJITBuf;
for (int i = 0; i < (int)size; i++) {
if (i % 4 == 0) {
SkDebugf("\n");
}
SkDebugf("0x%02x ", *cur++);
}
SkDebugf("\n");
#endif
// We're doing some really stateful things below so one thread at a time please...
static SkSpinlock dump_lock;
SkAutoSpinlock lock(dump_lock);
// Create a jit-<pid>.dump file that we can `perf inject -j` into a
// perf.data captured with `perf record -k 1`, letting us see each
// JIT'd Program as if a function named skvm-jit-<hash>. E.g.
//
// ninja -C out nanobench
// perf record -k 1 out/nanobench -m SkVM_4096_I32\$
// perf inject -j -i perf.data -o perf.data.jit
// perf report -i perf.data.jit
//
// Running `perf inject -j` will also dump an .so for each JIT'd
// program, named jitted-<pid>-<hash>.so.
//
// https://lwn.net/Articles/638566/
// https://v8.dev/docs/linux-perf
// https://cs.chromium.org/chromium/src/v8/src/diagnostics/perf-jit.cc
// https://lore.kernel.org/patchwork/patch/622240/
auto timestamp_ns = []() -> uint64_t {
// It's important to use CLOCK_MONOTONIC here so that perf can
// correlate our timestamps with those captured by `perf record
// -k 1`. That's also what `-k 1` does, by the way, tell perf
// record to use CLOCK_MONOTONIC.
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * (uint64_t)1e9 + ts.tv_nsec;
};
// We'll open the jit-<pid>.dump file and write a small header once,
// and just leave it open forever because we're lazy.
static FILE* jitdump = [&]{
// Must map as w+ for the mmap() call below to work.
char path[64];
sprintf(path, "jit-%d.dump", getpid());
FILE* f = fopen(path, "w+");
// Calling mmap() on the file adds a "hey they mmap()'d this" record to
// the perf.data file that will point `perf inject -j` at this log file.
// Kind of a strange way to tell `perf inject` where the file is...
void* marker = mmap(nullptr, sysconf(_SC_PAGESIZE),
PROT_READ|PROT_EXEC, MAP_PRIVATE,
fileno(f), /*offset=*/0);
SkASSERT_RELEASE(marker != MAP_FAILED);
// Like never calling fclose(f), we'll also just always leave marker mmap()'d.
#if defined(__x86_64__)
const uint32_t elf_mach = 62;
#elif defined(__aarch64__)
const uint32_t elf_mach = 183;
#endif
struct Header {
uint32_t magic, version, header_size, elf_mach, reserved, pid;
uint64_t timestamp_us, flags;
} header = {
0x4A695444, 1, sizeof(Header), elf_mach, 0, (uint32_t)getpid(),
timestamp_ns() / 1000, 0,
};
fwrite(&header, sizeof(header), 1, f);
return f;
}();
static uint64_t next_id = 1;
struct CodeLoad {
uint32_t event_type, event_size;
uint64_t timestamp_ns;
uint32_t pid, tid;
uint64_t vma/*???*/, code_addr, code_size, id;
} load = {
0/*code load*/, (uint32_t)(sizeof(CodeLoad) + strlen(debug_name) + 1 + size),
timestamp_ns(),
(uint32_t)getpid(), (uint32_t)SkGetThreadID(),
(uint64_t)fJITBuf, (uint64_t)fJITBuf, size, next_id++,
};
// Write the header, the JIT'd function name, and the JIT'd code itself.
fwrite(&load, sizeof(load), 1, jitdump);
fwrite(debug_name, 1, strlen(debug_name), jitdump);
fwrite("\0", 1, 1, jitdump);
fwrite(fJITBuf, 1, size, jitdump);
}
#endif
} // namespace skvm } // namespace skvm

View File

@ -593,9 +593,6 @@ namespace skvm {
} }
}; };
// Maps Builder::Instructions to regions of JIT'd code, for debugging in VTune.
struct LineTableEntry { int line; size_t offset; };
using Reg = int; using Reg = int;
class Program { class Program {
@ -635,7 +632,6 @@ namespace skvm {
bool hasJIT() const; // Has this Program been JITted? bool hasJIT() const; // Has this Program been JITted?
void dropJIT(); // If hasJIT(), drop it, forcing interpreter fallback. void dropJIT(); // If hasJIT(), drop it, forcing interpreter fallback.
void dumpJIT() const; // Disassemble to stdout.
void dump(SkWStream* = nullptr) const; void dump(SkWStream* = nullptr) const;
@ -645,12 +641,8 @@ namespace skvm {
bool jit(const std::vector<Builder::Instruction>&, bool jit(const std::vector<Builder::Instruction>&,
bool try_hoisting, bool try_hoisting,
std::vector<LineTableEntry>*,
Assembler*) const; Assembler*) const;
// Dump jit-*.dump files for perf inject.
void dumpJIT(const char* debug_name, size_t size) const;
std::vector<Instruction> fInstructions; std::vector<Instruction> fInstructions;
int fRegs = 0; int fRegs = 0;
int fLoop = 0; int fLoop = 0;

View File

@ -457,19 +457,17 @@ namespace {
skvm::Program program = builder.done(debug_name(key).c_str()); skvm::Program program = builder.done(debug_name(key).c_str());
if (false) { if (false) {
static std::atomic<int> done{0}; static std::atomic<int> missed{0},
if (0 == done++) { total{0};
atexit([]{ SkDebugf("%d calls to done\n", done.load()); }); if (!program.hasJIT()) {
SkDebugf("\ncouldn't JIT %s\n", debug_name(key).c_str());
builder.dump();
program.dump();
missed++;
} }
if (0 == total++) {
SkDebugf("%s\n", debug_name(key).c_str()); atexit([]{ SkDebugf("SkVMBlitter compiled %d programs, %d without JIT.\n",
builder.dump(); total.load(), missed.load()); });
program.dump();
if (program.hasJIT()) {
program.dumpJIT();
} else {
SkDebugf("\nfell back to interpreter for blitter with this key.\n");
} }
} }
return program; return program;