• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 #include "src/utils/SkVMVisualizer.h"
8 
9 #include "include/core/SkStream.h"
10 #include "include/private/SkOpts_spi.h"
11 #include "src/core/SkStreamPriv.h"
12 
13 #if defined(SK_ENABLE_SKSL)
14 #include "src/sksl/tracing/SkSLDebugInfo.h"
15 #include "src/sksl/tracing/SkVMDebugTrace.h"
16 #endif
17 
18 #include <cstdarg>
19 #include <cstring>
20 #include <string>
21 #include <utility>
22 
23 namespace skvm::viz {
24 
25 #if defined(SK_ENABLE_SKSL)
Visualizer(SkSL::SkVMDebugTrace * debugInfo)26 Visualizer::Visualizer(SkSL::SkVMDebugTrace* debugInfo) : fDebugInfo(debugInfo), fOutput(nullptr) {}
27 #else
28 Visualizer::Visualizer(SkSL::SkVMDebugTrace* debugInfo) : fOutput(nullptr) {}
29 #endif
30 
operator ==(const Instruction & o) const31 bool Instruction::operator == (const Instruction& o) const {
32     return this->kind == o.kind &&
33            this->instructionIndex == o.instructionIndex &&
34            this->instruction == o.instruction &&
35            this->duplicates == o.duplicates;
36 }
37 
classes() const38 SkString Instruction::classes() const {
39     SkString result((kind & InstructionFlags::kDead) ? "dead" : "normal");
40     if (duplicates > 0) result += " origin";
41     if (duplicates < 0) result += " deduped";
42     return result;
43 }
44 
operator ()(const Instruction & i) const45 uint32_t InstructionHash::operator()(const Instruction& i) const {
46     uint32_t hash = 0;
47     hash = SkOpts::hash_fn(&i.kind, sizeof(i.kind), hash);
48     hash = SkOpts::hash_fn(&i.instructionIndex, sizeof(i.instructionIndex), hash);
49     hash = SkOpts::hash_fn(&i.instruction, sizeof(i.instruction), hash);
50     return hash;
51 }
52 
dump(SkWStream * output)53 void Visualizer::dump(SkWStream* output) {
54     SkDebugfStream stream;
55     fOutput = output ? output : &stream;
56     this->dumpHead();
57     for (int id = 0; id < fInstructions.size(); ++id) {
58         this->dumpInstruction(id);
59     }
60     this->dumpTail();
61 }
62 
markAsDeadCode(std::vector<bool> & live,const std::vector<int> & newIds)63 void Visualizer::markAsDeadCode(std::vector<bool>& live, const std::vector<int>& newIds) {
64     for (int id = 0; id < fInstructions.size(); ++id) {
65         Instruction& instruction = fInstructions[id];
66         if (instruction.instructionIndex < 0) {
67             // We skip commands that are duplicates of some other commands
68             // They either will be dead or alive together with the origin
69             continue;
70         }
71         SkASSERT(instruction.instructionIndex < (int)live.size());
72         if (live[instruction.instructionIndex]) {
73             instruction.instructionIndex = newIds[instruction.instructionIndex];
74             fToDisassembler[instruction.instructionIndex] = id;
75         } else {
76             instruction.kind
77                     = static_cast<InstructionFlags>(instruction.kind | InstructionFlags::kDead);
78             fToDisassembler[instruction.instructionIndex] = -1;
79             // Anything negative meaning the command is duplicate/dead
80             instruction.instructionIndex = -2;
81         }
82     }
83 }
84 
addInstructions(std::vector<skvm::Instruction> & program)85 void Visualizer::addInstructions(std::vector<skvm::Instruction>& program) {
86     for (Val id = 0; id < (Val)program.size(); id++) {
87         skvm::Instruction& instr = program[id];
88         auto isDuplicate = instr.op == Op::duplicate;
89         if (isDuplicate) {
90             this->markAsDuplicate(instr.immA, id);
91             instr = program[instr.immA];
92         }
93         this->addInstruction({viz::InstructionFlags::kNormal, id, isDuplicate ? -1 : 0, instr});
94     }
95 }
96 
addInstruction(Instruction skvm)97 void Visualizer::addInstruction(Instruction skvm) {
98     if (!touches_varying_memory(skvm.instruction.op)) {
99         if (auto found = fIndex.find(skvm)) {
100             auto& instruction = fInstructions[*found];
101             ++(instruction.duplicates);
102             return;
103         }
104     }
105     fIndex.set(skvm, fInstructions.size());
106     fToDisassembler.set(skvm.instructionIndex, fInstructions.size());
107     fInstructions.emplace_back(std::move(skvm));
108 }
109 
finalize(const std::vector<skvm::Instruction> & all,const std::vector<skvm::OptimizedInstruction> & optimized)110 void Visualizer::finalize(const std::vector<skvm::Instruction>& all,
111                               const std::vector<skvm::OptimizedInstruction>& optimized) {
112     for (Val id = 0; id < (Val)all.size(); id++) {
113         if (optimized[id].can_hoist) {
114             size_t found = fToDisassembler[id];
115             Instruction& instruction = fInstructions[found];
116             instruction.kind =
117                     static_cast<InstructionFlags>(instruction.kind | InstructionFlags::kHoisted);
118         }
119     }
120 }
121 
V(int reg) const122 SkString Visualizer::V(int reg) const {
123     if (reg == -1) {
124         return SkString("{optimized}");
125     } else if (reg == -2) {
126         return SkString("{dead code}");
127     } else {
128         return SkStringPrintf("v%d", reg);
129     }
130 }
131 
formatVV(const char * op,int v1,int v2) const132 void Visualizer::formatVV(const char* op, int v1, int v2) const {
133     this->writeText("%s %s, %s", op, V(v1).c_str(), V(v2).c_str());
134 }
formatPV(const char * op,int imm,int v1) const135 void Visualizer::formatPV(const char* op, int imm, int v1) const {
136     this->writeText("%s Ptr%d, %s", op, imm, V(v1).c_str());
137 }
formatPVV(const char * op,int imm,int v1,int v2) const138 void Visualizer::formatPVV(const char* op, int imm, int v1, int v2) const {
139     this->writeText("%s Ptr%d, %s, %s", op, imm, V(v1).c_str(), V(v2).c_str());
140 }
formatPVVVV(const char * op,int imm,int v1,int v2,int v3,int v4) const141 void Visualizer::formatPVVVV(const char* op, int imm, int v1, int v2, int v3, int v4) const {
142     this->writeText("%s Ptr%d, %s, %s, %s, %s",
143               op, imm, V(v1).c_str(), V(v2).c_str(), V(v3).c_str(), V(v4).c_str());
144 }
formatA_(int id,const char * op) const145 void Visualizer::formatA_(int id, const char* op) const {
146     writeText("%s = %s", V(id).c_str(), op);
147 }
formatA_P(int id,const char * op,int imm) const148 void Visualizer::formatA_P(int id, const char* op, int imm) const {
149     this->writeText("%s = %s Ptr%d", V(id).c_str(), op, imm);
150 }
formatA_PH(int id,const char * op,int immA,int immB) const151 void Visualizer::formatA_PH(int id, const char* op, int immA, int immB) const {
152     this->writeText("%s = %s Ptr%d, %x", V(id).c_str(), op, immA, immB);
153 }
formatA_PHH(int id,const char * op,int immA,int immB,int immC) const154 void Visualizer::formatA_PHH(int id, const char* op, int immA, int immB, int immC) const {
155     this->writeText("%s = %s Ptr%d, %x, %x", V(id).c_str(), op, immA, immB, immC);
156 }
formatA_PHV(int id,const char * op,int immA,int immB,int v) const157 void Visualizer::formatA_PHV(int id, const char* op, int immA, int immB, int v) const {
158     this->writeText("%s = %s Ptr%d, %x, %s", V(id).c_str(), op, immA, immB, V(v).c_str());
159 }
formatA_S(int id,const char * op,int imm) const160 void Visualizer::formatA_S(int id, const char* op, int imm) const {
161     float f;
162     memcpy(&f, &imm, 4);
163     char buffer[kSkStrAppendScalar_MaxSize];
164     char* stop = SkStrAppendScalar(buffer, f);
165     this->writeText("%s = %s %x (", V(id).c_str(), op, imm);
166     fOutput->write(buffer, stop - buffer);
167     this->writeText(")");
168 }
formatA_V(int id,const char * op,int v) const169 void Visualizer::formatA_V(int id, const char* op, int v) const {
170     this->writeText("%s = %s %s", V(id).c_str(), op, V(v).c_str());
171 }
formatA_VV(int id,const char * op,int v1,int v2) const172 void Visualizer::formatA_VV(int id, const char* op, int v1, int v2) const {
173     this->writeText("%s = %s %s, %s", V(id).c_str(), op, V(v1).c_str(), V(v2).c_str());
174 }
formatA_VVV(int id,const char * op,int v1,int v2,int v3) const175 void Visualizer::formatA_VVV(int id, const char* op,  int v1, int v2, int v3) const {
176     this->writeText(
177             "%s = %s %s, %s, %s", V(id).c_str(), op, V(v1).c_str(), V(v2).c_str(), V(v3).c_str());
178 }
formatA_VC(int id,const char * op,int v,int imm) const179 void Visualizer::formatA_VC(int id, const char* op,  int v, int imm) const {
180     this->writeText("%s = %s %s, %d", V(id).c_str(), op, V(v).c_str(), imm);
181 }
182 
writeText(const char * format,...) const183 void Visualizer::writeText(const char* format, ...) const {
184     SkString message;
185     va_list argp;
186     va_start(argp, format);
187     message.appendVAList(format, argp);
188     va_end(argp);
189     fOutput->writeText(message.c_str());
190 }
191 
dumpInstruction(int id0) const192 void Visualizer::dumpInstruction(int id0) const {
193     const Instruction& instruction = fInstructions[id0];
194     const int id = instruction.instructionIndex;
195     const int x = instruction.instruction.x,
196               y = instruction.instruction.y,
197               z = instruction.instruction.z,
198               w = instruction.instruction.w;
199     const int immA = instruction.instruction.immA,
200               immB = instruction.instruction.immB,
201               immC = instruction.instruction.immC;
202 #if defined(SK_ENABLE_SKSL)
203     if (instruction.instruction.op == skvm::Op::trace_line) {
204         SkASSERT(fDebugInfo != nullptr);
205         SkASSERT(immA >= 0 && immB <= (int)fDebugInfo->fSource.size());
206         this->writeText("<tr class='source'><td class='mask'></td><td colspan=2>// %s</td></tr>\n",
207                         fDebugInfo->fSource[immB].c_str());
208         return;
209     } else if (instruction.instruction.op == skvm::Op::trace_var ||
210                instruction.instruction.op == skvm::Op::trace_scope) {
211         // TODO: We can add some visualization here
212         return;
213     } else if (instruction.instruction.op == skvm::Op::trace_enter) {
214         SkASSERT(fDebugInfo != nullptr);
215         SkASSERT(immA >= 0 && immA <= (int)fDebugInfo->fFuncInfo.size());
216         std::string& func = fDebugInfo->fFuncInfo[immA].name;
217         SkString mask;
218         mask.printf(immC == 1 ? "%s(-1)" : "%s", V(x).c_str());
219         this->writeText(
220                 "<tr class='source'><td class='mask'>&#8618;%s</td><td colspan=2>%s</td></tr>\n",
221                 mask.c_str(),
222                 func.c_str());
223         return;
224     } else if (instruction.instruction.op == skvm::Op::trace_exit) {
225         SkASSERT(fDebugInfo != nullptr);
226         SkASSERT(immA >= 0 && immA <= (int)fDebugInfo->fFuncInfo.size());
227         std::string& func = fDebugInfo->fFuncInfo[immA].name;
228         SkString mask;
229         mask.printf(immC == 1 ? "%s(-1)" : "%s", V(x).c_str());
230         this->writeText(
231                 "<tr class='source'><td class='mask'>&#8617;%s</td><td colspan=2>%s</td></tr>\n",
232                 mask.c_str(),
233                 func.c_str());
234         return;
235     }
236 #endif // defined(SK_ENABLE_SKSL)
237     // No label, to the operation
238     SkString label;
239     if ((instruction.kind & InstructionFlags::kHoisted) != 0) {
240         label.set("&#8593;&#8593;&#8593; ");
241     }
242     if (instruction.duplicates > 0) {
243         label.appendf("*%d", instruction.duplicates + 1);
244     }
245     SkString classes = instruction.classes();
246     this->writeText("<tr class='%s'><td>%s</td><td>", classes.c_str(), label.c_str());
247     // Operation
248     switch (instruction.instruction.op) {
249         case skvm::Op::assert_true:   formatVV("assert_true", x, y);                  break;
250         case skvm::Op::store8:        formatPV("store8", immA, x);                    break;
251         case skvm::Op::store16:       formatPV("store16", immA, x);                   break;
252         case skvm::Op::store32:       formatPV("store32", immA, x);                   break;
253         case skvm::Op::store64:       formatPVV("store64", immA, x, y);               break;
254         case skvm::Op::store128:      formatPVVVV("store128", immA, x, y, z, w);      break;
255         case skvm::Op::index:         formatA_(id, "index");                          break;
256         case skvm::Op::load8:         formatA_P(id, "load8", immA);                   break;
257         case skvm::Op::load16:        formatA_P(id, "load16", immA);                  break;
258         case skvm::Op::load32:        formatA_P(id, "load32", immA);                  break;
259         case skvm::Op::load64:        formatA_PH(id, "load64", immA, immB);           break;
260         case skvm::Op::load128:       formatA_PH(id, "load128", immA, immB);          break;
261         case skvm::Op::gather8:       formatA_PHV(id, "gather8", immA, immB, x);      break;
262         case skvm::Op::gather16:      formatA_PHV(id, "gather16", immA, immB, x);     break;
263         case skvm::Op::gather32:      formatA_PHV(id, "gather32", immA, immB, x);     break;
264         case skvm::Op::uniform32:     formatA_PH(id, "uniform32", immA, immB);        break;
265         case skvm::Op::array32:       formatA_PHH(id, "array32", immA, immB, immC);   break;
266         case skvm::Op::splat:         formatA_S(id, "splat", immA);                   break;
267         case skvm::Op:: add_f32:      formatA_VV(id, "add_f32", x, y);                break;
268         case skvm::Op:: sub_f32:      formatA_VV(id, "sub_f32", x, y);                break;
269         case skvm::Op:: mul_f32:      formatA_VV(id, "mul_f32", x, y);                break;
270         case skvm::Op:: div_f32:      formatA_VV(id, "div_f32", x, y);                break;
271         case skvm::Op:: min_f32:      formatA_VV(id, "min_f32", x, y);                break;
272         case skvm::Op:: max_f32:      formatA_VV(id, "max_f32", x, y);                break;
273         case skvm::Op:: fma_f32:      formatA_VVV(id, "fma_f32", x, y, z);            break;
274         case skvm::Op:: fms_f32:      formatA_VVV(id, "fms_f32", x, y, z);            break;
275         case skvm::Op::fnma_f32:      formatA_VVV(id, "fnma_f32", x, y, z);           break;
276         case skvm::Op::sqrt_f32:      formatA_V(id, "sqrt_f32", x);                   break;
277         case skvm::Op:: eq_f32:       formatA_VV(id, "eq_f32", x, y);                 break;
278         case skvm::Op::neq_f32:       formatA_VV(id, "neq_f32", x, y);                break;
279         case skvm::Op:: gt_f32:       formatA_VV(id, "gt_f32", x, y);                 break;
280         case skvm::Op::gte_f32:       formatA_VV(id, "gte_f32", x, y);                break;
281         case skvm::Op::add_i32:       formatA_VV(id, "add_i32", x, y);                break;
282         case skvm::Op::sub_i32:       formatA_VV(id, "sub_i32", x, y);                break;
283         case skvm::Op::mul_i32:       formatA_VV(id, "mul_i32", x, y);                break;
284         case skvm::Op::shl_i32:       formatA_VC(id, "shl_i32", x, immA);             break;
285         case skvm::Op::shr_i32:       formatA_VC(id, "shr_i32", x, immA);             break;
286         case skvm::Op::sra_i32:       formatA_VC(id, "sra_i32", x, immA);             break;
287         case skvm::Op::eq_i32:        formatA_VV(id, "eq_i32", x, y);                 break;
288         case skvm::Op::gt_i32:        formatA_VV(id, "gt_i32", x, y);                 break;
289         case skvm::Op::bit_and:       formatA_VV(id, "bit_and", x, y);                break;
290         case skvm::Op::bit_or:        formatA_VV(id, "bit_or", x, y);                 break;
291         case skvm::Op::bit_xor:       formatA_VV(id, "bit_xor", x, y);                break;
292         case skvm::Op::bit_clear:     formatA_VV(id, "bit_clear", x, y);              break;
293         case skvm::Op::select:        formatA_VVV(id, "select", x, y, z);             break;
294         case skvm::Op::ceil:          formatA_V(id, "ceil", x);                       break;
295         case skvm::Op::floor:         formatA_V(id, "floor", x);                      break;
296         case skvm::Op::to_f32:        formatA_V(id, "to_f32", x);                     break;
297         case skvm::Op::to_fp16:       formatA_V(id, "to_fp16", x);                    break;
298         case skvm::Op::from_fp16:     formatA_V(id, "from_fp16", x);                  break;
299         case skvm::Op::trunc:         formatA_V(id, "trunc", x);                      break;
300         case skvm::Op::round:         formatA_V(id, "round", x);                      break;
301         default: SkASSERT(false);
302     }
303     this->writeText("</td></tr>\n");
304 }
305 
dumpHead() const306 void Visualizer::dumpHead() const {
307     this->writeText("%s",
308     "<html>\n"
309     "<head>\n"
310     "   <title>SkVM Disassembler Output</title>\n"
311     "   <style>\n"
312     "   button { border-style: none; font-size: 10px; background-color: lightpink; }\n"
313     "   table { text-align: left; }\n"
314     "   table th { background-color: lightgray; }\n"
315     "   .dead, .dead1 { color: lightgray; text-decoration: line-through; }\n"
316     "   .normal, .normal1 { }\n"
317     "   .origin, .origin1 { font-weight: bold; }\n"
318     "   .source, .source1 { color: darkblue; }\n"
319     "   .mask, .mask1 { color: green; }\n"
320     "   .comments, .comments1 { }\n"
321     "   </style>\n"
322     "    <script>\n"
323     "    function initializeButton(className) {\n"
324     "      var btn = document.getElementById(className);\n"
325     "      var elems = document.getElementsByClassName(className);\n"
326     "      if (elems == undefined || elems.length == 0) {\n"
327     "        btn.disabled = true;\n"
328     "        btn.innerText = \"None\";\n"
329     "        btn.style.background = \"lightgray\";\n"
330     "        return;\n"
331     "      }\n"
332     "    };\n"
333     "    function initialize() {\n"
334     "      initializeButton('normal');\n"
335     "      initializeButton('source');\n"
336     "      initializeButton('dead');\n"
337     "    };\n"
338     "  </script>\n"
339     "</head>\n"
340     "<body onload='initialize();'>\n"
341     "    <script>\n"
342     "    function toggle(btn, className) {\n"
343     "      var elems = document.getElementsByClassName(className);\n"
344     "      for (var i = 0; i < elems.length; i++) {\n"
345     "        var elem = elems.item(i);\n"
346     "        if (elem.style.display === \"\") {\n"
347     "            elem.style.display = \"none\";\n"
348     "            btn.innerText = \"Show\";\n"
349     "            btn.style.background = \"lightcyan\";\n"
350     "        } else {\n"
351     "            elem.style.display = \"\";\n"
352     "            btn.innerText = \"Hide\";\n"
353     "            btn.style.background = \"lightpink\";\n"
354     "        }\n"
355     "      }\n"
356     "    };\n"
357     "    </script>"
358     "    <table border=\"0\" style='font-family:\"monospace\"; font-size: 10px;'>\n"
359     "     <caption style='font-family:Roboto; font-size:15px; text-align:left;'>Legend</caption>\n"
360     "     <tr>\n"
361     "        <th style=\"min-width:100px;\"><u>Kind</u></th>\n"
362     "        <th style=\"width:35%;\"><u>Example</u></th>\n"
363     "        <th style=\"width: 5%; min-width:50px;\"><u></u></th>\n"
364     "        <th style=\"width:60%;\"><u>Description</u></th>\n"
365     "     </tr>\n"
366     "      <tr class='normal1'>"
367             "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>"
368             "<td>v1 = load32 Ptr1</td>"
369             "<td><button id='normal' onclick=\"toggle(this, 'normal')\">Hide</button></td>"
370             "<td>A regular SkVM command</td></tr>\n"
371     "      <tr class='normal1 origin1'><td>*{N}</td>"
372             "<td>v9 = gt_f32 v0, v1</td>"
373             "<td><button id='dead' onclick=\"toggle(this, 'deduped')\">Hide</button></td>"
374             "<td>A {N} times deduped SkVM command</td></tr>\n"
375     "      <tr class='normal1'><td>&#8593;&#8593;&#8593; &nbsp;&nbsp;&nbsp;</td>"
376             "<td>v22 = splat 3f800000 (1)</td><td></td>"
377             "<td>A hoisted SkVM command</td></tr>\n"
378     "      <tr class='source1'><td class='mask'>mask&#8618;v{N}(-1)</td>"
379             "<td>// C++ source line</td><td></td>"
380             "<td>Enter into the procedure with mask v{N} (which has a constant value -1)"
381             "</td></tr>\n"
382     "      <tr class='source1'><td class='mask'>mask&#8617;v{N}</td>"
383             "<td>// C++ source line</td><td>"
384             "</td><td>Exit the procedure with mask v{N}</td></tr>\n"
385     "      <tr class='source1'><td class='mask'></td><td>// C++ source line</td>"
386             "<td><button id='source' onclick=\"toggle(this, 'source')\">Hide</button></td>"
387             "<td>Line trace back to C++ code</td></tr>\n"
388     "      <tr class='dead1'><td></td><td>{dead code} = mul_f32 v1, v18</td>"
389             "<td><button id='dead' onclick=\"toggle(this, 'dead')\">Hide</button></td>"
390             "<td>An eliminated \"dead code\" SkVM command</td></tr>\n"
391     "    </table>\n"
392     "    <table border = \"0\"style='font-family:\"monospace\"; font-size: 10px;'>\n"
393     "     <caption style='font-family:Roboto;font-size:15px;text-align:left;'>SkVM Code</caption>\n"
394     "     <tr>\n"
395     "        <th style=\"min-width:100px;\"><u>Kind</u></th>\n"
396     "        <th style=\"width:40%;min-width:100px;\"><u>Command</u></th>\n"
397     "        <th style=\"width:60%;\"><u>Comments</u></th>\n"
398     "     </tr>");
399 }
dumpTail() const400 void Visualizer::dumpTail() const {
401     this->writeText(
402     "      </table>\n"
403             "</body>\n"
404             "</html>"
405     );
406 }
407 } // namespace skvm::viz
408