root/src/Generator.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. is_alpha
  2. is_alnum
  3. is_valid_name
  4. compute_base_path
  5. get_extension
  6. compute_outputs
  7. to_argument
  8. rational_approximation_helper
  9. rational_approximation
  10. make_param_func
  11. parse_halide_type_list
  12. track_values
  13. parameter_constraints
  14. outputs
  15. filter_params
  16. indent
  17. emit_generator_params_struct
  18. emit_schedule_params_setters
  19. emit_inputs_struct
  20. emit
  21. verify_same_funcs
  22. verify_same_funcs
  23. get_halide_type_enum_map
  24. halide_type_to_c_source
  25. halide_type_to_c_type
  26. get_halide_looplevel_enum_map
  27. generate_filter_main
  28. check_value_readable
  29. check_value_writable
  30. fail_wrong_type
  31. get_registry
  32. register_factory
  33. unregister_factory
  34. create
  35. enumerate
  36. init_from_context
  37. get_externs_map
  38. param_info
  39. get_first_output
  40. get_output
  41. get_output_vector
  42. find_generator_param_by_name
  43. set_generator_and_schedule_param_values
  44. find_schedule_param_by_name
  45. set_inputs_vector
  46. track_parameter_values
  47. check_min_phase
  48. check_exact_phase
  49. advance_phase
  50. pre_generate
  51. post_generate
  52. pre_schedule
  53. post_schedule
  54. pre_build
  55. post_build
  56. get_pipeline
  57. build_module
  58. emit_cpp_stub
  59. check_scheduled
  60. check_input_is_singular
  61. check_input_is_array
  62. check_input_kind
  63. dimensions_
  64. array_size_defined
  65. array_size
  66. is_array
  67. name
  68. kind
  69. types_defined
  70. types
  71. type
  72. dimensions_defined
  73. dimensions
  74. funcs
  75. exprs
  76. verify_internals
  77. array_name
  78. check_matching_type_and_dim
  79. check_matching_array_size
  80. check_value_writable
  81. set_def_min_max
  82. init_parameters
  83. verify_internals
  84. init_internals
  85. set_inputs
  86. check_value_writable
  87. init_internals
  88. resize
  89. check_scheduled
  90. get_target
  91. generator_test
  92. generate
  93. schedule
  94. generate
  95. schedule
  96. build
  97. generate
  98. schedule
  99. generate
  100. schedule

#include <cmath>
#include <fstream>
#include <set>

#include "Generator.h"
#include "Outputs.h"
#include "Simplify.h"

namespace Halide {
namespace Internal {

namespace {

// Return true iff the name is valid for Generators or Params.
// (NOTE: gcc didn't add proper std::regex support until v4.9;
// we don't yet require this, hence the hand-rolled replacement.)

bool is_alpha(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }

// Note that this includes '_'
bool is_alnum(char c) { return is_alpha(c) || (c == '_') || (c >= '0' && c <= '9'); }

// Basically, a valid C identifier, except:
//
// -- initial _ is forbidden (rather than merely "reserved")
// -- two underscores in a row is also forbidden
bool is_valid_name(const std::string& n) {
    if (n.empty()) return false;
    if (!is_alpha(n[0])) return false;
    for (size_t i = 1; i < n.size(); ++i) {
        if (!is_alnum(n[i])) return false;
        if (n[i] == '_' && n[i-1] == '_') return false;
    }
    return true;
}

std::string compute_base_path(const std::string &output_dir,
                              const std::string &function_name,
                              const std::string &file_base_name) {
    std::vector<std::string> namespaces;
    std::string simple_name = extract_namespaces(function_name, namespaces);
    std::string base_path = output_dir + "/" + (file_base_name.empty() ? simple_name : file_base_name);
    return base_path;
}

std::string get_extension(const std::string& def, const GeneratorBase::EmitOptions &options) {
    auto it = options.substitutions.find(def);
    if (it != options.substitutions.end()) {
        return it->second;
    }
    return def;
}

Outputs compute_outputs(const Target &target,
                        const std::string &base_path,
                        const GeneratorBase::EmitOptions &options) {
    const bool is_windows_coff = target.os == Target::Windows &&
                                !target.has_feature(Target::MinGW);
    Outputs output_files;
    if (options.emit_o) {
        if (is_windows_coff) {
            // If it's windows, then we're emitting a COFF file
            output_files.object_name = base_path + get_extension(".obj", options);
        } else {
            // Otherwise it is an ELF or Mach-o
            output_files.object_name = base_path + get_extension(".o", options);
        }
    }
    if (options.emit_assembly) {
        output_files.assembly_name = base_path + get_extension(".s", options);
    }
    if (options.emit_bitcode) {
        // In this case, bitcode refers to the LLVM IR generated by Halide
        // and passed to LLVM.
        output_files.bitcode_name = base_path + get_extension(".bc", options);
    }
    if (options.emit_h) {
        output_files.c_header_name = base_path + get_extension(".h", options);
    }
    if (options.emit_cpp) {
        output_files.c_source_name = base_path + get_extension(".cpp", options);
    }
    if (options.emit_stmt) {
        output_files.stmt_name = base_path + get_extension(".stmt", options);
    }
    if (options.emit_stmt_html) {
        output_files.stmt_html_name = base_path + get_extension(".html", options);
    }
    if (options.emit_static_library) {
        if (is_windows_coff) {
            output_files.static_library_name = base_path + get_extension(".lib", options);
        } else {
            output_files.static_library_name = base_path + get_extension(".a", options);
        }
    }
    return output_files;
}

Argument to_argument(const Internal::Parameter &param) {
    Expr def, min, max;
    if (!param.is_buffer()) {
        def = param.get_scalar_expr();
        min = param.get_min_value();
        max = param.get_max_value();
    }
    return Argument(param.name(),
        param.is_buffer() ? Argument::InputBuffer : Argument::InputScalar,
        param.type(), param.dimensions(), def, min, max);
}

std::pair<int64_t, int64_t> rational_approximation_helper(double d, int max_depth) {
    const int64_t int_part = static_cast<int64_t>(std::floor(d));
    const double float_part = d - int_part;
    if (max_depth == 0 || float_part == 0.0) {
        return {int_part, 1};
    }

    const auto r = rational_approximation_helper(1.0/float_part, max_depth - 1);
    const int64_t num = r.second;
    const int64_t den = r.first;
    if (mul_would_overflow(64, int_part, den) ||
        add_would_overflow(64, num, int_part * den)) {
        return {0, 0};
    }

    return {num + int_part * den, den};
}
    
std::pair<int64_t, int64_t> rational_approximation(double d) {
    // Special-case non-finite numbers.
    if (std::isnan(d)) return {0, 0};

    const int64_t sign = (d < 0) ? -1 : 1;
    if (!std::isfinite(d)) return {sign, 0};

    d = std::abs(d);

    // The most accurate rationals to approximate a real come from
    // truncating its continued fraction representation.  We want the
    // largest continued fraction possible, but at some point they'll
    // overflow our rational type, and because they're evaluated
    // backwards it's not easy to stop at the point which will not
    // trigger overflow. Use binary search to find the right depth.
    std::pair<int64_t, int64_t> best {0, 0};
    int lo = 0, hi = 64; 
    while (lo + 1 < hi) {
        int mid = (lo + hi)/2;
        auto next = rational_approximation_helper(d, mid);
        if (next.first == 0 && next.second == 0) {
            hi = mid;
        } else {
            lo = mid;
            best = next;
        }
    }
    
    return {best.first * sign, best.second};
}

Func make_param_func(const Parameter &p, const std::string &name) {
    internal_assert(p.is_buffer());
    std::vector<Var> args;
    std::vector<Expr> args_expr;
    for (int i = 0; i < p.dimensions(); ++i) {
        Var v = Var::implicit(i);
        args.push_back(v);
        args_expr.push_back(v);
    }
    Func f = Func(name + "_im");
    f(args) = Internal::Call::make(p, args_expr);
    return f;
}

}  // namespace

std::vector<Type> parse_halide_type_list(const std::string &types) {
    const auto &e = get_halide_type_enum_map();
    std::vector<Type> result;
    for (auto t : split_string(types, ",")) {
        auto it = e.find(t);
        user_assert(it != e.end()) << "Type not found: " << t;
        result.push_back(it->second);
    }
    return result;
}

void ValueTracker::track_values(const std::string &name, const std::vector<Expr> &values) {
    std::vector<std::vector<Expr>> &history = values_history[name];
    if (history.empty()) {
        for (size_t i = 0; i < values.size(); ++i) {
            history.push_back({values[i]});
        }
        return;
    }

    internal_assert(history.size() == values.size())
        << "Expected values of size " << history.size()
        << " but saw size " << values.size()
        << " for name " << name << "\n";

    // For each item, see if we have a new unique value
    for (size_t i = 0; i < values.size(); ++i) {
        Expr oldval = history[i].back();
        Expr newval = values[i];
        if (oldval.defined() && newval.defined()) {
            if (can_prove(newval == oldval)) {
                continue;
            }
        } else if (!oldval.defined() && !newval.defined()) {
            // Expr::operator== doesn't work with undefined
            // values, but they are equal for our purposes here.
            continue;
        }
        history[i].push_back(newval);
        // If we exceed max_unique_values, fail immediately.
        // TODO: could be useful to log all the entries that
        // overflow max_unique_values before failing.
        // TODO: this could be more helpful about labeling the values
        // that have multiple setttings.
        if (history[i].size() > max_unique_values) {
            std::ostringstream o;
            o << "Saw too many unique values in ValueTracker[" + std::to_string(i) + "]; "
              << "expected a maximum of " << max_unique_values << ":\n";
            for (auto e : history[i]) {
                o << "    " << e << "\n";
            }
            user_error << o.str();
        }
    }
}

std::vector<Expr> parameter_constraints(const Parameter &p) {
    internal_assert(p.defined());
    std::vector<Expr> values;
    values.push_back(Expr(p.host_alignment()));
    if (p.is_buffer()) {
        for (int i = 0; i < p.dimensions(); ++i) {
            values.push_back(p.min_constraint(i));
            values.push_back(p.extent_constraint(i));
            values.push_back(p.stride_constraint(i));
        }
    } else {
        values.push_back(p.get_min_value());
        values.push_back(p.get_max_value());
    }
    return values;
}

class StubEmitter {
public:
    StubEmitter(std::ostream &dest, 
                const std::string &generator_name,
                const std::vector<Internal::GeneratorParamBase *>& generator_params,
                const std::vector<Internal::ScheduleParamBase *>& schedule_params,
                const std::vector<Internal::GeneratorInputBase *>& inputs,
                const std::vector<Internal::GeneratorOutputBase *>& outputs) 
        : stream(dest), 
          generator_name(generator_name), 
          generator_params(filter_params(generator_params)), 
          schedule_params(schedule_params), 
          inputs(inputs), 
          outputs(outputs) {
       namespaces = split_string(generator_name, "::");
       internal_assert(namespaces.size() >= 1);
       if (namespaces[0].empty()) {
           // We have a name like ::foo::bar::baz; omit the first empty ns.
           namespaces.erase(namespaces.begin());
           internal_assert(namespaces.size() >= 2);
       }
       class_name = namespaces.back();
       namespaces.pop_back();
    }

