同步操作将从 Gitee 极速下载/Halide 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
#include "AddImageChecks.h"#include "ExternFuncArgument.h"#include "Function.h"#include "IRMutator.h"#include "IROperator.h"#include "IRVisitor.h"#include "Simplify.h"#include "Substitute.h"#include "Target.h"namespace Halide {namespace Internal {using std::map;using std::pair;using std::string;using std::vector;namespace {/* Find all the externally referenced buffers in a stmt */class FindBuffers : public IRGraphVisitor {public:struct Result {Buffer<> image;Parameter param;Type type;int dimensions{0};bool used_on_host{false};};map<string, Result> buffers;bool in_device_loop = false;using IRGraphVisitor::visit;void visit(const For *op) override {op->min.accept(this);op->extent.accept(this);bool old = in_device_loop;if (op->device_api != DeviceAPI::None &&op->device_api != DeviceAPI::Host) {in_device_loop = true;}op->body.accept(this);in_device_loop = old;}void visit(const Call *op) override {IRGraphVisitor::visit(op);if (op->image.defined()) {Result &r = buffers[op->name];r.image = op->image;r.type = op->type.element_of();r.dimensions = (int)op->args.size();r.used_on_host = r.used_on_host || (!in_device_loop);} else if (op->param.defined()) {Result &r = buffers[op->name];r.param = op->param;r.type = op->type.element_of();r.dimensions = (int)op->args.size();r.used_on_host = r.used_on_host || (!in_device_loop);}}void visit(const Provide *op) override {IRGraphVisitor::visit(op);if (op->values.size() == 1) {auto it = buffers.find(op->name);if (it != buffers.end() && !in_device_loop) {it->second.used_on_host = true;}} else {for (size_t i = 0; i < op->values.size(); i++) {string name = op->name + "." + std::to_string(i);auto it = buffers.find(name);if (it != buffers.end() && !in_device_loop) {it->second.used_on_host = true;}}}}void visit(const Variable *op) override {if (op->param.defined() &&op->param.is_buffer() &&buffers.find(op->param.name()) == buffers.end()) {Result r;r.param = op->param;r.type = op->param.type();r.dimensions = op->param.dimensions();r.used_on_host = false;buffers[op->param.name()] = r;} else if (op->reduction_domain.defined()) {// The bounds of reduction domains are not yet defined,// and they may be the only reference to some parameters.op->reduction_domain.accept(this);}}};class TrimStmtToPartsThatAccessBuffers : public IRMutator {bool touches_buffer = false;const map<string, FindBuffers::Result> &buffers;using IRMutator::visit;Expr visit(const Call *op) override {touches_buffer |=(buffers.count(op->name) > 0) ||(buffers.count(op->name + "." + std::to_string(op->value_index)));// Output Tuple params are in the buffers map under their qualified// tuple name, not the Func name.return IRMutator::visit(op);}Stmt visit(const Provide *op) override {if (op->values.size() == 1) {touches_buffer |= (buffers.find(op->name) != buffers.end());} else {// It's a Tuple. Just check if the first Tuple component corresponds// to an output buffer. If it does, they all do.touches_buffer |= (buffers.find(op->name + ".0") != buffers.end());}return IRMutator::visit(op);}Expr visit(const Variable *op) override {if (op->type.is_handle() && op->param.defined() && op->param.is_buffer()) {touches_buffer |= (buffers.find(op->param.name()) != buffers.end());}return IRMutator::visit(op);}Stmt visit(const Block *op) override {bool old_touches_buffer = touches_buffer;touches_buffer = false;Stmt first = mutate(op->first);old_touches_buffer |= touches_buffer;if (!touches_buffer) {first = Evaluate::make(0);}touches_buffer = false;Stmt rest = mutate(op->rest);old_touches_buffer |= touches_buffer;if (!touches_buffer) {rest = Evaluate::make(0);}touches_buffer = old_touches_buffer;return Block::make(first, rest);}public:TrimStmtToPartsThatAccessBuffers(const map<string, FindBuffers::Result> &bufs): buffers(bufs) {}};Stmt add_image_checks_inner(Stmt s,const vector<Function> &outputs,const Target &t,const vector<string> &order,const map<string, Function> &env,const FuncValueBounds &fb,bool will_inject_host_copies) {bool no_asserts = t.has_feature(Target::NoAsserts);bool no_bounds_query = t.has_feature(Target::NoBoundsQuery);// First hunt for all the referenced buffersFindBuffers finder;map<string, FindBuffers::Result> &bufs = finder.buffers;// Add the output buffer(s).for (const Function &f : outputs) {for (size_t i = 0; i < f.values().size(); i++) {FindBuffers::Result output_buffer;output_buffer.type = f.values()[i].type();output_buffer.param = f.output_buffers()[i];output_buffer.dimensions = f.dimensions();if (f.values().size() > 1) {bufs[f.name() + '.' + std::to_string(i)] = output_buffer;} else {bufs[f.name()] = output_buffer;}}}// Add the input buffer(s) and annotate which output buffers are// used on host.s.accept(&finder);Scope<Interval> empty_scope;Stmt sub_stmt = TrimStmtToPartsThatAccessBuffers(bufs).mutate(s);map<string, Box> boxes = boxes_touched(sub_stmt, empty_scope, fb);// Now iterate through all the buffers, creating a list of lets// and a list of asserts.vector<pair<string, Expr>> lets_overflow;vector<pair<string, Expr>> lets_required;vector<pair<string, Expr>> lets_constrained;vector<pair<string, Expr>> lets_proposed;vector<Stmt> dims_no_overflow_asserts;vector<Stmt> asserts_required;vector<Stmt> asserts_constrained;vector<Stmt> asserts_proposed;vector<Stmt> asserts_type_checks;vector<Stmt> asserts_host_alignment;vector<Stmt> asserts_host_non_null;vector<Stmt> asserts_device_not_dirty;vector<Stmt> buffer_rewrites;vector<Stmt> msan_checks;// Inject the code that conditionally returns if we're in inference modeExpr maybe_return_condition = const_false();// We're also going to apply the constraints to the required min// and extent. To do this we have to substitute all references to// the actual sizes of the input images in the constraints with// references to the required sizes.map<string, Expr> replace_with_required;for (const pair<const string, FindBuffers::Result> &buf : bufs) {const string &name = buf.first;for (int i = 0; i < buf.second.dimensions; i++) {string dim = std::to_string(i);Expr min_required = Variable::make(Int(32), name + ".min." + dim + ".required");replace_with_required[name + ".min." + dim] = min_required;Expr extent_required = Variable::make(Int(32), name + ".extent." + dim + ".required");replace_with_required[name + ".extent." + dim] = simplify(extent_required);Expr stride_required = Variable::make(Int(32), name + ".stride." + dim + ".required");replace_with_required[name + ".stride." + dim] = stride_required;}}// We also want to build a map that lets us replace values passed// in with the constrained version. This is applied to the rest of// the lowered pipeline to take advantage of the constraints,// e.g. for constant folding.map<string, Expr> replace_with_constrained;for (pair<const string, FindBuffers::Result> &buf : bufs) {const string &name = buf.first;Buffer<> &image = buf.second.image;Parameter ¶m = buf.second.param;Type type = buf.second.type;int dimensions = buf.second.dimensions;bool used_on_host = buf.second.used_on_host;// Detect if this is one of the outputs of a multi-output pipeline.bool is_output_buffer = false;bool is_secondary_output_buffer = false;string buffer_name = name;for (const Function &f : outputs) {for (size_t i = 0; i < f.output_buffers().size(); i++) {if (param.defined() &¶m.same_as(f.output_buffers()[i])) {is_output_buffer = true;// If we're one of multiple output buffers, we should use the// region inferred for the func in general.buffer_name = f.name();if (i > 0) {is_secondary_output_buffer = true;}}}}Box touched = boxes[buffer_name];internal_assert(touched.empty() || (int)(touched.size()) == dimensions);// The buffer may be used in one or more extern stage. If so we need to// expand the box touched to include the results of the// top-level bounds query calls to those extern stages.if (param.defined()) {// Find the extern users.vector<string> extern_users;for (const auto &func_name : order) {Function f = env.find(func_name)->second;if (f.has_extern_definition() &&!f.extern_definition_proxy_expr().defined()) {const vector<ExternFuncArgument> &args = f.extern_arguments();for (const auto &arg : args) {if ((arg.image_param.defined() &&arg.image_param.name() == param.name()) ||(arg.buffer.defined() &&arg.buffer.name() == param.name())) {extern_users.push_back(func_name);}}}}// Expand the box by the result of the bounds query from each.for (auto &extern_user : extern_users) {Box query_box;Expr query_buf = Variable::make(type_of<struct halide_buffer_t *>(),param.name() + ".bounds_query." + extern_user);for (int j = 0; j < dimensions; j++) {Expr min = Call::make(Int(32), Call::buffer_get_min,{query_buf, j}, Call::Extern);Expr max = Call::make(Int(32), Call::buffer_get_max,{query_buf, j}, Call::Extern);query_box.push_back(Interval(min, max));}merge_boxes(touched, query_box);}}ReductionDomain rdom;// An expression returning whether or not we're in inference modestring buf_name = name + ".buffer";Expr handle = Variable::make(type_of<halide_buffer_t *>(), buf_name,image, param, rdom);Expr inference_mode = Call::make(Bool(), Call::buffer_is_bounds_query,{handle}, Call::Extern);maybe_return_condition = maybe_return_condition || inference_mode;// Come up with a name to refer to this buffer in the error messagesstring error_name = (is_output_buffer ? "Output" : "Input");error_name += " buffer " + name;if (!is_output_buffer && t.has_feature(Target::MSAN)) {Expr buffer = Variable::make(type_of<struct halide_buffer_t *>(), buf_name);Stmt check_contents = Evaluate::make(Call::make(Int(32), "halide_msan_check_buffer_is_initialized", {buffer, Expr(buf_name)}, Call::Extern));msan_checks.push_back(check_contents);}// Check the type matches the internally-understood type{string type_name = name + ".type";Expr type_var = Variable::make(UInt(32), type_name, image, param, rdom);uint32_t correct_type_bits = ((halide_type_t)type).as_u32();Expr correct_type_expr = make_const(UInt(32), correct_type_bits);Expr error = Call::make(Int(32), "halide_error_bad_type",{error_name, type_var, correct_type_expr},Call::Extern);Stmt type_check = AssertStmt::make(type_var == correct_type_expr, error);asserts_type_checks.push_back(type_check);}// Check the dimensions matches the internally-understood dimensions{string dimensions_name = name + ".dimensions";Expr dimensions_given = Variable::make(Int(32), dimensions_name, image, param, rdom);Expr error = Call::make(Int(32), "halide_error_bad_dimensions",{error_name,dimensions_given, make_const(Int(32), dimensions)},Call::Extern);asserts_type_checks.push_back(AssertStmt::make(dimensions_given == dimensions, error));}if (touched.maybe_unused()) {debug(3) << "Image " << name << " is only used when " << touched.used << "\n";}// Check that the region passed in (after applying constraints) is within the region usedif (debug::debug_level() >= 3) {debug(3) << "In image " << name << " region touched is:\n";for (int j = 0; j < dimensions; j++) {debug(3) << " " << j << ": " << (touched.empty() ? Expr() : touched[j].min)<< " .. "<< (touched.empty() ? Expr() : touched[j].max)<< "\n";}}for (int j = 0; j < dimensions; j++) {string dim = std::to_string(j);string actual_min_name = name + ".min." + dim;string actual_extent_name = name + ".extent." + dim;string actual_stride_name = name + ".stride." + dim;Expr actual_min = Variable::make(Int(32), actual_min_name, image, param, rdom);Expr actual_extent = Variable::make(Int(32), actual_extent_name, image, param, rdom);Expr actual_stride = Variable::make(Int(32), actual_stride_name, image, param, rdom);if (!touched.empty() && !touched[j].is_bounded()) {user_error << "Buffer " << name<< " may be accessed in an unbounded way in dimension "<< j << "\n";}Expr min_required = touched.empty() ? actual_min : touched[j].min;Expr extent_required = touched.empty() ? actual_extent : (touched[j].max + 1 - touched[j].min);if (touched.maybe_unused()) {min_required = select(touched.used, min_required, actual_min);extent_required = select(touched.used, extent_required, actual_extent);}string min_required_name = name + ".min." + dim + ".required";string extent_required_name = name + ".extent." + dim + ".required";Expr min_required_var = Variable::make(Int(32), min_required_name);Expr extent_required_var = Variable::make(Int(32), extent_required_name);lets_required.emplace_back(extent_required_name, extent_required);lets_required.emplace_back(min_required_name, min_required);Expr actual_max = actual_min + actual_extent - 1;Expr max_required = min_required_var + extent_required_var - 1;if (touched.maybe_unused()) {max_required = select(touched.used, max_required, actual_max);}Expr oob_condition = actual_min <= min_required_var && actual_max >= max_required;Expr oob_error = Call::make(Int(32), "halide_error_access_out_of_bounds",{error_name, j, min_required_var, max_required, actual_min, actual_max},Call::Extern);asserts_required.push_back(AssertStmt::make(oob_condition, oob_error));// Come up with a required stride to use in bounds// inference mode. We don't assert it. It's just used to// apply the constraints to to come up with a proposed// stride. Strides actually passed in may not be in this// order (e.g if storage is swizzled relative to dimension// order).Expr stride_required;if (j == 0) {stride_required = 1;} else {string last_dim = std::to_string(j - 1);stride_required = (Variable::make(Int(32), name + ".stride." + last_dim + ".required") *Variable::make(Int(32), name + ".extent." + last_dim + ".required"));}lets_required.emplace_back(name + ".stride." + dim + ".required", stride_required);// On 32-bit systems, insert checks to make sure the total// size of all input and output buffers is <= 2^31 - 1.// And that no product of extents overflows 2^31 - 1. This// second test is likely only needed if a fuse directive// is used in the schedule to combine multiple extents,// but it is here for extra safety. On 64-bit targets with the// LargeBuffers feature, the maximum size is 2^63 - 1.Expr max_size = make_const(UInt(64), t.maximum_buffer_size());Expr max_extent = make_const(UInt(64), 0x7fffffff);Expr actual_size = abs(cast<int64_t>(actual_extent) * actual_stride);Expr allocation_size_error = Call::make(Int(32), "halide_error_buffer_allocation_too_large",{name, actual_size, max_size}, Call::Extern);Stmt check = AssertStmt::make(actual_size <= max_size, allocation_size_error);dims_no_overflow_asserts.push_back(check);// Don't repeat extents check for secondary buffers as extents must be the same as for the first one.if (!is_secondary_output_buffer) {if (j == 0) {lets_overflow.emplace_back(name + ".total_extent." + dim, cast<int64_t>(actual_extent));} else {max_size = cast<int64_t>(max_size);Expr last_dim = Variable::make(Int(64), name + ".total_extent." + std::to_string(j - 1));Expr this_dim = actual_extent * last_dim;Expr this_dim_var = Variable::make(Int(64), name + ".total_extent." + dim);lets_overflow.emplace_back(name + ".total_extent." + dim, this_dim);Expr error = Call::make(Int(32), "halide_error_buffer_extents_too_large",{name, this_dim_var, max_size}, Call::Extern);Stmt check = AssertStmt::make(this_dim_var <= max_size, error);dims_no_overflow_asserts.push_back(check);}// It is never legal to have a negative buffer extent.Expr negative_extent_condition = actual_extent >= 0;Expr negative_extent_error = Call::make(Int(32), "halide_error_buffer_extents_negative",{error_name, j, actual_extent}, Call::Extern);asserts_required.push_back(AssertStmt::make(negative_extent_condition, negative_extent_error));}}// Create code that mutates the input buffers if we're in bounds inference mode.BufferBuilder builder;builder.buffer_memory = Variable::make(type_of<struct halide_buffer_t *>(), buf_name);builder.shape_memory = Call::make(type_of<struct halide_dimension_t *>(),Call::buffer_get_shape, {builder.buffer_memory},Call::Extern);builder.type = type;builder.dimensions = dimensions;for (int i = 0; i < dimensions; i++) {string dim = std::to_string(i);builder.mins.push_back(Variable::make(Int(32), name + ".min." + dim + ".proposed"));builder.extents.push_back(Variable::make(Int(32), name + ".extent." + dim + ".proposed"));builder.strides.push_back(Variable::make(Int(32), name + ".stride." + dim + ".proposed"));}Stmt rewrite = Evaluate::make(builder.build());rewrite = IfThenElse::make(inference_mode, rewrite);buffer_rewrites.push_back(rewrite);// Build the constraints tests and proposed sizes.vector<pair<Expr, Expr>> constraints;for (int i = 0; i < dimensions; i++) {string dim = std::to_string(i);string min_name = name + ".min." + dim;string stride_name = name + ".stride." + dim;string extent_name = name + ".extent." + dim;Expr stride_constrained, extent_constrained, min_constrained;Expr stride_orig = Variable::make(Int(32), stride_name, image, param, rdom);Expr extent_orig = Variable::make(Int(32), extent_name, image, param, rdom);Expr min_orig = Variable::make(Int(32), min_name, image, param, rdom);Expr stride_required = Variable::make(Int(32), stride_name + ".required");Expr extent_required = Variable::make(Int(32), extent_name + ".required");Expr min_required = Variable::make(Int(32), min_name + ".required");Expr stride_proposed = Variable::make(Int(32), stride_name + ".proposed");Expr extent_proposed = Variable::make(Int(32), extent_name + ".proposed");Expr min_proposed = Variable::make(Int(32), min_name + ".proposed");debug(2) << "Injecting constraints for " << name << "." << i << "\n";if (is_secondary_output_buffer) {// For multi-output (Tuple) pipelines, output buffers// beyond the first implicitly have their min and extent// constrained to match the first output.if (param.defined()) {user_assert(!param.extent_constraint(i).defined() &&!param.min_constraint(i).defined())<< "Can't constrain the min or extent of an output buffer beyond the "<< "first. They are implicitly constrained to have the same min and extent "<< "as the first output buffer.\n";stride_constrained = param.stride_constraint(i);} else if (image.defined() && (int)i < image.dimensions()) {stride_constrained = image.dim(i).stride();}std::string min0_name = buffer_name + ".0.min." + dim;if (replace_with_constrained.count(min0_name) > 0) {min_constrained = replace_with_constrained[min0_name];} else {min_constrained = Variable::make(Int(32), min0_name);}std::string extent0_name = buffer_name + ".0.extent." + dim;if (replace_with_constrained.count(extent0_name) > 0) {extent_constrained = replace_with_constrained[extent0_name];} else {extent_constrained = Variable::make(Int(32), extent0_name);}} else if (image.defined() && (int)i < image.dimensions()) {stride_constrained = image.dim(i).stride();extent_constrained = image.dim(i).extent();min_constrained = image.dim(i).min();} else if (param.defined()) {stride_constrained = param.stride_constraint(i);extent_constrained = param.extent_constraint(i);min_constrained = param.min_constraint(i);}if (stride_constrained.defined()) {// Come up with a suggested stride by passing the// required region through this constraint.constraints.emplace_back(stride_orig, stride_constrained);stride_constrained = substitute(replace_with_required, stride_constrained);lets_proposed.emplace_back(stride_name + ".proposed", stride_constrained);} else {lets_proposed.emplace_back(stride_name + ".proposed", stride_required);}if (min_constrained.defined()) {constraints.emplace_back(min_orig, min_constrained);min_constrained = substitute(replace_with_required, min_constrained);lets_proposed.emplace_back(min_name + ".proposed", min_constrained);} else {lets_proposed.emplace_back(min_name + ".proposed", min_required);}if (extent_constrained.defined()) {constraints.emplace_back(extent_orig, extent_constrained);extent_constrained = substitute(replace_with_required, extent_constrained);lets_proposed.emplace_back(extent_name + ".proposed", extent_constrained);} else {lets_proposed.emplace_back(extent_name + ".proposed", extent_required);}// In bounds inference mode, make sure the proposed// versions still satisfy the constraints.Expr max_proposed = min_proposed + extent_proposed - 1;Expr max_required = min_required + extent_required - 1;Expr check = (min_proposed <= min_required) && (max_proposed >= max_required);Expr error = Call::make(Int(32), "halide_error_constraints_make_required_region_smaller",{error_name, i, min_proposed, max_proposed, min_required, max_required},Call::Extern);asserts_proposed.push_back(AssertStmt::make((!inference_mode) || check, error));// stride_required is just a suggestion. It's ok if the// constraints shuffle them around in ways that make it// smaller./*check = (stride_proposed >= stride_required);error = "Applying the constraints to the required stride made it smaller";asserts_proposed.push_back(AssertStmt::make((!inference_mode) || check, error, vector<Expr>()));*/}// Assert all the conditions, and set the new valuesfor (const auto &constraint : constraints) {Expr var = constraint.first;const string &name = var.as<Variable>()->name;Expr constrained_var = Variable::make(Int(32), name + ".constrained");std::ostringstream ss;ss << constraint.second;string constrained_var_str = ss.str();lets_constrained.emplace_back(name + ".constrained", constraint.second);// Substituting in complex expressions is not typically a good ideaif (constraint.second.as<Variable>() ||is_const(constraint.second)) {replace_with_constrained[name] = constrained_var;}Expr error = 0;if (!no_asserts) {error = Call::make(Int(32), "halide_error_constraint_violated",{name, var, constrained_var_str, constrained_var},Call::Extern);}// Check the var passed in equals the constrained version (when not in inference mode)asserts_constrained.push_back(AssertStmt::make(var == constrained_var, error));}// For the buffers used on host, check the host field is non-nullExpr host_ptr = Variable::make(Handle(), name, image, param, ReductionDomain());if (used_on_host) {Expr error = Call::make(Int(32), "halide_error_host_is_null",{error_name}, Call::Extern);Expr check = (host_ptr != make_zero(host_ptr.type()));if (touched.maybe_unused()) {check = !touched.used || check;}asserts_host_non_null.push_back(AssertStmt::make(check, error));if (!will_inject_host_copies) {Expr device_dirty = Variable::make(Bool(), name + ".device_dirty",image, param, ReductionDomain());Expr error = Call::make(Int(32), "halide_error_device_dirty_with_no_device_support",{error_name}, Call::Extern);// If we have no device support, we can't handle// device_dirty, so every buffer touched needs checking.asserts_device_not_dirty.push_back(AssertStmt::make(!device_dirty, error));}}// and check alignment of the host fieldif (param.defined() && param.host_alignment() != param.type().bytes()) {int alignment_required = param.host_alignment();Expr u64t_host_ptr = reinterpret<uint64_t>(host_ptr);Expr align_condition = (u64t_host_ptr % alignment_required) == 0;Expr error = Call::make(Int(32), "halide_error_unaligned_host_ptr",{name, alignment_required}, Call::Extern);asserts_host_alignment.push_back(AssertStmt::make(align_condition, error));}}auto prepend_stmts = [&](vector<Stmt> *stmts) {while (!stmts->empty()) {s = Block::make(std::move(stmts->back()), s);stmts->pop_back();}};auto prepend_lets = [&](vector<pair<string, Expr>> *lets) {while (!lets->empty()) {auto &p = lets->back();s = LetStmt::make(p.first, std::move(p.second), s);lets->pop_back();}};if (!no_asserts) {// Inject the code that checks the host pointers.prepend_stmts(&asserts_host_non_null);prepend_stmts(&asserts_host_alignment);prepend_stmts(&asserts_device_not_dirty);prepend_stmts(&dims_no_overflow_asserts);prepend_lets(&lets_overflow);}// Replace uses of the var with the constrained versions in the// rest of the program. We also need to respect the existence of// constrained versions during storage flattening and bounds// inference.s = substitute(replace_with_constrained, s);// Now we add a bunch of code to the top of the pipeline. This is// all in reverse order compared to execution, as we incrementally// prepending code.// Inject the code that checks the constraints are correct. We// need these regardless of how NoAsserts is set, because they are// what gets Halide to actually exploit the constraint.prepend_stmts(&asserts_constrained);if (!no_asserts) {prepend_stmts(&asserts_required);prepend_stmts(&asserts_type_checks);}// Inject the code that returns early for inference mode.if (!no_bounds_query) {s = IfThenElse::make(!maybe_return_condition, s);prepend_stmts(&buffer_rewrites);}if (!no_asserts) {prepend_stmts(&asserts_proposed);}// Inject the code that defines the proposed sizes.prepend_lets(&lets_proposed);// Inject the code that defines the constrained sizes.prepend_lets(&lets_constrained);// Inject the code that defines the required sizes produced by bounds inference.prepend_lets(&lets_required);// Inject the code that checks that does msan checks. (Note that this ignores no_asserts.)prepend_stmts(&msan_checks);return s;}} // namespace// The following function repeats the arguments list it just passes// through six times. Surely there is a better way?Stmt add_image_checks(const Stmt &s,const vector<Function> &outputs,const Target &t,const vector<string> &order,const map<string, Function> &env,const FuncValueBounds &fb,bool will_inject_host_copies) {// Checks for images go at the marker deposited by computation// bounds inference.class Injector : public IRMutator {using IRMutator::visit;Stmt visit(const Block *op) override {const Evaluate *e = op->first.as<Evaluate>();if (e && Call::as_intrinsic(e->value, {Call::add_image_checks_marker})) {return add_image_checks_inner(op->rest, outputs, t, order, env, fb, will_inject_host_copies);} else {return IRMutator::visit(op);}}const vector<Function> &outputs;const Target &t;const vector<string> ℴconst map<string, Function> &env;const FuncValueBounds &fb;bool will_inject_host_copies;public:Injector(const vector<Function> &outputs,const Target &t,const vector<string> &order,const map<string, Function> &env,const FuncValueBounds &fb,bool will_inject_host_copies): outputs(outputs), t(t), order(order), env(env), fb(fb), will_inject_host_copies(will_inject_host_copies) {}} injector(outputs, t, order, env, fb, will_inject_host_copies);return injector.mutate(s);}} // namespace Internal} // namespace Halide
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。