Revert "SkVM visualizer, version 0.1"
This reverts commit bf57843b01
.
Build break
Original change's description:
> SkVM visualizer, version 0.1
>
> Simplified - "no static instructions"
> Added to skslc with .cpp -> .html
>
> Change-Id: I0b56ea0480f868ca4182acb7a23c03acf7f57519
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/473659
> Reviewed-by: John Stiles <johnstiles@google.com>
> Commit-Queue: Julia Lavrova <jlavrova@google.com>
Change-Id: I938bbf7bb650d1636a0d9bd19e455b455a49ef27
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/487096
Auto-Submit: Julia Lavrova <jlavrova@google.com>
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
This commit is contained in:
parent
bf57843b01
commit
372a36de75
4
BUILD.gn
4
BUILD.gn
@ -656,21 +656,17 @@ if (skia_compile_sksl_tests) {
|
||||
"src/utils/SkParse.cpp",
|
||||
"src/utils/SkShaderUtils.cpp",
|
||||
"src/utils/SkUTF.cpp",
|
||||
"src/utils/SkVMVisualizer.cpp",
|
||||
]
|
||||
libs = []
|
||||
if (is_win) {
|
||||
sources += [ "src/ports/SkOSFile_win.cpp" ]
|
||||
} else {
|
||||
sources += [ "src/ports/SkOSFile_posix.cpp" ]
|
||||
libs += [ "dl" ]
|
||||
}
|
||||
sources += skia_sksl_sources
|
||||
sources += skia_sksl_gpu_sources
|
||||
include_dirs = [ "." ]
|
||||
deps = [
|
||||
":run_sksllex",
|
||||
":skvm_jit",
|
||||
"//third_party/externals/spirv-tools:spvtools",
|
||||
"//third_party/externals/spirv-tools:spvtools_val",
|
||||
"//third_party/spirv-cross:spirv_cross",
|
||||
|
@ -80,8 +80,6 @@ skia_utils_sources = [
|
||||
"$_src/utils/SkThreadUtils_win.cpp",
|
||||
"$_src/utils/SkUTF.cpp",
|
||||
"$_src/utils/SkUTF.h",
|
||||
"$_src/utils/SkVMVisualizer.cpp",
|
||||
"$_src/utils/SkVMVisualizer.h",
|
||||
|
||||
#mac
|
||||
"$_src/utils/mac/SkCGBase.h",
|
||||
|
@ -17,7 +17,6 @@
|
||||
#include "src/core/SkOpts.h"
|
||||
#include "src/core/SkStreamPriv.h"
|
||||
#include "src/core/SkVM.h"
|
||||
#include "src/utils/SkVMVisualizer.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
@ -160,13 +159,13 @@ namespace skvm {
|
||||
Builder::Builder(Features features, bool createDuplicates)
|
||||
: fFeatures(features ), fCreateDuplicates(createDuplicates) {}
|
||||
|
||||
|
||||
struct Program::Impl {
|
||||
std::vector<InterpreterInstruction> instructions;
|
||||
int regs = 0;
|
||||
int loop = 0;
|
||||
std::vector<int> strides;
|
||||
std::vector<TraceHook*> traceHooks;
|
||||
std::unique_ptr<viz::Visualizer> visualizer;
|
||||
|
||||
std::atomic<void*> jit_entry{nullptr}; // TODO: minimal std::memory_orders
|
||||
size_t jit_size = 0;
|
||||
@ -370,13 +369,6 @@ namespace skvm {
|
||||
}
|
||||
}
|
||||
|
||||
void Program::visualize(SkWStream* output, const char* code) const {
|
||||
if (fImpl->visualizer) {
|
||||
fImpl->visualizer->dump(output, code);
|
||||
}
|
||||
}
|
||||
|
||||
viz::Visualizer* Program::visualizer() { return fImpl->visualizer.get(); }
|
||||
void Program::dump(SkWStream* o) const {
|
||||
SkDebugfStream debug;
|
||||
if (!o) { o = &debug; }
|
||||
@ -486,8 +478,8 @@ namespace skvm {
|
||||
write(o, "\n");
|
||||
}
|
||||
}
|
||||
std::vector<Instruction> eliminate_dead_code(std::vector<Instruction> program,
|
||||
viz::Visualizer* visualizer) {
|
||||
|
||||
std::vector<Instruction> eliminate_dead_code(std::vector<Instruction> program) {
|
||||
// Determine which Instructions are live by working back from side effects.
|
||||
std::vector<bool> live(program.size(), false);
|
||||
for (Val id = program.size(); id--;) {
|
||||
@ -517,11 +509,6 @@ namespace skvm {
|
||||
}
|
||||
}
|
||||
|
||||
if (visualizer) {
|
||||
visualizer->addInstructions(program);
|
||||
visualizer->markAsDeadCode(live, new_id);
|
||||
}
|
||||
|
||||
// Eliminate any non-live ops.
|
||||
auto it = std::remove_if(program.begin(), program.end(), [&](const Instruction& inst) {
|
||||
Val id = (Val)(&inst - program.data());
|
||||
@ -532,8 +519,7 @@ namespace skvm {
|
||||
return program;
|
||||
}
|
||||
|
||||
std::vector<OptimizedInstruction> finalize(const std::vector<Instruction> program,
|
||||
viz::Visualizer* visualizer) {
|
||||
std::vector<OptimizedInstruction> finalize(const std::vector<Instruction> program) {
|
||||
std::vector<OptimizedInstruction> optimized(program.size());
|
||||
for (Val id = 0; id < (Val)program.size(); id++) {
|
||||
Instruction inst = program[id];
|
||||
@ -577,38 +563,23 @@ namespace skvm {
|
||||
}
|
||||
}
|
||||
|
||||
if (visualizer) {
|
||||
visualizer->finalize(program, optimized);
|
||||
}
|
||||
|
||||
return optimized;
|
||||
}
|
||||
|
||||
std::vector<OptimizedInstruction> Builder::optimize(viz::Visualizer* visualizer) const {
|
||||
std::vector<OptimizedInstruction> Builder::optimize() const {
|
||||
std::vector<Instruction> program = this->program();
|
||||
program = eliminate_dead_code(std::move(program), visualizer);
|
||||
return finalize (std::move(program), visualizer);
|
||||
program = eliminate_dead_code(std::move(program));
|
||||
return finalize (std::move(program));
|
||||
}
|
||||
|
||||
Program Builder::done(const char* debug_name,
|
||||
bool allow_jit) const {
|
||||
return this->done(debug_name, allow_jit, /*visualizer=*/nullptr);
|
||||
}
|
||||
|
||||
Program Builder::done(const char* debug_name,
|
||||
bool allow_jit,
|
||||
std::unique_ptr<viz::Visualizer> visualizer) const {
|
||||
Program Builder::done(const char* debug_name, bool allow_jit) const {
|
||||
char buf[64] = "skvm-jit-";
|
||||
if (!debug_name) {
|
||||
*SkStrAppendU32(buf+9, this->hash()) = '\0';
|
||||
debug_name = buf;
|
||||
}
|
||||
|
||||
auto optimized = this->optimize(visualizer ? visualizer.get() : nullptr);
|
||||
return {optimized,
|
||||
std::move(visualizer),
|
||||
fStrides,
|
||||
fTraceHooks, debug_name, allow_jit};
|
||||
return {this->optimize(), fStrides, fTraceHooks, debug_name, allow_jit};
|
||||
}
|
||||
|
||||
uint64_t Builder::hash() const {
|
||||
@ -3109,11 +3080,9 @@ namespace skvm {
|
||||
}
|
||||
|
||||
Program::Program(const std::vector<OptimizedInstruction>& instructions,
|
||||
std::unique_ptr<viz::Visualizer> visualizer,
|
||||
const std::vector<int>& strides,
|
||||
const std::vector<TraceHook*>& traceHooks,
|
||||
const char* debug_name, bool allow_jit) : Program() {
|
||||
fImpl->visualizer = std::move(visualizer);
|
||||
fImpl->strides = strides;
|
||||
fImpl->traceHooks = traceHooks;
|
||||
if (gSkVMAllowJIT && allow_jit) {
|
||||
@ -4303,17 +4272,9 @@ namespace skvm {
|
||||
|
||||
enter();
|
||||
for (Val id = 0; id < (Val)instructions.size(); id++) {
|
||||
if (fImpl->visualizer && is_trace(instructions[id].op)) {
|
||||
// Make sure trace commands stay on JIT for visualizer
|
||||
continue;
|
||||
}
|
||||
auto start = a->size();
|
||||
if (instructions[id].can_hoist && !emit(id, /*scalar=*/false)) {
|
||||
return false;
|
||||
}
|
||||
if (fImpl->visualizer && instructions[id].can_hoist) {
|
||||
fImpl->visualizer->addMachineCommands(id, start, a->size());
|
||||
}
|
||||
}
|
||||
|
||||
// This point marks a kind of canonical fixed point for register contents: if loop
|
||||
@ -4339,17 +4300,9 @@ namespace skvm {
|
||||
a->cmp(N, K);
|
||||
jump_if_less(&tail);
|
||||
for (Val id = 0; id < (Val)instructions.size(); id++) {
|
||||
if (fImpl->visualizer != nullptr && is_trace(instructions[id].op)) {
|
||||
// Make sure trace commands stay on JIT for visualizer
|
||||
continue;
|
||||
}
|
||||
auto start = a->size();
|
||||
if (!instructions[id].can_hoist && !emit(id, /*scalar=*/false)) {
|
||||
return false;
|
||||
}
|
||||
if (fImpl->visualizer && !instructions[id].can_hoist) {
|
||||
fImpl->visualizer->addMachineCommands(id, start, a->size());
|
||||
}
|
||||
}
|
||||
restore_incoming_regs();
|
||||
for (int i = 0; i < (int)fImpl->strides.size(); i++) {
|
||||
@ -4366,10 +4319,6 @@ namespace skvm {
|
||||
a->cmp(N, 1);
|
||||
jump_if_less(&done);
|
||||
for (Val id = 0; id < (Val)instructions.size(); id++) {
|
||||
if (fImpl->visualizer && is_trace(instructions[id].op)) {
|
||||
// Make sure trace commands stay on JIT for visualizer
|
||||
continue;
|
||||
}
|
||||
if (!instructions[id].can_hoist && !emit(id, /*scalar=*/true)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -42,10 +42,6 @@ class SkWStream;
|
||||
|
||||
namespace skvm {
|
||||
|
||||
namespace viz {
|
||||
class Visualizer;
|
||||
}
|
||||
|
||||
class Assembler {
|
||||
public:
|
||||
explicit Assembler(void* buf);
|
||||
@ -622,18 +618,15 @@ namespace skvm {
|
||||
|
||||
class Builder {
|
||||
public:
|
||||
|
||||
Builder(bool createDuplicates = false);
|
||||
Builder(Features, bool createDuplicates = false);
|
||||
|
||||
Program done(const char* debug_name,
|
||||
bool allow_jit,
|
||||
std::unique_ptr<viz::Visualizer> visualizer) const;
|
||||
Program done(const char* debug_name = nullptr,
|
||||
bool allow_jit=true) const;
|
||||
Program done(const char* debug_name = nullptr, bool allow_jit=true) const;
|
||||
|
||||
// Mostly for debugging, tests, etc.
|
||||
std::vector<Instruction> program() const { return fProgram; }
|
||||
std::vector<OptimizedInstruction> optimize(viz::Visualizer* visualizer = nullptr) const;
|
||||
std::vector<OptimizedInstruction> optimize() const;
|
||||
|
||||
// Returns a trace-hook ID which must be passed to the trace opcodes.
|
||||
int attachTraceHook(TraceHook*);
|
||||
@ -1024,10 +1017,8 @@ namespace skvm {
|
||||
|
||||
// Optimization passes and data structures normally used by Builder::optimize(),
|
||||
// extracted here so they can be unit tested.
|
||||
std::vector<Instruction> eliminate_dead_code(std::vector<Instruction>,
|
||||
viz::Visualizer* visualizer = nullptr);
|
||||
std::vector<OptimizedInstruction> finalize(std::vector<Instruction>,
|
||||
viz::Visualizer* visualizer = nullptr);
|
||||
std::vector<Instruction> eliminate_dead_code(std::vector<Instruction>);
|
||||
std::vector<OptimizedInstruction> finalize (std::vector<Instruction>);
|
||||
|
||||
using Reg = int;
|
||||
|
||||
@ -1041,7 +1032,6 @@ namespace skvm {
|
||||
class Program {
|
||||
public:
|
||||
Program(const std::vector<OptimizedInstruction>& instructions,
|
||||
std::unique_ptr<viz::Visualizer> visualizer,
|
||||
const std::vector<int>& strides,
|
||||
const std::vector<TraceHook*>& traceHooks,
|
||||
const char* debug_name, bool allow_jit);
|
||||
@ -1074,10 +1064,8 @@ namespace skvm {
|
||||
bool hasJIT() const; // Has this Program been JITted?
|
||||
bool hasTraceHooks() const; // Is this program instrumented for debugging?
|
||||
|
||||
void visualize(SkWStream* output, const char* code) const;
|
||||
void dump(SkWStream* = nullptr) const;
|
||||
void disassemble(SkWStream* = nullptr) const;
|
||||
viz::Visualizer* visualizer();
|
||||
|
||||
private:
|
||||
void setupInterpreter(const std::vector<OptimizedInstruction>&);
|
||||
|
@ -6,12 +6,11 @@
|
||||
*/
|
||||
|
||||
#define SK_OPTS_NS skslc_standalone
|
||||
#include "include/core/SkGraphics.h"
|
||||
#include "include/core/SkStream.h"
|
||||
#include "src/core/SkCpu.h"
|
||||
#include "src/core/SkOpts.h"
|
||||
#include "src/opts/SkChecksum_opts.h"
|
||||
#include "src/opts/SkVM_opts.h"
|
||||
|
||||
#include "include/core/SkStream.h"
|
||||
#include "src/sksl/SkSLCompiler.h"
|
||||
#include "src/sksl/SkSLDehydrator.h"
|
||||
#include "src/sksl/SkSLFileOutputStream.h"
|
||||
@ -23,7 +22,6 @@
|
||||
#include "src/sksl/ir/SkSLVarDeclarations.h"
|
||||
#include "src/sksl/tracing/SkVMDebugTrace.h"
|
||||
#include "src/utils/SkShaderUtils.h"
|
||||
#include "src/utils/SkVMVisualizer.h"
|
||||
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
|
||||
@ -32,8 +30,6 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
extern bool gSkVMAllowJIT;
|
||||
|
||||
void SkDebugf(const char format[], ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
@ -292,7 +288,7 @@ ResultCode processCommand(std::vector<SkSL::String>& args) {
|
||||
kind = SkSL::ProgramKind::kRuntimeShader;
|
||||
} else {
|
||||
printf("input filename must end in '.vert', '.frag', '.rtb', '.rtcf', "
|
||||
"'.rts' or '.sksl'\n");
|
||||
"'.rts', or '.sksl'\n");
|
||||
return ResultCode::kInputError;
|
||||
}
|
||||
|
||||
@ -322,7 +318,6 @@ ResultCode processCommand(std::vector<SkSL::String>& args) {
|
||||
settings.fRTFlipBinding = 0;
|
||||
|
||||
const SkSL::String& outputPath = args[2];
|
||||
|
||||
auto emitCompileError = [&](SkSL::FileOutputStream& out, const char* errorText) {
|
||||
// Overwrite the compiler output, if any, with an error message.
|
||||
out.close();
|
||||
@ -515,41 +510,9 @@ ResultCode processCommand(std::vector<SkSL::String>& args) {
|
||||
printf("error writing '%s'\n", outputPath.c_str());
|
||||
return ResultCode::kOutputError;
|
||||
}
|
||||
} else if (outputPath.ends_with(".html")) {
|
||||
settings.fAllowTraceVarInSkVMDebugTrace = false;
|
||||
|
||||
SkCpu::CacheRuntimeFeatures();
|
||||
gSkVMAllowJIT = true;
|
||||
return compileProgramForSkVM(
|
||||
[&](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
|
||||
if (!debugTrace) {
|
||||
debugTrace = std::make_unique<SkSL::SkVMDebugTrace>();
|
||||
debugTrace->setSource(text.c_str());
|
||||
}
|
||||
auto visualizer = std::make_unique<skvm::viz::Visualizer>(debugTrace.get());
|
||||
skvm::Builder builder(skvm::Features{}, /*createDuplicates=*/true);
|
||||
if (!SkSL::testingOnly_ProgramToSkVMShader(program, &builder, debugTrace.get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<SkWStream> redirect = as_SkWStream(out);
|
||||
skvm::Program p = builder.done(
|
||||
/*debug_name=*/nullptr, /*allow_jit=*/true, std::move(visualizer));
|
||||
#if defined(SKVM_JIT)
|
||||
SkDynamicMemoryWStream asmFile;
|
||||
p.disassemble(&asmFile);
|
||||
auto dumpData = asmFile.detachAsData();
|
||||
std::string dumpString(static_cast<const char*>(dumpData->data()),dumpData->size());
|
||||
p.visualize(redirect.get(), dumpString.c_str());
|
||||
#else
|
||||
p.visualize(redirect.get(), nullptr);
|
||||
#endif
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
printf("expected output path to end with one of: .glsl, .html, .metal, .hlsl, .spirv, "
|
||||
".asm.frag, .skvm, .stage, .asm.vert, .dehydrated.sksl (got '%s')\n",
|
||||
outputPath.c_str());
|
||||
printf("expected output path to end with one of: .glsl, .metal, .hlsl, .spirv, .asm.frag, "
|
||||
".skvm, .stage, .asm.vert, .dehydrated.sksl (got '%s')\n", outputPath.c_str());
|
||||
return ResultCode::kConfigurationError;
|
||||
}
|
||||
return ResultCode::kSuccess;
|
||||
|
@ -1,505 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
#include "src/utils/SkVMVisualizer.h"
|
||||
#include <sstream>
|
||||
#include "src/core/SkStreamPriv.h"
|
||||
|
||||
namespace {
|
||||
|
||||
size_t get_addr(const char* str) {
|
||||
size_t addr;
|
||||
std::istringstream ss(str);
|
||||
ss >> std::hex >> addr;
|
||||
SkASSERT(!ss.fail());
|
||||
return addr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace skvm::viz {
|
||||
|
||||
bool Instruction::operator == (const Instruction& o) const {
|
||||
return this->kind == o.kind &&
|
||||
this->startCode == o.startCode &&
|
||||
this->endCode == o.endCode &&
|
||||
this->instructionIndex == o.instructionIndex &&
|
||||
this->instruction == o.instruction &&
|
||||
this->duplicates == o.duplicates;
|
||||
}
|
||||
|
||||
SkString Instruction::classes() const {
|
||||
SkString result((kind & InstructionFlags::kDead) ? "dead" : "normal");
|
||||
if (duplicates > 0) result += " origin";
|
||||
if (duplicates < 0) result += " deduped";
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t InstructionHash::operator()(const Instruction& i) const {
|
||||
uint32_t hash = 0;
|
||||
hash = SkOpts::hash_fn(&i.kind, sizeof(i.kind), hash);
|
||||
hash = SkOpts::hash_fn(&i.instructionIndex, sizeof(i.instructionIndex), hash);
|
||||
hash = SkOpts::hash_fn(&i.instruction, sizeof(i.instruction), hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
void Visualizer::parseDisassembler(SkWStream* output, const char* code) {
|
||||
if (code == nullptr) {
|
||||
fAsmLine = 0;
|
||||
return;
|
||||
}
|
||||
// Read the disassembled code from <_skvm_jit> until
|
||||
// the last command that is attached to the byte code
|
||||
// We skip all the prelude (main loop organizing and such)
|
||||
// generate the main loop running on vector values (keeping hoisted commands in place)
|
||||
// and skip the tail loop (which is the same as the main, only on scalar values)
|
||||
// We stop after the last byte code.
|
||||
SkTArray<SkString> commands;
|
||||
SkStrSplit(code, "\n", kStrict_SkStrSplitMode, &commands);
|
||||
for (const SkString& line : commands) {
|
||||
++fAsmLine;
|
||||
if (line.find("<_skvm_jit>") >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fAsmLine < commands.size()) {
|
||||
const SkString& line = commands[fAsmLine];
|
||||
SkTArray<SkString> tokens;
|
||||
SkStrSplit(line.c_str(), "\t", kStrict_SkStrSplitMode, &tokens);
|
||||
if (tokens.size() >= 2 && tokens[0].size() > 1) {
|
||||
fAsmStart = get_addr(tokens[0].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
fAsmEnd += fAsmStart;
|
||||
for (size_t i = fAsmLine; i < commands.size(); ++i) {
|
||||
const SkString& line = commands[i];
|
||||
SkTArray<SkString> tokens;
|
||||
SkStrSplit(line.c_str(), "\t", kStrict_SkStrSplitMode, &tokens);
|
||||
size_t addr = 0;
|
||||
if (tokens.size() >= 2 && tokens[0].size() > 1) {
|
||||
addr = get_addr(tokens[0].c_str());
|
||||
}
|
||||
if (addr > fAsmEnd) {
|
||||
break;
|
||||
}
|
||||
addr -= fAsmStart;
|
||||
if (!fAsm.empty()) {
|
||||
MachineCommand& prev = fAsm.back();
|
||||
if (prev.command.isEmpty()) {
|
||||
int len = addr - prev.address;
|
||||
prev.command.printf("{ align %d bytes }", len);
|
||||
}
|
||||
}
|
||||
SkString command;
|
||||
for (size_t t = 2; t < tokens.size(); ++t) {
|
||||
command += tokens[t];
|
||||
}
|
||||
fAsm.push_back({addr, tokens[0], command, tokens[1]});
|
||||
}
|
||||
if (!fAsm.empty()) {
|
||||
MachineCommand& prev = fAsm.back();
|
||||
if (prev.command.isEmpty()) {
|
||||
int len = fInstructions.back().endCode - prev.address;
|
||||
prev.command.printf("{ align %d bytes }", len);
|
||||
}
|
||||
}
|
||||
fAsmLine = 0;
|
||||
}
|
||||
|
||||
void Visualizer::dump(SkWStream* output, const char* code) {
|
||||
SkDebugfStream stream;
|
||||
fOutput = output ? output : &stream;
|
||||
this->parseDisassembler(output, code);
|
||||
this->dumpHead();
|
||||
for (size_t id = 0ul; id < fInstructions.size(); ++id) {
|
||||
this->dumpInstruction(id);
|
||||
}
|
||||
this->dumpTail();
|
||||
}
|
||||
|
||||
void Visualizer::markAsDeadCode(std::vector<bool>& live, const std::vector<int>& newIds) {
|
||||
for (size_t id = 0ul; id < fInstructions.size(); ++id) {
|
||||
Instruction& instruction = fInstructions[id];
|
||||
if (instruction.instructionIndex < 0) {
|
||||
// We skip commands that are duplicates of some other commands
|
||||
// They either will be dead or alive together with the origin
|
||||
continue;
|
||||
}
|
||||
SkASSERT(instruction.instructionIndex < (int)live.size());
|
||||
if (live[instruction.instructionIndex]) {
|
||||
instruction.instructionIndex = newIds[instruction.instructionIndex];
|
||||
fToDisassembler[instruction.instructionIndex] = id;
|
||||
} else {
|
||||
instruction.kind
|
||||
= static_cast<InstructionFlags>(instruction.kind | InstructionFlags::kDead);
|
||||
fToDisassembler[instruction.instructionIndex] = -1;
|
||||
// Anything negative meaning the command is duplicate/dead
|
||||
instruction.instructionIndex = -2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Visualizer::addInstructions(std::vector<skvm::Instruction>& program) {
|
||||
for (Val id = 0; id < (Val)program.size(); id++) {
|
||||
skvm::Instruction& instr = program[id];
|
||||
auto isDuplicate = instr.op == Op::duplicate;
|
||||
if (isDuplicate) {
|
||||
this->markAsDuplicate(instr.immA, id);
|
||||
instr = program[instr.immA];
|
||||
}
|
||||
this->addInstruction({
|
||||
viz::InstructionFlags::kNormal,
|
||||
/*startCode=*/0, /*endCode=0*/0,
|
||||
id,
|
||||
isDuplicate ? -1 : 0,
|
||||
instr
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Visualizer::addInstruction(Instruction skvm) {
|
||||
if (!touches_varying_memory(skvm.instruction.op)) {
|
||||
if (auto found = fIndex.find(skvm)) {
|
||||
auto& instruction = fInstructions[*found];
|
||||
++(instruction.duplicates);
|
||||
return;
|
||||
}
|
||||
}
|
||||
fIndex.set(skvm, fInstructions.size());
|
||||
fToDisassembler.set(skvm.instructionIndex, fInstructions.size());
|
||||
fInstructions.emplace_back(std::move(skvm));
|
||||
}
|
||||
|
||||
void Visualizer::finalize(const std::vector<skvm::Instruction>& all,
|
||||
const std::vector<skvm::OptimizedInstruction>& optimized) {
|
||||
for (Val id = 0; id < (Val)all.size(); id++) {
|
||||
if (optimized[id].can_hoist) {
|
||||
size_t found = fToDisassembler[id];
|
||||
Instruction& instruction = fInstructions[found];
|
||||
instruction.kind =
|
||||
static_cast<InstructionFlags>(instruction.kind | InstructionFlags::kHoisted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Visualizer::addMachineCommands(int id, size_t start, size_t end) {
|
||||
size_t found = fToDisassembler[id];
|
||||
Instruction& instruction = fInstructions[found];
|
||||
instruction.startCode = start;
|
||||
instruction.endCode = end;
|
||||
fAsmEnd = std::max(fAsmEnd, end);
|
||||
}
|
||||
|
||||
SkString Visualizer::V(int reg) const {
|
||||
if (reg == -1) {
|
||||
return SkString("{optimized}");
|
||||
} else if (reg == -2) {
|
||||
return SkString("{dead code}");
|
||||
} else {
|
||||
return SkStringPrintf("v%d", reg);
|
||||
}
|
||||
}
|
||||
|
||||
void Visualizer::formatVV(const char* op, int v1, int v2) const {
|
||||
this->writeText("%s %s, %s", op, V(v1).c_str(), V(v2).c_str());
|
||||
}
|
||||
void Visualizer::formatPV(const char* op, int imm, int v1) const {
|
||||
this->writeText("%s Ptr%d, %s", op, imm, V(v1).c_str());
|
||||
}
|
||||
void Visualizer::formatPVV(const char* op, int imm, int v1, int v2) const {
|
||||
this->writeText("%s Ptr%d, %s, %s", op, imm, V(v1).c_str(), V(v2).c_str());
|
||||
}
|
||||
void Visualizer::formatPVVVV(const char* op, int imm, int v1, int v2, int v3, int v4) const {
|
||||
this->writeText("%s Ptr%d, %s, %s, %s, %s",
|
||||
op, imm, V(v1).c_str(), V(v2).c_str(), V(v3).c_str(), V(v4).c_str());
|
||||
}
|
||||
void Visualizer::formatA_(int id, const char* op) const {
|
||||
writeText("%s = %s", V(id).c_str(), op);
|
||||
}
|
||||
void Visualizer::formatA_P(int id, const char* op, int imm) const {
|
||||
this->writeText("%s = %s Ptr%d", V(id).c_str(), op, imm);
|
||||
}
|
||||
void Visualizer::formatA_PH(int id, const char* op, int immA, int immB) const {
|
||||
this->writeText("%s = %s Ptr%d, %x", V(id).c_str(), op, immA, immB);
|
||||
}
|
||||
void Visualizer::formatA_PHH(int id, const char* op, int immA, int immB, int immC) const {
|
||||
this->writeText("%s = %s Ptr%d, %x, %x", V(id).c_str(), op, immA, immB, immC);
|
||||
}
|
||||
void Visualizer::formatA_PHV(int id, const char* op, int immA, int immB, int v) const {
|
||||
this->writeText("%s = %s Ptr%d, %x, V%d", V(id).c_str(), op, immA, immB, V(v).c_str());
|
||||
}
|
||||
void Visualizer::formatA_S(int id, const char* op, int imm) const {
|
||||
float f;
|
||||
memcpy(&f, &imm, 4);
|
||||
char buffer[kSkStrAppendScalar_MaxSize];
|
||||
char* stop = SkStrAppendScalar(buffer, f);
|
||||
this->writeText("%s = %s %x (", V(id).c_str(), op, imm);
|
||||
fOutput->write(buffer, stop - buffer);
|
||||
this->writeText(")");
|
||||
}
|
||||
void Visualizer::formatA_V(int id, const char* op, int v) const {
|
||||
this->writeText("%s = %s %s", V(id).c_str(), op, V(v).c_str());
|
||||
}
|
||||
void Visualizer::formatA_VV(int id, const char* op, int v1, int v2) const {
|
||||
this->writeText("%s = %s %s, %s", V(id).c_str(), op, V(v1).c_str(), V(v2).c_str());
|
||||
}
|
||||
void Visualizer::formatA_VVV(int id, const char* op, int v1, int v2, int v3) const {
|
||||
this->writeText(
|
||||
"%s = %s %s, %s, %s", V(id).c_str(), op, V(v1).c_str(), V(v2).c_str(), V(v3).c_str());
|
||||
}
|
||||
void Visualizer::formatA_VC(int id, const char* op, int v, int imm) const {
|
||||
this->writeText("%s = %s %s, %d", V(id).c_str(), op, V(v).c_str(), imm);
|
||||
}
|
||||
|
||||
void Visualizer::writeText(const char* format, ...) const {
|
||||
SkString message;
|
||||
va_list argp;
|
||||
va_start(argp, format);
|
||||
message.appendVAList(format, argp);
|
||||
va_end(argp);
|
||||
fOutput->writeText(message.c_str());
|
||||
}
|
||||
|
||||
void Visualizer::dumpInstruction(int id0) const {
|
||||
const Instruction& instruction = fInstructions[id0];
|
||||
const int id = instruction.instructionIndex;
|
||||
const int x = instruction.instruction.x,
|
||||
y = instruction.instruction.y,
|
||||
z = instruction.instruction.z,
|
||||
w = instruction.instruction.w;
|
||||
const int immA = instruction.instruction.immA,
|
||||
immB = instruction.instruction.immB,
|
||||
immC = instruction.instruction.immC;
|
||||
if (instruction.instruction.op == skvm::Op::trace_line) {
|
||||
SkASSERT(fDebugInfo != nullptr);
|
||||
SkASSERT(immA >= 0 && immB <= (int)fDebugInfo->fSource.size());
|
||||
this->writeText("<tr class='source'><td class='mask'></td><td colspan=2>// %s</td></tr>\n",
|
||||
fDebugInfo->fSource[immB].c_str());
|
||||
return;
|
||||
} else if (instruction.instruction.op == skvm::Op::trace_var ||
|
||||
instruction.instruction.op == skvm::Op::trace_scope) {
|
||||
// TODO: We can add some visualization here
|
||||
return;
|
||||
} else if (instruction.instruction.op == skvm::Op::trace_enter) {
|
||||
SkASSERT(fDebugInfo != nullptr);
|
||||
SkASSERT(immA >= 0 && immA <= (int)fDebugInfo->fFuncInfo.size());
|
||||
std::string& func = fDebugInfo->fFuncInfo[immA].name;
|
||||
SkString mask;
|
||||
mask.printf(immC == 1 ? "%s(-1)" : "%s", V(x).c_str());
|
||||
this->writeText(
|
||||
"<tr class='source'><td class='mask'>↪%s</td><td colspan=2>%s</td></tr>\n",
|
||||
mask.c_str(),
|
||||
func.c_str());
|
||||
return;
|
||||
} else if (instruction.instruction.op == skvm::Op::trace_exit) {
|
||||
SkASSERT(fDebugInfo != nullptr);
|
||||
SkASSERT(immA >= 0 && immA <= (int)fDebugInfo->fFuncInfo.size());
|
||||
std::string& func = fDebugInfo->fFuncInfo[immA].name;
|
||||
SkString mask;
|
||||
mask.printf(immC == 1 ? "%s(-1)" : "%s", V(x).c_str());
|
||||
this->writeText(
|
||||
"<tr class='source'><td class='mask'>↩%s</td><td colspan=2>%s</td></tr>\n",
|
||||
mask.c_str(),
|
||||
func.c_str());
|
||||
return;
|
||||
}
|
||||
// No label, to the operation
|
||||
SkString label;
|
||||
if ((instruction.kind & InstructionFlags::kHoisted) != 0) {
|
||||
label.set("↑↑↑ ");
|
||||
}
|
||||
if (instruction.duplicates > 0) {
|
||||
label.appendf("*%d", instruction.duplicates + 1);
|
||||
}
|
||||
SkString classes = instruction.classes();
|
||||
this->writeText("<tr class='%s'><td>%s</td><td>", classes.c_str(), label.c_str());
|
||||
// Operation
|
||||
switch (instruction.instruction.op) {
|
||||
case skvm::Op::assert_true: formatVV("assert_true", x, y); break;
|
||||
case skvm::Op::store8: formatPV("store8", immA, x); break;
|
||||
case skvm::Op::store16: formatPV("store16", immA, x); break;
|
||||
case skvm::Op::store32: formatPV("store32", immA, x); break;
|
||||
case skvm::Op::store64: formatPVV("store64", immA, x, y); break;
|
||||
case skvm::Op::store128: formatPVVVV("store128", immA, x, y, z, w); break;
|
||||
case skvm::Op::index: formatA_(id, "index"); break;
|
||||
case skvm::Op::load8: formatA_P(id, "load8", immA); break;
|
||||
case skvm::Op::load16: formatA_P(id, "load16", immA); break;
|
||||
case skvm::Op::load32: formatA_P(id, "load32", immA); break;
|
||||
case skvm::Op::load64: formatA_PH(id, "load64", immA, immB); break;
|
||||
case skvm::Op::load128: formatA_PH(id, "load128", immA, immB); break;
|
||||
case skvm::Op::gather8: formatA_PHV(id, "gather8", immA, immB, x); break;
|
||||
case skvm::Op::gather16: formatA_PHV(id, "gather16", immA, immB, x); break;
|
||||
case skvm::Op::gather32: formatA_PHV(id, "gather32", immA, immB, x); break;
|
||||
case skvm::Op::uniform32: formatA_PH(id, "uniform32", immA, immB); break;
|
||||
case skvm::Op::array32: formatA_PHH(id, "array32", immA, immB, immC); break;
|
||||
case skvm::Op::splat: formatA_S(id, "splat", immA); break;
|
||||
case skvm::Op:: add_f32: formatA_VV(id, "add_f32", x, y); break;
|
||||
case skvm::Op:: sub_f32: formatA_VV(id, "sub_f32", x, y); break;
|
||||
case skvm::Op:: mul_f32: formatA_VV(id, "mul_f32", x, y); break;
|
||||
case skvm::Op:: div_f32: formatA_VV(id, "div_f32", x, y); break;
|
||||
case skvm::Op:: min_f32: formatA_VV(id, "min_f32", x, y); break;
|
||||
case skvm::Op:: max_f32: formatA_VV(id, "max_f32", x, y); break;
|
||||
case skvm::Op:: fma_f32: formatA_VVV(id, "fma_f32", x, y, z); break;
|
||||
case skvm::Op:: fms_f32: formatA_VVV(id, "fms_f32", x, y, z); break;
|
||||
case skvm::Op::fnma_f32: formatA_VVV(id, "fnma_f32", x, y, z); break;
|
||||
case skvm::Op::sqrt_f32: formatA_V(id, "sqrt_f32", x); break;
|
||||
case skvm::Op:: eq_f32: formatA_VV(id, "eq_f32", x, y); break;
|
||||
case skvm::Op::neq_f32: formatA_VV(id, "neq_f32", x, y); break;
|
||||
case skvm::Op:: gt_f32: formatA_VV(id, "gt_f32", x, y); break;
|
||||
case skvm::Op::gte_f32: formatA_VV(id, "gte_f32", x, y); break;
|
||||
case skvm::Op::add_i32: formatA_VV(id, "add_i32", x, y); break;
|
||||
case skvm::Op::sub_i32: formatA_VV(id, "sub_i32", x, y); break;
|
||||
case skvm::Op::mul_i32: formatA_VV(id, "mul_i32", x, y); break;
|
||||
case skvm::Op::shl_i32: formatA_VC(id, "shl_i32", x, immA); break;
|
||||
case skvm::Op::shr_i32: formatA_VC(id, "shr_i32", x, immA); break;
|
||||
case skvm::Op::sra_i32: formatA_VC(id, "sra_i32", x, immA); break;
|
||||
case skvm::Op::eq_i32: formatA_VV(id, "eq_i32", x, y); break;
|
||||
case skvm::Op::gt_i32: formatA_VV(id, "gt_i32", x, y); break;
|
||||
case skvm::Op::bit_and: formatA_VV(id, "bit_and", x, y); break;
|
||||
case skvm::Op::bit_or: formatA_VV(id, "bit_or", x, y); break;
|
||||
case skvm::Op::bit_xor: formatA_VV(id, "bit_xor", x, y); break;
|
||||
case skvm::Op::bit_clear: formatA_VV(id, "bit_clear", x, y); break;
|
||||
case skvm::Op::select: formatA_VVV(id, "select", x, y, z); break;
|
||||
case skvm::Op::ceil: formatA_V(id, "ceil", x); break;
|
||||
case skvm::Op::floor: formatA_V(id, "floor", x); break;
|
||||
case skvm::Op::to_f32: formatA_V(id, "to_f32", x); break;
|
||||
case skvm::Op::to_fp16: formatA_V(id, "to_fp16", x); break;
|
||||
case skvm::Op::from_fp16: formatA_V(id, "from_fp16", x); break;
|
||||
case skvm::Op::trunc: formatA_V(id, "trunc", x); break;
|
||||
case skvm::Op::round: formatA_V(id, "round", x); break;
|
||||
default: SkASSERT(false);
|
||||
}
|
||||
// Generation
|
||||
if ((instruction.kind & InstructionFlags::kDead) == 0) {
|
||||
struct Compare
|
||||
{
|
||||
bool operator() (const MachineCommand& c, std::pair<size_t, size_t> p) const
|
||||
{ return c.address < p.first; }
|
||||
bool operator() (std::pair<size_t, size_t> p, const MachineCommand& c) const
|
||||
{ return p.second <= c.address; }
|
||||
};
|
||||
|
||||
std::pair<size_t, size_t> range(instruction.startCode, instruction.endCode);
|
||||
auto commands = std::equal_range(fAsm.begin(), fAsm.end(), range, Compare{ });
|
||||
for (const MachineCommand* line = commands.first; line != commands.second; ++line) {
|
||||
this->writeText("</td></tr>\n<tr class='machine'><td>%s</td><td colspan='2'>%s",
|
||||
line->label.c_str(),
|
||||
line->command.c_str());
|
||||
}
|
||||
fAsmLine = commands.second - fAsm.begin();
|
||||
}
|
||||
this->writeText("</td></tr>\n");
|
||||
}
|
||||
|
||||
void Visualizer::dumpHead() const {
|
||||
this->writeText(
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
" <title>SkVM Disassembler Output</title>\n"
|
||||
" <style>\n"
|
||||
" button { border-style: none; font-size: 10px; background-color: lightpink; }\n"
|
||||
" table { text-align: left; }\n"
|
||||
" table th { background-color: lightgray; }\n"
|
||||
" .dead, .dead1 { color: lightgray; text-decoration: line-through; }\n"
|
||||
" .normal, .normal1 { }\n"
|
||||
" .origin, .origin1 { font-weight: bold; }\n"
|
||||
" .source, .source1 { color: darkblue; }\n"
|
||||
" .mask, .mask1 { color: green; }\n"
|
||||
" .comments, .comments1 { }\n"
|
||||
" .machine, .machine1 { color: lightblue; }\n"
|
||||
" </style>\n"
|
||||
" <script>\n"
|
||||
" function initializeButton(className) {\n"
|
||||
" var btn = document.getElementById(className);\n"
|
||||
" var elems = document.getElementsByClassName(className);\n"
|
||||
" if (elems == undefined || elems.length == 0) {\n"
|
||||
" btn.disabled = true;\n"
|
||||
" btn.innerText = \"None\";\n"
|
||||
" btn.style.background = \"lightgray\";\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" };\n"
|
||||
" function initialize() {\n"
|
||||
" initializeButton('normal');\n"
|
||||
" initializeButton('source');\n"
|
||||
" initializeButton('dead');\n"
|
||||
" initializeButton('machine');\n"
|
||||
" };\n"
|
||||
" </script>\n"
|
||||
"</head>\n"
|
||||
"<body onload='initialize();'>\n"
|
||||
" <script>\n"
|
||||
" function toggle(btn, className) {\n"
|
||||
" var elems = document.getElementsByClassName(className);\n"
|
||||
" for (var i = 0; i < elems.length; i++) {\n"
|
||||
" var elem = elems.item(i);\n"
|
||||
" if (elem.style.display === \"\") {\n"
|
||||
" elem.style.display = \"none\";\n"
|
||||
" btn.innerText = \"Show\";\n"
|
||||
" btn.style.background = \"lightcyan\";\n"
|
||||
" } else {\n"
|
||||
" elem.style.display = \"\";\n"
|
||||
" btn.innerText = \"Hide\";\n"
|
||||
" btn.style.background = \"lightpink\";\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" };\n"
|
||||
" </script>"
|
||||
" <table border=\"0\" style='font-family:\"monospace\"; font-size: 10px;'>\n"
|
||||
" <caption style='font-family:Roboto; font-size:15px; text-align:left;'>Legend</caption>\n"
|
||||
" <tr>\n"
|
||||
" <th style=\"min-width:100px;\"><u>Kind</u></th>\n"
|
||||
" <th style=\"width:35%;\"><u>Example</u></th>\n"
|
||||
" <th style=\"width: 5%; min-width:50px;\"><u></u></th>\n"
|
||||
" <th style=\"width:60%;\"><u>Description</u></th>\n"
|
||||
" </tr>\n"
|
||||
" <tr class='normal1'>"
|
||||
"<td> </td>"
|
||||
"<td>v1 = load32 Ptr1</td>"
|
||||
"<td><button id='normal' onclick=\"toggle(this, 'normal')\">Hide</button></td>"
|
||||
"<td>A regular SkVM command</td></tr>\n"
|
||||
" <tr class='normal1 origin1'><td>*{N}</td>"
|
||||
"<td>v9 = gt_f32 v0, v1</td>"
|
||||
"<td><button id='dead' onclick=\"toggle(this, 'deduped')\">Hide</button></td>"
|
||||
"<td>A {N} times deduped SkVM command</td></tr>\n"
|
||||
" <tr class='normal1'><td>↑↑↑ </td>"
|
||||
"<td>v22 = splat 3f800000 (1)</td><td></td>"
|
||||
"<td>A hoisted SkVM command</td></tr>\n"
|
||||
" <tr class='source1'><td class='mask'>mask↪v{N}(-1)</td>"
|
||||
"<td>// C++ source line</td><td></td>"
|
||||
"<td>Enter into the procedure with mask v{N} (which has a constant value -1)"
|
||||
"</td></tr>\n"
|
||||
" <tr class='source1'><td class='mask'>mask↩v{N}</td>"
|
||||
"<td>// C++ source line</td><td>"
|
||||
"</td><td>Exit the procedure with mask v{N}</td></tr>\n"
|
||||
" <tr class='source1'><td class='mask'></td><td>// C++ source line</td>"
|
||||
"<td><button id='source' onclick=\"toggle(this, 'source')\">Hide</button></td>"
|
||||
"<td>Line trace back to C++ code</td></tr>\n"
|
||||
" <tr class='dead1'><td></td><td>{dead code} = mul_f32 v1, v18</td>"
|
||||
"<td><button id='dead' onclick=\"toggle(this, 'dead')\">Hide</button></td>"
|
||||
"<td>An eliminated \"dead code\" SkVM command</td></tr>\n"
|
||||
" <tr class='machine1'><td>{address}</td><td>vmovups (%rsi),%ymm0</td>"
|
||||
"<td><button id='machine' onclick=\"toggle(this, 'machine')\">Hide</button></td>"
|
||||
"<td>A disassembled machine command generated by SkVM command</td></tr>\n"
|
||||
" </table>\n"
|
||||
" <table border = \"0\"style='font-family:\"monospace\"; font-size: 10px;'>\n"
|
||||
" <caption style='font-family:Roboto;font-size:15px;text-align:left;'>SkVM Code</caption>\n"
|
||||
" <tr>\n"
|
||||
" <th style=\"min-width:100px;\"><u>Kind</u></th>\n"
|
||||
" <th style=\"width:40%;min-width:100px;\"><u>Command</u></th>\n"
|
||||
" <th style=\"width:60%;\"><u>Comments</u></th>\n"
|
||||
" </tr>");
|
||||
}
|
||||
void Visualizer::dumpTail() const {
|
||||
this->writeText(
|
||||
" </table>\n"
|
||||
"</body>\n"
|
||||
"</html>"
|
||||
);
|
||||
}
|
||||
} // namespace skvm::viz
|
@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
#ifndef SkVMVisualizer_DEFINED
|
||||
#define SkVMVisualizer_DEFINED
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "include/core/SkSpan.h"
|
||||
#include "include/core/SkStream.h"
|
||||
#include "include/core/SkString.h"
|
||||
#include "include/private/SkBitmaskEnum.h"
|
||||
#include "include/private/SkChecksum.h"
|
||||
#include "include/private/SkTHash.h"
|
||||
#include "src/core/SkOpts.h"
|
||||
#include "src/sksl/SkSLOutputStream.h"
|
||||
#include "src/sksl/tracing/SkVMDebugTrace.h"
|
||||
|
||||
namespace skvm::viz {
|
||||
enum InstructionFlags : uint8_t {
|
||||
kNormal = 0x00,
|
||||
kHoisted = 0x01,
|
||||
kDead = 0x02,
|
||||
};
|
||||
|
||||
struct MachineCommand {
|
||||
size_t address;
|
||||
SkString label;
|
||||
SkString command;
|
||||
SkString extra;
|
||||
};
|
||||
|
||||
struct Instruction {
|
||||
InstructionFlags kind = InstructionFlags::kNormal;
|
||||
// Machine commands range (for disassembling):
|
||||
size_t startCode = 0;
|
||||
size_t endCode = 0;
|
||||
int instructionIndex; // index in the actual instructions list
|
||||
int duplicates = 0; // number of duplicates;
|
||||
// -1 means it's a duplicate itself; 0 - it does not have dups
|
||||
skvm::Instruction instruction;
|
||||
bool operator == (const Instruction& o) const;
|
||||
SkString classes() const;
|
||||
};
|
||||
|
||||
struct InstructionHash {
|
||||
uint32_t operator()(const Instruction& i) const;
|
||||
};
|
||||
|
||||
class Visualizer {
|
||||
public:
|
||||
explicit Visualizer(SkSL::SkVMDebugTrace* debugInfo)
|
||||
: fDebugInfo(debugInfo), fOutput(nullptr) {}
|
||||
~Visualizer() = default;
|
||||
void dump(SkWStream* output, const char* code);
|
||||
void markAsDeadCode(std::vector<bool>& live, const std::vector<int>& newIds);
|
||||
void finalize(const std::vector<skvm::Instruction>& all,
|
||||
const std::vector<skvm::OptimizedInstruction>& optimized);
|
||||
void addInstructions(std::vector<skvm::Instruction>& program);
|
||||
void markAsDuplicate(int origin, int id) {
|
||||
++fInstructions[origin].duplicates;
|
||||
}
|
||||
void addInstruction(Instruction skvm);
|
||||
void addMachineCommands(int id, size_t start, size_t end);
|
||||
SkString V(int reg) const;
|
||||
private:
|
||||
void parseDisassembler(SkWStream* output, const char* code);
|
||||
void dumpInstruction(int id) const;
|
||||
void dumpHead() const;
|
||||
void dumpTail() const;
|
||||
void formatVV(const char* op, int v1, int v2) const;
|
||||
void formatPV(const char* op, int imm, int v1) const;
|
||||
void formatPVV(const char* op, int imm, int v1, int v2) const;
|
||||
void formatPVVVV(const char* op, int imm, int v1, int v2, int v3, int v4) const;
|
||||
void formatA_(int id, const char* op) const;
|
||||
void formatA_P(int id, const char* op, int imm) const;
|
||||
void formatA_PH(int id, const char* op, int immA, int immB) const;
|
||||
void formatA_PHH(int id, const char* op, int immA, int immB, int immC) const;
|
||||
void formatA_PHV(int id, const char* op, int immA, int immB, int v) const;
|
||||
void formatA_S(int id, const char* op, int imm) const;
|
||||
void formatA_V(int id, const char* op, int v) const;
|
||||
void formatA_VV(int id, const char* op, int v1, int v2) const;
|
||||
void formatA_VVV(int id, const char* op, int v1, int v2, int v3) const;
|
||||
void formatA_VC(int id, const char* op, int v, int imm) const;
|
||||
|
||||
void writeText(const char* format, ...) const;
|
||||
|
||||
SkSL::SkVMDebugTrace* fDebugInfo;
|
||||
SkTHashMap<Instruction, size_t, InstructionHash> fIndex;
|
||||
SkTArray<Instruction> fInstructions;
|
||||
SkWStream* fOutput;
|
||||
SkTHashMap<int, size_t> fToDisassembler;
|
||||
SkTArray<MachineCommand> fAsm;
|
||||
mutable size_t fAsmLine = 0;
|
||||
size_t fAsmStart = 0;
|
||||
size_t fAsmEnd = 0;
|
||||
};
|
||||
} // namespace skvm::viz
|
||||
|
||||
namespace sknonstd {
|
||||
template <> struct is_bitmask_enum<skvm::viz::InstructionFlags> : std::true_type {};
|
||||
} // namespace sknonstd
|
||||
|
||||
#endif // SkVMVisualizer_DEFINED
|
@ -10,11 +10,6 @@
|
||||
#include "src/core/SkCpu.h"
|
||||
#include "src/core/SkMSAN.h"
|
||||
#include "src/core/SkVM.h"
|
||||
#include "src/gpu/GrShaderCaps.h"
|
||||
#include "src/sksl/SkSLCompiler.h"
|
||||
#include "src/sksl/codegen/SkSLVMCodeGenerator.h"
|
||||
#include "src/sksl/tracing/SkVMDebugTrace.h"
|
||||
#include "src/utils/SkVMVisualizer.h"
|
||||
#include "tests/Test.h"
|
||||
|
||||
template <typename Fn>
|
||||
@ -2798,59 +2793,3 @@ DEF_TEST(SkVM_duplicates, reporter) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEF_TEST(SkVM_Visualizer, r) {
|
||||
const char* src =
|
||||
"int main(int x, int y) {\n"
|
||||
" int a = 99;\n"
|
||||
" if (x > 0) a += 100;\n"
|
||||
" if (y > 0) a += 101;\n"
|
||||
" a = 102;\n"
|
||||
" return a;\n"
|
||||
"}";
|
||||
GrShaderCaps caps;
|
||||
SkSL::Compiler compiler(&caps);
|
||||
SkSL::Program::Settings settings;
|
||||
auto program = compiler.convertProgram(SkSL::ProgramKind::kGeneric,
|
||||
SkSL::String(src), settings);
|
||||
const SkSL::FunctionDefinition* main = SkSL::Program_GetFunction(*program, "main");
|
||||
SkSL::SkVMDebugTrace d;
|
||||
d.setSource(src);
|
||||
auto v = std::make_unique<skvm::viz::Visualizer>(&d);
|
||||
skvm::Builder b(skvm::Features{}, /*createDuplicates=*/true);
|
||||
SkSL::ProgramToSkVM(*program, *main, &b, &d, /*uniforms=*/{});
|
||||
|
||||
skvm::Program p = b.done(nullptr, true, std::move(v));
|
||||
#if defined(SKVM_JIT)
|
||||
SkDynamicMemoryWStream asmFile;
|
||||
p.disassemble(&asmFile);
|
||||
auto dumpData = asmFile.detachAsData();
|
||||
std::string dumpString((const char*)dumpData->data(), dumpData->size());
|
||||
#else
|
||||
std::string dumpString(nullptr, 0);
|
||||
#endif
|
||||
SkDynamicMemoryWStream vizFile;
|
||||
p.visualizer()->dump(&vizFile, dumpString.c_str());
|
||||
auto vizData = vizFile.detachAsData();
|
||||
std::string html((const char*)vizData->data(), vizData->size());
|
||||
//b.dump();
|
||||
//std::printf(html.c_str());
|
||||
// Check that html contains all types of information:
|
||||
if (!dumpString.empty()) {
|
||||
REPORTER_ASSERT(r, std::strstr(html.c_str(), "<tr class='machine'>")); // machine commands
|
||||
}
|
||||
REPORTER_ASSERT(r, std::strstr(html.c_str(), "<tr class='normal'>")); // SkVM byte code
|
||||
REPORTER_ASSERT(r, std::strstr(html.c_str(), "<tr class='source'>")); // C++ source
|
||||
REPORTER_ASSERT(r, std::strstr(html.c_str(), "<tr class='dead'>")); // dead code
|
||||
REPORTER_ASSERT(r, std::strstr(html.c_str(), "<tr class='dead deduped'>")); // deduped removed
|
||||
REPORTER_ASSERT(r, std::strstr(html.c_str(), // deduped origins
|
||||
"<tr class='normal origin'>"
|
||||
"<td>↑↑↑ *13</td>"
|
||||
"<td>v2 = splat 0 (0)</td></tr>"));
|
||||
REPORTER_ASSERT(r, std::strstr(html.c_str(), // trace enter
|
||||
"<tr class='source'><td class='mask'>↪v9</td>"
|
||||
"<td colspan=2>int main(int x, int y)</td></tr>"));
|
||||
REPORTER_ASSERT(r, std::strstr(html.c_str(), // trace exit
|
||||
"<tr class='source'><td class='mask'>↩v9</td>"
|
||||
"<td colspan=2>int main(int x, int y)</td></tr>"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user