#include #include #include #include "DebugToFile.h" #include "Function.h" #include "IRMutator.h" #include "IROperator.h" namespace Halide { namespace Internal { using std::map; using std::string; using std::vector; namespace { class DebugToFile : public IRMutator { const map &env; using IRMutator::visit; Stmt visit(const Realize *op) override { map::const_iterator iter = env.find(op->name); if (iter != env.end() && !iter->second.debug_file().empty()) { Function f = iter->second; vector args; user_assert(op->types.size() == 1) << "debug_to_file doesn't handle functions with multiple values yet\n"; // The name of the file args.emplace_back(f.debug_file()); // Inject loads to the corners of the function so that any // passes doing further analysis of buffer use understand // what we're doing (e.g. so we trigger a copy-back from a // device pointer). Expr num_elements = 1; for (const auto &bound : op->bounds) { num_elements *= bound.extent; } int type_code = 0; Type t = op->types[0]; if (t == Float(32)) { type_code = 0; } else if (t == Float(64)) { type_code = 1; } else if (t == UInt(8) || t == UInt(1)) { type_code = 2; } else if (t == Int(8)) { type_code = 3; } else if (t == UInt(16)) { type_code = 4; } else if (t == Int(16)) { type_code = 5; } else if (t == UInt(32)) { type_code = 6; } else if (t == Int(32)) { type_code = 7; } else if (t == UInt(64)) { type_code = 8; } else if (t == Int(64)) { type_code = 9; } else { user_error << "Type " << t << " not supported for debug_to_file\n"; } args.emplace_back(type_code); Expr buf = Variable::make(Handle(), f.name() + ".buffer"); args.push_back(buf); Expr call = Call::make(Int(32), Call::debug_to_file, args, Call::Intrinsic); string call_result_name = unique_name("debug_to_file_result"); Expr call_result_var = Variable::make(Int(32), call_result_name); Stmt body = AssertStmt::make(call_result_var == 0, Call::make(Int(32), "halide_error_debug_to_file_failed", {f.name(), f.debug_file(), call_result_var}, Call::Extern)); body = LetStmt::make(call_result_name, call, body); body = Block::make(mutate(op->body), body); return Realize::make(op->name, op->types, op->memory_type, op->bounds, op->condition, body); } else { return IRMutator::visit(op); } } public: DebugToFile(const map &e) : env(e) { } }; class RemoveDummyRealizations : public IRMutator { const vector &outputs; using IRMutator::visit; Stmt visit(const Realize *op) override { for (const Function &f : outputs) { if (op->name == f.name()) { return mutate(op->body); } } return IRMutator::visit(op); } public: RemoveDummyRealizations(const vector &o) : outputs(o) { } }; class AddDummyRealizations : public IRMutator { const vector &outputs; using IRMutator::visit; Stmt visit(const ProducerConsumer *op) override { Stmt s = IRMutator::visit(op); for (const Function &out : outputs) { if (op->name == out.name()) { vector output_bounds; for (int i = 0; i < out.dimensions(); i++) { string dim = std::to_string(i); Expr min = Variable::make(Int(32), out.name() + ".min." + dim); Expr extent = Variable::make(Int(32), out.name() + ".extent." + dim); output_bounds.emplace_back(min, extent); } return Realize::make(out.name(), out.output_types(), MemoryType::Auto, output_bounds, const_true(), s); } } return s; } public: AddDummyRealizations(const vector &o) : outputs(o) { } }; } // namespace Stmt debug_to_file(Stmt s, const vector &outputs, const map &env) { // Temporarily wrap the produce nodes for the output functions in // realize nodes so that we know when to write the debug outputs. s = AddDummyRealizations(outputs).mutate(s); s = DebugToFile(env).mutate(s); // Remove the realize node we wrapped around the output s = RemoveDummyRealizations(outputs).mutate(s); return s; } } // namespace Internal } // namespace Halide