#include "StmtToHtml.h" #include "IROperator.h" #include "IRVisitor.h" #include "Module.h" #include "Scope.h" #include #include #include #include #include namespace Halide { namespace Internal { using std::string; namespace { template std::string to_string(T value) { std::ostringstream os; os << value; return os.str(); } class StmtToHtml : public IRVisitor { static const std::string css, js; // This allows easier access to individual elements. int id_count; private: std::ofstream stream; int unique_id() { return ++id_count; } // 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. std::vector context_stack; string open_tag(const string &tag, const string &cls, int id = -1) { std::stringstream s; s << "<" << tag << " class='" << cls << "' id='"; if (id == -1) { s << context_stack.back() << "-"; s << unique_id(); } else { s << id; } s << "'>"; context_stack.push_back(unique_id()); return s.str(); } string tag(const string &tag, const string &cls, const string &body, int id = -1) { std::stringstream s; s << open_tag(tag, cls, id); s << body; s << close_tag(tag); return s.str(); } string close_tag(const string &tag) { context_stack.pop_back(); return ""; } string open_span(const string &cls, int id = -1) { return open_tag("span", cls, id); } string close_span() { return close_tag("span"); } string span(const string &cls, const string &body, int id = -1) { return tag("span", cls, body, id); } string matched(const string &cls, const string &body, int id = -1) { return span(cls + " Matched", body, id); } string matched(const string &body) { return span("Matched", body); } string open_div(const string &cls, int id = -1) { return open_tag("div", cls, id) + "\n"; } string close_div() { return close_tag("div") + "\n"; } string open_line() { return "

"; } string close_line() { return "

