Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/camera_pipe/camera_pipe_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class Demosaic : public Halide::Generator<Demosaic> {

private:
// Intermediate stencil stages to schedule
vector<Func> intermediates;
FuncVec intermediates;
};

class CameraPipe : public Halide::Generator<CameraPipe> {
Expand Down
18 changes: 5 additions & 13 deletions apps/interpolate/interpolate_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@

namespace {

std::vector<Halide::Func> func_vector(const std::string &name, int size) {
std::vector<Halide::Func> funcs;
for (int i = 0; i < size; i++) {
funcs.emplace_back(Halide::Func{name + "_" + std::to_string(i)});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about: name + "[" + std::to_string(i) + "]"

@alexreinking alexreinking Aug 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what [ would do in a Func name.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I prefer to keep f0, f1, ..., fN rather than burn two characters (on []) of std::string's short-string optimizations.

@shoaibkamil shoaibkamil Aug 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think [ eventually gets legalized into ___ (three underscoeres)
except if the Func is used as an input/output generator, in which case it gets rejected. Seems safer to avoid [.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like e.g. gPyramid[3] because it reads way better in the profiler output and corresponds more closely to source code. That's what I've been using locally.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we hard-reject it for output funcs though, that seems decisive

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this: Have the constructor also take an optional suffix string. Right now it's a prefix string and a count. If it had a suffix too I could just say:

FuncVec v("foo[", 17, "]");

}
return funcs;
}

class Interpolate : public Halide::Generator<Interpolate> {
public:
GeneratorParam<int> levels{"levels", 10};
Expand All @@ -23,11 +15,11 @@ class Interpolate : public Halide::Generator<Interpolate> {
// 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);

Expand Down
4 changes: 2 additions & 2 deletions apps/lens_blur/lens_blur_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class LensBlur : public Halide::Generator<LensBlur> {
// 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)});

Expand All @@ -63,7 +63,7 @@ class LensBlur : public Halide::Generator<LensBlur> {
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),
Expand Down
10 changes: 5 additions & 5 deletions apps/local_laplacian/local_laplacian_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class LocalLaplacian : public Halide::Generator<LocalLaplacian> {
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<float>(levels - 1) * 256.0f;
Expand All @@ -47,21 +47,21 @@ class LocalLaplacian : public Halide::Generator<LocalLaplacian> {
}

// 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<float>(levels - 1);
Expand All @@ -72,7 +72,7 @@ class LocalLaplacian : public Halide::Generator<LocalLaplacian> {
}

// 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);
Expand Down
2 changes: 1 addition & 1 deletion apps/onnx/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ HalideModel convert_onnx_model(
result.model = std::make_shared<Model>(
convert_model(onnx_model, expected_dim_sizes, layout));

std::vector<Halide::Func> 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);
Expand Down
2 changes: 1 addition & 1 deletion apps/onnx/onnx_converter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1850,7 +1850,7 @@ Node convert_concat_node(
}

Halide::Var concat_axis = tgt_indices[axis];
std::vector<Halide::Func> 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;
Expand Down
14 changes: 5 additions & 9 deletions apps/stencil_chain/stencil_chain_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,20 @@ class StencilChain : public Halide::Generator<StencilChain> {

void generate() {

std::vector<Func> stages;
FuncVec stages("stage_", (int)stencils + 1);
Comment thread
alexreinking marked this conversation as resolved.

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<uint16_t>(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);
Expand Down
15 changes: 6 additions & 9 deletions python_bindings/halide/src/halide_/PyPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,12 @@ void define_pipeline(py::module &m) {
.def(py::init<Func>())
.def(py::init<const std::vector<Func> &>())

.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<RuntimeLinkage, std::string> &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<Func>(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<RuntimeLinkage, std::string> &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)

Expand Down
14 changes: 14 additions & 0 deletions src/Func.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
26 changes: 26 additions & 0 deletions src/Func.h
Original file line number Diff line number Diff line change
Expand Up @@ -2856,6 +2856,32 @@ class Func {
}
};

/** A vector of Funcs with conveniences for constructing and consuming
* collections of Funcs. */
class FuncVec : public std::vector<Func> {
using Base = std::vector<Func>;

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<typename... Args>
HALIDE_NO_USER_CODE_INLINE std::enable_if_t<Internal::all_are_convertible<Func, Args...>::value, Stage &>
Stage::eager_inline(const Func &first, Args &&...args) {
Expand Down
1 change: 1 addition & 0 deletions src/Generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/Pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ Pipeline::Pipeline(const std::vector<Func> &outputs, const std::vector<Internal:
}
}

vector<Func> Pipeline::outputs() const {
vector<Func> funcs;
FuncVec Pipeline::outputs() const {
FuncVec funcs;
for (const Function &f : contents->outputs) {
funcs.emplace_back(f);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -205,7 +206,7 @@ class Pipeline {
std::vector<Argument> infer_arguments(const Internal::Stmt &body);

/** Get the Funcs this pipeline outputs. */
std::vector<Func> outputs() const;
FuncVec outputs() const;

/** Get the requirements of this pipeline. */
std::vector<Internal::Stmt> requirements() const;
Expand Down
1 change: 1 addition & 0 deletions test/correctness/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 81 additions & 0 deletions test/correctness/func_vec.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "Halide.h"
#include "expect_user_error.h"

#include <cstdio>
#include <string>
#include <type_traits>
#include <vector>

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<Func> &funcs) {
return funcs.size();
}

} // namespace

int main(int argc, char **argv) {
static_assert(std::is_base_of_v<std::vector<Func>, FuncVec>);
static_assert(std::is_convertible_v<FuncVec, Func>);

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<Func>");

std::vector<Func> base{a};
FuncVec copied(base);
FuncVec assigned;
assigned = base;
std::vector<Func> round_trip = copied;
success &= check(assigned.size() == 1 && round_trip.size() == 1,
"FuncVec conversion to or from std::vector<Func> 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;
}
Loading