    void emit();
private:
    std::ostream &stream;
    const std::string generator_name;
    std::string class_name;
    std::vector<std::string> namespaces;
    const std::vector<Internal::GeneratorParamBase *> generator_params;
    const std::vector<Internal::GeneratorParamBase *> schedule_params_old;
    const std::vector<Internal::ScheduleParamBase *> schedule_params;
    const std::vector<Internal::GeneratorInputBase *> inputs;
    const std::vector<Internal::GeneratorOutputBase *> outputs;
    int indent_level{0};

    std::vector<Internal::GeneratorParamBase *> filter_params(const std::vector<Internal::GeneratorParamBase *> &in) {
        std::vector<Internal::GeneratorParamBase *> out;
        for (auto p : in) {
            if (p->name == "target") continue;
            if (p->is_synthetic_param()) continue;
            out.push_back(p);
        }
        return out;
    }

    /** Emit spaces according to the current indentation level */
    std::string indent();

    void emit_inputs_struct();
    void emit_generator_params_struct();
    void emit_schedule_params_setters();
};

std::string StubEmitter::indent() {
    std::ostringstream o;
    for (int i = 0; i < indent_level; i++) {
        o << "  ";
    }
    return o.str();
}

void StubEmitter::emit_generator_params_struct() {
    const auto &v = generator_params;
    std::string name = "GeneratorParams";
    stream << indent() << "struct " << name << " final {\n";
    indent_level++;
    if (!v.empty()) {
        for (auto p : v) {
            stream << indent() << p->get_c_type() << " " << p->name << "{ " << p->get_default_value() << " };\n";
        }
        stream << "\n";
    }

    stream << indent() << name << "() {}\n";
    stream << "\n";

    if (!v.empty()) {
        stream << indent() << name << "(\n";
        indent_level++;
        std::string comma = "";
        for (auto p : v) {
            stream << indent() << comma << p->get_c_type() << " " << p->name << "\n";
            comma = ", ";
        }
        indent_level--;
        stream << indent() << ") : \n";
        indent_level++;
        comma = "";
        for (auto p : v) {
            stream << indent() << comma << p->name << "(" << p->name << ")\n";
            comma = ", ";
        }
        indent_level--;
        stream << indent() << "{\n";
        stream << indent() << "}\n";
        stream << "\n";
    }

    stream << indent() << "inline NO_INLINE std::map<std::string, std::string> to_string_map() const {\n";
    indent_level++;
    stream << indent() << "std::map<std::string, std::string> m;\n";
    for (auto p : v) {
        stream << indent() << "if (" << p->name << " != " << p->get_default_value() << ") "
                        << "m[\"" << p->name << "\"] = " << p->call_to_string(p->name) << ";\n";
    }
    stream << indent() << "return m;\n";
    indent_level--;
    stream << indent() << "}\n";

    indent_level--;
    stream << indent() << "};\n";
    stream << "\n";
}

void StubEmitter::emit_schedule_params_setters() {
    stream << indent() << "// set_schedule_param methods\n";
    stream << indent() << "template <typename T>\n";
    stream << indent() << class_name << " &set_schedule_param(const std::string &name, const T &value) {\n";
    indent_level++;
    stream << indent() << "(void) GeneratorStub::set_schedule_param(name, value);\n";
    stream << indent() << "return *this;\n";
    indent_level--;
    stream << indent() << "}\n";

    // TODO: do we still want these, now that we have ScheduleParams replicated?
    // for (auto *sp : schedule_params) {
    //     std::string c_type = sp->is_looplevel_param() ? "LoopLevel" : halide_type_to_c_type(sp->scalar_type());
    //     stream << indent() << class_name << " &set_" << sp->name() << "(const " << c_type << " &value) {\n";
    //     indent_level++;
    //     stream << indent() << "return set_schedule_param(\"" << sp->name() <<  "\", value);\n";
    //     indent_level--;
    //     stream << indent() << "}\n";
    // }
    stream << "\n";
}

void StubEmitter::emit_inputs_struct() {
    struct InInfo {
        std::string c_type;
        std::string name;
    };
    std::vector<InInfo> in_info;
    for (auto input : inputs) {
        std::string c_type = input->get_c_type();
        if (input->is_array()) {
            c_type = "std::vector<" + c_type + ">";
        }
        in_info.push_back({c_type, input->name()});
    }

    const std::string name = "Inputs";
    stream << indent() << "struct " << name << " final {\n";
    indent_level++;
    for (auto in : in_info) {
        stream << indent() << in.c_type << " " << in.name << ";\n";
    }
    stream << "\n";

    stream << indent() << name << "() {}\n";
    stream << "\n";

    stream << indent() << name << "(\n";
    indent_level++;
    std::string comma = "";
    for (auto in : in_info) {
        stream << indent() << comma << "const " << in.c_type << "& " << in.name << "\n";
        comma = ", ";
    }
    indent_level--;
    stream << indent() << ") : \n";
    indent_level++;
    comma = "";
    for (auto in : in_info) {
        stream << indent() << comma << in.name << "(" << in.name << ")\n";
        comma = ", ";
    }
    indent_level--;
    stream << indent() << "{\n";
    stream << indent() << "}\n";

    indent_level--;
    stream << indent() << "};\n";
    stream << "\n";
}

void StubEmitter::emit() {
    if (outputs.empty()) {
        // The generator can't support a real stub. Instead, generate an (essentially) 
        // empty .stub.h file, so that build systems like Bazel will still get the output file
        // they expected. Note that we deliberately don't emit an ifndef header guard,
        // since we can't reliably assume that the generator_name will be globally unique;
        // on the other hand, since this file is just a couple of comments, it's
        // really not an issue if it's included multiple times.
        stream << "/* MACHINE-GENERATED - DO NOT EDIT */\n";
        stream << "/* The Generator named " << generator_name << " uses ImageParam or Param, thus cannot have a Stub generated. */\n";
        return;
    }

    struct OutputInfo {
        std::string name;
        std::string ctype;
        std::string getter;
    };
    std::vector<OutputInfo> out_info;
    for (auto output : outputs) {
        std::string c_type = output->get_c_type();
        std::string getter;
        if (output->is_array()) getter = "get_output_vector";
        else if (c_type == "Func") getter = "get_output";
        else getter = "get_output_buffer<" + c_type + ">";
        out_info.push_back({
            output->name(),
            output->is_array() ? "std::vector<" + c_type + ">" : c_type,
            getter + "(\"" + output->name() + "\")"
        });
    }

    std::ostringstream guard;
    guard << "HALIDE_STUB";
    for (const auto &ns : namespaces) {
        guard << "_" << ns;
    }
    guard << "_" << class_name;

    stream << indent() << "#ifndef " << guard.str() << "\n";
    stream << indent() << "#define " << guard.str() << "\n";
    stream << "\n";

    stream << indent() << "/* MACHINE-GENERATED - DO NOT EDIT */\n";
    stream << "\n";

    stream << indent() << "#include <cassert>\n";
    stream << indent() << "#include <map>\n";
    stream << indent() << "#include <memory>\n";
    stream << indent() << "#include <string>\n";
    stream << indent() << "#include <utility>\n";
    stream << indent() << "#include <vector>\n";
    stream << "\n";
    stream << indent() << "#include \"Halide.h\"\n";
    stream << "\n";

    for (const auto &ns : namespaces) {
        stream << indent() << "namespace " << ns << " {\n";
    }
    stream << "\n";

    for (auto p : generator_params) {
        std::string decl = p->get_type_decls();
        if (decl.empty()) continue;
        stream << decl << "\n";
    }

    stream << indent() << "class " << class_name << " final : public Halide::Internal::GeneratorStub {\n";
    stream << indent() << "public:\n";
    indent_level++;

    emit_inputs_struct();
    emit_generator_params_struct();

    if (!schedule_params.empty()) {
        stream << indent() << "NO_INLINE " << class_name << "() :\n";
        indent_level++;
        for (auto *sp : schedule_params) {
            std::string comma = sp != schedule_params.back() ? "," : "";
            stream << indent() << sp->name() << "(\"" << sp->name() << "\")" << comma << "\n";
        }
        indent_level--;
        stream << indent() << "{}\n";

    } else {
        stream << indent() << class_name << "() {}\n";
    }
    stream << "\n";

    stream << indent() << "NO_INLINE " << class_name << "(\n";
    indent_level++;
    stream << indent() << "const GeneratorContext& context,\n";
    stream << indent() << "const Inputs& inputs,\n";
    stream << indent() << "const GeneratorParams& params = GeneratorParams()\n";
    indent_level--;
    stream << indent() << ")\n";
    indent_level++;
    stream << indent() << ": GeneratorStub(context, &factory, params.to_string_map(), {\n";
    indent_level++;
    for (size_t i = 0; i < inputs.size(); ++i) {
        stream << indent() << "to_stub_input_vector(inputs." << inputs[i]->name() << ")";
        stream << ",\n";
    }
    indent_level--;
    stream << indent() << "})\n";
    for (auto *sp : schedule_params) {
        stream << indent() << ", " << sp->name() << "(get_schedule_param(\"" << sp->name() << "\"))\n";
    }
    for (const auto &out : out_info) {
        stream << indent() << ", " << out.name << "(" << out.getter << ")\n";
    }
    indent_level--;
    stream << indent() << "{\n";
    stream << indent() << "}\n";
    stream << "\n";

    stream << indent() << "// delegating ctor to allow GeneratorContext-pointer\n";
    stream << indent() << class_name << "(\n";
    indent_level++;
    stream << indent() << "const GeneratorContext* context,\n";
    stream << indent() << "const Inputs& inputs,\n";
    stream << indent() << "const GeneratorParams& params = GeneratorParams()\n";
    indent_level--;
    stream << indent() << ")\n";
    indent_level++;
    stream << indent() << ": " << class_name << "(*context, inputs, params) {}\n";
    stream << "\n";
    indent_level--;

    if (!generator_params.empty()) {
        stream << indent() << "// templated construction method with inputs\n";
        stream << indent() << "template<\n";
        std::string comma = "";
        indent_level++;
        for (auto p : generator_params) {
            std::string type = p->get_template_type();
            std::string value = p->get_template_value();
            if (type == "float" || type == "double") {
                // floats and doubles can't be used as template value arguments;
                // it turns out to be pretty uncommon use floating point types
                // in GeneratorParams, but to avoid breaking these cases entirely, 
                // use std::ratio as an approximation for the default value.
                auto ratio = rational_approximation(std::atof(value.c_str()));
                stream << indent() << comma << "typename" << " " << p->name << " = std::ratio<" << ratio.first << ", " << ratio.second << ">\n";
            } else {
                stream << indent() << comma << type << " " << p->name << " = " << value << "\n";
            }
            comma = ", ";
        }
        indent_level--;
        stream << indent() << ">\n";
        stream << indent() << "static " << class_name << " make(const GeneratorContext& context, const Inputs& inputs) {\n";
        indent_level++;
        stream << indent() << "GeneratorParams gp(\n";
        indent_level++;
        comma = "";
        for (auto p : generator_params) {
            std::string type = p->get_template_type();
            if (type == "typename") {
                stream << indent() << comma << "Halide::type_of<" << p->name << ">()\n";
            } else if (type == "float" || type == "double") {
                stream << indent() << comma << "ratio_to_double<" << p->name << ">()\n";
            } else {
                stream << indent() << comma << p->name << "\n";
            }
            comma = ", ";
        }
        indent_level--;
        stream << indent() << ");\n";
        stream << indent() << "return " << class_name << "(context, inputs, gp);\n";
        indent_level--;
        stream << indent() << "}\n";
        stream << "\n";
    }

    stream << indent() << "// schedule method\n";
    stream << indent() << class_name << " &schedule() {\n";
    indent_level++;
    stream << indent() << "(void) GeneratorStub::schedule();\n";
    stream << indent() << "return *this;\n";
    indent_level--;
    stream << indent() << "}\n";
    stream << "\n";

    emit_schedule_params_setters();

    stream << indent() << "// move constructor\n";
    stream << indent() << class_name << "("<< class_name << "&& that)\n";
    indent_level++;
    stream << indent() << ": GeneratorStub(std::move(that))\n";
    for (auto *sp : schedule_params) {
        stream << indent() << ", " << sp->name() << "(std::move(that." << sp->name() << "))\n";
    }
    for (const auto &out : out_info) {
        stream << indent() << ", " << out.name << "(std::move(that." << out.name << "))\n";
    }
    indent_level--;
    stream << indent() << "{\n";
    stream << indent() << "}\n";
    stream << "\n";

    stream << indent() << "// move assignment operator\n";
    stream << indent() << class_name << "& operator=("<< class_name << "&& that) {\n";
    indent_level++;
    stream << indent() << "GeneratorStub::operator=(std::move(that));\n";
    for (auto *sp : schedule_params) {
        stream << indent() << sp->name() << " = std::move(that." << sp->name() << ");\n";
    }
    for (const auto &out : out_info) {
        stream << indent() << out.name << " = std::move(that." << out.name << ");\n";
    }
    stream << indent() << "return *this;\n";
    indent_level--;
    stream << indent() << "}\n";
    stream << "\n";

    stream << indent() << "// ScheduleParams(s)\n";
    for (auto *sp : schedule_params) {
        std::string c_type = sp->is_looplevel_param() ? "LoopLevel" : halide_type_to_c_type(sp->scalar_type());
        stream << indent() << "ScheduleParam<" << c_type << "> " << sp->name() << ";\n";
    }
    stream << "\n";

    stream << indent() << "// Output(s)\n";
    stream << indent() << "// TODO: identify vars used\n";
    for (const auto &out : out_info) {
        stream << indent() << out.ctype << " " << out.name << ";\n";
    }
    stream << "\n";

    stream << indent() << "~" << class_name << "() { if (has_generator()) verify(); }\n";
    stream << "\n";

    indent_level--;
    stream << indent() << "protected:\n";
    indent_level++;
    stream << indent() << "NO_INLINE void verify() {\n";
    indent_level++;
    for (const auto &out : out_info) {
        stream << indent() << "verify_same_funcs(" << out.name << ", " << out.getter << ");\n";
    }
    indent_level--;
    stream << indent() << "}\n";
    stream << "\n";

    indent_level--;
    stream << indent() << "private:\n";
    indent_level++;
    stream << indent() << "static std::unique_ptr<Halide::Internal::GeneratorBase> factory(const GeneratorContext& context, const std::map<std::string, std::string>& params) {\n";
    indent_level++;
    stream << indent() << "return Halide::Internal::GeneratorRegistry::create(\"" << generator_name << "\", context, params);\n";
    indent_level--;
    stream << indent() << "};\n";
    stream << "\n";

    indent_level--;
    stream << indent() << "};\n";
    stream << "\n";

    for (int i = (int)namespaces.size() - 1; i >= 0 ; --i) {
        stream << indent() << "}  // namespace " << namespaces[i] << "\n";
    }
    stream << "\n";

    stream << indent() << "#endif  // " << guard.str() << "\n";
}

GeneratorStub::GeneratorStub(const GeneratorContext &context,
                             GeneratorFactory generator_factory,
                             const std::map<std::string, std::string> &generator_params,
                             const std::vector<std::vector<Internal::StubInput>> &inputs)
    : generator(generator_factory(context, generator_params)) {
    generator->externs_map = context.get_externs_map();
    generator->set_inputs_vector(inputs);
    generator->call_generate();
}

void GeneratorStub::verify_same_funcs(const Func &a, const Func &b) {
    user_assert(a.function().get_contents().same_as(b.function().get_contents())) 
        << "Expected Func " << a.name() << " and " << b.name() << " to match.\n";
}

void GeneratorStub::verify_same_funcs(const std::vector<Func>& a, const std::vector<Func>& b) {
    user_assert(a.size() == b.size()) << "Mismatch in Function vector length.\n";
    for (size_t i = 0; i < a.size(); ++i) {
        verify_same_funcs(a[i], b[i]);
    }
}

const std::map<std::string, Type> &get_halide_type_enum_map() {
    static const std::map<std::string, Type> halide_type_enum_map{
        {"bool", Bool()},
        {"int8", Int(8)},
        {"int16", Int(16)},
        {"int32", Int(32)},
        {"uint8", UInt(8)},
        {"uint16", UInt(16)},
        {"uint32", UInt(32)},
        {"float32", Float(32)},
        {"float64", Float(64)}
    };
    return halide_type_enum_map;
}

std::string halide_type_to_c_source(const Type &t) {
    static const std::map<halide_type_code_t, std::string> m = {
        { halide_type_int, "Int" },
        { halide_type_uint, "UInt" },
        { halide_type_float, "Float" },
        { halide_type_handle, "Handle" },
    };
    std::ostringstream oss;
    oss << "Halide::" << m.at(t.code()) << "(" << t.bits() << + ")";
    return oss.str();
}

std::string halide_type_to_c_type(const Type &t) {
    auto encode = [](const Type &t) -> int { return t.code() << 16 | t.bits(); };
    static const std::map<int, std::string> m = {
        { encode(Int(8)), "int8_t" },
        { encode(Int(16)), "int16_t" },
        { encode(Int(32)), "int32_t" },
        { encode(Int(64)), "int64_t" },
        { encode(UInt(1)), "bool" },
        { encode(UInt(8)), "uint8_t" },
        { encode(UInt(16)), "uint16_t" },
        { encode(UInt(32)), "uint32_t" },
        { encode(UInt(64)), "uint64_t" },
        { encode(Float(32)), "float" },
        { encode(Float(64)), "double" },
        { encode(Handle(64)), "void*" }
    };
    internal_assert(m.count(encode(t))) << t << " " << encode(t);
    return m.at(encode(t));
}

const std::map<std::string, LoopLevel> &get_halide_looplevel_enum_map() {
    static const std::map<std::string, LoopLevel> halide_looplevel_enum_map{
        {"root", LoopLevel::root()},
        {"inline", LoopLevel::inlined()},
    };
    return halide_looplevel_enum_map;
}

int generate_filter_main(int argc, char **argv, std::ostream &cerr) {
    const char kUsage[] = "gengen [-g GENERATOR_NAME] [-f FUNCTION_NAME] [-o OUTPUT_DIR] [-r RUNTIME_NAME] [-e EMIT_OPTIONS] [-x EXTENSION_OPTIONS] [-n FILE_BASE_NAME] "
                          "target=target-string[,target-string...] [generator_arg=value [...]]\n\n"
                          "  -e  A comma separated list of files to emit. Accepted values are "
                          "[assembly, bitcode, cpp, h, html, o, static_library, stmt, cpp_stub]. If omitted, default value is [static_library, h].\n"
                          "  -x  A comma separated list of file extension pairs to substitute during file naming, "
                          "in the form [.old=.new[,.old2=.new2]]\n";

    std::map<std::string, std::string> flags_info = { { "-f", "" },
                                                      { "-g", "" },
                                                      { "-o", "" },
                                                      { "-e", "" },
                                                      { "-n", "" },
                                                      { "-x", "" },
                                                      { "-r", "" }};
    std::map<std::string, std::string> generator_args;

    for (int i = 1; i < argc; ++i) {
        if (argv[i][0] != '-') {
            std::vector<std::string> v = split_string(argv[i], "=");
            if (v.size() != 2 || v[0].empty() || v[1].empty()) {
                cerr << kUsage;
                return 1;
            }
            generator_args[v[0]] = v[1];
            continue;
        }
        auto it = flags_info.find(argv[i]);
        if (it != flags_info.end()) {
            if (i + 1 >= argc) {
                cerr << kUsage;
                return 1;
            }
            it->second = argv[i + 1];
            ++i;
            continue;
        }
        cerr << "Unknown flag: " << argv[i] << "\n";
        cerr << kUsage;
        return 1;
    }

    std::string runtime_name = flags_info["-r"];

    std::vector<std::string> generator_names = GeneratorRegistry::enumerate();
    if (generator_names.size() == 0 && runtime_name.empty()) {
        cerr << "No generators have been registered and not compiling a standalone runtime\n";
        cerr << kUsage;
        return 1;
    }

    std::string generator_name = flags_info["-g"];
    if (generator_name.empty() && runtime_name.empty()) {
        // If -g isn't specified, but there's only one generator registered, just use that one.
        if (generator_names.size() > 1) {
            cerr << "-g must be specified if multiple generators are registered:\n";
            for (auto name : generator_names) {
                cerr << "    " << name << "\n";
            }
            cerr << kUsage;
            return 1;
        }
        generator_name = generator_names[0];
    }
    std::string function_name = flags_info["-f"];
    if (function_name.empty()) {
        // If -f isn't specified, assume function name = generator name.
        function_name = generator_name;
    }
    std::string output_dir = flags_info["-o"];
    if (output_dir.empty()) {
        cerr << "-o must always be specified.\n";
        cerr << kUsage;
        return 1;
    }

    // It's ok to omit "target=" if we are generating *only* a cpp_stub
    const std::vector<std::string> emit_flags = split_string(flags_info["-e"], ",");
    const bool stub_only = (emit_flags.size() == 1 && emit_flags[0] == "cpp_stub");
    if (!stub_only) {
        if (generator_args.find("target") == generator_args.end()) {
            cerr << "Target missing\n";
            cerr << kUsage;
            return 1;
        }
    }

    // it's OK for file_base_name to be empty: filename will be based on function name
    std::string file_base_name = flags_info["-n"];

    GeneratorBase::EmitOptions emit_options;
    // Ensure all flags start as false.
    emit_options.emit_static_library = emit_options.emit_h = false;

    if (emit_flags.empty() || (emit_flags.size() == 1 && emit_flags[0].empty())) {
        // If omitted or empty, assume .a and .h
        emit_options.emit_static_library = emit_options.emit_h = true;
    } else {
        // If anything specified, only emit what is enumerated
        for (const std::string &opt : emit_flags) {
            if (opt == "assembly") {
                emit_options.emit_assembly = true;
            } else if (opt == "bitcode") {
                emit_options.emit_bitcode = true;
            } else if (opt == "stmt") {
                emit_options.emit_stmt = true;
            } else if (opt == "html") {
                emit_options.emit_stmt_html = true;
            } else if (opt == "cpp") {
                emit_options.emit_cpp = true;
            } else if (opt == "o") {
                emit_options.emit_o = true;
            } else if (opt == "h") {
                emit_options.emit_h = true;
            } else if (opt == "static_library") {
                emit_options.emit_static_library = true;
            } else if (opt == "cpp_stub") {
                emit_options.emit_cpp_stub = true;
            } else if (!opt.empty()) {
                cerr << "Unrecognized emit option: " << opt
                     << " not one of [assembly, bitcode, cpp, h, html, o, static_library, stmt, cpp_stub], ignoring.\n";
            }
        }
    }

    auto substitution_flags = split_string(flags_info["-x"], ",");
    for (const std::string &x : substitution_flags) {
        if (x.empty()) {
            continue;
        }
        auto subst_pair = split_string(x, "=");
        if (subst_pair.size() != 2) {
            cerr << "Malformed -x option: " << x << "\n";
            cerr << kUsage;
            return 1;
        }
        emit_options.substitutions[subst_pair[0]] = subst_pair[1];
    }

    const auto target_string = generator_args["target"];
    auto target_strings = split_string(target_string, ",");
    std::vector<Target> targets;
    for (const auto &s : target_strings) {
        targets.push_back(Target(s));
    }

    if (!runtime_name.empty()) {
        if (targets.size() != 1) {
            cerr << "Only one target allowed here";
            return 1;
        }
        std::string base_path = compute_base_path(output_dir, runtime_name, "");
        Outputs output_files = compute_outputs(targets[0], base_path, emit_options);
        compile_standalone_runtime(output_files, targets[0]);
    }

    if (!generator_name.empty()) {
        std::string base_path = compute_base_path(output_dir, function_name, file_base_name);
        debug(1) << "Generator " << generator_name << " has base_path " << base_path << "\n";
        if (emit_options.emit_cpp_stub) {
            // When generating cpp_stub, we ignore all generator args passed in, and supply a fake Target.
            // (Note that "JITGeneratorContext" is misleading, since we're actually doing AOT here, but it does exactly what we need)
            auto gen = GeneratorRegistry::create(generator_name, JITGeneratorContext(Target()), {});
            auto stub_file_path = base_path + get_extension(".stub.h", emit_options);
            gen->emit_cpp_stub(stub_file_path);
        }

        // Don't bother with this if we're just emitting a cpp_stub.
        if (!stub_only) {
            Outputs output_files = compute_outputs(targets[0], base_path, emit_options);
            auto module_producer = [&generator_name, &generator_args]
                (const std::string &name, const Target &target) -> Module {
                    auto sub_generator_args = generator_args;
                    sub_generator_args.erase("target");
                    // Must re-create each time since each instance will have a different Target.
                    // (Note that "JITGeneratorContext" is misleading, since we're actually doing AOT here, but it does exactly what we need)
                    auto gen = GeneratorRegistry::create(generator_name, JITGeneratorContext(target), sub_generator_args);
                    return gen->build_module(name);
                };
            if (targets.size() > 1 || !emit_options.substitutions.empty()) {
                compile_multitarget(function_name, output_files, targets, module_producer, emit_options.substitutions);
            } else {
                user_assert(emit_options.substitutions.empty()) << "substitutions not supported for single-target";
                // compile_multitarget() will fail if we request anything but library and/or header,
                // so defer directly to Module::compile if there is a single target.
                module_producer(function_name, targets[0]).compile(output_files);
            }
        }
    }

    return 0;
}

GeneratorParamBase::GeneratorParamBase(const std::string &name) : name(name) {
    ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorParam,
                                              this, nullptr);
}

GeneratorParamBase::~GeneratorParamBase() { ObjectInstanceRegistry::unregister_instance(this); }

void GeneratorParamBase::check_value_readable() const {
    user_assert(generator && generator->phase >= GeneratorBase::GenerateCalled)  << "The GeneratorParam " << name << " cannot be read before build() or generate() is called.\n";
}

void GeneratorParamBase::check_value_writable() const {
    // Allow writing when no Generator is set, to avoid having to special-case ctor initing code
    if (!generator) return;
    // Special-case for legacy: allow 'target' to be writable. Yikes.
    if (name == "target") return;
    user_assert(generator->phase < GeneratorBase::GenerateCalled)  << "The GeneratorParam " << name << " cannot be written after build() or generate() is called.\n";
}

void GeneratorParamBase::fail_wrong_type(const char *type) {
    user_error << "The GeneratorParam " << name << " cannot be set with a value of type " << type << ".\n";
}

/* static */
GeneratorRegistry &GeneratorRegistry::get_registry() {
    static GeneratorRegistry *registry = new GeneratorRegistry;
    return *registry;
}

/* static */
void GeneratorRegistry::register_factory(const std::string &name,
                                         std::unique_ptr<GeneratorFactory> factory) {
    for (auto n : split_string(name, "::")) {
        user_assert(is_valid_name(n)) << "Invalid Generator name part: " << n;
    }
    GeneratorRegistry &registry = get_registry();
    std::lock_guard<std::mutex> lock(registry.mutex);
    internal_assert(registry.factories.find(name) == registry.factories.end())
        << "Duplicate Generator name: " << name;
    registry.factories[name] = std::move(factory);
}

/* static */
void GeneratorRegistry::unregister_factory(const std::string &name) {
    GeneratorRegistry &registry = get_registry();
    std::lock_guard<std::mutex> lock(registry.mutex);
    internal_assert(registry.factories.find(name) != registry.factories.end())
        << "Generator not found: " << name;
    registry.factories.erase(name);
}

/* static */
std::unique_ptr<GeneratorBase> GeneratorRegistry::create(const std::string &name,
                                                         const GeneratorContext &context,
                                                         const std::map<std::string, std::string> &params) {
    GeneratorRegistry &registry = get_registry();
    std::lock_guard<std::mutex> lock(registry.mutex);
    auto it = registry.factories.find(name);
    if (it == registry.factories.end()) {
        std::ostringstream o;
        o << "Generator not found: " << name << "\n";
        o << "Did you mean:\n";
        for (const auto &n : registry.factories) {
            o << "    " << n.first << "\n";
        }
        user_error << o.str();
    }
    std::unique_ptr<GeneratorBase> g = it->second->create(context, params);
    internal_assert(g != nullptr);
    return g;
}

/* static */
std::vector<std::string> GeneratorRegistry::enumerate() {
    GeneratorRegistry &registry = get_registry();
    std::lock_guard<std::mutex> lock(registry.mutex);
    std::vector<std::string> result;
    for (const auto& i : registry.factories) {
        result.push_back(i.first);
    }
    return result;
}

GeneratorBase::GeneratorBase(size_t size, const void *introspection_helper) 
    : size(size) {
    ObjectInstanceRegistry::register_instance(this, size, ObjectInstanceRegistry::Generator, this, introspection_helper);
}

void GeneratorBase::init_from_context(const Halide::GeneratorContext &context) {
    target.set(context.get_target());
    value_tracker = context.get_value_tracker();
}

GeneratorBase::~GeneratorBase() { 
    ObjectInstanceRegistry::unregister_instance(this); 
}

std::shared_ptr<GeneratorContext::ExternsMap> GeneratorBase::get_externs_map() const {
    // Lazily create the ExternsMap.
    if (externs_map == nullptr) {
        externs_map = std::make_shared<ExternsMap>();
    }
    return externs_map;
}

GeneratorBase::ParamInfo::ParamInfo(GeneratorBase *generator, const size_t size) {
    std::set<std::string> names;
    std::vector<void *> vf = ObjectInstanceRegistry::instances_in_range(
        generator, size, ObjectInstanceRegistry::FilterParam);
    for (auto v : vf) {
        auto param = static_cast<Parameter *>(v);
        internal_assert(param != nullptr);
        user_assert(param->is_explicit_name()) << "Params in Generators must have explicit names: " << param->name();
        user_assert(is_valid_name(param->name())) << "Invalid Param name: " << param->name();
        user_assert(!names.count(param->name())) << "Duplicate Param name: " << param->name();
        names.insert(param->name());
        filter_params.push_back(param);
    }

    const auto add_synthetic_params = [this](GIOBase *gio) {
        if (!gio->allow_synthetic_generator_params()) {
            return;
        }
        const std::string &n = gio->name();
        if (gio->kind() != IOKind::Scalar) {
            owned_synthetic_params.emplace_back(new GeneratorParam_Synthetic<Type>(n + ".type", *gio, GeneratorParam_Synthetic<Type>::Type));
            generator_params.push_back(owned_synthetic_params.back().get());
            owned_synthetic_params.emplace_back(new GeneratorParam_Synthetic<int>(n + ".dim", *gio, GeneratorParam_Synthetic<int>::Dim));
            generator_params.push_back(owned_synthetic_params.back().get());
        }
        if (gio->is_array()) {
            owned_synthetic_params.emplace_back(new GeneratorParam_Synthetic<size_t>(n + ".size", *gio, GeneratorParam_Synthetic<size_t>::ArraySize));
            generator_params.push_back(owned_synthetic_params.back().get());
        }
    };

    std::vector<void *> vi = ObjectInstanceRegistry::instances_in_range(
        generator, size, ObjectInstanceRegistry::GeneratorInput);
    for (auto v : vi) {
        auto input = static_cast<Internal::GeneratorInputBase *>(v);
        internal_assert(input != nullptr);
        user_assert(is_valid_name(input->name())) << "Invalid Input name: (" << input->name() << ")\n";
        user_assert(!names.count(input->name())) << "Duplicate Input name: " << input->name();
        names.insert(input->name());
        internal_assert(input->generator == nullptr || input->generator == generator);
        input->generator = generator;
        filter_inputs.push_back(input);
        add_synthetic_params(input);
    }

    std::vector<void *> vo = ObjectInstanceRegistry::instances_in_range(
        generator, size, ObjectInstanceRegistry::GeneratorOutput);
    for (auto v : vo) {
        auto output = static_cast<Internal::GeneratorOutputBase *>(v);
        internal_assert(output != nullptr);
        user_assert(is_valid_name(output->name())) << "Invalid Output name: (" << output->name() << ")\n";
        user_assert(!names.count(output->name())) << "Duplicate Output name: " << output->name();
        names.insert(output->name());
        internal_assert(output->generator == nullptr || output->generator == generator);
        output->generator = generator;
        filter_outputs.push_back(output);
        add_synthetic_params(output);
    }

    if (filter_params.size() > 0 && filter_inputs.size() > 0) {
        user_error << "Input<> may not be used with Param<> or ImageParam in Generators.\n";
    }

    if (filter_params.size() > 0 && filter_outputs.size() > 0) {
        user_error << "Output<> may not be used with Param<> or ImageParam in Generators.\n";
    }

    if (filter_inputs.size() > 0 && filter_outputs.size() == 0) {
        // This doesn't catch *every* possibility (since a Generator can have zero Inputs).
        user_error << "Output<> must be used with Input<> in Generators.\n";
    }

    std::vector<void *> vg = ObjectInstanceRegistry::instances_in_range(
        generator, size, ObjectInstanceRegistry::GeneratorParam);
    for (auto v : vg) {
        auto param = static_cast<GeneratorParamBase *>(v);
        internal_assert(param != nullptr);
        user_assert(is_valid_name(param->name)) << "Invalid GeneratorParam name: " << param->name;
        user_assert(!names.count(param->name)) << "Duplicate GeneratorParam name: " << param->name;
        names.insert(param->name);
        internal_assert(param->generator == nullptr || param->generator == generator);
        param->generator = generator;
        generator_params.push_back(param);
    }

    // Do in separate loop so that synthetic params are also included
    for (auto *g : generator_params) {
        generator_params_by_name[g->name] = g;
    }

    for (auto &g : owned_synthetic_params) {
        g->generator = generator;
    }

    std::vector<void *> vs = ObjectInstanceRegistry::instances_in_range(
        generator, size, ObjectInstanceRegistry::ScheduleParam);
    for (auto v : vs) {
        auto *param = static_cast<ScheduleParamBase*>(v);
        internal_assert(param != nullptr);
        user_assert(!param->name().empty()) << "ScheduleParams must have explicit names when used in Generators.";
        user_assert(is_valid_name(param->name())) << "Invalid ScheduleParam name: " << param->name();
        user_assert(!names.count(param->name())) << "Duplicate ScheduleParam name: " << param->name();
        names.insert(param->name());
        schedule_params.push_back(param);
        schedule_params_by_name[param->name()] = param;
    }
}

GeneratorBase::ParamInfo &GeneratorBase::param_info() {
    if (!param_info_ptr) {
        param_info_ptr.reset(new ParamInfo(this, size));
    }
    return *param_info_ptr;
}

Func GeneratorBase::get_first_output() {
    ParamInfo &pi = param_info();
    return get_output(pi.filter_outputs[0]->name());
}

Func GeneratorBase::get_output(const std::string &n) {
    check_min_phase(GenerateCalled);
    // There usually are very few outputs, so a linear search is fine
    ParamInfo &pi = param_info();
    for (auto output : pi.filter_outputs) {
        if (output->name() == n) {
            user_assert(output->array_size_defined()) << "Output " << n << " has no ArraySize defined.\n";
            user_assert(!output->is_array() && output->funcs().size() == 1) << "Output " << n << " must be accessed via get_output_vector()\n";
            Func f = output->funcs().at(0);
            user_assert(f.defined()) << "Output " << n << " was not defined.\n";
            return f;
        }
    }
    internal_error << "Output " << n << " not found.\n";
    return Func();
}

std::vector<Func> GeneratorBase::get_output_vector(const std::string &n) {
    check_min_phase(GenerateCalled);
    // There usually are very few outputs, so a linear search is fine
    ParamInfo &pi = param_info();
    for (auto output : pi.filter_outputs) {
        if (output->name() == n) {
            user_assert(output->array_size_defined()) << "Output " << n << " has no ArraySize defined.\n";
            for (const auto &f : output->funcs()) {
                user_assert(f.defined()) << "Output " << n << " was not fully defined.\n";
            }
            return output->funcs();
        }
    }
    internal_error << "Output " << n << " not found.\n";
    return {};
}

Internal::GeneratorParamBase &GeneratorBase::find_generator_param_by_name(const std::string &name) {
    ParamInfo &pi = param_info();
    auto it = pi.generator_params_by_name.find(name);
    user_assert(it != pi.generator_params_by_name.end()) << "Generator has no GeneratorParam named: " << name << "\n";
    internal_assert(it->second != nullptr);
    return *it->second;
}

void GeneratorBase::set_generator_and_schedule_param_values(const std::map<std::string, std::string> &params) {
    ParamInfo &pi = param_info();
    for (auto &key_value : params) {
        auto gp = pi.generator_params_by_name.find(key_value.first);
        if (gp != pi.generator_params_by_name.end()) {
            gp->second->set_from_string(key_value.second);
            continue;
        }
        auto sp = pi.schedule_params_by_name.find(key_value.first);
        if (sp != pi.schedule_params_by_name.end()) {
            sp->second->set_from_string(key_value.second);
            continue;
        }
        user_error << "Generator has no GeneratorParam or ScheduleParam named: " << key_value.first << "\n";
    }
}

Internal::ScheduleParamBase &GeneratorBase::find_schedule_param_by_name(const std::string &name) {
    ParamInfo &pi = param_info();
    auto it = pi.schedule_params_by_name.find(name);
    user_assert(it != pi.schedule_params_by_name.end()) << "Generator has no ScheduleParam named: " << name << "\n";
    internal_assert(it->second != nullptr);
    return *it->second;
}


void GeneratorBase::set_inputs_vector(const std::vector<std::vector<StubInput>> &inputs) {
    advance_phase(InputsSet);
    internal_assert(!inputs_set) << "set_inputs_vector() must be called at most once per Generator instance.\n";
    ParamInfo &pi = param_info();
    user_assert(pi.filter_params.size() == 0) 
        << "The set_inputs_vector() method cannot be used for Generators that use Param<> or ImageParam.";
    user_assert(inputs.size() == pi.filter_inputs.size()) 
            << "Expected exactly " << pi.filter_inputs.size() 
            << " inputs but got " << inputs.size() << "\n";
    for (size_t i = 0; i < pi.filter_inputs.size(); ++i) {
        pi.filter_inputs[i]->set_inputs(inputs[i]);
    }
    inputs_set = true;
}

void GeneratorBase::track_parameter_values(bool include_outputs) {
    if (value_tracker == nullptr) {
        value_tracker = std::make_shared<ValueTracker>();
    }
    ParamInfo &pi = param_info();
    for (auto input : pi.filter_inputs) {
        if (input->kind() == IOKind::Buffer) {
            Parameter p = input->parameter();
            // This must use p.name(), *not* input->name()
            value_tracker->track_values(p.name(), parameter_constraints(p));
        }
    }
    if (include_outputs) {
        for (auto output : pi.filter_outputs) {
            if (output->kind() == IOKind::Buffer) {
                Parameter p = output->parameter();
                // This must use p.name(), *not* output->name()
                value_tracker->track_values(p.name(), parameter_constraints(p));
            }
        }
    }
}

void GeneratorBase::check_min_phase(Phase expected_phase) const {
    user_assert(phase >= expected_phase) << "You may not do this operation at this phase.";
}

void GeneratorBase::check_exact_phase(Phase expected_phase) const {
    user_assert(phase == expected_phase) << "You may not do this operation at this phase.";
}

void GeneratorBase::advance_phase(Phase new_phase) {
    switch (new_phase) {
    case Created: 
        internal_error << "Impossible"; 
        break;
    case InputsSet:
        internal_assert(phase == Created);
        break;
    case GenerateCalled:
        // It's OK to advance from Created to GenerateCalled, skipping InputsSet.
        internal_assert(phase == Created || phase == InputsSet);
        break;
    case ScheduleCalled:
        internal_assert(phase == GenerateCalled);
        break;
    }
    phase = new_phase;
}


void GeneratorBase::pre_generate() {
    advance_phase(GenerateCalled);
    ParamInfo &pi = param_info();
    user_assert(pi.filter_params.size() == 0) << "May not use generate() method with Param<> or ImageParam.";
    user_assert(pi.filter_outputs.size() > 0) << "Must use Output<> with generate() method.";

    if (!inputs_set) {
        for (auto input : pi.filter_inputs) {
            input->init_internals();
        }
        inputs_set = true;
    }
    for (auto output : pi.filter_outputs) {
        output->init_internals();
    }
    track_parameter_values(false);
}

void GeneratorBase::post_generate() {
    track_parameter_values(true);
}

void GeneratorBase::pre_schedule() {
    advance_phase(ScheduleCalled);
    track_parameter_values(true);
}

void GeneratorBase::post_schedule() {
    track_parameter_values(true);
}

void GeneratorBase::pre_build() {
    advance_phase(GenerateCalled);
    advance_phase(ScheduleCalled);
    ParamInfo &pi = param_info();
    user_assert(pi.filter_inputs.size() == 0) << "May not use build() method with Input<>.";
    user_assert(pi.filter_outputs.size() == 0) << "May not use build() method with Output<>.";
    track_parameter_values(false);
}

void GeneratorBase::post_build() {
    track_parameter_values(true);
}

Pipeline GeneratorBase::get_pipeline() {
    check_min_phase(GenerateCalled);
    if (!pipeline.defined()) {
        ParamInfo &pi = param_info();
        user_assert(pi.filter_outputs.size() > 0) << "Must use get_pipeline<> with Output<>.";
        std::vector<Func> funcs;
        for (auto output : pi.filter_outputs) {
            for (const auto &f : output->funcs()) {
                user_assert(f.defined()) << "Output \"" << f.name() << "\" was not defined.\n";
                if (output->dimensions_defined()) {
                    user_assert(f.dimensions() == output->dimensions()) << "Output \"" << f.name() 
                        << "\" requires dimensions=" << output->dimensions() 
                        << " but was defined as dimensions=" << f.dimensions() << ".\n";
                }
                if (output->types_defined()) {
                    user_assert((int)f.outputs() == (int)output->types().size()) << "Output \"" << f.name() 
                            << "\" requires a Tuple of size " << output->types().size() 
                            << " but was defined as Tuple of size " << f.outputs() << ".\n";
                    for (size_t i = 0; i < f.output_types().size(); ++i) {
                        Type expected = output->types().at(i);
                        Type actual = f.output_types()[i];
                        user_assert(expected == actual) << "Output \"" << f.name() 
                            << "\" requires type " << expected 
                            << " but was defined as type " << actual << ".\n";
                    }
                }
                funcs.push_back(f);
            }
        }
        pipeline = Pipeline(funcs);
    }
    return pipeline;
}

Module GeneratorBase::build_module(const std::string &function_name,
                                   const LoweredFunc::LinkageType linkage_type) {
    Pipeline pipeline = build_pipeline();

    // Special-case here: for certain legacy Generators, building the pipeline 
    // can mutate the Params/ImageParams (mainly, to customize the type/dim 
    // of an ImageParam based on a GeneratorParam); to handle these, we discard (and rebuild)
    // the ParamInfo for all "old-style" Generators. This isn't really desirable
    // and hopefully can be eliminated someday.
    if (param_info().filter_params.size() > 0) {
        param_info_ptr.reset();
    }

    ParamInfo &pi = param_info();
    std::vector<Argument> filter_arguments;
    for (auto param : pi.filter_params) {
        filter_arguments.push_back(to_argument(*param));
    }
    for (auto input : pi.filter_inputs) {
        for (const auto &p : input->parameters_) {
            filter_arguments.push_back(to_argument(p));
        }
    }

    Module result = pipeline.compile_to_module(filter_arguments, function_name, target, linkage_type);
    std::shared_ptr<ExternsMap> externs_map = get_externs_map();
    if (externs_map) {
        for (const auto &map_entry : *externs_map) {
            result.append(map_entry.second);
        }
    }
    return result;
}

void GeneratorBase::emit_cpp_stub(const std::string &stub_file_path) {
    user_assert(!generator_name.empty()) << "Generator has no name.\n";
    // StubEmitter will want to access the GP/SP values, so advance the phase to avoid assert-fails.
    advance_phase(GenerateCalled);
    advance_phase(ScheduleCalled);
    ParamInfo &pi = param_info();
    std::ofstream file(stub_file_path);
    StubEmitter emit(file, generator_name, pi.generator_params, pi.schedule_params, pi.filter_inputs, pi.filter_outputs);
    emit.emit();
}

void GeneratorBase::check_scheduled(const char* m) const {
    check_min_phase(ScheduleCalled);
}

void GeneratorBase::check_input_is_singular(Internal::GeneratorInputBase *in) {
    user_assert(!in->is_array())
        << "Input " << in->name() << " is an array, and must be set with a vector type.";
}

void GeneratorBase::check_input_is_array(Internal::GeneratorInputBase *in) {
    user_assert(in->is_array())
        << "Input " << in->name() << " is not an array, and must not be set with a vector type.";
}

void GeneratorBase::check_input_kind(Internal::GeneratorInputBase *in, Internal::IOKind kind) {
    user_assert(in->kind() == kind)
        << "Input " << in->name() << " cannot be set with the type specified.";
}

GIOBase::GIOBase(size_t array_size, 
                 const std::string &name, 
                 IOKind kind,             
                 const std::vector<Type> &types,
                 int dimensions) 
    : array_size_(array_size), name_(name), kind_(kind), types_(types), dimensions_(dimensions) {
}

GIOBase::~GIOBase() { 
    // nothing
}

bool GIOBase::array_size_defined() const {
    return array_size_ != -1;  
}

size_t GIOBase::array_size() const { 
    internal_assert(array_size_defined()) << "ArraySize is unspecified for " << name() 
        << "; you need to explicit set it via the resize() method or by setting " 
        << name() << ".size = value in your build rules.";
    return (size_t) array_size_; 
}

bool GIOBase::is_array() const { 
    internal_error << "Unimplemented"; return false; 
}

const std::string &GIOBase::name() const { 
    return name_; 
}

IOKind GIOBase::kind() const { 
    return kind_; 
}

bool GIOBase::types_defined() const {
    return !types_.empty();  
}

const std::vector<Type> &GIOBase::types() const { 
    internal_assert(types_defined()) << "Type is unspecified for " << name() << "\n";
    return types_; 
}

Type GIOBase::type() const { 
    internal_assert(types_.size() == 1) << "Expected types_.size() == 1, saw " << types_.size() << " for " << name() << "\n";
    return types_.at(0); 
}

bool GIOBase::dimensions_defined() const {
    return dimensions_ != -1;  
}

int GIOBase::dimensions() const { 
    internal_assert(dimensions_defined()) << "Dimensions unspecified for " << name() << "\n";
    return dimensions_; 
}

const std::vector<Func> &GIOBase::funcs() const {
    internal_assert(funcs_.size() == array_size() && exprs_.empty());
    return funcs_;
}

const std::vector<Expr> &GIOBase::exprs() const {
    internal_assert(exprs_.size() == array_size() && funcs_.empty());
    return exprs_;
}

void GIOBase::verify_internals() const {
    user_assert(dimensions_ >= 0) << "Generator Input/Output Dimensions must have positive values";

    if (kind() != IOKind::Scalar) {
        for (const Func &f : funcs()) {
            user_assert(f.defined()) << "Input/Output " << name() << " is not defined.\n";
            user_assert(f.dimensions() == dimensions()) 
                << "Expected dimensions " << dimensions() 
                << " but got " << f.dimensions()
                << " for " << name() << "\n";
            user_assert(f.outputs() == 1)
                << "Expected outputs() == " << 1 
                << " but got " << f.outputs()
                << " for " << name() << "\n";
            user_assert(f.output_types().size() == 1)
                << "Expected output_types().size() == " << 1 
                << " but got " << f.outputs()
                << " for " << name() << "\n";
            user_assert(f.output_types()[0] == type()) 
                << "Expected type " << type() 
                << " but got " << f.output_types()[0] 
                << " for " << name() << "\n";
        }
    } else {
        for (const Expr &e : exprs()) {
            user_assert(e.defined()) << "Input/Ouput " << name() << " is not defined.\n";
            user_assert(e.type() == type())
                << "Expected type " << type() 
                << " but got " << e.type()
                << " for " << name() << "\n";
        }
    }
}

std::string GIOBase::array_name(size_t i) const {
    std::string n = name();
    if (is_array()) {
        n += "_" + std::to_string(i);
    }
    return n;
}

// If our type(s) are defined, ensure it matches the ones passed in, asserting if not.
// If our type(s) are not defined, just set to the ones passed in.
// (Ditto for dims.)
void GIOBase::check_matching_type_and_dim(const std::vector<Type> &t, int d) {
    if (types_defined()) {
        user_assert(types().size() == t.size()) << "Type mismatch for " << name() << ": expected " << types().size() << " types but saw " << t.size();
        for (size_t i = 0; i < t.size(); ++i) {
            user_assert(types().at(i) == t.at(i)) << "Type mismatch for " << name() << ": expected " << types().at(i) << " saw " << t.at(i);
        }
    } else {
        types_ = t;
    }
    internal_assert(d >= 0);
    if (dimensions_defined()) {
        user_assert(dimensions() == d) << "Dimensions mismatch for " << name() << ": expected " << dimensions() << " saw " << d;
    } else {
        dimensions_ = d;
    }
}

void GIOBase::check_matching_array_size(size_t size) {
    if (array_size_defined()) {
        user_assert(array_size() == size) << "ArraySize mismatch for " << name() << ": expected " << array_size() << " saw " << size;
    } else {
        array_size_ = size;
    }
}

GeneratorInputBase::GeneratorInputBase(size_t array_size,
                                       const std::string &name, 
                                       IOKind kind, 
                                       const std::vector<Type> &t, 
                                       int d) 
    : GIOBase(array_size, name, kind, t, d) {
    ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorInput, this, nullptr);
}

