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:
parent
b18e74dcbd
commit
9f64f4c377
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user