diff --git a/.gitignore b/.gitignore index 4a19876f849a..2a338353fa36 100644 --- a/.gitignore +++ b/.gitignore @@ -234,6 +234,9 @@ xcuserdata # Vim .clang_complete +# NeoVim + clangd +.cache + # Emacs tags TAGS diff --git a/Makefile b/Makefile index 244fcc00d4e5..f8e51b22320e 100644 --- a/Makefile +++ b/Makefile @@ -615,7 +615,7 @@ SOURCE_FILES = \ SpirvIR.cpp \ SplitTuples.cpp \ StageStridedLoads.cpp \ - StmtToViz.cpp \ + StmtToHTML.cpp \ StorageFlattening.cpp \ StorageFolding.cpp \ StrictifyFloat.cpp \ @@ -641,9 +641,9 @@ SOURCE_FILES = \ CodeGen_C_vectors HTML_TEMPLATE_FILES = \ - StmtToViz_dependencies \ - StmtToViz_javascript \ - StmtToViz_stylesheet + StmtToHTML_dependencies.html \ + StmtToHTML.js \ + StmtToHTML.css # The externally-visible header files that go into making Halide.h. # Don't include anything here that includes llvm headers. @@ -796,7 +796,7 @@ HEADER_FILES = \ Solve.h \ SplitTuples.h \ StageStridedLoads.h \ - StmtToViz.h \ + StmtToHTML.h \ StorageFlattening.h \ StorageFolding.h \ StrictifyFloat.h \ @@ -1199,8 +1199,8 @@ $(BUILD_DIR)/initmod_ptx.%_ll.cpp: $(BIN_DIR)/binary2cpp $(SRC_DIR)/runtime/nvid $(BUILD_DIR)/c_template.%.cpp: $(BIN_DIR)/binary2cpp $(SRC_DIR)/%.template.cpp ./$(BIN_DIR)/binary2cpp halide_c_template_$* < $(SRC_DIR)/$*.template.cpp > $@ -$(BUILD_DIR)/html_template.%.cpp: $(BIN_DIR)/binary2cpp $(SRC_DIR)/irvisualizer/%.template.html - ./$(BIN_DIR)/binary2cpp halide_html_template_$* < $(SRC_DIR)/irvisualizer/$*.template.html > $@ +$(BUILD_DIR)/html_template.%.cpp: $(BIN_DIR)/binary2cpp $(SRC_DIR)/irvisualizer/html_template_% + ./$(BIN_DIR)/binary2cpp halide_html_template_$(subst .,_,$*) < $(SRC_DIR)/irvisualizer/html_template_$* > $@ $(BIN_DIR)/binary2cpp: $(ROOT_DIR)/tools/binary2cpp.cpp @mkdir -p $(@D) diff --git a/apps/bilateral_grid/bilateral_grid_generator.cpp b/apps/bilateral_grid/bilateral_grid_generator.cpp index 7ef7102d081c..ce0c6659403d 100644 --- a/apps/bilateral_grid/bilateral_grid_generator.cpp +++ b/apps/bilateral_grid/bilateral_grid_generator.cpp @@ -84,7 +84,6 @@ class BilateralGrid : public Halide::Generator { // nothing } else if (get_target().has_gpu_feature()) { // 0.50ms on an RTX 2060 - Var xi("xi"), yi("yi"), zi("zi"); // Schedule blurz in 8x8 tiles. This is a tile in diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1edc296aa775..d5d6a8a3832e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -151,7 +151,7 @@ set(HEADER_FILES Solve.h SplitTuples.h StageStridedLoads.h - StmtToViz.h + StmtToHTML.h StorageFlattening.h StorageFolding.h StrictifyFloat.h @@ -334,7 +334,7 @@ set(SOURCE_FILES SpirvIR.cpp SplitTuples.cpp StageStridedLoads.cpp - StmtToViz.cpp + StmtToHTML.cpp StorageFlattening.cpp StorageFolding.cpp StrictifyFloat.cpp @@ -362,9 +362,9 @@ set(C_TEMPLATE_FILES ) set(HTML_TEMPLATE_FILES - StmtToViz_dependencies - StmtToViz_javascript - StmtToViz_stylesheet + StmtToHTML_dependencies.html + StmtToHTML.js + StmtToHTML.css ) ## @@ -390,11 +390,12 @@ foreach (f IN LISTS C_TEMPLATE_FILES) endforeach () foreach (f IN LISTS HTML_TEMPLATE_FILES) - set(SRC "$") - set(DST "html_template.${f}.template.cpp") + set(SRC "$") + string(REPLACE "." "_" VARNAME "halide_html_template_${f}") + set(DST "html_template.${f}.cpp") add_custom_command(OUTPUT "${DST}" - COMMAND binary2cpp "halide_html_template_${f}" < "${SRC}" > "${DST}" + COMMAND binary2cpp "${VARNAME}" < "${SRC}" > "${DST}" DEPENDS "${SRC}" binary2cpp VERBATIM) target_sources(Halide_c_templates PRIVATE ${DST}) diff --git a/src/Generator.cpp b/src/Generator.cpp index 7637df5774e9..5228a9c2d918 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -652,7 +652,8 @@ gengen -e A comma separated list of files to emit. Accepted values are: [assembly, bitcode, c_header, c_source, cpp_stub, featurization, llvm_assembly, object, python_extension, pytorch_wrapper, registration, - schedule, static_library, stmt, stmt_html, compiler_log, hlpipe]. + schedule, static_library, stmt, stmt_html, conceptual_stmt, + conceptual_stmt_html, compiler_log, hlpipe, device_code]. If omitted, default value is [c_header, static_library, registration]. -p A comma-separated list of shared libraries that will be loaded before the @@ -662,7 +663,7 @@ gengen (Note that this does not change the default autoscheduler; use the -s flag to set that value.)" - -r The name of a standalone runtime to generate. Only honors EMIT_OPTIONS 'o' + -r The name of a standalone runtime to generate. Only honors EMIT_OPTIONS 'o' and 'static_library'. When multiple targets are specified, it picks a runtime that is compatible with all of the targets, or fails if it cannot find one. Flags across all of the targets that do not affect runtime code @@ -773,6 +774,12 @@ gengen std::set output_types; std::string emit_flags_string = flags_info["-e"]; + + // If omitted or empty, assume .a and .h and registration.cpp + if (emit_flags_string.empty()) { + emit_flags_string = "c_header,registration,static_library"; + } + // If HL_EXTRA_OUTPUTS is defined, assume it's extra outputs we want to generate // (usually for temporary debugging purposes) and just tack it on to the -e contents. std::string extra_outputs = get_env_variable("HL_EXTRA_OUTPUTS"); @@ -784,50 +791,45 @@ gengen } const std::vector emit_flags = split_string(emit_flags_string, ","); + internal_assert(!emit_flags.empty()) << "Empty emit flags.\n"; + + // Build a reverse lookup table. Allow some legacy aliases on the command line, + // to allow legacy build systems to work more easily. + std::map output_name_to_enum = { + {"cpp", OutputFileType::c_source}, + {"h", OutputFileType::c_header}, + {"hlpipe", OutputFileType::hlpipe}, + {"html", OutputFileType::stmt_html}, + {"o", OutputFileType::object}, + {"py.c", OutputFileType::python_extension}, + }; + // extensions won't vary across multitarget output + const Target t = args.targets.empty() ? Target() : args.targets[0]; + const std::map output_info = get_output_info(t); + for (const auto &it : output_info) { + output_name_to_enum[it.second.name] = it.first; + } - if (emit_flags.empty() || (emit_flags.size() == 1 && emit_flags[0].empty())) { - // If omitted or empty, assume .a and .h and registration.cpp - output_types.insert(OutputFileType::c_header); - output_types.insert(OutputFileType::registration); - output_types.insert(OutputFileType::static_library); - } else { - // Build a reverse lookup table. Allow some legacy aliases on the command line, - // to allow legacy build systems to work more easily. - std::map output_name_to_enum = { - {"cpp", OutputFileType::c_source}, - {"h", OutputFileType::c_header}, - {"hlpipe", OutputFileType::hlpipe}, - {"html", OutputFileType::stmt_html}, - {"o", OutputFileType::object}, - {"py.c", OutputFileType::python_extension}, - }; - // extensions won't vary across multitarget output - const Target t = args.targets.empty() ? Target() : args.targets[0]; - const std::map output_info = get_output_info(t); - for (const auto &it : output_info) { - output_name_to_enum[it.second.name] = it.first; - } - - for (const std::string &opt : emit_flags) { - auto it = output_name_to_enum.find(opt); - if (it == output_name_to_enum.end()) { - std::ostringstream o; - o << "Unrecognized emit option: " << opt << " is not one of ["; - auto end = output_info.cend(); - auto last = std::prev(end); - for (auto iter = output_info.cbegin(); iter != end; ++iter) { - o << iter->second.name; - if (iter != last) { - o << " "; - } + for (const std::string &opt : emit_flags) { + auto it = output_name_to_enum.find(opt); + if (it == output_name_to_enum.end()) { + std::ostringstream o; + o << "Unrecognized emit option: " << opt << " is not one of ["; + auto end = output_info.cend(); + auto last = std::prev(end); + for (auto iter = output_info.cbegin(); iter != end; ++iter) { + o << iter->second.name; + if (iter != last) { + o << " "; } - o << "], ignoring.\n"; - o << kUsage; - user_error << o.str(); } - output_types.insert(it->second); + o << "], ignoring.\n"; + o << kUsage; + user_error << o.str(); } + output_types.insert(it->second); } + return output_types; }; diff --git a/src/IRPrinter.cpp b/src/IRPrinter.cpp index 0d746e034483..96c472698011 100644 --- a/src/IRPrinter.cpp +++ b/src/IRPrinter.cpp @@ -304,25 +304,25 @@ ostream &operator<<(ostream &out, const ForType &type) { ostream &operator<<(ostream &out, const VectorReduce::Operator &op) { switch (op) { case VectorReduce::Add: - out << "Add"; + out << "add"; break; case VectorReduce::SaturatingAdd: - out << "SaturatingAdd"; + out << "saturating_add"; break; case VectorReduce::Mul: - out << "Mul"; + out << "mul"; break; case VectorReduce::Min: - out << "Min"; + out << "min"; break; case VectorReduce::Max: - out << "Max"; + out << "max"; break; case VectorReduce::And: - out << "And"; + out << "and"; break; case VectorReduce::Or: - out << "Or"; + out << "or"; break; } return out; @@ -1087,8 +1087,7 @@ void IRPrinter::visit(const Shuffle *op) { void IRPrinter::visit(const VectorReduce *op) { stream << "(" << op->type - << ")vector_reduce(" - << op->op + << ")vector_reduce_" << op->op << "(" << ", " << op->value << ")"; diff --git a/src/Lower.cpp b/src/Lower.cpp index ecff36ee58b4..67aedde288d0 100644 --- a/src/Lower.cpp +++ b/src/Lower.cpp @@ -432,6 +432,9 @@ void lower_impl(const vector &output_funcs, } } + // Make a copy of the Stmt code, before we lower anything to less human-readable code. + result_module.set_conceptual_code_stmt(s); + if (t.arch != Target::Hexagon && t.has_feature(Target::HVX)) { debug(1) << "Splitting off Hexagon offload...\n"; s = inject_hexagon_rpc(s, t, result_module); diff --git a/src/Module.cpp b/src/Module.cpp index 89239e41086c..573f26210717 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -18,7 +18,7 @@ #include "LLVM_Runtime_Linker.h" #include "Pipeline.h" #include "PythonExtensionGen.h" -#include "StmtToViz.h" +#include "StmtToHTML.h" namespace Halide { namespace Internal { @@ -49,7 +49,10 @@ std::map get_output_info(const Target &target) {OutputFileType::schedule, {"schedule", ".schedule.h", IsSingle}}, {OutputFileType::static_library, {"static_library", is_windows_coff ? ".lib" : ".a", IsSingle}}, {OutputFileType::stmt, {"stmt", ".stmt", IsMulti}}, + {OutputFileType::conceptual_stmt, {"conceptual_stmt", ".conceptual.stmt", IsMulti}}, {OutputFileType::stmt_html, {"stmt_html", ".stmt.html", IsMulti}}, + {OutputFileType::conceptual_stmt_html, {"conceptual_stmt_html", ".conceptual.stmt.html", IsMulti}}, + {OutputFileType::device_code, {"device_code", ".device_code", IsMulti}}, }; return ext; } @@ -325,6 +328,13 @@ struct ModuleContents { MetadataNameMap metadata_name_map; bool any_strict_float{false}; std::unique_ptr auto_scheduler_results; + + /** This is a copy of the code throughout the lowering process, which + * reflects best the actual pipeline, without introducing device-specific + * generated code from device-specific offloads (such as Cuda PTX, + * OpenGL Compute, etc...). In other words, we'd like to keep this + * conceptually relevant and human-readable. */ + Stmt conceptual_code; }; template<> @@ -408,6 +418,26 @@ const std::vector &Module::submodules() const { return contents->submodules; } +Buffer<> Module::get_cuda_ptx_assembly_buffer() const { + for (const Buffer<> &buf : buffers()) { + if (ends_with(buf.name(), "_gpu_source_kernels")) { + if (starts_with(buf.name(), "cuda_")) { + return buf; + } + } + } + return {}; +} + +Buffer<> Module::get_device_code_buffer() const { + for (const Buffer<> &buf : buffers()) { + if (ends_with(buf.name(), "_gpu_source_kernels")) { + return buf; + } + } + return {}; +} + Internal::LoweredFunc Module::get_function_by_name(const std::string &name) const { for (const auto &f : functions()) { if (f.name == name) { @@ -519,6 +549,14 @@ MetadataNameMap Module::get_metadata_name_map() const { return contents->metadata_name_map; } +void Module::set_conceptual_code_stmt(const Internal::Stmt &stmt) { + contents->conceptual_code = stmt; +} + +const Internal::Stmt &Module::get_conceptual_stmt() const { + return contents->conceptual_code; +} + void Module::compile(const std::map &output_files) const { validate_outputs(output_files); @@ -551,7 +589,7 @@ void Module::compile(const std::map &output_files) std::string assembly_path; if (contains(output_files, OutputFileType::assembly)) { assembly_path = output_files.at(OutputFileType::assembly); - } else if (contains(output_files, OutputFileType::stmt_html)) { + } else if (contains(output_files, OutputFileType::stmt_html) || contains(output_files, OutputFileType::conceptual_stmt_html)) { // We need assembly in order to generate stmt_html, but the user doesn't // want it on its own, so we will generate it to a temp directory, since some // build systems (e.g. Bazel) are strict about what you can generate to the 'expected' @@ -627,10 +665,43 @@ void Module::compile(const std::map &output_files) std::ofstream file(output_files.at(OutputFileType::stmt)); file << *this; } + if (contains(output_files, OutputFileType::conceptual_stmt)) { + debug(1) << "Module.compile(): conceptual_stmt " << output_files.at(OutputFileType::conceptual_stmt) << "\n"; + std::ofstream file(output_files.at(OutputFileType::conceptual_stmt)); + file << get_conceptual_stmt(); + } if (contains(output_files, OutputFileType::stmt_html)) { internal_assert(!assembly_path.empty()); debug(1) << "Module.compile(): stmt_html " << output_files.at(OutputFileType::stmt_html) << "\n"; - Internal::print_to_viz(output_files.at(OutputFileType::stmt_html), *this, assembly_path); + Internal::print_to_stmt_html(output_files.at(OutputFileType::stmt_html), + *this, assembly_path); + } + if (contains(output_files, OutputFileType::conceptual_stmt_html)) { + internal_assert(!assembly_path.empty()); + debug(1) << "Module.compile(): conceptual_stmt_html " << output_files.at(OutputFileType::conceptual_stmt_html) << "\n"; + Internal::print_to_conceptual_stmt_html(output_files.at(OutputFileType::conceptual_stmt_html), + *this, assembly_path); + } + if (contains(output_files, OutputFileType::device_code)) { + debug(1) << "Module.compile(): device_code " << output_files.at(OutputFileType::device_code) << "\n"; + Buffer<> buf = get_device_code_buffer(); + if (buf.defined()) { + int length = buf.size_in_bytes(); + while (length > 0 && ((const char *)buf.data())[length - 1] == '\0') { + length--; + } + std::string str((const char *)buf.data(), length); + std::string device_code = std::string((const char *)buf.data(), buf.size_in_bytes()); + while (!device_code.empty() && device_code.back() == '\0') { + device_code = device_code.substr(0, device_code.length() - 1); + } + std::ofstream file(output_files.at(OutputFileType::device_code)); + file << device_code << "\n"; + file.close(); + debug(1) << "Saved GPU kernel sources (" << device_code.size() << " bytes).\n"; + } else { + debug(1) << "No GPU kernel sources emitted.\n"; + } } if (contains(output_files, OutputFileType::function_info_header)) { debug(1) << "Module.compile(): function_info_header " << output_files.at(OutputFileType::function_info_header) << "\n"; diff --git a/src/Module.h b/src/Module.h index 779c837036e2..efb984529458 100644 --- a/src/Module.h +++ b/src/Module.h @@ -41,7 +41,10 @@ enum class OutputFileType { schedule, static_library, stmt, + conceptual_stmt, stmt_html, + conceptual_stmt_html, + device_code, }; /** Type of linkage a function in a lowered Halide module can have. @@ -164,6 +167,17 @@ class Module { const std::vector &submodules() const; // @} + /** Tries to locate the offloaded CUDA PTX assembly contained in this Module. + * Might return a nullptr in case such buffer is not present in this Module. + */ + Buffer<> get_cuda_ptx_assembly_buffer() const; + + /** + * Tries to locate the offloaded (GPU) Device assembly contained in this Module. + * This can be any of the GPU kernel sources, etc... + */ + Buffer<> get_device_code_buffer() const; + /** Return the function with the given name. If no such function * exists in this module, assert. */ Internal::LoweredFunc get_function_by_name(const std::string &name) const; @@ -201,6 +215,12 @@ class Module { /** Set whether this module uses strict floating-point directives anywhere. */ void set_any_strict_float(bool any_strict_float); + + /** Remember the Stmt during lowing before device-specific offloading. */ + void set_conceptual_code_stmt(const Internal::Stmt &stmt); + + /** Get the remembered conceptual Stmt, remembered before device-specific offloading. */ + const Internal::Stmt &get_conceptual_stmt() const; }; /** Link a set of modules together into one module. */ diff --git a/src/StmtToViz.cpp b/src/StmtToHTML.cpp similarity index 64% rename from src/StmtToViz.cpp rename to src/StmtToHTML.cpp index f41a74424306..ce90d645104b 100644 --- a/src/StmtToViz.cpp +++ b/src/StmtToHTML.cpp @@ -1,4 +1,4 @@ -#include "StmtToViz.h" +#include "StmtToHTML.h" #include "Debug.h" #include "Error.h" #include "IROperator.h" @@ -17,20 +17,30 @@ #include #include +// Setting this to 0 is meant to speed up the development iteration cycle. +// Not inlining these template files, but just linking them by an absolute path +// causes you to be able to just edit the files without having to recompile Halide +// and then rerun your generator. +// For distribution purposes, they should be inlined, and this define should be on 1. +#define INLINE_TEMPLATES 1 + +#if !INLINE_TEMPLATES +#include +#endif + namespace Halide { namespace Internal { -extern "C" unsigned char halide_html_template_StmtToViz_dependencies[]; -extern "C" unsigned char halide_html_template_StmtToViz_stylesheet[]; -extern "C" unsigned char halide_html_template_StmtToViz_javascript[]; +extern "C" unsigned char halide_html_template_StmtToHTML_dependencies_html[]; +extern "C" unsigned char halide_html_template_StmtToHTML_css[]; +extern "C" unsigned char halide_html_template_StmtToHTML_js[]; // Classes defined within this file class CostModel; class AssemblyInfo; template class HTMLCodePrinter; -class HTMLVisualizationPrinter; -class IRVisualizer; +class PipelineHTMLInspector; /** IRCostModel * A basic cost model for Halide IR. Estimates computation @@ -39,9 +49,7 @@ class IRVisualizer; */ class IRCostModel : public IRVisitor { public: - IRCostModel() - - = default; + IRCostModel() = default; // Pre-compute all costs to avoid repeated work void compute_all_costs(const Module &m) { @@ -49,7 +57,13 @@ class IRCostModel : public IRVisitor { for (const auto &fn : m.functions()) { fn.body.accept(this); } + } + void compute_conceptual_costs(const Module &m) { + m.get_conceptual_stmt().accept(this); + } + + void finalize_cost_computation() { // Compute the max cost for each category max_compute_cost = -1; for (auto const &entry : compute_cost) { @@ -521,15 +535,27 @@ class AssemblyInfo : public IRVisitor { public: AssemblyInfo() = default; - void generate(const std::string &code, const Module &m) { + void gather_nodes_from_functions(const Module &m) { // Traverse the module to populate the list of // nodes we need to map and generate their assembly // markers (comments that appear in the assembly code // associating the code with this node) + ids_are_known = true; for (const auto &fn : m.functions()) { fn.body.accept(this); } + } + + void gather_nodes_from_conceptual_stmt(const Module &m) { + // Traverse the module's conceptual Stmt to populate the list of + // nodes we need to map and generate their assembly + // markers (comments that appear in the assembly code + // associating the code with this node) + ids_are_known = false; + m.get_conceptual_stmt().accept(this); + } + void generate(const std::string &code) { // Find markers in asm code std::istringstream asm_stream(code); std::string line; @@ -537,8 +563,8 @@ class AssemblyInfo : public IRVisitor { while (getline(asm_stream, line)) { // Try all markers std::vector matched_nodes; - for (auto const &[node, regex] : markers) { - if (std::regex_search(line, regex)) { + for (auto const &[node, marker] : markers) { + if (std::regex_search(line, marker)) { // Save line number lnos[node] = lno; // Save this node's id @@ -562,18 +588,32 @@ class AssemblyInfo : public IRVisitor { return -1; } + std::string get_label(uint64_t node_id) { + if (labels.count(node_id)) { + return labels[node_id]; + } + return "(label not found)"; + } + private: // Generate asm markers for Halide loops + bool ids_are_known{true}; int loop_id = 0; int gen_loop_id() { return ++loop_id; } - std::regex gen_loop_asm_marker(int id, const std::string &loop_var) { + std::string gen_loop_asm_marker(int id, const std::string &loop_var) { std::regex dollar("\\$"); - std::string marker = "%\"" + std::to_string(id) + "_for_" + loop_var; + std::string marker = "%\""; + if (ids_are_known) { + marker += std::to_string(id); + } else { + marker += "\\d+"; + } + marker += "_for_" + loop_var; marker = std::regex_replace(marker, dollar, "\\$"); - return std::regex(marker); + return marker; } // Generate asm markers for Halide producer/consumer ndoes @@ -582,15 +622,22 @@ class AssemblyInfo : public IRVisitor { return ++prodcons_id; } - std::regex gen_prodcons_asm_marker(int id, const std::string &var, bool is_producer) { + std::string gen_prodcons_asm_marker(int id, const std::string &var, bool is_producer) { std::regex dollar("\\$"); - std::string marker = "%\"" + std::to_string(id) + (is_producer ? "_produce_" : "_consume_") + var; + std::string marker = "%\""; + if (ids_are_known) { + marker += std::to_string(id); + } else { + marker += "\\d+"; + } + marker += (is_producer ? "_produce_" : "_consume_") + var; marker = std::regex_replace(marker, dollar, "\\$"); - return std::regex(marker); + return marker; } // Mapping of IR nodes to their asm markers std::map markers; + std::map labels; // Mapping of IR nodes to their asm line numbers std::map lnos; @@ -599,14 +646,18 @@ class AssemblyInfo : public IRVisitor { void visit(const ProducerConsumer *op) override { // Generate asm marker - markers[(uint64_t)op] = gen_prodcons_asm_marker(gen_prodcons_id(), op->name, op->is_producer); + std::string marker = gen_prodcons_asm_marker(gen_prodcons_id(), op->name, op->is_producer); + markers[(uint64_t)op] = std::regex(marker); + labels[(uint64_t)op] = marker; // Continue traversal IRVisitor::visit(op); } void visit(const For *op) override { // Generate asm marker - markers[(uint64_t)op] = gen_loop_asm_marker(gen_loop_id(), op->name); + std::string marker = gen_loop_asm_marker(gen_loop_id(), op->name); + markers[(uint64_t)op] = std::regex(marker); + labels[(uint64_t)op] = marker; // Continue traversal IRVisitor::visit(op); } @@ -619,8 +670,9 @@ class AssemblyInfo : public IRVisitor { template class HTMLCodePrinter : public IRVisitor { public: - HTMLCodePrinter(T &os, std::map &nids) - : stream(os), node_ids(nids), context_stack(1, 0) { + HTMLCodePrinter(T &os, std::map &nids, bool enable_assembly_features) + : stream(os), node_ids(nids), context_stack(1, 0), + enable_assembly_features(enable_assembly_features) { } // Make class non-copyable and non-moveable @@ -631,8 +683,9 @@ class HTMLCodePrinter : public IRVisitor { cost_model = std::move(cm); } - void print(const Module &m, AssemblyInfo asm_info) { - assembly_info = std::move(asm_info); + void print_conceptual_stmt(const Module &m, AssemblyInfo host_asm_info, AssemblyInfo device_asm_info) { + host_assembly_info = std::move(host_asm_info); + device_assembly_info = std::move(device_asm_info); // Generate a unique ID for this module int id = gen_unique_id(); @@ -640,19 +693,11 @@ class HTMLCodePrinter : public IRVisitor { // Enter new scope for this module scope.push(m.name(), id); - // The implementation doesn't need to support submodules: - // we only call this for Modules that have already had their submodules - // resolved. - internal_assert(m.submodules().empty()) << "StmtToViz does not support submodules."; - // Open div to hold this module print_opening_tag("div", "Module"); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); // -- print text print_opening_tag("span", "matched"); @@ -660,23 +705,14 @@ class HTMLCodePrinter : public IRVisitor { print_text(" name=" + m.name() + ", target=" + m.target().to_string()); print_closing_tag("span"); - print_toggle_anchor_closing_tag(); - // Open code block to hold module body - print_html_element("span", "matched", " {"); + print_opening_brace(); + print_show_hide_btn_end(nullptr); // Open indented div to hold body code print_opening_tag("div", "indent ModuleBody", id); - // Print module buffers - for (const auto &buf : m.buffers()) { - print(buf); - } - - // Print module functions - for (const auto &fn : m.functions()) { - print(fn); - } + print(m.get_conceptual_stmt()); // Close indented div holding body code print_closing_tag("div"); @@ -691,260 +727,77 @@ class HTMLCodePrinter : public IRVisitor { scope.pop(m.name()); } -private: - // Handle to output file stream - T &stream; - - // Used to generate unique ids - int id = 0; - std::map &node_ids; - - // Used to track scope during IR traversal - Scope scope; - - /* All spans and divs will have an id of the form "x-y", where x - * is shared among all spans/divs in the same context, and y is unique. - * These variables are used to track the context within generated HTML */ - std::vector context_stack; - std::vector context_stack_tags; - - // Holds cost information for visualized program - IRCostModel cost_model; - AssemblyInfo assembly_info; + void print(const Module &m, AssemblyInfo host_asm_info, AssemblyInfo device_asm_info) { + host_assembly_info = std::move(host_asm_info); + device_assembly_info = std::move(device_asm_info); - /* Private print functions to handle various IR types */ - void print(const Buffer<> &buf) { // Generate a unique ID for this module int id = gen_unique_id(); - // Determine whether to print buffer data - bool print_data = ends_with(buf.name(), "_gpu_source_kernels"); - - // Open div to hold this buffer - print_opening_tag("div", "Buffer"); - - if (print_data) { - // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); - - // -- print text - print_html_element("span", "keyword", "buffer "); - print_variable(buf.name()); - - print_toggle_anchor_closing_tag(); - - // Print data - print_text(" = "); - - // Open code block to hold module body - print_html_element("span", "matched", " {"); - - // Open indented div to hold buffer data - print_opening_tag("div", "indent BufferData", id); - - std::string str((const char *)buf.data(), buf.size_in_bytes()); - if (starts_with(buf.name(), "cuda_")) { - print_cuda_gpu_source_kernels(str); - } else { - stream << "
\n"
-                       << str << "
\n"; - } - - print_closing_tag("div"); - - // Close code block holding buffer body - print_html_element("span", "matched ClosingBrace cb-" + std::to_string(id), " }"); - } else { - // Print buffer name and move on - print_html_element("span", "keyword", "buffer "); - print_variable(buf.name()); - } - - // Close div holding this buffer - print_closing_tag("div"); - } - - void print(const LoweredFunc &fn) { - // Generate a unique ID for this function - int id = gen_unique_id(); + // Enter new scope for this module + scope.push(m.name(), id); - // Enter new scope for this function - scope.push(fn.name, id); + // The implementation doesn't need to support submodules: + // we only call this for Modules that have already had their submodules + // resolved. + internal_assert(m.submodules().empty()) << "StmtToHTML does not support submodules."; - // Open div to hold this buffer - print_opening_tag("div", "Function"); + // Open div to hold this module + print_opening_tag("div", "Module"); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); - // -- print text (fn name and args) - // Note: We wrap the show/hide buttons in a navigation anchor - // that lets us sync text and visualization tabs. + // -- print text print_opening_tag("span", "matched"); - print_html_element("span", "keyword nav-anchor", "func ", "lowered-func-" + fn.name); - print_text(fn.name + "("); + print_html_element("span", "keyword", "module"); + print_text(" name=" + m.name() + ", target=" + m.target().to_string()); print_closing_tag("span"); - print_fndecl_args(fn.args); - print_html_element("span", "matched", ")"); - - print_toggle_anchor_closing_tag(); - // Add a button to jump to this function in the viz - print_visualization_button("lowered-func-viz-" + std::to_string(id)); - - // Open code block to hold function body - print_html_element("span", "matched", "{"); + // Open code block to hold module body + print_opening_brace(); + print_show_hide_btn_end(nullptr); // Open indented div to hold body code - print_opening_tag("div", "indent FunctionBody", id); + print_opening_tag("div", "indent ModuleBody", id); - // Print function body - print(fn.body); + // Print module buffers + for (const auto &buf : m.buffers()) { + print(buf); + } + + // Print module functions + for (const auto &fn : m.functions()) { + print(fn); + } // Close indented div holding body code print_closing_tag("div"); - // Close code block holding func body + // Close code block holding module body print_html_element("span", "matched ClosingBrace cb-" + std::to_string(id), "}"); - // Close div holding this function + // Close div holding this module print_closing_tag("div"); // Pop out to outer scope - scope.pop(fn.name); - } - - void print(const Expr &ir) { - ir.accept(this); - } - - void print(const Stmt &ir) { - ir.accept(this); - } - - /* Methods used to emit common HTML patterns */ - - // Prints the opening tag for the specified html element. A unique ID is - // auto-generated unless provided. - void print_opening_tag(const std::string &tag, const std::string &cls, int id = -1) { - stream << "<" << tag << " class='" << cls << "' id='"; - if (id == -1) { - stream << context_stack.back() << "-"; - stream << gen_unique_id(); - } else { - stream << id; - } - stream << "'>"; - context_stack.push_back(gen_unique_id()); - context_stack_tags.push_back(tag); - } - - void print_opening_tag(const std::string &tag, const std::string &cls, std::string id) { - stream << "<" << tag << " class='" << cls << "' id='" << id << "'>"; - context_stack.push_back(gen_unique_id()); - context_stack_tags.push_back(tag); - } - - // Prints the closing tag for the specified html element. - void print_closing_tag(const std::string &tag) { - internal_assert(!context_stack.empty() && tag == context_stack_tags.back()) - << tag << " " << context_stack.empty() << " " << context_stack_tags.back(); - context_stack.pop_back(); - context_stack_tags.pop_back(); - stream << ""; - } - - // Prints an html element: opening tag, body and closing tag - void print_html_element(const std::string &tag, const std::string &cls, const std::string &body, int id = -1) { - print_opening_tag(tag, cls, id); - stream << body; - print_closing_tag(tag); - } - - void print_html_element(const std::string &tag, const std::string &cls, const std::string &body, std::string id) { - print_opening_tag(tag, cls, id); - stream << body; - print_closing_tag(tag); - } - - // Prints the opening/closing tags for an anchor that toggles code block view - void print_toggle_anchor_opening_tag(int id) { - stream << ""; - } - - void print_toggle_anchor_closing_tag() { - stream << ""; - } - - // Prints newline to stream - void print_ln() { - stream << '\n'; - } - - // Prints a variable to stream - void print_variable(const std::string &x) { - stream << variable(x); - } - - std::string variable(const std::string &x) { - int id; - if (scope.contains(x)) { - id = scope.get(x); - } else { - id = gen_unique_id(); - scope.push(x, id); - } - std::ostringstream s; - s << ""; - s << x; - s << ""; - return s.str(); - } - - // Prints text to stream - void print_text(const std::string &x) { - stream << x; - } - - // Prints the button to show or hide a code scope - void print_show_hide_icon(int id) { - stream << "
" - << " " - << "
" - << " " - << "
" - << "
"; - } - - // Prints a button to sync text with visualization - void print_visualization_button(std::string id) { - stream << ""; + scope.pop(m.name()); } - // Prints a button to sync text with visualization - void print_assembly_button(const void *op) { - int asm_lno = assembly_info.get_asm_lno((uint64_t)op); - if (asm_lno != -1) { - stream << ""; - } + inline std::string escape_html(std::string src) { + src = replace_all(src, "&", "&"); + src = replace_all(src, "<", "<"); + src = replace_all(src, ">", ">"); + src = replace_all(src, "\"", """); + src = replace_all(src, "/", "/"); + src = replace_all(src, "'", "'"); + return src; } // CUDA kernels are embedded into modules as PTX assembly. This // routine pretty - prints that assembly format. void print_cuda_gpu_source_kernels(const std::string &str) { - print_opening_tag("code", "ptx"); + print_opening_tag("div", "code ptx"); int current_id = -1; bool in_braces = false; @@ -955,38 +808,32 @@ class HTMLCodePrinter : public IRVisitor { for (std::string line; std::getline(ss, line);) { if (line.empty()) { - stream << "\n"; + stream << "\n"; continue; } - line = replace_all(line, "&", "&"); - line = replace_all(line, "<", "<"); - line = replace_all(line, ">", ">"); - line = replace_all(line, "\"", """); - line = replace_all(line, "/", "/"); - line = replace_all(line, "'", "'"); + line = escape_html(line); + + bool should_print_open_indent = false; if (starts_with(line, ".visible .entry")) { std::vector parts = split_string(line, " "); if (parts.size() == 3) { in_func_signature = true; current_id = gen_unique_id(); - print_toggle_anchor_opening_tag(current_id); - print_show_hide_icon(current_id); + print_show_hide_btn_begin(current_id); std::string kernel_name = parts[2].substr(0, parts[2].length() - 1); line = ".visible .entry "; line += variable(kernel_name) + " ("; current_kernel = kernel_name; } } else if (starts_with(line, ")") && in_func_signature) { - print_toggle_anchor_closing_tag(); in_func_signature = false; line = ")" + line.substr(1); } else if (starts_with(line, "{") && !in_braces) { + print_opening_brace(); in_braces = true; - print_toggle_anchor_closing_tag(); - print_html_element("span", "matched", "{"); internal_assert(current_id != -1); - print_opening_tag("div", "indent", current_id); + should_print_open_indent = true; current_id = -1; line = line.substr(1); scope.push(current_kernel, gen_unique_id()); @@ -1042,8 +889,8 @@ class HTMLCodePrinter : public IRVisitor { line = "@" + variable(pred) + "" + line.substr(idx); } - // Labels - if (line.front() == 'L' && !indent && (idx = line.find(':')) != std::string::npos) { + // Labels (depending on the LLVM version we get L with or without a dollar) + if (starts_with(line, "$L_") && !indent && (idx = line.find(':')) != std::string::npos) { std::string label = line.substr(0, idx); line = "" + variable(label) + ":" + line.substr(idx + 1); } @@ -1092,7 +939,7 @@ class HTMLCodePrinter : public IRVisitor { } else if (op.front() == '{') { std::string reg = op.substr(1); operands_str += '{' + variable(reg); - } else if (op.front() == 'L') { + } else if (starts_with(op, "$L_")) { // Labels operands_str += "" + variable(op) + ""; } else { @@ -1103,44 +950,265 @@ class HTMLCodePrinter : public IRVisitor { line = line.substr(0, idx + 2) + operands_str; } + stream << ""; if (indent) { stream << " "; } - stream << line << "\n"; - } - print_closing_tag("code"); - } + stream << line << "\n"; - // Prints the args in a function declaration - void print_fndecl_args(const std::vector &args) { - bool print_delim = false; - for (const auto &arg : args) { - if (print_delim) { - print_html_element("span", "matched", ","); - print_text(" "); + // Indent-divs can only be opened after the line is finished. + if (should_print_open_indent) { + print_show_hide_btn_end(nullptr); + print_opening_tag("div", "indent", current_id); } - print_variable(arg.name); - print_delim = true; } + print_closing_tag("div"); } - /* Helper functions for printing IR nodes */ - void print_constant(std::string cls, Expr c) { - print_opening_tag("span", cls); - stream << c; - print_closing_tag("span"); - } +private: + // Handle to output file stream + T &stream; - void print_type(Type t) { - print_opening_tag("span", "Type"); - stream << t; - print_closing_tag("span"); - } + // Used to generate unique ids + int id = 0; + std::map &node_ids; - void print_binary_op(const Expr &a, const Expr &b, std::string op) { - print_opening_tag("span", "BinaryOp"); - print_html_element("span", "matched", "("); - print(a); + // Used to track scope during IR traversal + Scope scope; + + /* All spans and divs will have an id of the form "x-y", where x + * is shared among all spans/divs in the same context, and y is unique. + * These variables are used to track the context within generated HTML */ + std::vector context_stack; + std::vector context_stack_tags; + + // Holds cost information for visualized program + IRCostModel cost_model; + AssemblyInfo host_assembly_info; + AssemblyInfo device_assembly_info; + bool enable_assembly_features; + + /* Private print functions to handle various IR types */ + void print(const Buffer<> &buf) { + // Open div to hold this buffer + print_opening_tag("div", "Buffer"); + + // Print buffer name and move on + print_html_element("span", "keyword", "buffer "); + print_variable(buf.name()); + + // Close div holding this buffer + print_closing_tag("div"); + } + + void print(const LoweredFunc &fn) { + // Generate a unique ID for this function + int id = gen_unique_id(); + + // Enter new scope for this function + scope.push(fn.name, id); + + // Open div to hold this buffer + print_opening_tag("div", "Function"); + + // Generate the show hide icon/text buttons + print_show_hide_btn_begin(id); + + // -- print text (fn name and args) + print_opening_tag("span", "matched"); + print_html_element("span", "keyword ", "func ", "lowered-func-" + fn.name); + print_text(fn.name + "("); + print_closing_tag("span"); + print_fndecl_args(fn.args); + print_html_element("span", "matched", ")"); + + // Open code block to hold function body + print_opening_brace(); + print_show_hide_btn_end(nullptr); + + // Open indented div to hold body code + print_opening_tag("div", "indent FunctionBody", id); + + // Print function body + print(fn.body); + + // Close indented div holding body code + print_closing_tag("div"); + + // Close code block holding func body + print_html_element("span", "matched ClosingBrace cb-" + std::to_string(id), "}"); + + // Close div holding this function + print_closing_tag("div"); + + // Pop out to outer scope + scope.pop(fn.name); + } + + void print(const Expr &ir) { + ir.accept(this); + } + + void print(const Stmt &ir) { + ir.accept(this); + } + + /* Methods used to emit common HTML patterns */ + + // Prints the opening tag for the specified html element. A unique ID is + // auto-generated unless provided. + void print_opening_tag(const std::string &tag, const std::string &cls, int id = -1) { + stream << "<" << tag << " class='" << cls << "' id='"; + if (id == -1) { + stream << context_stack.back() << "-"; + stream << gen_unique_id(); + } else { + stream << id; + } + stream << "'>"; + context_stack.push_back(gen_unique_id()); + context_stack_tags.push_back(tag); + } + + void print_opening_tag(const std::string &tag, const std::string &cls, std::string id) { + stream << "<" << tag << " class='" << cls << "' id='" << id << "'>"; + context_stack.push_back(gen_unique_id()); + context_stack_tags.push_back(tag); + } + + // Prints the closing tag for the specified html element. + void print_closing_tag(const std::string &tag) { + internal_assert(!context_stack.empty() && tag == context_stack_tags.back()) + << tag << " " << context_stack.empty() << " " << context_stack_tags.back(); + context_stack.pop_back(); + context_stack_tags.pop_back(); + stream << ""; + } + + // Prints an html element: opening tag, body and closing tag + void print_html_element(const std::string &tag, const std::string &cls, const std::string &body, int id = -1) { + print_opening_tag(tag, cls, id); + stream << body; + print_closing_tag(tag); + } + + void print_html_element(const std::string &tag, const std::string &cls, const std::string &body, std::string id) { + print_opening_tag(tag, cls, id); + stream << body; + print_closing_tag(tag); + } + + void print_opening_brace() { + print_html_element("span", "matched OpeningBrace", "{"); + } + + void print_closing_brace() { + print_html_element("span", "matched ClosingBrace", "}"); + } + + // Prints the opening/closing tags for an anchor that toggles code block view + void print_show_hide_btn_begin(int id, bool collapsed = false) { + stream << ""; + stream << "
"; + if (op) { + print_assembly_button(op); + } + stream << "
"; + } + + // Prints newline to stream + void print_ln() { + stream << '\n'; + } + + // Prints a variable to stream + void print_variable(const std::string &x) { + stream << variable(x); + } + + std::string variable(const std::string &x) { + int id; + if (scope.contains(x)) { + id = scope.get(x); + } else { + id = gen_unique_id(); + scope.push(x, id); + } + std::ostringstream s; + s << ""; + s << x; + s << ""; + return s.str(); + } + + // Prints text to stream + void print_text(const std::string &x) { + stream << x; + } + + // Prints a button to sync text with visualization + void print_assembly_button(const void *op) { + if (!enable_assembly_features) { + return; + } + { + int asm_lno = host_assembly_info.get_asm_lno((uint64_t)op); + if (asm_lno != -1) { + stream << "
" + << "Jump to Host Assembly" + << "" << host_assembly_info.get_label((uint64_t)op) << "" + << "
"; + } + } + { + int asm_lno = device_assembly_info.get_asm_lno((uint64_t)op); + if (asm_lno != -1) { + stream << "
" + << "Jump to Device Code" + << "" << device_assembly_info.get_label((uint64_t)op) << "" + << "
"; + } + } + } + + // Prints the args in a function declaration + void print_fndecl_args(const std::vector &args) { + bool print_delim = false; + for (const auto &arg : args) { + if (print_delim) { + print_html_element("span", "matched", ","); + print_text(" "); + } + print_variable(arg.name); + print_delim = true; + } + } + + /* Helper functions for printing IR nodes */ + void print_constant(std::string cls, Expr c) { + print_opening_tag("span", cls); + stream << c; + print_closing_tag("span"); + } + + void print_type(Type t) { + print_opening_tag("span", "Type"); + stream << t; + print_closing_tag("span"); + } + + void print_binary_op(const Expr &a, const Expr &b, std::string op) { + print_opening_tag("span", "BinaryOp"); + print_html_element("span", "matched", "("); + print(a); print_text(" "); print_html_element("span", "matched Operator", op); print_text(" "); @@ -1150,7 +1218,7 @@ class HTMLCodePrinter : public IRVisitor { } void print_function_call(std::string fn_name, const std::vector &args, int id) { - print_opening_tag("span", "nav-anchor", "fn-call-" + std::to_string(id)); + print_opening_tag("span", "", "fn-call-" + std::to_string(id)); print_function_call(fn_name, args); print_closing_tag("span"); } @@ -1194,18 +1262,14 @@ class HTMLCodePrinter : public IRVisitor { print_opening_tag("div", "ForkTask"); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); // -- print text print_html_element("span", "keyword matched", "task"); - print_toggle_anchor_closing_tag(); - // Open code block to hold task body - print_html_element("span", "matched", " {"); + print_opening_brace(); + print_show_hide_btn_end(nullptr); // Open indented div to hold body code print_opening_tag("div", "indent ForkTask", id); @@ -1240,30 +1304,40 @@ class HTMLCodePrinter : public IRVisitor { // Prints the button/indicator for the compute cost of a line in the program void print_compute_cost(const IRNode *op, int id) { int max_line_cost = cost_model.get_max_compute_cost(false); + int max_block_cost = cost_model.get_max_compute_cost(true); int line_cost = cost_model.get_compute_cost(op, false); int block_cost = cost_model.get_compute_cost(op, true); + if (dynamic_cast(op) || dynamic_cast(op)) { + block_cost = line_cost; + } std::string _id = "cc-" + std::to_string(id); - print_cost_btn(line_cost, block_cost, max_line_cost, _id, "Op Count: "); + print_cost_btn(line_cost, block_cost, max_line_cost, max_block_cost, _id, "Op Count: "); } // Prints the button/indicator for the data movement cost of a line in the program void print_data_movement_cost(const IRNode *op, int id) { int max_line_cost = cost_model.get_max_data_movement_cost(false); + int max_block_cost = cost_model.get_max_data_movement_cost(true); int line_cost = cost_model.get_data_movement_cost(op, false); int block_cost = cost_model.get_data_movement_cost(op, true); + if (dynamic_cast(op) || dynamic_cast(op)) { + block_cost = line_cost; + } std::string _id = "dc-" + std::to_string(id); - print_cost_btn(line_cost, block_cost, max_line_cost, _id, "Bits Moved: "); + print_cost_btn(line_cost, block_cost, max_line_cost, max_block_cost, _id, "Bits Moved: "); } // Prints a cost button/indicator - void print_cost_btn(int line_cost, int block_cost, int max_line_cost, std::string id, std::string prefix) { + void print_cost_btn(int line_cost, int block_cost, int max_line_cost, int max_block_cost, std::string id, std::string prefix) { const int num_cost_buckets = 20; + const auto compand = [](int v) -> int { return (int)std::sqrt(v * 10); }; - int line_cost_bin_size = (max_line_cost / num_cost_buckets) + 1; - int block_cost_bin_size = (max_line_cost / num_cost_buckets) + 1; + int max_cost = std::max(max_line_cost, max_block_cost); // This should always be the block cost. + int line_cost_bin_size = (compand(max_cost) / num_cost_buckets) + 1; + int block_cost_bin_size = (compand(max_cost) / num_cost_buckets) + 1; - int line_costc = line_cost / line_cost_bin_size; - int block_costc = block_cost / block_cost_bin_size; + int line_costc = compand(line_cost) / line_cost_bin_size; + int block_costc = compand(block_cost) / block_cost_bin_size; if (line_costc >= num_cost_buckets) { line_costc = num_cost_buckets - 1; @@ -1272,13 +1346,32 @@ class HTMLCodePrinter : public IRVisitor { block_costc = num_cost_buckets - 1; } - stream << "
"; + std::string line_cost_class; + std::string block_cost_class; + if (line_cost == 0) { + line_cost_class = "CostColorNone"; + } else { + line_cost_class = "CostColor" + std::to_string(line_costc); + } + if (block_cost == 0) { + block_cost_class = "CostColorNone"; + } else { + block_cost_class = "CostColor" + std::to_string(block_costc); + } + if (block_cost == line_cost) { + block_cost_class += " NoChildCost"; + } + + stream << "
"; - stream << "" - << prefix << line_cost - << ""; + stream << "" + << prefix << line_cost; + if (line_cost != block_cost) { + stream << "
Total " << prefix << block_cost; + } + stream << "
"; stream << "
"; } @@ -1384,7 +1477,7 @@ class HTMLCodePrinter : public IRVisitor { } void visit(const LE *op) override { - print_binary_op(op->a, op->b, "<="); + print_binary_op(op->a, op->b, "<="); } void visit(const GT *op) override { @@ -1418,7 +1511,7 @@ class HTMLCodePrinter : public IRVisitor { void visit(const Load *op) override { int id = gen_node_id(op); - print_opening_tag("span", "Load nav-anchor", "load-" + std::to_string(id)); + print_opening_tag("span", "Load", "load-" + std::to_string(id)); print_opening_tag("span", "matched"); print_variable(op->name); print_text("["); @@ -1518,26 +1611,18 @@ class HTMLCodePrinter : public IRVisitor { print_cost_buttons(op, id); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); // -- print text print_opening_tag("span", "matched"); - print_html_element("span", "keyword nav-anchor", op->is_producer ? "produce " : "consume ", + print_html_element("span", "keyword", op->is_producer ? "produce " : "consume ", "prodcons-" + std::to_string(id)); print_variable(op->name); print_closing_tag("span"); - print_toggle_anchor_closing_tag(); - - // Add a button to jump to this producer/consumer in the viz - print_visualization_button("prodcons-viz-" + std::to_string(id)); - print_assembly_button(op); - // Open code block to hold function body - print_html_element("span", "matched", "{"); + print_opening_brace(); + print_show_hide_btn_end(op); // Open indented div to hold body code print_opening_tag("div", "indent ProducerConsumerBody", id); @@ -1560,6 +1645,12 @@ class HTMLCodePrinter : public IRVisitor { scope.pop(op->name); } + std::string ForType_to_string(ForType type) { + std::ostringstream ss; + ss << type; + return ss.str(); + } + void visit(const For *op) override { // Give this loop a unique id int id = gen_node_id(op); @@ -1568,20 +1659,17 @@ class HTMLCodePrinter : public IRVisitor { scope.push(op->name, id); // Start a dive to hold code for this allocate - print_opening_tag("div", "For"); + print_opening_tag("div", "For for-type-" + ForType_to_string(op->for_type)); // Print cost buttons print_cost_buttons(op, id); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); // -- print text print_opening_tag("span", "matched"); - print_opening_tag("span", "keyword nav-anchor", "loop-" + std::to_string(id)); + print_opening_tag("span", "keyword", "loop-" + std::to_string(id)); stream << op->for_type << op->device_api; print_closing_tag("span"); print_text(" ("); @@ -1593,14 +1681,9 @@ class HTMLCodePrinter : public IRVisitor { print(op->extent); print_html_element("span", "matched", ")"); - print_toggle_anchor_closing_tag(); - - // Add a button to jump to this loop in the viz - print_visualization_button("loop-viz-" + std::to_string(id)); - print_assembly_button(op); - // Open code block to hold function body - print_html_element("span", "matched", "{"); + print_opening_brace(); + print_show_hide_btn_end(op); // Open indented div to hold body code print_opening_tag("div", "indent ForBody", id); @@ -1631,10 +1714,7 @@ class HTMLCodePrinter : public IRVisitor { print_opening_tag("div", "Acquire"); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); // -- print text print_opening_tag("span", "matched"); @@ -1646,13 +1726,9 @@ class HTMLCodePrinter : public IRVisitor { print(op->count); print_html_element("span", "matched", ")"); - print_toggle_anchor_closing_tag(); - - // Add a button to jump to this acquire in the viz - print_visualization_button("acquire-viz-" + std::to_string(id)); - // Open code block to hold function body - print_html_element("span", "matched", "{"); + print_opening_brace(); + print_show_hide_btn_end(op); // Open indented div to hold body code print_opening_tag("div", "indent AcquireBody", id); @@ -1683,7 +1759,7 @@ class HTMLCodePrinter : public IRVisitor { // Print store target print_opening_tag("span", "matched"); - print_opening_tag("span", "nav-anchor", "store-" + std::to_string(id)); + print_opening_tag("span", "", "store-" + std::to_string(id)); print_variable(op->name); print_text("["); print_closing_tag("span"); @@ -1741,7 +1817,7 @@ class HTMLCodePrinter : public IRVisitor { // Print allocation name, type and extents print_opening_tag("span", "matched"); - print_html_element("span", "keyword nav-anchor", "allocate ", "allocate-" + std::to_string(id)); + print_html_element("span", "keyword", "allocate ", "allocate-" + std::to_string(id)); print_variable(op->name); print_text("["); print_closing_tag("span"); @@ -1783,9 +1859,6 @@ class HTMLCodePrinter : public IRVisitor { print_html_element("span", "matched", "}"); } - // Add a button to jump to this allocation in the viz - print_visualization_button("allocate-viz-" + std::to_string(id)); - // Print allocation body print_ln(); print_opening_tag("div", "AllocateBody"); @@ -1821,10 +1894,7 @@ class HTMLCodePrinter : public IRVisitor { print_opening_tag("div", "Realize"); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); // -- print text print_opening_tag("span", "matched"); @@ -1849,13 +1919,9 @@ class HTMLCodePrinter : public IRVisitor { print(op->condition); } - print_toggle_anchor_closing_tag(); - - // Add a button to jump to this realize in the viz - print_visualization_button("realize-viz-" + std::to_string(id)); - // Open code block to hold function body - print_html_element("span", "matched", " {"); + print_opening_brace(); + print_show_hide_btn_end(op); // Open indented div to hold body code print_opening_tag("div", "indent RealizeBody", id); @@ -1892,18 +1958,14 @@ class HTMLCodePrinter : public IRVisitor { print_opening_tag("div", "Fork"); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); // -- print text print_html_element("span", "keyword matched", "fork"); - print_toggle_anchor_closing_tag(); - // Open code block to hold fork body - print_html_element("span", "matched", " {"); + print_opening_brace(); + print_show_hide_btn_end(op); // Open indented div to hold body code print_opening_tag("div", "indent ForkBody", id); @@ -1927,55 +1989,47 @@ class HTMLCodePrinter : public IRVisitor { // Give this acquire a unique id int then_block_id = gen_unique_id(); int then_node_id = gen_node_id(op->then_case.get()); + int last_then_block_id = -1; - // Start a dive to hold code for this conditional + // Start a div to hold code for this conditional print_opening_tag("div", "IfThenElse"); // Print cost buttons print_cost_buttons(op, then_block_id); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(then_block_id); - - // -- print icon - print_show_hide_icon(then_block_id); + print_show_hide_btn_begin(then_block_id); - // -- print text + // Print the actual "if (...) {" print_opening_tag("span", "matched"); - print_html_element("span", "keyword nav-anchor IfSpan", "if", "cond-" + std::to_string(then_node_id)); + print_html_element("span", "keyword IfSpan", "if", "cond-" + std::to_string(then_node_id)); print_text(" ("); print_closing_tag("span"); print(op->condition); print_html_element("span", "matched", ")"); - print_toggle_anchor_closing_tag(); - - // Add a button to jump to this conditional in the viz - print_visualization_button("cond-viz-" + std::to_string(then_node_id)); - // Flatten nested if's in the else case as an // `if-then-else_if-else` sequence while (true) { /* Handle the `then` case */ // Open code block to hold `then` case - print_html_element("span", "matched", " {"); + print_opening_brace(); + print_show_hide_btn_end(op); // Open indented div to hold code for the `then` case print_opening_tag("div", "indent ThenBody", then_block_id); - - // Print then case body print(op->then_case); - - // Close indented div holding `then` case print_closing_tag("div"); print_ln(); - - // Close code block holding `then` case - print_html_element("span", "matched ClosingBrace cb-" + std::to_string(then_block_id), "}"); + last_then_block_id = then_block_id; // If there is no `else` case, we are done! if (!op->else_case.defined()) { + + // Close code block holding `then` case + print_html_element("span", "matched ClosingBrace cb-" + std::to_string(then_block_id), "}"); + break; } @@ -1991,27 +2045,23 @@ class HTMLCodePrinter : public IRVisitor { print_cost_buttons(op, then_block_id); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(then_block_id); + print_show_hide_btn_begin(then_block_id); - // -- print icon - print_show_hide_icon(then_block_id); + // Close code block with a "}" from the previous block, *after* we have printed the new collapser button. + internal_assert(last_then_block_id != -1); + print_html_element("span", "matched ClosingBrace cb-" + std::to_string(last_then_block_id), "}"); - // -- print text + // Print the actual "} else if (...) {" condition statement print_opening_tag("span", "matched"); - print_html_element("span", "keyword nav-anchor IfSpan", "else if", "cond-" + std::to_string(then_node_id)); + print_html_element("span", "keyword IfSpan", " else if", "cond-" + std::to_string(then_node_id)); print_text(" ("); print_closing_tag("span"); print(nested_if->condition); print_html_element("span", "matched", ")"); - print_toggle_anchor_closing_tag(); - - // Add a button to jump to this conditional branch in the viz - print_visualization_button("cond-viz-" + std::to_string(then_node_id)); - // Update op to the nested if for next loop iteration op = nested_if; - + last_then_block_id = then_block_id; } else { // Otherwise, print it and we are done! int else_block_id = gen_unique_id(); @@ -2021,23 +2071,20 @@ class HTMLCodePrinter : public IRVisitor { print_cost_buttons(op, else_block_id); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(else_block_id); + print_show_hide_btn_begin(else_block_id); - // -- print icon - print_show_hide_icon(else_block_id); + // Close code block with a "}" from the previous block, *after* we have printed the new collapser button. + internal_assert(last_then_block_id != -1); + print_html_element("span", "matched ClosingBrace cb-" + std::to_string(last_then_block_id), "}"); // -- print text print_opening_tag("span", "matched"); - print_html_element("span", "keyword nav-anchor IfSpan", "else", "cond-" + std::to_string(else_node_id)); + print_html_element("span", "keyword IfSpan", " else", "cond-" + std::to_string(else_node_id)); print_closing_tag("span"); - print_toggle_anchor_closing_tag(); - - // Add a button to jump to this conditional branch in the viz - print_visualization_button("cond-viz-" + std::to_string(else_node_id)); - // Open code block to hold `else` case - print_html_element("span", "matched", " {"); + print_opening_brace(); + print_show_hide_btn_end(op); // Open indented div to hold code for the `then` case print_opening_tag("div", "indent ElseBody", else_block_id); @@ -2062,7 +2109,7 @@ class HTMLCodePrinter : public IRVisitor { } void visit(const Evaluate *op) override { - print_opening_tag("div", "Block"); + print_opening_tag("div", "Block Evaluate"); // Print cost buttons print_cost_buttons(op); print(op->value); @@ -2099,7 +2146,9 @@ class HTMLCodePrinter : public IRVisitor { print_text("("); print_type(op->type); print_text(")"); - print_function_call("vector_reduce", {op->op, op->value}); + std::ostringstream op_ss; + op_ss << op->op; + print_function_call("vector_reduce_" + op_ss.str(), {op->value}); print_closing_tag("span"); print_ln(); } @@ -2149,10 +2198,7 @@ class HTMLCodePrinter : public IRVisitor { print_opening_tag("div", "Atomic"); // Generate the show hide icon/text buttons - print_toggle_anchor_opening_tag(id); - - // -- print icon - print_show_hide_icon(id); + print_show_hide_btn_begin(id); // -- print text print_html_element("span", "matched keyword", "atomic"); @@ -2162,10 +2208,9 @@ class HTMLCodePrinter : public IRVisitor { print_html_element("span", "matched", ")"); } - print_toggle_anchor_closing_tag(); - // Open code block to hold atomic body - print_html_element("span", "matched", " {"); + print_opening_brace(); + print_show_hide_btn_end(op); // Open indented div to hold atomic code print_opening_tag("div", "indent AtomicBody", id); @@ -2185,614 +2230,22 @@ class HTMLCodePrinter : public IRVisitor { } }; -/** HTMLVisualizationPrinter - * Visualizes the IR in HTML. The visualization is essentially - * an abstracted version of the code, highlighting the higher - * level execution pipeline along with key properties of the - * computation performed at each stage. +/** PipelineHTMLInspector Class + * Generates the output html page. Currently the html page has + * three key tabs: IR code, Visualized pipeline and the generated + * assembly. */ -class HTMLVisualizationPrinter : public IRVisitor { +class PipelineHTMLInspector { public: - HTMLVisualizationPrinter(std::ofstream &os, std::map &nids) - : stream(os), node_ids(nids) { - } - - // Make class non-copyable and non-moveable - HTMLVisualizationPrinter(const HTMLVisualizationPrinter &) = delete; - HTMLVisualizationPrinter &operator=(const HTMLVisualizationPrinter &) = delete; - - void init_cost_info(IRCostModel cm) { - cost_model = std::move(cm); - } - - void print(const Module &m, AssemblyInfo asm_info) { - assembly_info = std::move(asm_info); - for (const auto &fn : m.functions()) { - print(fn); - } - } - -private: - // Handle to output file stream - std::ofstream &stream; - - // Used to track the context within generated HTML - std::vector context_stack_tags; - - // Assembly line number info - AssemblyInfo assembly_info; - - // Holds cost information for visualized program - IRCostModel cost_model; - - // Generate unique ids - int id = 0; - std::map &node_ids; - - int gen_unique_id() { - return id++; - } - - int gen_node_id(const IRNode *node) { - if (node_ids.count(node) == 0) { - node_ids[node] = gen_unique_id(); - } - return node_ids[node]; - } - - /* Private print functions to handle various IR types */ - void print(const LoweredFunc &fn) { - int id = gen_unique_id(); - - // Start a div to hold the function viz - print_opening_tag("div", "center fn-wrapper"); - - // Create the header bar - print_opening_tag("div", "fn-header"); - print_collapse_expand_btn(id); - print_code_button("lowered-func-" + fn.name); - print_html_element("span", "fn-title", "Func: " + fn.name, "lowered-func-viz-" + fn.name); - print_closing_tag("div"); - - // Print function body - print_opening_tag("div", "fn-body", "viz-" + std::to_string(id)); - fn.body.accept(this); - print_closing_tag("div"); - - // Close function div - print_closing_tag("div"); - } - - /* Methods used to emit common HTML patterns */ - - // Prints the opening tag for the specified html element. - void print_opening_tag(const std::string &tag, const std::string &cls) { - stream << "<" << tag << " class='" << cls << "'>"; - context_stack_tags.push_back(tag); - } - - void print_opening_tag(const std::string &tag, const std::string &cls, const std::string &id) { - stream << "<" << tag << " class='" << cls << "' id='" << id << "'>"; - context_stack_tags.push_back(tag); - } - - // Prints the closing tag for the specified html element. - void print_closing_tag(const std::string &tag) { - internal_assert(tag == context_stack_tags.back()); - context_stack_tags.pop_back(); - stream << ""; - } - - // Prints an html element: opening tag, body and closing tag - void print_html_element(const std::string &tag, const std::string &cls, const std::string &body) { - print_opening_tag(tag, cls); - stream << body; - print_closing_tag(tag); - } - - void print_html_element(const std::string &tag, const std::string &cls, const std::string &body, const std::string &id) { - print_opening_tag(tag, cls, id); - stream << body; - print_closing_tag(tag); - } - - // Prints text to stream - void print_text(const std::string &x) { - stream << x; - } - - // Prints a button to sync visualization with code - void print_code_button(const std::string &id) { - stream << ""; - } - - // Prints a button to sync visualization with assembly - void print_asm_button(const std::string &id) { - stream << ""; - } - - // Prints a function-call box - void print_fn_button(const std::string &name, int id) { - print_opening_tag("div", "fn-call"); - print_code_button("fn-call-" + std::to_string(id)); - print_text(get_as_var(name) + "(...)"); - print_closing_tag("div"); - } - - // Prints a button to collapse or expand a visualization box - void print_collapse_expand_btn(int id) { - stream << "" - << ""; - } - - // Prints the box title within the div.box-header - void print_box_title(const std::string &title, const std::string &anchor) { - print_opening_tag("div", "box-title"); - print_html_element("span", "", title, anchor); - print_closing_tag("div"); - } - - // Prints the cost indicator buttons within div.box-header - void print_cost_buttons(int id, const IRNode *op) { - print_opening_tag("div", "viz-cost-btns"); - - // Print compute cost indicator - int max_line_ccost = cost_model.get_max_compute_cost(false); - int line_ccost = cost_model.get_compute_cost(op, false); - int block_ccost = cost_model.get_compute_cost(op, true); - print_cost_button(line_ccost, block_ccost, max_line_ccost, "vcc-" + std::to_string(id), "Op Count: "); - - // Print data movement cost indicator - int max_line_dcost = cost_model.get_max_data_movement_cost(false); - int line_dcost = cost_model.get_data_movement_cost(op, false); - int block_dcost = cost_model.get_data_movement_cost(op, true); - // Special handling for Store nodes; since unlike the code view - // the viz view prints stores and loads seperately, therefore using - // inclusive cost is confusing. - if (op->node_type == IRNodeType::Store) { - const Store *st = static_cast(op); - line_dcost = st->value.type().bits() * st->value.type().lanes(); - block_dcost = line_dcost; - } - print_cost_button(line_dcost, block_dcost, max_line_dcost, "vdc-" + std::to_string(id), "Bits Moved: "); - - print_closing_tag("div"); - } - - void print_cost_button(int line_cost, int block_cost, int max_line_cost, const std::string &id, const std::string &prefix) { - const int num_cost_buckets = 20; - - int line_cost_bin_size = (max_line_cost / num_cost_buckets) + 1; - int block_cost_bin_size = (max_line_cost / num_cost_buckets) + 1; - - int line_costc = line_cost / line_cost_bin_size; - int block_costc = block_cost / block_cost_bin_size; - - if (line_costc >= num_cost_buckets) { - line_costc = num_cost_buckets - 1; - } - if (block_costc >= num_cost_buckets) { - block_costc = num_cost_buckets - 1; - } - - stream << "
"; - - stream << "" - << prefix << line_cost - << ""; - - stream << "
"; - } - - // Prints the box .box-header within div.box - void print_box_header(int id, const IRNode *op, const std::string &anchor, const std::string &code_anchor, const std::string &title) { - print_opening_tag("div", "box-header"); - print_collapse_expand_btn(id); - print_code_button(code_anchor); - print_box_title(title, anchor); - print_cost_buttons(id, op); - print_closing_tag("div"); - } - - // Prints the box .box-header within div.box, contains the asm info button - void print_box_header_asm(int id, const IRNode *op, const std::string &anchor, const std::string &code_anchor, const std::string &asm_anchor, const std::string &title) { - print_opening_tag("div", "box-header"); - print_collapse_expand_btn(id); - print_code_button(code_anchor); - print_asm_button(asm_anchor); - print_box_title(title, anchor); - print_cost_buttons(id, op); - print_closing_tag("div"); - } - - // Converts an expr to a string without printing to stream - std::string get_as_str(const Expr &e) { - return get_as_str(e, ""); - } - - std::string get_as_str(const Expr &e, const std::string &prefix) { - if (prefix == "Else") { - return "Else"; - } - - std::ostringstream ss; - HTMLCodePrinter printer(ss, node_ids); - e.accept(&printer); - std::string html_e = ss.str(); - - if (large_expr(e)) { - return prefix + truncate_html(html_e); - } else { - return prefix + html_e; - } - } - - // Return variable name wrapped with html that enables matching - std::string get_as_var(const std::string &name) { - return "" + name + ""; - } - - // Sometimes the expressions are too large to show within the viz. In - // such cases we use tooltips. - bool large_expr(const Expr &e) { - std::ostringstream ss; - ss << e; - return ss.str().size() > 50; - } - - std::string truncate_html(const std::string &cond) { - int id = gen_unique_id(); - - std::ostringstream ss; - - // Show condition expression button - ss << ""; - - return ss.str(); - } - - // Prints a single node in an `if-elseif-...-else` chain - void print_if_tree_node(const Stmt &node, const Expr &cond, const std::string &prefix) { - // Assign unique id to this node - int box_id = gen_unique_id(); - int node_id = gen_node_id(node.get()); - - // Start tree node - print_opening_tag("li", ""); - print_opening_tag("span", "tf-nc if-node"); - - // Start a box to hold viz - print_opening_tag("div", "box center IfBox"); - - // Create viz content - std::string aid = std::to_string(node_id); - print_box_header(box_id, node.get(), "cond-viz-" + aid, "cond-" + aid, get_as_str(cond, prefix)); - - // Print contents of node - print_opening_tag("div", "box-body", "viz-" + std::to_string(box_id)); - node.accept(this); - print_closing_tag("div"); - - // Close box holding viz - print_closing_tag("div"); - - // Close tree node - print_closing_tag("span"); - print_closing_tag("li"); - } - - /* Visitor functions for each IR node */ - using IRVisitor::visit; - - /* Override key visit functions */ - - void visit(const Allocate *op) override { - // Assign unique id to this node - int id = gen_node_id(op); - - // Start a box to hold viz - print_opening_tag("div", "box center AllocateBox"); - - // Print box header - std::string aid = std::to_string(id); - print_box_header(id, op, "allocate-viz-" + aid, "allocate-" + aid, "Allocate: " + op->name); - - // Start a box to hold viz - print_opening_tag("div", "box-body", "viz-" + std::to_string(id)); - - // Generate a table with allocation details - print_opening_tag("table", "allocate-table"); - - // - Memory type - stream << "Memory Type" << op->memory_type << ""; - - // - Allocation condition - if (!is_const_one(op->condition)) { - stream << "Condition" << op->condition << ""; - } - - // - Data type - stream << "Data Type" << op->type << ""; - - // - Dimensions - for (size_t i = 0; i < op->extents.size(); i++) { - stream << "Dim-" << i << "" << get_as_str(op->extents[i]) << ""; - } - - print_closing_tag("table"); - - op->body.accept(this); - - print_closing_tag("div"); - - print_closing_tag("div"); - } - - void visit(const For *op) override { - // Assign unique id to this node - int id = gen_node_id(op); - - // Start a box to hold viz - print_opening_tag("div", "box center ForBox"); - - // Print box header - std::string aid = std::to_string(id); - int asm_lno = assembly_info.get_asm_lno((uint64_t)op); - if (asm_lno == -1) { - print_box_header(id, op, "loop-viz-" + aid, "loop-" + aid, "For: " + get_as_var(op->name)); - } else { - print_box_header_asm(id, op, "loop-viz-" + aid, "loop-" + aid, std::to_string(asm_lno), "For: " + get_as_var(op->name)); - } - - // Start a box to hold viz - print_opening_tag("div", "box-body", "viz-" + std::to_string(id)); - - // Generate a table with loop details - print_opening_tag("table", "allocate-table"); - - // - Loop type - if (op->for_type != ForType::Serial) { - stream << "Loop Type" << op->for_type << ""; - } - // - Device API - if (op->device_api != DeviceAPI::None) { - stream << "Device API" << op->device_api << ""; - } - // - Min - stream << "Min" << get_as_str(op->min) << ""; - // - Extent - stream << "Extent" << get_as_str(op->extent) << ""; - - print_closing_tag("table"); - - op->body.accept(this); - - print_closing_tag("div"); - - print_closing_tag("div"); - } - - void visit(const IfThenElse *op) override { - // Open If tree - print_opening_tag("div", "tf-tree tf-gap-sm tf-custom-ir-viz"); - - // Create root 'cond' node - if (op->else_case.defined()) { - print_opening_tag("ul", ""); - print_opening_tag("li", ""); - print_html_element("span", "tf-nc if-node if-root-node", "Control Flow Branching"); - } - - // Create children nodes ('then', 'else if' and 'else' cases) - print_opening_tag("ul", ""); - - // `then` case - print_if_tree_node(op->then_case, op->condition, "If: "); - - // `else if` cases - const IfThenElse *nested_if = op; - while (nested_if->else_case.defined() && (nested_if->else_case.as())) { - nested_if = nested_if->else_case.as(); - print_if_tree_node(nested_if->then_case, nested_if->condition, "Else If: "); - } - - // `else` case - if (nested_if->else_case.defined()) { - print_if_tree_node(nested_if->else_case, UIntImm::make(UInt(1), 1), "Else"); - } - - print_closing_tag("ul"); - - // Close If tree - if (op->else_case.defined()) { - print_closing_tag("li"); - print_closing_tag("ul"); - } - print_closing_tag("div"); - } - - void visit(const ProducerConsumer *op) override { - // Assign unique id to this node - int id = gen_node_id(op); - - // Start a box to hold viz - std::string box_name = op->is_producer ? "ProducerBox" : "ConsumerBox"; - print_opening_tag("div", "box center " + box_name); - - // Print box header - std::string aid = std::to_string(id); - std::string prefix = op->is_producer ? "Produce: " : "Consume: "; - int asm_lno = assembly_info.get_asm_lno((uint64_t)op); - if (asm_lno == -1) { - print_box_header(id, op, "prodcons-viz-" + aid, "prodcons-" + aid, prefix + get_as_var(op->name)); - } else { - print_box_header_asm(id, op, "prodcons-viz-" + aid, "prodcons-" + aid, std::to_string(asm_lno), prefix + get_as_var(op->name)); - } - - // Print the body - print_opening_tag("div", "box-body", "viz-" + std::to_string(id)); - op->body.accept(this); - print_closing_tag("div"); - - // Close div holding the producer/consumer - print_closing_tag("div"); - } - - void visit(const Store *op) override { - // Visit the value first. We want to show any loads - // that happen before the store operation - op->value.accept(this); - - // Assign unique id to this node - int id = gen_node_id(op); - - // Start a box to hold viz - print_opening_tag("div", "box center StoreBox"); - - // Print box header - std::string aid = std::to_string(id); - print_box_header(id, op, "store-viz-" + aid, "store-" + aid, "Store: " + get_as_var(op->name)); - - // Start a box to hold viz - print_opening_tag("div", "box-body", "viz-" + std::to_string(id)); - - // Generate a table with store details - print_opening_tag("table", "allocate-table"); - - // - Store predicate - if (!is_const_one(op->predicate)) { - stream << "Predicate" << get_as_str(op->predicate) << ""; - } - - // - Alignment - const bool show_alignment = op->value.type().is_vector() && op->alignment.modulus > 1; - if (show_alignment) { - stream << "Alignment" - << "aligned(" << op->alignment.modulus << ", " << op->alignment.remainder << ")" - << ""; - } - - // - Qualifiers - if (op->value.type().is_vector()) { - const Ramp *idx = op->index.as(); - if (idx && is_const_one(idx->stride)) { - stream << "TypeDense Vector"; - } else { - stream << "TypeStrided Vector"; - } - stream << "Output Tile" << op->value.type() << ""; - } else { - stream << "TypeScalar"; - stream << "Output" << op->value.type() << ""; - } - - print_closing_tag("table"); - - print_closing_tag("div"); - - print_closing_tag("div"); - } - - void visit(const Load *op) override { - // Assign unique id to this node - int id = gen_node_id(op); - - // Start a box to hold viz - print_opening_tag("div", "box center LoadBox"); - - // Print box header - std::string aid = std::to_string(id); - print_box_header(id, op, "load-viz-" + aid, "load-" + aid, "Load: " + get_as_var(op->name)); - - // Start a box to hold viz - print_opening_tag("div", "box-body", "viz-" + std::to_string(id)); - - // Generate a table with load details - print_opening_tag("table", "allocate-table"); - - // - Load predicate - if (!is_const_one(op->predicate)) { - stream << "Predicate" << get_as_str(op->predicate) << ""; - } - - // - Alignment - const bool show_alignment = op->type.is_vector() && op->alignment.modulus > 1; - if (show_alignment) { - stream << "Alignment" - << "aligned(" << op->alignment.modulus << ", " << op->alignment.remainder << ")" - << ""; - } - - // - Qualifiers - if (op->type.is_vector()) { - const Ramp *idx = op->index.as(); - if (idx && is_const_one(idx->stride)) { - stream << "TypeDense Vector"; - } else { - stream << "TypeStrided Vector"; - } - stream << "Output Tile" << op->type << ""; - } else { - stream << "TypeScalar"; - stream << "Output" << op->type << ""; - } - - print_closing_tag("table"); - - print_closing_tag("div"); - - print_closing_tag("div"); - } - - void visit(const Call *op) override { - int id = gen_node_id(op); - // Add viz support for key functions/intrinsics - if (op->name == "halide_do_par_for") { - print_fn_button(op->name, id); - } else if (op->name == "halide_do_par_task") { - print_fn_button(op->name, id); - } else if (op->name == "_halide_buffer_init") { - print_fn_button(op->name, id); - } else if (op->name.rfind("_halide", 0) != 0) { - // Assumption: We want to ignore intrinsics starting with _halide - // but for everything else, generate a warning - debug(2) << "Function call ignored by IRVisualizer: " << op->name << "\n"; - } - } -}; - -/** IRVisualizer Class - * Generates the output html page. Currently the html page has - * three key tabs: IR code, Visualized pipeline and the generated - * assembly. - */ -class IRVisualizer { -public: - // Construct the visualizer and point it to the output file - explicit IRVisualizer(const std::string &html_output_filename, - const Module &m, - const std::string &assembly_input_filename) - : html_code_printer(stream, node_ids), html_viz_printer(stream, node_ids) { - // Open output file - stream.open(html_output_filename.c_str()); + // Construct the visualizer and point it to the output file + explicit PipelineHTMLInspector(const std::string &html_output_filename, + const Module &m, + const std::string &assembly_input_filename, + bool use_conceptual_stmt_ir) + : use_conceptual_stmt_ir(use_conceptual_stmt_ir), + html_code_printer(stream, node_ids, true) { + // Open output file + stream.open(html_output_filename.c_str()); // Load assembly code -- if not explicit specified, assume it will have matching pathname // as our output file, with a different extension. @@ -2821,17 +2274,36 @@ class IRVisualizer { // code is based on darya-ver's original implementation. We // use comments in the generated assembly to infer association // between Halide IR and assembly -- unclear how reliable this is. - asm_info.generate(asm_stream.str(), m); + host_asm_info.gather_nodes_from_functions(m); + host_asm_info.generate(asm_stream.str()); + + Buffer<> device_code_buf = m.get_device_code_buffer(); + if (device_code_buf.defined()) { + std::string device_assembly((char *)device_code_buf.data(), + ((char *)device_code_buf.data() + device_code_buf.size_in_bytes())); + debug(1) << "Generating device AssemblyInfo\n"; + // TODO(mcourteaux): This doesn't generate anything useful, as the + // LLVM comments are only added later in the LLVM CodeGen IRVisitor. + // This conceptual Stmt hasn't seen this seen this + device_asm_info.gather_nodes_from_conceptual_stmt(m); + device_asm_info.generate(device_assembly); + } else { + debug(1) << "No device code buffer found.\n"; + } // Run the cost model over this module to pre-compute all // node costs - cost_model.compute_all_costs(m); + if (use_conceptual_stmt_ir) { + cost_model.compute_conceptual_costs(m); + } else { + cost_model.compute_all_costs(m); + } + cost_model.finalize_cost_computation(); html_code_printer.init_cost_info(cost_model); - html_viz_printer.init_cost_info(cost_model); // Generate html page stream << "\n"; - stream << "\n"; + stream << "\n"; generate_head(m); generate_body(m); stream << ""; @@ -2841,9 +2313,6 @@ class IRVisualizer { // Handle to output file stream std::ofstream stream; - // Handle to assembly file stream - std::ifstream assembly; - // Holds cost information for visualized program IRCostModel cost_model; @@ -2851,128 +2320,147 @@ class IRVisualizer { std::map node_ids; // Used to translate IR to code in HTML + bool use_conceptual_stmt_ir; HTMLCodePrinter html_code_printer; - // Used to translate IR to visualization in HTML - HTMLVisualizationPrinter html_viz_printer; - /* Methods for generating the section of the html file */ void generate_head(const Module &m) { stream << "\n"; - stream << "Visualizing Module: " << m.name() << "\n"; - generate_dependency_links(); - generate_stylesheet(); + stream << "Halide Module: " << m.name() << "\n"; + stream << halide_html_template_StmtToHTML_dependencies_html; +#if INLINE_TEMPLATES + stream << "\n"; +#else + std::filesystem::path dir = std::filesystem::path(__FILE__).parent_path() / "irvisualizer"; + debug(1) << "Will link CSS in directory: " << dir << "\n"; + internal_assert(std::filesystem::exists(dir)); + stream << "\n"; +#endif stream << "\n"; } - // Loads the html code responsible for linking with various js/css libraries from - // `ir_visualizer/dependencies.html` - void generate_dependency_links() { - stream << halide_html_template_StmtToViz_dependencies; - } - - // Loads the stylesheet code from `ir_visualizer/stylesheet.html` - void generate_stylesheet() { - stream << halide_html_template_StmtToViz_stylesheet; - } - /* Methods for generating the section of the html file */ void generate_body(const Module &m) { stream << "\n"; stream << "
\n"; - generate_visualization_tabs(m); + generate_visualization_panes(m); stream << "
\n"; +#if INLINE_TEMPLATES + stream << ""; +#else + std::filesystem::path dir = std::filesystem::path(__FILE__).parent_path() / "irvisualizer"; + debug(1) << "Will link Javascript in directory: " << dir << "\n"; + internal_assert(std::filesystem::exists(dir)); + stream << "\n"; +#endif stream << ""; - generate_javascript(); } - // Generate the three visualization tabs - void generate_visualization_tabs(const Module &m) { - stream << "
\n"; - generate_ir_tab(m); - generate_resize_bar_1(); - generate_visualization_tab(m); - generate_resize_bar_2(); - generate_assembly_tab(m); - stream << "
\n"; - } + // Generate the three visualization panes + void generate_visualization_panes(const Module &m) { + int pane_count = 0; + stream << "
\n"; + stream << "\n"; + generate_ir_pane(m); + generate_resize_bar(pane_count++); + generate_host_assembly_pane(m); + Buffer<> device_code_buf = m.get_device_code_buffer(); + if (device_code_buf.defined()) { + generate_resize_bar(pane_count++); + generate_device_code_pane(device_code_buf); + } - // Generate tab 1/3: Lowered IR code with syntax highlighting in HTML - void generate_ir_tab(const Module &m) { - stream << "
\n"; - html_code_printer.print(m, asm_info); stream << "
\n"; } - // Generate tab 2/3: Lowered IR code with syntax highlighting in HTML - void generate_visualization_tab(const Module &m) { - stream << "
\n"; - html_viz_printer.print(m, asm_info); - stream << "
\n"; + // Generate pane: Lowered IR code with syntax highlighting in HTML + void generate_ir_pane(const Module &m) { + if (use_conceptual_stmt_ir) { + stream << "
\n"; + html_code_printer.print_conceptual_stmt(m, host_asm_info, device_asm_info); + stream << "
\n"; + } else { + stream << "
\n"; + html_code_printer.print(m, host_asm_info, device_asm_info); + stream << "
\n"; + } } - // Generate tab 3/3: Generated assembly code - void generate_assembly_tab(const Module &m) { - stream << "
\n"; + // Generate pane: Generated host assembly code + void generate_host_assembly_pane(const Module &m) { + stream << "
\n"; stream << "
\n"; stream << "
\n";
-        stream << asm_stream.str();
+        std::istringstream ss{asm_stream.str()};
+        for (std::string line; std::getline(ss, line);) {
+            if (line.length() > 500) {
+                // Very long lines in the assembly are typically the _gpu_kernel_sources
+                // as a raw ASCII block in the assembly. Let's chop that off to make
+                // browsers faster when dealing with this.
+                line = line.substr(0, 100) + "\" # omitted the remainder of the ASCII buffer";
+            }
+            stream << html_code_printer.escape_html(line) << "\n";
+        }
+        stream << "\n";
         stream << "
\n"; stream << "
\n"; stream << "
\n"; } - // Generate a resizing bar to control the width of code and visualization tabs - void generate_resize_bar_1() { - stream << R"(
-
-
- -
-
- -
-
-
)"; - } - - // Generate a resizing bar to control the width of visualization and assembly tabs - void generate_resize_bar_2() { - stream << R"(
-
-
- -
-
- -
-
-
)"; - } - - // Loads and initializes the javascript template from `ir_visualizer / javascript_template.html` - void generate_javascript() { - stream << halide_html_template_StmtToViz_javascript; + // Generate pane: Generated device code + void generate_device_code_pane(const Buffer<> &buf) { + stream << "
\n"; + int length = buf.size_in_bytes(); + while (length > 0 && ((const char *)buf.data())[length - 1] == '\0') { + length--; + } + std::string str((const char *)buf.data(), length); + if (starts_with(buf.name(), "cuda_")) { + html_code_printer.print_cuda_gpu_source_kernels(str); + } else { + std::istringstream ss{str}; + stream << "
\n"; + for (std::string line; std::getline(ss, line);) { + stream << "" << html_code_printer.escape_html(line) << "\n"; + // stream << html_code_printer.escape_html(line) << "\n"; + } + stream << "\n
\n"; + } + stream << "
\n"; + } + + // Generate a resizing bar to control the width of code and visualization panes + void generate_resize_bar(int num) { + stream << "
\n"; + stream << "
\n"; + stream << "
\n"; + stream << " \n"; + stream << "
\n"; + stream << "
\n"; + stream << " \n"; + stream << "
\n"; + stream << "
\n"; + stream << "
\n"; } /* Misc helper methods */ // Load assembly code from file std::ostringstream asm_stream; - AssemblyInfo asm_info; + AssemblyInfo host_asm_info; + AssemblyInfo device_asm_info; void load_asm_code(const std::string &asm_file) { user_assert(file_exists(asm_file)) << "Unable to open assembly file: " << asm_file << "\n"; // Open assembly file + std::ifstream assembly; assembly.open(asm_file.c_str()); // Slurp the code into asm_stream @@ -2984,12 +2472,20 @@ class IRVisualizer { }; // The external interface to this module -void print_to_viz(const std::string &html_output_filename, - const Module &m, - const std::string &assembly_input_filename) { - IRVisualizer visualizer(html_output_filename, m, assembly_input_filename); - visualizer.generate_html(m); - debug(1) << "Done generating HTML IR Visualization - printed to: " << html_output_filename << "\n"; +void print_to_stmt_html(const std::string &html_output_filename, + const Module &m, + const std::string &assembly_input_filename) { + PipelineHTMLInspector inspector(html_output_filename, m, assembly_input_filename, false); + inspector.generate_html(m); + debug(1) << "Done generating HTML IR Inspector - printed to: " << html_output_filename << "\n"; +} + +void print_to_conceptual_stmt_html(const std::string &html_output_filename, + const Module &m, + const std::string &assembly_input_filename) { + PipelineHTMLInspector inspector(html_output_filename, m, assembly_input_filename, true); + inspector.generate_html(m); + debug(1) << "Done generating HTML Conceptual IR Inspector - printed to: " << html_output_filename << "\n"; } } // namespace Internal diff --git a/src/StmtToHTML.h b/src/StmtToHTML.h new file mode 100644 index 000000000000..fb7b6a06d9ba --- /dev/null +++ b/src/StmtToHTML.h @@ -0,0 +1,39 @@ +#ifndef HALIDE_STMT_TO_HTML +#define HALIDE_STMT_TO_HTML + +/** \file + * Defines a function to dump an HTML-formatted visualization to a file. + */ + +#include + +namespace Halide { + +class Module; + +namespace Internal { + +struct Stmt; + +/** Dump an HTML-formatted visualization of a Module to filename. + * If assembly_input_filename is not empty, it is expected to be the path + * to assembly output. If empty, the code will attempt to find such a + * file based on output_filename (replacing ".stmt.html" with ".s"), + * and will assert-fail if no such file is found. */ +void print_to_stmt_html(const std::string &html_output_filename, + const Module &m, + const std::string &assembly_input_filename = ""); + +/** Dump an HTML-formatted visualization of a Module's conceptual Stmt code to filename. + * If assembly_input_filename is not empty, it is expected to be the path + * to assembly output. If empty, the code will attempt to find such a + * file based on output_filename (replacing ".stmt.html" with ".s"), + * and will assert-fail if no such file is found. */ +void print_to_conceptual_stmt_html(const std::string &html_output_filename, + const Module &m, + const std::string &assembly_input_filename = ""); + +} // namespace Internal +} // namespace Halide + +#endif diff --git a/src/StmtToViz.h b/src/StmtToViz.h deleted file mode 100644 index 46e3fad0d541..000000000000 --- a/src/StmtToViz.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef HALIDE_STMT_TO_VIZ -#define HALIDE_STMT_TO_VIZ - -/** \file - * Defines a function to dump an HTML-formatted visualization to a file. - */ - -#include - -namespace Halide { - -class Module; - -namespace Internal { - -struct Stmt; - -/** Dump an HTML-formatted visualization of a Module to filename. - * If assembly_input_filename is not empty, it is expected to be the path - * to assembly output. If empty, the code will attempt to find such a - * file based on output_filename (replacing ".stmt.html" with ".s"), - * and will assert-fail if no such file is found. */ -void print_to_viz(const std::string &html_output_filename, - const Module &m, - const std::string &assembly_input_filename = ""); - -} // namespace Internal -} // namespace Halide - -#endif diff --git a/src/irvisualizer/StmtToViz_dependencies.template.html b/src/irvisualizer/StmtToViz_dependencies.template.html deleted file mode 100644 index e8122c67ae91..000000000000 --- a/src/irvisualizer/StmtToViz_dependencies.template.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/irvisualizer/StmtToViz_javascript.template.html b/src/irvisualizer/StmtToViz_javascript.template.html deleted file mode 100644 index c8d1f4a99275..000000000000 --- a/src/irvisualizer/StmtToViz_javascript.template.html +++ /dev/null @@ -1,415 +0,0 @@ - - diff --git a/src/irvisualizer/StmtToViz_stylesheet.template.html b/src/irvisualizer/StmtToViz_stylesheet.template.html deleted file mode 100644 index 45b2b1dfa392..000000000000 --- a/src/irvisualizer/StmtToViz_stylesheet.template.html +++ /dev/null @@ -1,657 +0,0 @@ - diff --git a/src/irvisualizer/generate_palettes.py b/src/irvisualizer/generate_palettes.py new file mode 100644 index 000000000000..7c6f43d23704 --- /dev/null +++ b/src/irvisualizer/generate_palettes.py @@ -0,0 +1,34 @@ +def make_oklch(l, c, h): + return ("oklch(%.1f%% %.2f %.0f)" % (l * 100, c, h)) + + +STEPS = 20 + +for i in range(STEPS): + f = i / (STEPS - 1) + col = make_oklch(0.9 - f * 0.5, 0.05 + 0.1 * f, 140) + print(".block-CostColor%d:first-child { border-left: 8px solid %s; }" % (i, col)) +print(".block-CostColorNone:first-child { border-left: transparent; }") +print() + +for i in range(STEPS): + f = i / (STEPS - 1) + col = make_oklch(0.9 - f * 0.5, 0.05 + 0.1 * f, 140) + print(".line-CostColor%d:first-child { border-right: 8px solid %s; }" % (i, col)) +print(".line-CostColorNone:first-child { border-right: transparent; }") +print() + + +for i in range(STEPS): + f = i / (STEPS - 1) + col = make_oklch(0.9 - f * 0.5, 0.05 + 0.1 * f, 300) + print(".block-CostColor%d:last-child { border-left: 8px solid %s; }" % (i, col)) +print(".block-CostColorNone:last-child { border-left: transparent; }") +print() + +for i in range(STEPS): + f = i / (STEPS - 1) + col = make_oklch(0.9 - f * 0.5, 0.05 + 0.1 * f, 300) + print(".line-CostColor%d:last-child { border-right: 8px solid %s; }" % (i, col)) +print(".line-CostColorNone:last-child { border-right: transparent; }") +print() diff --git a/src/irvisualizer/html_template_StmtToHTML.css b/src/irvisualizer/html_template_StmtToHTML.css new file mode 100644 index 000000000000..d76bda15710a --- /dev/null +++ b/src/irvisualizer/html_template_StmtToHTML.css @@ -0,0 +1,791 @@ +/* General CSS Rules*/ +* { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 12px; +} + +body { + padding: 0; + margin: 0; + line-height: 14px; +} + +.no-select { + user-select: none; + -webkit-user-select: none; +} + +div#page-container { + height: 100vh; + display: flex; + flex-direction: column; +} + +a, +a:hover, +a:visited, +a:active { + color: inherit; + text-decoration: none; +} + +b { + font-weight: normal; +} + +table { + font-size: 12px; +} + +/* Visualization panes */ +div#visualization-panes { + display: flex; + flex-grow: 1; + width: 100%; + height: 100%; + position: relative; +} + +div.pane { + overflow: scroll; + position: relative; + flex-grow: 1; + flex-shrink: 1; + counter-reset: line; + min-width: 5vw; + width: 33vw; +} +div.pane.collapsed-pane { + display: none; +} + +div#ir-code-pane { + padding-left: 50px; + padding-top: 20px; + width: 50vw; +} + +div#host-assembly-pane { + width: 20vw; +} + +div#device-code-pane { + padding-left: 6em; + width: 30vw; +} + +span.line { + counter-increment: line; +} +span.line:before { + content: counter(line) "."; + color: rgb(175, 175, 175); + position: absolute; + left: 0px; + width: 4em; + text-align: right; +} + + +/* Resize bars */ +div.resize-bar { + background: rgb(201, 231, 190); + cursor: col-resize; + border-left: 1px solid rgb(0, 0, 0); + border-right: 1px solid rgb(0, 0, 0); +} + +div.resize-bar > div.collapse-btns { + position: relative; + top: 50%; + margin: 0px; +} + +div.resize-bar > div.collapse-btns button { + font-size: 11px; + height: 3em; + width: 1.5em; + border-radius: 0; + margin: 1em 0; + border: none; + padding: 0; + background-color: rgba(0, 0, 0, 0.1); +} + +div.resize-bar > div.collapse-btns button:hover { background-color: rgba(255, 255, 255, 0.5); } +/*div.resize-bar > div.collapse-btns button.collapse-left:before { content: "\21E4"; }*/ +/*div.resize-bar > div.collapse-btns button.collapse-right:before { content: "\21E5"; }*/ +div.resize-bar > div.collapse-btns button.collapse-left:before { content: "<"; } +div.resize-bar > div.collapse-btns button.collapse-right:before { content: ">"; } + +/* Revert collapser button that points to the left. */ +div.resize-bar > div.collapse-btns button.collapse-left.active { + background-color: rgba(0, 0, 0, 0.4); + border: 2px solid rgba(0, 0, 0, 0.4); +} +div.resize-bar > div.collapse-btns button.collapse-left.active:before { + /*content: "\21A6";*/ + content: ">>"; +} +div.resize-bar > div.collapse-btns button.collapse-right.active { + background-color: rgba(0, 0, 0, 0.4); + border: 2px solid rgba(0, 0, 0, 0.4); +} +div.resize-bar > div.collapse-btns button.collapse-right.active:before { + /*content: "\21A4";*/ + content: "<<"; +} + +/* Resizer Preview */ +div#resizer-preview { + position: absolute; + width: 4px; + height: 100%; + background-color: gray; + padding: 0; + margin: 0; + opacity: 50%; + z-index: 190000; +} + + +div#device-code-pane code { + line-height: 14px; + white-space: pre; +} + +/* IR Code Section CSS */ +.ModuleBody { + padding-left: 70px !important; +} + +div.Function { + margin: 0.2em 0.0em; + padding: 0.1em; + margin-right: 0.8em; + margin-left: calc(-0.2em - 16px); + padding-left: calc(0.2em + 16px); + border-width: 3px; + border-style: solid; + border-radius: 8px; + min-width: fit-content; +} +div.Function + div.Function { + margin-top: 1.5em; /* Leave space between two functions. */ +} + +/* Give functions a slightly different background color to visually help navigate quickly. */ +div.Function:nth-child(3n + 1) { background-color: rgba(200, 0, 0, 0.025); border-color: rgba(200, 0, 0, 0.03); } +div.Function:nth-child(3n + 2) { background-color: rgba(0, 200, 0, 0.025); border-color: rgba(0, 200, 0, 0.03); } +div.Function:nth-child(3n + 3) { background-color: rgba(0, 0, 200, 0.025); border-color: rgba(0, 0, 200, 0.03); } + +/* Give Parallel fors a different color */ +div.For { border-radius: 8px; } +div.For.for-type-parallel, +div.For.for-type-gpu_block { + min-width: fit-content; + background-color: rgba(240, 200, 0, 0.03); + border: 3px solid rgba(240, 200, 0, 0.10); + margin: 0.2em 0.0em; + padding: 0.1em; + margin-right: 0.8em; + margin-left: calc(-0.2em - 16px); + padding-left: calc(0.2em + 16px); +} +div.For.for-type-gpu_block div.For.for-type-gpu_block { + background-color: transparent; + border: none; + margin: 0; + padding: 0; +} +div.For.for-type-gpu_thread { + background-color: rgba(240, 100, 50, 0.03); + border: 3px solid rgba(240, 100, 50, 0.10); + margin: 0.2em 0.0em; + padding: 0.1em; + margin-right: 0.8em; + margin-left: calc(-0.2em - 16px); + padding-left: calc(0.2em + 16px); +} +div.For.for-type-gpu_thread div.For.for-type-gpu_thread { + background-color: transparent; + border: none; + margin: 0; + padding: 0; +} + +b.Highlight { + font-weight: bold; + background-color: #DDD; +} + +span.Highlight { + font-weight: bold; + background-color: #FF0; +} + +span.OpF32 { + color: hsl(106deg 100% 40%); + font-weight: bold; +} + +span.OpF64 { + color: hsl(106deg 100% 30%); + font-weight: bold; +} + +span.OpB8 { + color: hsl(208deg 100% 80%); + font-weight: bold; +} + +span.OpB16 { + color: hsl(208deg 100% 70%); + font-weight: bold; +} + +span.OpB32 { + color: hsl(208deg 100% 60%); + font-weight: bold; +} + +span.OpB64 { + color: hsl(208deg 100% 50%); + font-weight: bold; +} + +span.OpI8 { + color: hsl(46deg 100% 45%); + font-weight: bold; +} + +span.OpI16 { + color: hsl(46deg 100% 40%); + font-weight: bold; +} + +span.OpI32 { + color: hsl(46deg 100% 34%); + font-weight: bold; +} + +span.OpI64 { + color: hsl(46deg 100% 27%); + font-weight: bold; +} + +span.OpVec2 { + background-color: hsl(100deg 100% 90%); + font-weight: bold; +} + +span.OpVec4 { + background-color: hsl(100deg 100% 80%); + font-weight: bold; +} + +span.Memory { + color: #d22; + font-weight: bold; +} + +span.Pred { + background-color: #ffe8bd; + font-weight: bold; +} + +span.Label { + background-color: #bde4ff; + font-weight: bold; +} + +/* Collapse button and indent div logic */ +input.show-hide-btn { + appearance: none; + margin-left: -17px; + margin-top: 0px; + margin-bottom: 0px; + padding: 0; + height: 12px; + vertical-align: top; + width: 12px; + height: 12px; + border: 1px solid black; + border-radius: 4px; + transition: transform 0.2s; + transform: rotate(0deg); +} +input.show-hide-btn:checked { + transform: rotate(-90deg); +} + +input.show-hide-btn:before { + content: "V"; + font-size: 9px; + width: 100%; + box-sizing: border-box; + text-align: center; + display: inline-block; + padding: 0; + margin: 0; + overflow: hidden; +} + + + +div.indent { + box-sizing: border-box; + border-left: 2px solid transparent; + padding-left: 25px; + margin-left: -11px; +} + +input.show-hide-btn:hover { + color: #c30000; +} + +/* The structure always has to be