GeneratorInputBase::GeneratorInputBase(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
    : GeneratorInputBase(1, name, kind, t, d) {
    // nothing
}

GeneratorInputBase::~GeneratorInputBase() { 
    ObjectInstanceRegistry::unregister_instance(this); 
}

void GeneratorInputBase::check_value_writable() const {
    user_assert(generator && generator->phase == GeneratorBase::InputsSet)  << "The Input " << name() << " cannot be set at this point.\n";
}

void GeneratorInputBase::set_def_min_max() {
    // nothing
}

void GeneratorInputBase::init_parameters() {
    parameters_.clear();
    for (size_t i = 0; i < array_size(); ++i) {
        parameters_.emplace_back(type(), kind() != IOKind::Scalar, dimensions(), array_name(i), true, false);
    }
    set_def_min_max();
}

void GeneratorInputBase::verify_internals() const {
    GIOBase::verify_internals();

    const size_t expected = (kind() != IOKind::Scalar) ? funcs().size() : exprs().size();
    user_assert(parameters_.size() == expected) << "Expected parameters_.size() == " 
        << expected << ", saw " << parameters_.size() << " for " << name() << "\n";
}

void GeneratorInputBase::init_internals() {
    user_assert(array_size_defined()) << "ArraySize is not defined for Input " << name() << "; you may need to specify a GeneratorParam.\n";
    user_assert(types_defined()) << "Type is not defined for Input " << name() << "; you may need to specify a GeneratorParam.\n";
    user_assert(dimensions_defined()) << "Dimensions is not defined for Input " << name() << "; you may need to specify a GeneratorParam.\n";

    init_parameters();

    exprs_.clear();
    funcs_.clear();
    for (size_t i = 0; i < array_size(); ++i) {
        if (kind() != IOKind::Scalar) {
            internal_assert(dimensions() == parameters_[i].dimensions());
            funcs_.push_back(make_param_func(parameters_[i], array_name(i) + "_im"));
        } else {
            Expr e = Internal::Variable::make(type(), array_name(i), parameters_[i]);
            exprs_.push_back(e);
        }
    }
    
    verify_internals();
}

void GeneratorInputBase::set_inputs(const std::vector<StubInput> &inputs) {
    generator->check_exact_phase(GeneratorBase::InputsSet);
    parameters_.clear();
    exprs_.clear();
    funcs_.clear();
    check_matching_array_size(inputs.size());
    for (size_t i = 0; i < inputs.size(); ++i) {
        const StubInput &in = inputs.at(i);
        user_assert(in.kind() == kind()) << "An input for " << name() << " is not of the expected kind.\n";
        if (kind() == IOKind::Function) {
            auto f = in.func();
            check_matching_type_and_dim(f.output_types(), f.dimensions());
            funcs_.push_back(f);
            parameters_.emplace_back(f.output_types().at(0), true, f.dimensions(), array_name(i), true, false);
        } else if (kind() == IOKind::Buffer) {
            auto p = in.parameter();
            check_matching_type_and_dim({p.type()}, p.dimensions());
            auto b = p.get_buffer();
            if (b.defined()) {
                // If the Parameter has an explicit BufferPtr set, bind directly to
                // it (this happens in JIT mode and also with statically-compiled 
                // Buffers)
                Func f(name() + "_im");
                f(_) = b(_);
                funcs_.push_back(f);
            } else {
                funcs_.push_back(make_param_func(p, name()));
            }
            parameters_.push_back(p);
        } else {
            auto e = in.expr();
            check_matching_type_and_dim({e.type()}, 0);
            exprs_.push_back(e);
            parameters_.emplace_back(e.type(), false, 0, array_name(i), true, false);
        }
    }
    
    set_def_min_max();

    verify_internals();
}

GeneratorOutputBase::GeneratorOutputBase(size_t array_size, const std::string &name, IOKind kind, const std::vector<Type> &t, int d) 
    : GIOBase(array_size, name, kind, t, d) {
    internal_assert(kind != IOKind::Scalar);
    ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorOutput,
                                              this, nullptr);
}