"; } string keyword(const string &x) { return span("Keyword", x); } string type(const string &x) { return span("Type", x); } string symbol(const string &x) { return span("Symbol", x); } Scope scope; string var(const string &x) { int id; if (scope.contains(x)) { id = scope.get(x); } else { id = unique_id(); scope.push(x, id); } std::stringstream s; s << ""; s << x; s << ""; return s.str(); } void print_list(const std::vector &args) { for (size_t i = 0; i < args.size(); i++) { if (i > 0) { stream << matched(",") << " "; } print(args[i]); } } void print_list(const string &l, const std::vector &args, const string &r) { stream << matched(l); print_list(args); stream << matched(r); } string open_expand_button(int id) { std::stringstream button; button << "" << "
" << "" << "
" << "
"; return button.str(); } string close_expand_button() { return "
"; } void visit(const IntImm *op) override { stream << open_span("IntImm Imm"); stream << Expr(op); stream << close_span(); } void visit(const UIntImm *op) override { stream << open_span("UIntImm Imm"); stream << Expr(op); stream << close_span(); } void visit(const FloatImm *op) override { stream << open_span("FloatImm Imm"); stream << Expr(op); stream << close_span(); } void visit(const StringImm *op) override { stream << open_span("StringImm"); stream << "\""; for (size_t i = 0; i < op->value.size(); i++) { unsigned char c = op->value[i]; if (c >= ' ' && c <= '~' && c != '\\' && c != '"') { stream << c; } else { stream << "\\"; switch (c) { case '"': stream << "\""; break; case '\\': stream << "\\"; break; case '\t': stream << "t"; break; case '\r': stream << "r"; break; case '\n': stream << "n"; break; default: string hex_digits = "0123456789ABCDEF"; stream << "x" << hex_digits[c >> 4] << hex_digits[c & 0xf]; } } } stream << "\"" << close_span(); } void visit(const Variable *op) override { stream << var(op->name); } void visit(const Cast *op) override { stream << open_span("Cast"); stream << open_span("Matched"); stream << open_span("Type") << op->type << close_span(); stream << "("; stream << close_span(); print(op->value); stream << matched(")"); stream << close_span(); } void visit_binary_op(const Expr &a, const Expr &b, const char *op) { stream << open_span("BinaryOp"); stream << matched("("); print(a); stream << " " << matched("Operator", op) << " "; print(b); stream << matched(")"); stream << close_span(); } void visit(const Add *op) override { visit_binary_op(op->a, op->b, "+"); } void visit(const Sub *op) override { visit_binary_op(op->a, op->b, "-"); } void visit(const Mul *op) override { visit_binary_op(op->a, op->b, "*"); } void visit(const Div *op) override { visit_binary_op(op->a, op->b, "/"); } void visit(const Mod *op) override { visit_binary_op(op->a, op->b, "%"); } void visit(const And *op) override { visit_binary_op(op->a, op->b, "&&"); } void visit(const Or *op) override { visit_binary_op(op->a, op->b, "||"); } void visit(const NE *op) override { visit_binary_op(op->a, op->b, "!="); } void visit(const LT *op) override { visit_binary_op(op->a, op->b, "<"); } void visit(const LE *op) override { visit_binary_op(op->a, op->b, "<="); } void visit(const GT *op) override { visit_binary_op(op->a, op->b, ">"); } void visit(const GE *op) override { visit_binary_op(op->a, op->b, ">="); } void visit(const EQ *op) override { visit_binary_op(op->a, op->b, "=="); } void visit(const Min *op) override { stream << open_span("Min"); print_list(symbol("min") + "(", {op->a, op->b}, ")"); stream << close_span(); } void visit(const Max *op) override { stream << open_span("Max"); print_list(symbol("max") + "(", {op->a, op->b}, ")"); stream << close_span(); } void visit(const Not *op) override { stream << open_span("Not"); stream << "!"; print(op->a); stream << close_span(); } void visit(const Select *op) override { stream << open_span("Select"); print_list(symbol("select") + "(", {op->condition, op->true_value, op->false_value}, ")"); stream << close_span(); } void visit(const Load *op) override { stream << open_span("Load"); stream << open_span("Matched"); stream << var(op->name) << "["; stream << close_span(); print(op->index); stream << matched("]"); if (!is_const_one(op->predicate)) { stream << " " << keyword("if") << " "; print(op->predicate); } stream << close_span(); } void visit(const Ramp *op) override { stream << open_span("Ramp"); print_list(symbol("ramp") + "(", {op->base, op->stride, Expr(op->lanes)}, ")"); stream << close_span(); } void visit(const Broadcast *op) override { stream << open_span("Broadcast"); stream << open_span("Matched"); stream << symbol("x") << op->lanes << "("; stream << close_span(); print(op->value); stream << matched(")"); stream << close_span(); } void visit(const Call *op) override { stream << open_span("Call"); print_list(symbol(op->name) + "(", op->args, ")"); stream << close_span(); } void visit(const Let *op) override { scope.push(op->name, unique_id()); stream << open_span("Let"); stream << open_span("Matched"); stream << "(" << keyword("let") << " "; stream << var(op->name); stream << close_span(); stream << " " << matched("Operator Assign", "=") << " "; print(op->value); stream << " " << matched("Keyword", "in") << " "; print(op->body); stream << matched(")"); stream << close_span(); scope.pop(op->name); } void visit(const LetStmt *op) override { scope.push(op->name, unique_id()); stream << open_div("LetStmt") << open_line(); stream << open_span("Matched"); stream << keyword("let") << " "; stream << var(op->name); stream << close_span(); stream << " " << matched("Operator Assign", "=") << " "; print(op->value); stream << close_line(); print(op->body); stream << close_div(); scope.pop(op->name); } void visit(const AssertStmt *op) override { stream << open_div("AssertStmt WrapLine"); std::vector args; args.push_back(op->condition); args.push_back(op->message); print_list(symbol("assert") + "(", args, ")"); stream << close_div(); } void visit(const ProducerConsumer *op) override { scope.push(op->name, unique_id()); stream << open_div(op->is_producer ? "Produce" : "Consumer"); int produce_id = unique_id(); stream << open_span("Matched"); stream << open_expand_button(produce_id); stream << keyword(op->is_producer ? "produce" : "consume") << " "; stream << var(op->name); stream << close_expand_button() << " {"; stream << close_span(); ; stream << open_div(op->is_producer ? "ProduceBody Indent" : "ConsumeBody Indent", produce_id); print(op->body); stream << close_div(); stream << matched("}"); stream << close_div(); scope.pop(op->name); } void visit(const For *op) override { scope.push(op->name, unique_id()); stream << open_div("For"); int id = unique_id(); stream << open_expand_button(id); stream << open_span("Matched"); if (op->for_type == ForType::Serial) { stream << keyword("for"); } else if (op->for_type == ForType::Parallel) { stream << keyword("parallel"); } else if (op->for_type == ForType::Vectorized) { stream << keyword("vectorized"); } else if (op->for_type == ForType::Unrolled) { stream << keyword("unrolled"); } else if (op->for_type == ForType::GPUBlock) { stream << keyword("gpu_block"); } else if (op->for_type == ForType::GPUThread) { stream << keyword("gpu_thread"); } else if (op->for_type == ForType::GPULane) { stream << keyword("gpu_lane"); } else { internal_error << "Unknown for type: " << ((int)op->for_type) << "\n"; } stream << " ("; stream << close_span(); print_list({Variable::make(Int(32), op->name), op->min, op->extent}); stream << matched(")"); stream << close_expand_button(); stream << " " << matched("{"); stream << open_div("ForBody Indent", id); print(op->body); stream << close_div(); stream << matched("}"); stream << close_div(); scope.pop(op->name); } void visit(const Acquire *op) override { stream << open_div("Acquire"); int id = unique_id(); stream << open_span("Matched"); stream << open_expand_button(id); stream << keyword("acquire ("); stream << close_span(); print(op->semaphore); stream << ", "; print(op->count); stream << matched(")"); stream << close_expand_button() << " {"; stream << open_div("Acquire Indent", id); print(op->body); stream << close_div(); stream << matched("}"); stream << close_div(); } void visit(const Store *op) override { stream << open_div("Store WrapLine"); stream << open_span("Matched"); stream << var(op->name) << "["; stream << close_span(); print(op->index); stream << matched("]"); stream << " " << span("Operator Assign Matched", "=") << " "; stream << open_span("StoreValue"); print(op->value); if (!is_const_one(op->predicate)) { stream << " " << keyword("if") << " "; print(op->predicate); } stream << close_span(); stream << close_div(); } void visit(const Provide *op) override { stream << open_div("Provide WrapLine"); stream << open_span("Matched"); stream << var(op->name) << "("; stream << close_span(); print_list(op->args); stream << matched(")") << " "; stream << matched("=") << " "; if (op->values.size() > 1) { print_list("{", op->values, "}"); } else { print(op->values[0]); } stream << close_div(); } void visit(const Allocate *op) override { scope.push(op->name, unique_id()); stream << open_div("Allocate"); stream << open_span("Matched"); stream << keyword("allocate") << " "; stream << var(op->name) << "["; stream << close_span(); stream << open_span("Type"); stream << op->type; stream << close_span(); for (size_t i = 0; i < op->extents.size(); i++) { stream << " * "; print(op->extents[i]); } stream << matched("]"); if (!is_const_one(op->condition)) { stream << " " << keyword("if") << " "; print(op->condition); } if (op->new_expr.defined()) { stream << open_span("Matched"); stream << keyword("custom_new") << "{"; print(op->new_expr); stream << matched("}"); } if (!op->free_function.empty()) { stream << open_span("Matched"); stream << keyword("custom_delete") << "{ " << op->free_function << "(); "; stream << matched("}"); } stream << open_div("AllocateBody"); print(op->body); stream << close_div(); stream << close_div(); scope.pop(op->name); } void visit(const Free *op) override { stream << open_div("Free WrapLine"); stream << keyword("free") << " "; stream << var(op->name); stream << close_div(); } void visit(const Realize *op) override { scope.push(op->name, unique_id()); stream << open_div("Realize"); int id = unique_id(); stream << open_expand_button(id); stream << keyword("realize") << " "; stream << var(op->name); stream << matched("("); for (size_t i = 0; i < op->bounds.size(); i++) { print_list("[", {op->bounds[i].min, op->bounds[i].extent}, "]"); if (i < op->bounds.size() - 1) { stream << ", "; } } stream << matched(")"); if (!is_const_one(op->condition)) { stream << " " << keyword("if") << " "; print(op->condition); } stream << close_expand_button(); stream << " " << matched("{"); stream << open_div("RealizeBody Indent", id); print(op->body); stream << close_div(); stream << matched("}"); stream << close_div(); scope.pop(op->name); } void visit(const Prefetch *op) override { stream << open_span("Prefetch"); stream << keyword("prefetch") << " "; stream << var(op->name); stream << matched("("); for (size_t i = 0; i < op->bounds.size(); i++) { print_list("[", {op->bounds[i].min, op->bounds[i].extent}, "]"); if (i < op->bounds.size() - 1) { stream << ", "; } } stream << matched(")"); if (!is_const_one(op->condition)) { stream << " " << keyword("if") << " "; print(op->condition); } stream << close_span(); stream << open_div("PrefetchBody"); print(op->body); stream << close_div(); } // To avoid generating ridiculously deep DOMs, we flatten blocks here. void visit_block_stmt(const Stmt &stmt) { if (const Block *b = stmt.as()) { visit_block_stmt(b->first); visit_block_stmt(b->rest); } else if (stmt.defined()) { print(stmt); } } void visit(const Block *op) override { stream << open_div("Block"); visit_block_stmt(op->first); visit_block_stmt(op->rest); stream << close_div(); } // We also flatten forks void visit_fork_stmt(const Stmt &stmt) { if (const Fork *f = stmt.as()) { visit_fork_stmt(f->first); visit_fork_stmt(f->rest); } else if (stmt.defined()) { stream << open_div("ForkTask"); int id = unique_id(); stream << open_expand_button(id); stream << matched("task {"); stream << close_expand_button(); stream << open_div("ForkTask Indent", id); print(stmt); stream << close_div(); stream << matched("}"); stream << close_div(); } } void visit(const Fork *op) override { stream << open_div("Fork"); int id = unique_id(); stream << open_expand_button(id); stream << keyword("fork") << " " << matched("{"); stream << close_expand_button(); stream << open_div("Fork Indent", id); visit_fork_stmt(op->first); visit_fork_stmt(op->rest); stream << close_div(); stream << matched("}"); stream << close_div(); } void visit(const IfThenElse *op) override { stream << open_div("IfThenElse"); int id = unique_id(); stream << open_expand_button(id); stream << open_span("Matched"); stream << keyword("if") << " ("; stream << close_span(); while (true) { print(op->condition); stream << matched(")"); stream << close_expand_button() << " "; stream << matched("{"); // close if (or else if) span stream << open_div("ThenBody Indent", id); print(op->then_case); stream << close_div(); // close thenbody div if (!op->else_case.defined()) { stream << matched("}"); break; } id = unique_id(); if (const IfThenElse *nested_if = op->else_case.as()) { stream << matched("}") << " "; stream << open_expand_button(id); stream << open_span("Matched"); stream << keyword("else if") << " ("; stream << close_span(); op = nested_if; } else { stream << open_span("Matched") << "} "; stream << open_expand_button(id); stream << keyword("else"); stream << close_expand_button() << "{"; stream << close_span(); stream << open_div("ElseBody Indent", id); print(op->else_case); stream << close_div() << matched("}"); break; } } stream << close_div(); // Closing ifthenelse div. } void visit(const Evaluate *op) override { stream << open_div("Evaluate"); print(op->value); stream << close_div(); } void visit(const Shuffle *op) override { stream << open_span("Shuffle"); if (op->is_concat()) { print_list(symbol("concat_vectors("), op->vectors, ")"); } else if (op->is_interleave()) { print_list(symbol("interleave_vectors("), op->vectors, ")"); } else if (op->is_extract_element()) { std::vector args = op->vectors; args.emplace_back(op->slice_begin()); print_list(symbol("extract_element("), args, ")"); } else if (op->is_slice()) { std::vector args = op->vectors; args.emplace_back(op->slice_begin()); args.emplace_back(op->slice_stride()); args.emplace_back(static_cast(op->indices.size())); print_list(symbol("slice_vectors("), args, ")"); } else { std::vector args = op->vectors; for (int i : op->indices) { args.emplace_back(i); } print_list(symbol("shuffle("), args, ")"); } stream << close_span(); } void visit(const VectorReduce *op) override { stream << open_span("VectorReduce"); stream << open_span("Type") << op->type << close_span(); print_list(symbol("vector_reduce") + "(", {op->op, op->value}, ")"); stream << close_span(); } void visit(const Atomic *op) override { stream << open_div("Atomic"); int id = unique_id(); stream << open_expand_button(id); stream << open_span("Matched"); if (op->mutex_name.empty()) { stream << keyword("atomic") << matched("{"); } else { stream << keyword("atomic") << " ("; stream << symbol(op->mutex_name); stream << ")" << matched("{"); } stream << close_span(); stream << open_div("Atomic Body Indent", id); print(op->body); stream << close_div() << matched("}"); stream << close_div(); } public: void print(const Expr &ir) { ir.accept(this); } void print(const Stmt &ir) { ir.accept(this); } void print(const LoweredFunc &op) { scope.push(op.name, unique_id()); stream << open_div("Function"); int id = unique_id(); stream << open_expand_button(id); stream << open_span("Matched"); stream << keyword("func"); stream << " " << op.name << "("; stream << close_span(); for (size_t i = 0; i < op.args.size(); i++) { if (i > 0) { stream << matched(",") << " "; } stream << var(op.args[i].name); } stream << matched(")"); stream << close_expand_button(); stream << " " << matched("{"); stream << open_div("FunctionBody Indent", id); print(op.body); stream << close_div(); stream << matched("}"); stream << close_div(); scope.pop(op.name); } void print(const Buffer<> &op) { stream << open_div("Buffer<>"); stream << keyword("buffer ") << var(op.name()); stream << close_div(); } void print(const Module &m) { scope.push(m.name(), unique_id()); for (const auto &s : m.submodules()) { print(s); } int id = unique_id(); stream << open_expand_button(id); stream << open_div("Module"); stream << open_span("Matched"); stream << keyword("module") << " name=" << m.name() << ", target=" << m.target().to_string(); stream << close_span(); stream << close_expand_button(); stream << " " << matched("{"); stream << open_div("ModuleBody Indent", id); for (const auto &b : m.buffers()) { print(b); } for (const auto &f : m.functions()) { print(f); } stream << close_div(); stream << matched("}"); stream << close_div(); scope.pop(m.name()); } StmtToHtml(const string &filename) : id_count(0), context_stack(1, 0) { stream.open(filename.c_str()); stream << ""; stream << "\n"; stream << "\n"; stream << "\n"; stream << "\n"; stream << "\n"; stream << "\n"; stream << "\n \n"; } ~StmtToHtml() override { stream << "\n"; stream << ""; } }; const std::string StmtToHtml::css = "\n \ body { font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; background: #f8f8f8; margin-left:15px; } \n \ a, a:hover, a:visited, a:active { color: inherit; text-decoration: none; } \n \ b { font-weight: normal; }\n \ p.WrapLine { margin: 0px; margin-left: 30px; text-indent:-30px; } \n \ div.WrapLine { margin-left: 30px; text-indent:-30px; } \n \ div.Indent { padding-left: 15px; }\n \ div.ShowHide { position:absolute; left:-12px; width:12px; height:12px; } \n \ span.Comment { color: #998; font-style: italic; }\n \ span.Keyword { color: #333; font-weight: bold; }\n \ span.Assign { color: #d14; font-weight: bold; }\n \ span.Symbol { color: #990073; }\n \ span.Type { color: #445588; font-weight: bold; }\n \ span.StringImm { color: #d14; }\n \ span.IntImm { color: #099; }\n \ span.FloatImm { color: #099; }\n \ b.Highlight { font-weight: bold; background-color: #DDD; }\n \ span.Highlight { font-weight: bold; background-color: #FF0; }\n \ "; const std::string StmtToHtml::js = "\n \ function toggle(id) { \n \ e = document.getElementById(id); \n \ show = document.getElementById(id + '-show'); \n \ hide = document.getElementById(id + '-hide'); \n \ if (e.style.display != 'none') { \n \ e.style.display = 'none'; \n \ show.style.display = 'block'; \n \ hide.style.display = 'none'; \n \ } else { \n \ e.style.display = 'block'; \n \ show.style.display = 'none'; \n \ hide.style.display = 'block'; \n \ } \n \ return false; \n \ }"; } // namespace void print_to_html(const string &filename, const Stmt &s) { StmtToHtml sth(filename); sth.print(s); } void print_to_html(const string &filename, const Module &m) { StmtToHtml sth(filename); sth.print(m); } } // namespace Internal } // namespace Halide