diff --git a/apps/camera_pipe/camera_pipe_generator.cpp b/apps/camera_pipe/camera_pipe_generator.cpp index 06251f5691bb..3897184490fe 100644 --- a/apps/camera_pipe/camera_pipe_generator.cpp +++ b/apps/camera_pipe/camera_pipe_generator.cpp @@ -207,7 +207,7 @@ class Demosaic : public Halide::Generator { private: // Intermediate stencil stages to schedule - vector intermediates; + FuncVec intermediates; }; class CameraPipe : public Halide::Generator { diff --git a/apps/interpolate/interpolate_generator.cpp b/apps/interpolate/interpolate_generator.cpp index ca751bab253f..218f3f2233bd 100644 --- a/apps/interpolate/interpolate_generator.cpp +++ b/apps/interpolate/interpolate_generator.cpp @@ -2,14 +2,6 @@ namespace { -std::vector func_vector(const std::string &name, int size) { - std::vector funcs; - for (int i = 0; i < size; i++) { - funcs.emplace_back(Halide::Func{name + "_" + std::to_string(i)}); - } - return funcs; -} - class Interpolate : public Halide::Generator { public: GeneratorParam levels{"levels", 10}; @@ -23,11 +15,11 @@ class Interpolate : public Halide::Generator { // Input must have four color channels - rgba input.dim(2).set_bounds(0, 4); - auto downsampled = func_vector("downsampled", levels); - auto downx = func_vector("downx", levels); - auto interpolated = func_vector("interpolated", levels); - auto upsampled = func_vector("upsampled", levels); - auto upsampledx = func_vector("upsampledx", levels); + FuncVec downsampled("downsampled_", levels); + FuncVec downx("downx_", levels); + FuncVec interpolated("interpolated_", levels); + FuncVec upsampled("upsampled_", levels); + FuncVec upsampledx("upsampledx_", levels); Func clamped = Halide::BoundaryConditions::repeat_edge(input); diff --git a/apps/lens_blur/lens_blur_generator.cpp b/apps/lens_blur/lens_blur_generator.cpp index 4b815bdb1be5..c70d4d6ed724 100644 --- a/apps/lens_blur/lens_blur_generator.cpp +++ b/apps/lens_blur/lens_blur_generator.cpp @@ -51,7 +51,7 @@ class LensBlur : public Halide::Generator { // Do a push-pull thing to blur the cost volume with an // exponential-decay type thing to inpaint over regions with low // confidence. - Func cost_pyramid_push[8]; + FuncVec cost_pyramid_push("cost_pyramid_push", 8); cost_pyramid_push[0](x, y, z, c) = mux(c, {cost(x, y, z) * cost_confidence(x, y), cost_confidence(x, y)}); @@ -63,7 +63,7 @@ class LensBlur : public Halide::Generator { cost_pyramid_push[i] = BoundaryConditions::repeat_edge(cost_pyramid_push[i], {{0, w}, {0, h}}); } - Func cost_pyramid_pull[8]; + FuncVec cost_pyramid_pull("cost_pyramid_pull", 8); cost_pyramid_pull[7](x, y, z, c) = cost_pyramid_push[7](x, y, z, c); for (int i = 6; i >= 0; i--) { cost_pyramid_pull[i](x, y, z, c) = lerp(upsample(cost_pyramid_pull[i + 1])(x, y, z, c), diff --git a/apps/local_laplacian/local_laplacian_generator.cpp b/apps/local_laplacian/local_laplacian_generator.cpp index e00dde32ec15..91e1032f3754 100644 --- a/apps/local_laplacian/local_laplacian_generator.cpp +++ b/apps/local_laplacian/local_laplacian_generator.cpp @@ -36,7 +36,7 @@ class LocalLaplacian : public Halide::Generator { gray(x, y) = 0.299f * floating(x, y, 0) + 0.587f * floating(x, y, 1) + 0.114f * floating(x, y, 2); // Make the processed Gaussian pyramid. - Func gPyramid[maxJ]; + FuncVec gPyramid("gPyramid", J); // Do a lookup into a lut with 256 entries per intensity level Expr level = k * (1.0f / (levels - 1)); Expr idx = gray(x, y) * cast(levels - 1) * 256.0f; @@ -47,21 +47,21 @@ class LocalLaplacian : public Halide::Generator { } // Get its laplacian pyramid - Func lPyramid[maxJ]; + FuncVec lPyramid("lPyramid", J); lPyramid[J - 1](x, y, k) = gPyramid[J - 1](x, y, k); for (int j = J - 2; j >= 0; j--) { lPyramid[j](x, y, k) = gPyramid[j](x, y, k) - upsample(gPyramid[j + 1])(x, y, k); } // Make the Gaussian pyramid of the input - Func inGPyramid[maxJ]; + FuncVec inGPyramid("inGPyramid", J); inGPyramid[0](x, y) = gray(x, y); for (int j = 1; j < J; j++) { inGPyramid[j](x, y) = downsample(inGPyramid[j - 1])(x, y); } // Make the laplacian pyramid of the output - Func outLPyramid[maxJ]; + FuncVec outLPyramid("outLPyramid", J); for (int j = 0; j < J; j++) { // Split input pyramid value into integer and floating parts Expr level = inGPyramid[j](x, y) * cast(levels - 1); @@ -72,7 +72,7 @@ class LocalLaplacian : public Halide::Generator { } // Make the Gaussian pyramid of the output - Func outGPyramid[maxJ]; + FuncVec outGPyramid("outGPyramid", J); outGPyramid[J - 1](x, y) = outLPyramid[J - 1](x, y); for (int j = J - 2; j >= 0; j--) { outGPyramid[j](x, y) = upsample(outGPyramid[j + 1])(x, y) + outLPyramid[j](x, y); diff --git a/apps/onnx/model.cpp b/apps/onnx/model.cpp index b84c6b318130..39d3d80bc37a 100644 --- a/apps/onnx/model.cpp +++ b/apps/onnx/model.cpp @@ -49,7 +49,7 @@ HalideModel convert_onnx_model( result.model = std::make_shared( convert_model(onnx_model, expected_dim_sizes, layout)); - std::vector funcs; + Halide::FuncVec funcs; for (const auto &output : onnx_model.graph().output()) { const auto &tensor = result.model->outputs.at(output.name()); funcs.push_back(tensor.rep); diff --git a/apps/onnx/onnx_converter.cc b/apps/onnx/onnx_converter.cc index 8668142928f7..1d7c4bdf78f7 100644 --- a/apps/onnx/onnx_converter.cc +++ b/apps/onnx/onnx_converter.cc @@ -1850,7 +1850,7 @@ Node convert_concat_node( } Halide::Var concat_axis = tgt_indices[axis]; - std::vector concat_funcs; + Halide::FuncVec concat_funcs; concat_funcs.resize(inputs.size()); concat_funcs[0](tgt_indices) = inputs[0].rep(tgt_indices); Halide::Expr concat_offset = 0; diff --git a/apps/stencil_chain/stencil_chain_generator.cpp b/apps/stencil_chain/stencil_chain_generator.cpp index f62f269d6146..8d8a802bdbe9 100644 --- a/apps/stencil_chain/stencil_chain_generator.cpp +++ b/apps/stencil_chain/stencil_chain_generator.cpp @@ -11,24 +11,20 @@ class StencilChain : public Halide::Generator { void generate() { - std::vector stages; + FuncVec stages("stage_", (int)stencils + 1); Var x("x"), y("y"); - Func f = Halide::BoundaryConditions::repeat_edge(input); + stages[0] = Halide::BoundaryConditions::repeat_edge(input); - stages.push_back(f); - - for (int s = 0; s < (int)stencils; s++) { - Func f("stage_" + std::to_string(s)); + for (int s = 1; s <= (int)stencils; s++) { Expr e = cast(0); for (int i = -2; i <= 2; i++) { for (int j = -2; j <= 2; j++) { - e += ((i + 3) * (j + 3)) * stages.back()(x + i, y + j); + e += ((i + 3) * (j + 3)) * stages[s - 1](x + i, y + j); } } - f(x, y) = e; - stages.push_back(f); + stages[s](x, y) = e; } output(x, y) = stages.back()(x, y); diff --git a/python_bindings/halide/src/halide_/PyPipeline.cpp b/python_bindings/halide/src/halide_/PyPipeline.cpp index ec551a501e56..abb2390127b9 100644 --- a/python_bindings/halide/src/halide_/PyPipeline.cpp +++ b/python_bindings/halide/src/halide_/PyPipeline.cpp @@ -70,15 +70,12 @@ void define_pipeline(py::module &m) { .def(py::init()) .def(py::init &>()) - .def("outputs", &Pipeline::outputs) - - .def("apply_autoscheduler", &Pipeline::apply_autoscheduler, - py::arg("target"), py::arg("autoscheduler_params")) - .def( - "apply_runtime_prefixes", [](Pipeline &p, const Target &target, const std::map &namespace_map) { - p.apply_runtime_prefixes(target, RuntimePrefixParams(namespace_map)); - }, - py::arg("target"), py::arg("namespace_map")) + .def("outputs", [](const Pipeline &p) { + return std::vector(p.outputs()); + }) + + .def("apply_autoscheduler", &Pipeline::apply_autoscheduler, py::arg("target"), py::arg("autoscheduler_params")) + .def("apply_runtime_prefixes", [](Pipeline &p, const Target &target, const std::map &namespace_map) { p.apply_runtime_prefixes(target, RuntimePrefixParams(namespace_map)); }, py::arg("target"), py::arg("namespace_map")) .def("get_func", &Pipeline::get_func, py::arg("index")) .def("print_loop_nest", &Pipeline::print_loop_nest) diff --git a/src/Func.cpp b/src/Func.cpp index ffc20edb05e3..7ecd7ed98ee1 100644 --- a/src/Func.cpp +++ b/src/Func.cpp @@ -99,6 +99,20 @@ Func::Func(Function f) << "Can't construct Func from undefined Function"; } +FuncVec::FuncVec(const string &base_name, size_t count) { + reserve(count); + for (size_t i = 0; i < count; ++i) { + emplace_back(count == 1 ? base_name : base_name + std::to_string(i)); + } +} + +FuncVec::operator Func() const { + user_assert(size() == 1) + << "Cannot convert a FuncVec of size " << size() + << " to a Func; exactly one Func is required.\n"; + return front(); +} + const string &Func::name() const { return func.name(); } diff --git a/src/Func.h b/src/Func.h index 0018fb20b461..583efbce14f1 100644 --- a/src/Func.h +++ b/src/Func.h @@ -2856,6 +2856,32 @@ class Func { } }; +/** A vector of Funcs with conveniences for constructing and consuming + * collections of Funcs. */ +class FuncVec : public std::vector { + using Base = std::vector; + +public: + using Base::Base; + using Base::operator=; + + FuncVec() = default; + FuncVec(const Base &funcs) + : Base(funcs) { + } + FuncVec(Base &&funcs) + : Base(std::move(funcs)) { + } + + /** Construct count undefined Funcs. A singleton is named base_name; otherwise + * the Funcs are named base_name + their index. */ + FuncVec(const std::string &base_name, size_t count); + + /** Convert a singleton FuncVec to its sole Func. It is a user error if + * the FuncVec does not contain exactly one Func. */ + operator Func() const; +}; + template HALIDE_NO_USER_CODE_INLINE std::enable_if_t::value, Stage &> Stage::eager_inline(const Func &first, Args &&...args) { diff --git a/src/Generator.h b/src/Generator.h index 7d7f2d1a7d5c..d88bff8bea3f 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -3089,6 +3089,7 @@ class NamesInterface { using EvictionKey = Halide::EvictionKey; using ExternFuncArgument = Halide::ExternFuncArgument; using Func = Halide::Func; + using FuncVec = Halide::FuncVec; using GeneratorContext = Halide::GeneratorContext; using ImageParam = Halide::ImageParam; using LoopLevel = Halide::LoopLevel; diff --git a/src/Pipeline.cpp b/src/Pipeline.cpp index e935fd4852df..e8faf1561e3c 100644 --- a/src/Pipeline.cpp +++ b/src/Pipeline.cpp @@ -219,8 +219,8 @@ Pipeline::Pipeline(const std::vector &outputs, const std::vector Pipeline::outputs() const { - vector funcs; +FuncVec Pipeline::outputs() const { + FuncVec funcs; for (const Function &f : contents->outputs) { funcs.emplace_back(f); } diff --git a/src/Pipeline.h b/src/Pipeline.h index 0fa8b593eedf..fac90b99a8dd 100644 --- a/src/Pipeline.h +++ b/src/Pipeline.h @@ -25,6 +25,7 @@ namespace Halide { struct Argument; class Callable; class Func; +class FuncVec; struct PipelineContents; /** Special the Autoscheduler to be used (if any), along with arbitrary @@ -205,7 +206,7 @@ class Pipeline { std::vector infer_arguments(const Internal::Stmt &body); /** Get the Funcs this pipeline outputs. */ - std::vector outputs() const; + FuncVec outputs() const; /** Get the requirements of this pipeline. */ std::vector requirements() const; diff --git a/test/correctness/CMakeLists.txt b/test/correctness/CMakeLists.txt index 3f40bfc57e2a..5d102a63c202 100644 --- a/test/correctness/CMakeLists.txt +++ b/test/correctness/CMakeLists.txt @@ -139,6 +139,7 @@ tests( force_onto_stack.cpp func_lifetime.cpp func_lifetime_2.cpp + func_vec.cpp fuse.cpp fuse_gpu_threads.cpp fused_where_inner_extent_is_zero.cpp diff --git a/test/correctness/func_vec.cpp b/test/correctness/func_vec.cpp new file mode 100644 index 000000000000..9a1808e628df --- /dev/null +++ b/test/correctness/func_vec.cpp @@ -0,0 +1,81 @@ +#include "Halide.h" +#include "expect_user_error.h" + +#include +#include +#include +#include + +using namespace Halide; + +namespace { + +bool check(bool condition, const char *message) { + if (!condition) { + std::printf("FAIL: %s\n", message); + } + return condition; +} + +size_t vector_size(const std::vector &funcs) { + return funcs.size(); +} + +} // namespace + +int main(int argc, char **argv) { + static_assert(std::is_base_of_v, FuncVec>); + static_assert(std::is_convertible_v); + + bool success = true; + + FuncVec named("func_vec_stage_", 3); + success &= check(named.size() == 3, "named constructor created the wrong number of Funcs"); + for (size_t i = 0; i < named.size(); ++i) { + const std::string expected = "func_vec_stage_" + std::to_string(i); + success &= check(named[i].name() == expected, "named constructor created an incorrect Func name"); + } + + FuncVec singleton_named("func_vec_singleton", 1); + success &= check(singleton_named[0].name() == "func_vec_singleton", + "singleton named constructor should preserve its base name"); + + Func a("func_vec_a"); + Func b("func_vec_b"); + FuncVec funcs{a}; + funcs.push_back(b); + success &= check(vector_size(funcs) == 2, "FuncVec is not usable as a std::vector"); + + std::vector base{a}; + FuncVec copied(base); + FuncVec assigned; + assigned = base; + std::vector round_trip = copied; + success &= check(assigned.size() == 1 && round_trip.size() == 1, + "FuncVec conversion to or from std::vector failed"); + + Func singleton = copied; + success &= check(singleton.name() == a.name(), "singleton FuncVec converted to the wrong Func"); + + Pipeline pipeline(copied); + Func pipeline_output = pipeline.outputs(); + success &= check(pipeline_output.name() == a.name(), "Pipeline::outputs() did not decay to its singleton Func"); + +#if HALIDE_WITH_EXCEPTIONS + FuncVec empty; + success &= expect_user_error("empty_func_vec", "size 0", [&]() { + Func f = empty; + (void)f; + }); + success &= expect_user_error("multi_func_vec", "size 2", [&]() { + Func f = funcs; + (void)f; + }); +#endif + + if (!success) { + return 1; + } + std::printf("Success!\n"); + return 0; +}