GeneratorOutputBase::GeneratorOutputBase(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
    : GeneratorOutputBase(1, name, kind, t, d) {
    // nothing
}

GeneratorOutputBase::~GeneratorOutputBase() { 
    ObjectInstanceRegistry::unregister_instance(this); 
}

void GeneratorOutputBase::check_value_writable() const {
    user_assert(generator && generator->phase == GeneratorBase::GenerateCalled)  << "The Output " << name() << " can only be set inside generate().\n";
}

void GeneratorOutputBase::init_internals() {
    exprs_.clear();
    funcs_.clear();
    if (array_size_defined()) {
        for (size_t i = 0; i < array_size(); ++i) {
            funcs_.push_back(Func(array_name(i)));
        }
    }
}

void GeneratorOutputBase::resize(size_t size) {
    internal_assert(is_array());
    internal_assert(!array_size_defined()) << "You may only call " << name() 
        << ".resize() when then size is undefined\n";
    array_size_ = (int) size;
    init_internals();
}

void StubOutputBufferBase::check_scheduled(const char* m) const { 
    generator->check_scheduled(m);
}

Target StubOutputBufferBase::get_target() const { 
    return generator->get_target();
}

void generator_test() {
    // Verify that the Generator's internal phase actually prevents unsupported
    // order of operations.
    {
        class Tester : public Generator<Tester> {
        public:
            GeneratorParam<int> gp0{"gp0", 0};
            GeneratorParam<float> gp1{"gp1", 1.f};
            GeneratorParam<uint64_t> gp2{"gp2", 2};

            ScheduleParam<int> sp0{"sp0", 100};
            ScheduleParam<float> sp1{"sp1", 101.f};
            ScheduleParam<uint64_t> sp2{"sp2", 102};

            Input<int> input{"input"};
            Output<Func> output{"output", Int(32), 1};

            void generate() {
                internal_assert(gp0 == 1);
                internal_assert(gp1 == 2.f);
                internal_assert(gp2 == (uint64_t) 2);  // unchanged
                Var x;
                output(x) = input + gp0;
            }
            void schedule() {
                // internal_assert(sp0 == 200);
                // internal_assert(sp1 == 201.f);
                // internal_assert(sp2 == (uint64_t) 202);
            }
        };

        Tester tester;
        internal_assert(tester.phase == GeneratorBase::Created);

        // Verify that calling GeneratorParam::set() and ScheduleParam::set() works.
        tester.gp0.set(1);
        tester.sp0.set(200);

        tester.set_inputs_vector({{StubInput(42)}});
        internal_assert(tester.phase == GeneratorBase::InputsSet);

        // tester.set_inputs_vector({{StubInput(43)}});  // This will assert-fail.

        // Also ok to call in this phase.
        tester.gp1.set(2.f);
        tester.sp1.set(201.f);

        tester.call_generate();
        internal_assert(tester.phase == GeneratorBase::GenerateCalled);

        // tester.set_inputs_vector({{StubInput(44)}});  // This will assert-fail.
        // tester.gp2.set(2);  // This will assert-fail.
        tester.sp2.set(202);  // OK to set ScheduleParams after generate(), but not after schedule()

        tester.call_schedule();
        internal_assert(tester.phase == GeneratorBase::ScheduleCalled);

        // tester.set_inputs_vector({{StubInput(45)}});  // This will assert-fail.
        // tester.gp2.set(2);  // This will assert-fail.
        // tester.sp2.set(202);  // This will assert-fail.
    }

    // Verify that set_generator_param<T> and set_schedule_param<T>
    // work properly, even if the specific subtype of Generator is not known.
    {
        class Tester : public Generator<Tester> {
        public:
            GeneratorParam<int> gp0{"gp0", 0};
            GeneratorParam<float> gp1{"gp1", 1.f};
            GeneratorParam<uint64_t> gp2{"gp2", 2};

            ScheduleParam<int> sp0{"sp0", 100};
            ScheduleParam<float> sp1{"sp1", 101.f};
            ScheduleParam<uint64_t> sp2{"sp2", 102};

            Input<int> input{"input"};
            Output<Func> output{"output", Int(32), 1};

            void generate() {
                internal_assert(gp0 == 1);
                internal_assert(gp1 == 2.f);
                internal_assert(gp2 == (uint64_t) 2);  // unchanged
                Var x;
                output(x) = input + gp0;
            }
            void schedule() {
                // internal_assert(sp0 == 200);
                // internal_assert(sp1 == 201.f);
                // internal_assert(sp2 == (uint64_t) 202);
            }
        };

        Tester tester_instance;
        // Use a base-typed reference to verify the code below doesn't know about subtype
        GeneratorBase &tester = tester_instance;

        // Verify that calling GeneratorParam::set() and ScheduleParam::set() works.
        tester.set_generator_param("gp0", 1)
              .set_schedule_param("sp0", 200);

        tester.set_inputs_vector({{StubInput(42)}});

        tester.set_generator_param("gp1", 2.f)
              .set_schedule_param("sp1", 201.f);

        tester.call_generate();

        tester.set_schedule_param("sp2", 202);

        tester.call_schedule();
    }

    // Verify that the Generator's internal phase actually prevents unsupported
    // order of operations (with old-style Generator)
    {
        class Tester : public Generator<Tester> {
        public:
            GeneratorParam<int> gp0{"gp0", 0};
            GeneratorParam<float> gp1{"gp1", 1.f};
            GeneratorParam<uint64_t> gp2{"gp2", 2};

            ScheduleParam<int> sp0{"sp0", 100};
            ScheduleParam<float> sp1{"sp1", 101.f};
            ScheduleParam<uint64_t> sp2{"sp2", 102};

            Param<int> input{"input"};

            Func build() {
                internal_assert(gp0 == 1);
                internal_assert(gp1 == 2.f);
                internal_assert(gp2 == (uint64_t) 2);  // unchanged
                // internal_assert(sp0 == 200);
                // internal_assert(sp1 == 201.f);
                // internal_assert(sp2 == (uint64_t) 102);
                Var x;
                Func output;
                output(x) = input + gp0;
                return output;
            }
        };

        Tester tester;
        internal_assert(tester.phase == GeneratorBase::Created);

        // Verify that calling GeneratorParam::set() and ScheduleParam::set() works.
        tester.gp0.set(1);
        tester.sp0.set(200);

        // set_inputs_vector() can't be called on an old-style Generator;
        // that's OK, since we can skip from Created -> GenerateCalled anyway
        // tester.set_inputs_vector({{StubInput(42)}});
        // internal_assert(tester.phase == GeneratorBase::InputsSet);

        // tester.set_inputs_vector({{StubInput(43)}});  // This will assert-fail.

        // Also ok to call in this phase.
        tester.gp1.set(2.f);
        tester.sp1.set(201.f);

        tester.build_pipeline();
        internal_assert(tester.phase == GeneratorBase::ScheduleCalled);

        // tester.set_inputs_vector({{StubInput(45)}});  // This will assert-fail.
        // tester.gp2.set(2);  // This will assert-fail.
        // tester.sp2.set(202);  // This will assert-fail.
    }    

    // Verify that set_inputs() works properly, even if the specific subtype of Generator is not known.
    {
        class Tester : public Generator<Tester> {
        public:
            Input<int> input_int{"input_int"};
            Input<float> input_float{"input_float"};
            Input<uint8_t> input_byte{"input_byte"};
            Input<uint64_t[4]> input_scalar_array{ "input_scalar_array" };
            Input<Func> input_func_typed{"input_func_typed", Int(16), 1};
            Input<Func> input_func_untyped{"input_func_untyped", 1};
            Input<Func[]> input_func_array{ "input_func_array", 1 };
            Input<Buffer<uint8_t>> input_buffer_typed{ "input_buffer_typed", 3 };
            Input<Buffer<>> input_buffer_untyped{ "input_buffer_untyped" };
            Output<Func> output{"output", Float(32), 1};


            void generate() {
                Var x;
                output(x) = input_int + 
                            input_float +
                            input_byte +
                            input_scalar_array[3] +
                            input_func_untyped(x) + 
                            input_func_typed(x) + 
                            input_func_array[0](x) + 
                            input_buffer_typed(x, 0, 0) +
                            input_buffer_untyped(x, Halide::_);
            }
            void schedule() {
                // nothing
            }
        };

        Tester tester_instance;
        // Use a base-typed reference to verify the code below doesn't know about subtype
        GeneratorBase &tester = tester_instance;

        const int i = 1234;
        const float f = 2.25f;
        const uint8_t b = 0x42;
        const std::vector<uint64_t> a = { 1, 2, 3, 4 };
        Var x;
        Func fn_typed, fn_untyped;
        fn_typed(x) = cast<int16_t>(38);
        fn_untyped(x) = 32.f;
        const std::vector<Func> fn_array = { fn_untyped, fn_untyped };

        Buffer<uint8_t> buf_typed(1, 1, 1);
        Buffer<float> buf_untyped(1);

        buf_typed.fill(33);
        buf_untyped.fill(34);

        // set_inputs() requires inputs in Input<>-decl-order,
        // and all inputs match type exactly. 
        tester.set_inputs(i, f, b, a, fn_typed, fn_untyped, fn_array, buf_typed, buf_untyped);
        tester.call_generate();
        tester.call_schedule();

        Buffer<float> im = tester_instance.realize(1);
        internal_assert(im.dim(0).extent() == 1);
        internal_assert(im(0) == 1475.25f) << "Expected 1475.25 but saw " << im(0);
    }

    class GPTester : public Generator<GPTester> {
    public:
        GeneratorParam<int> gp{"gp", 0};
        Output<Func> output{"output", Int(32), 0};
        void generate() { output() = 0; }
        void schedule() {}
    };
    GPTester gp_tester;
    // Accessing the GeneratorParam will assert-fail if we
    // don't do some minimal setup here.
    gp_tester.set_inputs_vector({});
    gp_tester.call_generate();
    gp_tester.call_schedule();
    auto &gp = gp_tester.gp;


    // Verify that RDom parameter-pack variants can convert GeneratorParam to Expr
    RDom rdom(0, gp, 0, gp);

    // Verify that Func parameter-pack variants can convert GeneratorParam to Expr
    Var x, y;
    Func f, g;
    f(x, y) = x + y;
    g(x, y) = f(gp, gp);                            // check Func::operator() overloads
    g(rdom.x, rdom.y) += f(rdom.x, rdom.y);
    g.update(0).reorder(rdom.y, rdom.x);            // check Func::reorder() overloads for RDom::operator RVar()

    // Verify that print() parameter-pack variants can convert GeneratorParam to Expr
    print(f(0, 0), g(1, 1), gp);
    print_when(true, f(0, 0), g(1, 1), gp);

    // Verify that Tuple parameter-pack variants can convert GeneratorParam to Expr
    Tuple t(gp, gp, gp);

    // Test rational_approximation
    auto check_ratio = [](double d, std::pair<int64_t, int64_t> expected) {
        auto actual = rational_approximation(d);
        internal_assert(actual == expected) 
            << "rational_approximation(" << d << ") failed:"
            << " expected " << expected.first << "/" << expected.second
            << " actual " << actual.first << "/" << actual.second << "\n";
    };

    // deliberately use fractional values that are exactly representable so that
    // we minimize testing variation across compilers
    const double kFrac1 = 1234.125;
    const double kFrac2 = 123412341234.125;
    const double kFrac3 = 1.0/65536.0;

    check_ratio(0.0,        {0, 1});
    check_ratio(1.0,        {1, 1});
    check_ratio(2.0,        {2, 1});
    check_ratio(kFrac1,     {9873, 8});
    check_ratio(kFrac2,     {987298729873, 8});
    check_ratio(kFrac3,     {1, 65536});

    check_ratio(-0.0,       {0, 1});
    check_ratio(-1.0,       {-1, 1});
    check_ratio(-2.0,       {-2, 1});
    check_ratio(-kFrac1,    {-9873, 8});
    check_ratio(-kFrac2,    {-987298729873, 8});
    check_ratio(-kFrac3,    {-1, 65536});

    check_ratio(NAN, {0, 0});
    check_ratio(INFINITY, {1, 0});
    check_ratio(-INFINITY, {-1, 0});

    std::cout << "Generator test passed" << std::endl;
}

}  // namespace Internal
}  // namespace Halide

/* [<][>][^][v][top][bottom][index][help] */