summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/utils/SkVMVisualizer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/skia/skia/src/utils/SkVMVisualizer.cpp')
-rw-r--r--gfx/skia/skia/src/utils/SkVMVisualizer.cpp407
1 files changed, 407 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/utils/SkVMVisualizer.cpp b/gfx/skia/skia/src/utils/SkVMVisualizer.cpp
new file mode 100644
index 0000000000..cee4735bfd
--- /dev/null
+++ b/gfx/skia/skia/src/utils/SkVMVisualizer.cpp
@@ -0,0 +1,407 @@
+/*
+ * 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 "include/core/SkStream.h"
+#include "include/private/SkOpts_spi.h"
+#include "src/core/SkStreamPriv.h"
+
+#if defined(SK_ENABLE_SKSL)
+#include "src/sksl/tracing/SkSLDebugInfo.h"
+#include "src/sksl/tracing/SkVMDebugTrace.h"
+#endif
+
+#include <cstdarg>
+#include <cstring>
+#include <string>
+#include <utility>
+
+namespace skvm::viz {
+
+#if defined(SK_ENABLE_SKSL)
+Visualizer::Visualizer(SkSL::SkVMDebugTrace* debugInfo) : fDebugInfo(debugInfo), fOutput(nullptr) {}
+#else
+Visualizer::Visualizer(SkSL::SkVMDebugTrace* debugInfo) : fOutput(nullptr) {}
+#endif
+
+bool Instruction::operator == (const Instruction& o) const {
+ return this->kind == o.kind &&
+ 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::dump(SkWStream* output) {
+ SkDebugfStream stream;
+ fOutput = output ? output : &stream;
+ this->dumpHead();
+ for (int id = 0; id < fInstructions.size(); ++id) {
+ this->dumpInstruction(id);
+ }
+ this->dumpTail();
+}
+
+void Visualizer::markAsDeadCode(std::vector<bool>& live, const std::vector<int>& newIds) {
+ for (int id = 0; 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, 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);
+ }
+ }
+}
+
+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, %s", 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 defined(SK_ENABLE_SKSL)
+ 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'>&#8618;%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'>&#8617;%s</td><td colspan=2>%s</td></tr>\n",
+ mask.c_str(),
+ func.c_str());
+ return;
+ }
+#endif // defined(SK_ENABLE_SKSL)
+ // No label, to the operation
+ SkString label;
+ if ((instruction.kind & InstructionFlags::kHoisted) != 0) {
+ label.set("&#8593;&#8593;&#8593; ");
+ }
+ 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);
+ }
+ this->writeText("</td></tr>\n");
+}
+
+void Visualizer::dumpHead() const {
+ this->writeText("%s",
+ "<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"
+ " </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"
+ " };\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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</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>&#8593;&#8593;&#8593; &nbsp;&nbsp;&nbsp;</td>"
+ "<td>v22 = splat 3f800000 (1)</td><td></td>"
+ "<td>A hoisted SkVM command</td></tr>\n"
+ " <tr class='source1'><td class='mask'>mask&#8618;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&#8617;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"
+ " </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