Repository: fixstars/ion-kit (language: python)

Issue:
Error when compiled with Halide-10.0.1
Compiling target `complex_graph_aot_test` makes following error

```
terminate called after throwing an instance of 'Halide::CompileError'
  what():  Error: All Params and embedded Buffers must have unique names, but the name 'height' was seen multiple times.
```
Halide 10.0.1 strictly check the parameter identity not only by name, but also contents ptr.


Discussion / hints:

Fix patch:
diff --git a/example/dnn.cc b/example/dnn.cc
index a230fc65..15ae8650 100644
--- a/example/dnn.cc
+++ b/example/dnn.cc
@@ -21,7 +21,7 @@ int main(int argc, char *argv[]) {
         b.set_target(Halide::get_target_from_environment());
 
         Node n;
-        n = b.add("image_io_image_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/pedestrian.jpg"});
+        n = b.add("image_io_color_data_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/pedestrian.jpg"}, Param{"width", std::to_string(width)}, Param{"height", std::to_string(height)});
         n = b.add("core_normalize_3d_uint8")(n["output"]);
         n = b.add("core_reorder_buffer_3d_float")(n["output"]).set_param(Param{"dim0", "2"}, Param{"dim1", "0"}, Param{"dim2", "1"});  // CHW -> HWC
         n = b.add("dnn_object_detection")(n["output"]);
diff --git a/example/dnn_compile.cc b/example/dnn_compile.cc
index df44618b..e8f63630 100644
--- a/example/dnn_compile.cc
+++ b/example/dnn_compile.cc
@@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
         b.set_target(Halide::get_target_from_environment());
 
         Node n;
-        n = b.add("image_io_image_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/pedestrian.jpg"});
+        n = b.add("image_io_color_data_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/pedestrian.jpg"}, Param{"width", std::to_string(input_width)}, Param{"height", std::to_string(input_height)});
         n = b.add("core_normalize_3d_uint8")(n["output"]);
         n = b.add("core_reorder_buffer_3d_float")(n["output"]).set_param(Param{"dim0", "2"}, Param{"dim1", "0"}, Param{"dim2", "1"});  // CHW -> HWC
         n = b.add("dnn_object_detection")(n["output"]);
diff --git a/example/dnn_fpga.cc b/example/dnn_fpga.cc
index a230fc65..581877af 100644
--- a/example/dnn_fpga.cc
+++ b/example/dnn_fpga.cc
@@ -21,7 +21,7 @@ int main(int argc, char *argv[]) {
         b.set_target(Halide::get_target_from_environment());
 
         Node n;
-        n = b.add("image_io_image_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/pedestrian.jpg"});
+        n = b.add("image_io_color_data_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/pedestrian.jpg"});
         n = b.add("core_normalize_3d_uint8")(n["output"]);
         n = b.add("core_reorder_buffer_3d_float")(n["output"]).set_param(Param{"dim0", "2"}, Param{"dim1", "0"}, Param{"dim2", "1"});  // CHW -> HWC
         n = b.add("dnn_object_detection")(n["output"]);
diff --git a/example/dnn_fpga_compile.cc b/example/dnn_fpga_compile.cc
index 2f993621..6f1aff88 100644
--- a/example/dnn_fpga_compile.cc
+++ b/example/dnn_fpga_compile.cc
@@ -18,7 +18,7 @@ int main(int argc, char *argv[]) {
         // b.set_target(Halide::Target("arm-64-linux"));
 
         Node n;
-        n = b.add("image_io_image_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/pedestrian.jpg"});
+        n = b.add("image_io_color_data_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/pedestrian.jpg"});
         n = b.add("fpga_normalize_3d_chw_uint8")
             .set_param(
                 Param{"width", "512"},
diff --git a/example/sgm_compile.cc b/example/sgm_compile.cc
index 69010d4f..6bffa736 100644
--- a/example/sgm_compile.cc
+++ b/example/sgm_compile.cc
@@ -20,14 +20,14 @@ int main() {
         Builder b;
         b.set_target(Halide::get_target_from_environment());
 
-        Node ln = b.add("image_io_image_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/aloe_left.jpg"});
+        Node ln = b.add("image_io_color_data_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/aloe_left.jpg"}, Param{"width", std::to_string(input_width)}, Param{"height", std::to_string(input_height)});
         ln = b.add("core_normalize_3d_uint8")(ln["output"]);
         ln = b.add("image_processing_resize_nearest_3d")(ln["output"]).set_param(Param{"width", std::to_string(input_width)}, Param{"height", std::to_string(input_height)}, Param{"scale", std::to_string(scale)});
         ln = b.add("internal_schedule")(ln["output"]).set_param(Param{"output_name", "scaled_left"}, Param{"compute_level", "compute_root"});
         ln = b.add("image_processing_calc_luminance")(ln["output"]).set_param(Param{"luminance_method", "Average"});
         ln = b.add("core_denormalize_2d_uint8")(ln["output"]);
 
-        Node rn = b.add("image_io_image_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/aloe_right.jpg"});
+        Node rn = b.add("image_io_color_data_loader").set_param(Param{"url", "http://ion-archives.s3-us-west-2.amazonaws.com/aloe_right.jpg"}, Param{"width", std::to_string(input_width)}, Param{"height", std::to_string(input_height)});
         rn = b.add("core_normalize_3d_uint8")(rn["output"]);
         rn = b.add("image_processing_resize_nearest_3d")(rn["output"]).set_param(Param{"width", std::to_string(input_width)}, Param{"height", std::to_string(input_height)}, Param{"scale", std::to_string(scale)});
         rn = b.add("internal_schedule")(rn["output"]).set_param(Param{"output_name", "scaled_right"}, Param{"compute_level", "compute_root"});
diff --git a/include/ion-bb-image-io/rt_common.h b/include/ion-bb-image-io/rt_common.h
index a60cf34d..28c37d23 100644
--- a/include/ion-bb-image-io/rt_common.h
+++ b/include/ion-bb-image-io/rt_common.h
@@ -258,7 +258,7 @@ class ImageSequence {
             std::ifstream ifs(path, std::ios::binary);
             ifs.read(reinterpret_cast<char*>(frame.ptr()), size);
         } else {
-            auto frame = cv::imread(path, imread_flags);
+            frame = cv::imread(path, imread_flags);
             if (frame.empty()) {
                 throw std::runtime_error("Failed to load data : " + path.string());
             }
diff --git a/include/ion-bb-image-io/rt_v4l2.h b/include/ion-bb-image-io/rt_v4l2.h
index ea4dea7d..98dd9d7d 100644
--- a/include/ion-bb-image-io/rt_v4l2.h
+++ b/include/ion-bb-image-io/rt_v4l2.h
@@ -286,6 +286,8 @@ class V4L2 {
         deafult:
             throw std::runtime_error("Unreachable");
         }
+
+        return -1;
     }
 
     template<typename T>
diff --git a/include/ion-bb-image-processing/bb.h b/include/ion-bb-image-processing/bb.h
index 239a813d..c07bf2ce 100644
--- a/include/ion-bb-image-processing/bb.h
+++ b/include/ion-bb-image-processing/bb.h
@@ -1451,12 +1451,13 @@ class FitImageToCenter : public BuildingBlock<X> {
     GeneratorOutput<Halide::Func> output{"output", Halide::type_of<T>(), D};
 
     void generate() {
-        Halide::Var x;
-        Halide::Var y;
+        using namespace  Halide;
+
+        Var x, y;
 
         Halide::Func input_wrapper = Halide::BoundaryConditions::constant_exterior(input, 0, {{0, input_width}, {0, input_height}});
-        Halide::Expr left = Halide::cast<int32_t>((output_width - input_width) / 2);
-        Halide::Expr top = Halide::cast<int32_t>((output_height - input_height) / 2);
+        Halide::Expr left = cast<int32_t>(output_width) - cast<int32_t>(input_width) / 2;
+        Halide::Expr top = cast<int32_t>(output_height) - cast<int32_t>(input_height) / 2;
 
         output(x, y, Halide::_) = input_wrapper(x - left, y - top, Halide::_);
     }
@@ -1553,10 +1554,12 @@ class OverlayImage : public BuildingBlock<X> {
     GeneratorOutput<Halide::Func> output{"output", Halide::type_of<T>(), D};
 
     void generate() {
-        Halide::Func input0_wrapper;
-        Halide::Func input1_wrapper;
+        using namespace Halide;
 
-        Halide::Region region(D, {Halide::Expr(), Halide::Expr()});
+        Func input0_wrapper;
+        Func input1_wrapper;
+
+        Region region(D, {Expr(), Expr()});
 
         region[x_dim] = {0, input0_width};
         region[y_dim] = {0, input0_height};
@@ -1565,16 +1568,16 @@ class OverlayImage : public BuildingBlock<X> {
         region[y_dim] = {0, input1_height};
         input1_wrapper = Halide::BoundaryConditions::constant_exterior(input1, 0, region);
 
-        std::vector<Halide::Var> vars(D);
-        Halide::Var x = vars[x_dim];
-        Halide::Var y = vars[y_dim];
+        std::vector<Var> vars(D);
+        Var x = vars[x_dim];
+        Var y = vars[y_dim];
 
-        std::vector<Halide::Expr> args(vars.begin(), vars.end());
+        std::vector<Expr> args(vars.begin(), vars.end());
         args[x_dim] -= input1_left;
         args[y_dim] -= input1_top;
 
-        output(vars) = Halide::select(
-            x >= input1_left && x < input1_left + input1_width && y >= input1_top && y < input1_top + input1_height,
+        output(vars) = select(
+            x >= input1_left && x < cast<int32_t>(input1_left) + cast<int32_t>(input1_width) && y >= cast<int32_t>(input1_top) && y < cast<int32_t>(input1_top) + cast<int32_t>(input1_height),
             input1_wrapper(args),
             input0_wrapper(vars));
     }
diff --git a/include/ion/generator.h b/include/ion/generator.h
index be27b41e..12db0242 100644
--- a/include/ion/generator.h
+++ b/include/ion/generator.h
@@ -23,13 +23,12 @@
 #define user_assert(c)            _halide_internal_assertion(c, Halide::Internal::ErrorReport::User)
 #endif
 
-#include "json.hpp"
+// #include "json.hpp"
 
-// Halide/src/Generator.h
 
 /** \file
  *
- * Generator is a class used to encapsulate the building of Halide::Funcs in user
+ * Generator is a class used to encapsulate the building of Funcs in user
  * pipelines. A Generator is agnostic to JIT vs AOT compilation; it can be used for
  * either purpose, but is especially convenient to use for AOT compilation.
  *
@@ -40,8 +39,8 @@
  * \code
  *     class Blur : public Generator<Blur> {
  *     public:
- *         Input<Halide::Func> input{"input", UInt(16), 2};
- *         Output<Halide::Func> output{"output", UInt(16), 2};
+ *         Input<Func> input{"input", UInt(16), 2};
+ *         Output<Func> output{"output", UInt(16), 2};
  *         void generate() {
  *             blur_x(x, y) = (input(x, y) + input(x+1, y) + input(x+2, y))/3;
  *             blur_y(x, y) = (blur_x(x, y) + blur_x(x, y+1) + blur_x(x, y+2))/3;
@@ -52,8 +51,8 @@
  *             blur_x.store_at(blur_y, y).compute_at(blur_y, yi).vectorize(x, 8);
  *         }
  *     private:
- *         Halide::Var x, y, xi, yi;
- *         Halide::Func blur_x, blur_y;
+ *         Var x, y, xi, yi;
+ *         Func blur_x, blur_y;
  *     };
  * \endcode
  *
@@ -62,7 +61,7 @@
  *
  * A Generator provides implementations of two methods:
  *
- *   - generate(), which must fill in all Output Halide::Func(s); it may optionally also do scheduling
+ *   - generate(), which must fill in all Output Func(s); it may optionally also do scheduling
  *   if no schedule() method is present.
  *   - schedule(), which (if present) should contain all scheduling code.
  *
@@ -73,54 +72,54 @@
  *     Input<int32_t> increment{"increment"};
  * \endcode
  *
- * An Input<Halide::Func> is (essentially) like an Halide::ImageParam, except that it may (or may
+ * An Input<Func> is (essentially) like an ImageParam, except that it may (or may
  * not) not be backed by an actual buffer, and thus has no defined extents.
  *
  * \code
- *     Input<Halide::Func> input{"input", Float(32), 2};
+ *     Input<Func> input{"input", Float(32), 2};
  * \endcode
  *
- * You can optionally make the type and/or dimensions of Input<Halide::Func> unspecified,
- * in which case the value is simply inferred from the actual Halide::Funcs passed to them.
- * Of course, if you specify an explicit Halide::Type or Dimension, we still require the
- * input Halide::Func to match, or a compilation error results.
+ * You can optionally make the type and/or dimensions of Input<Func> unspecified,
+ * in which case the value is simply inferred from the actual Funcs passed to them.
+ * Of course, if you specify an explicit Type or Dimension, we still require the
+ * input Func to match, or a compilation error results.
  *
  * \code
- *     Input<Halide::Func> input{ "input", 3 };  // require 3-dimensional Halide::Func,
- *                                       // but leave Halide::Type unspecified
+ *     Input<Func> input{ "input", 3 };  // require 3-dimensional Func,
+ *                                       // but leave Type unspecified
  * \endcode
  *
  * A Generator must explicitly list the output(s) it produces:
  *
  * \code
- *     Output<Halide::Func> output{"output", Float(32), 2};
+ *     Output<Func> output{"output", Float(32), 2};
  * \endcode
  *
- * You can specify an output that returns a Tuple by specifying a list of Halide::Types:
+ * You can specify an output that returns a Tuple by specifying a list of Types:
  *
  * \code
  *     class Tupler : Generator<Tupler> {
- *       Input<Halide::Func> input{"input", Int(32), 2};
- *       Output<Halide::Func> output{"output", {Float(32), UInt(8)}, 2};
+ *       Input<Func> input{"input", Int(32), 2};
+ *       Output<Func> output{"output", {Float(32), UInt(8)}, 2};
  *       void generate() {
- *         Halide::Var x, y;
- *         Halide::Expr a = Halide::cast<float>(input(x, y));
- *         Halide::Expr b = Halide::cast<uint8_t>(input(x, y));
+ *         Var x, y;
+ *         Expr a = cast<float>(input(x, y));
+ *         Expr b = cast<uint8_t>(input(x, y));
  *         output(x, y) = Tuple(a, b);
  *       }
  *     };
  * \endcode
  *
  * You can also specify Output<X> for any scalar type (except for Handle types);
- * this is merely syntactic sugar on top of a zero-dimensional Halide::Func, but can be
+ * this is merely syntactic sugar on top of a zero-dimensional Func, but can be
  * quite handy, especially when used with multiple outputs:
  *
  * \code
- *     Output<float> sum{"sum"};  // equivalent to Output<Halide::Func> {"sum", Float(32), 0}
+ *     Output<float> sum{"sum"};  // equivalent to Output<Func> {"sum", Float(32), 0}
  * \endcode
  *
- * As with Input<Halide::Func>, you can optionally make the type and/or dimensions of an
- * Output<Halide::Func> unspecified; any unspecified types must be resolved via an
+ * As with Input<Func>, you can optionally make the type and/or dimensions of an
+ * Output<Func> unspecified; any unspecified types must be resolved via an
  * implicit GeneratorParam in order to use top-level compilation.
  *
  * You can also declare an *array* of Input or Output, by using an array type
@@ -129,14 +128,14 @@
  * \code
  *     // Takes exactly 3 images and outputs exactly 3 sums.
  *     class SumRowsAndColumns : Generator<SumRowsAndColumns> {
- *       Input<Halide::Func[3]> inputs{"inputs", Float(32), 2};
+ *       Input<Func[3]> inputs{"inputs", Float(32), 2};
  *       Input<int32_t[2]> extents{"extents"};
- *       Output<Halide::Func[3]> sums{"sums", Float(32), 1};
+ *       Output<Func[3]> sums{"sums", Float(32), 1};
  *       void generate() {
  *         assert(inputs.size() == sums.size());
  *         // assume all inputs are same extent
- *         Halide::Expr width = extent[0];
- *         Halide::Expr height = extent[1];
+ *         Expr width = extent[0];
+ *         Expr height = extent[1];
  *         for (size_t i = 0; i < inputs.size(); ++i) {
  *           RDom r(0, width, 0, height);
  *           sums[i]() = 0.f;
@@ -159,8 +158,8 @@
  *     class Pyramid : public Generator<Pyramid> {
  *     public:
  *         GeneratorParam<int32_t> levels{"levels", 10};
- *         Input<Halide::Func> input{ "input", Float(32), 2 };
- *         Output<Halide::Func[]> pyramid{ "pyramid", Float(32), 2 };
+ *         Input<Func> input{ "input", Float(32), 2 };
+ *         Output<Func[]> pyramid{ "pyramid", Float(32), 2 };
  *         void generate() {
  *             pyramid.resize(levels);
  *             pyramid[0](x, y) = input(x, y);
@@ -182,13 +181,13 @@
  *
  * \code
  *     GeneratorParam(s)
- *     Input<Halide::Func>(s)
- *     Input<non-Halide::Func>(s)
- *     Output<Halide::Func>(s)
+ *     Input<Func>(s)
+ *     Input<non-Func>(s)
+ *     Output<Func>(s)
  * \endcode
  *
  * Note that the Inputs and Outputs will appear in the C function call in the order
- * they are declared. All Input<Halide::Func> and Output<Halide::Func> are represented as halide_buffer_t;
+ * they are declared. All Input<Func> and Output<Func> are represented as halide_buffer_t;
  * all other Input<> are the appropriate C++ scalar type. (GeneratorParams are
  * always referenced by name, not position, so their order is irrelevant.)
  *
@@ -200,7 +199,8 @@
  * You can dynamically add Inputs and Outputs to your Generator via adding a
  * configure() method; if present, it will be called before generate(). It can
  * examine GeneratorParams but it may not examine predeclared Inputs or Outputs;
- * the only thing it should do is call add_input<>() and/or add_output<>().
+ * the only thing it should do is call add_input<>() and/or add_output<>(), or call
+ * set_type()/set_dimensions()/set_array_size() on an Input or Output with an unspecified type.
  * Added inputs will be appended (in order) after predeclared Inputs but before
  * any Outputs; added outputs will be appended after predeclared Outputs.
  *
@@ -212,33 +212,33 @@
  *     class MultiSum : public Generator<MultiSum> {
  *     public:
  *         GeneratorParam<int32_t> input_count{"input_count", 10};
- *         Output<Halide::Func> output{ "output", Float(32), 2 };
+ *         Output<Func> output{ "output", Float(32), 2 };
  *
  *         void configure() {
  *             for (int i = 0; i < input_count; ++i) {
  *                 extra_inputs.push_back(
- *                     add_input<Halide::Func>("input_" + std::to_string(i), Float(32), 2);
+ *                     add_input<Func>("input_" + std::to_string(i), Float(32), 2);
  *             }
  *         }
  *
  *         void generate() {
- *             Halide::Expr sum = 0.f;
+ *             Expr sum = 0.f;
  *             for (int i = 0; i < input_count; ++i) {
  *                 sum += (*extra_inputs)[i](x, y);
  *             }
  *             output(x, y) = sum;
  *         }
  *     private:
- *         std::vector<Input<Halide::Func>* extra_inputs;
+ *         std::vector<Input<Func>* extra_inputs;
  *     };
  * \endcode
  *
  *  All Generators have three GeneratorParams that are implicitly provided
  *  by the base class:
  *
- *      GeneratorParam<Halide::Target> target{"target", Halide::Target()};
+ *      GeneratorParam<Target> target{"target", Target()};
  *      GeneratorParam<bool> auto_schedule{"auto_schedule", false};
- *      GeneratorParam<Halide::MachineParams> machine_params{"machine_params", Halide::MachineParams::generic()};
+ *      GeneratorParam<MachineParams> machine_params{"machine_params", MachineParams::generic()};
  *
  *  - 'target' is the Halide::Target for which the Generator is producing code.
  *    It is read-only during the Generator's lifetime, and must not be modified;
@@ -247,8 +247,8 @@
  *    (for JIT compilation).
  *  - 'auto_schedule' indicates whether the auto-scheduler should be run for this
  *    Generator:
- *      - if 'false', the Generator should schedule its Halide::Funcs as it sees fit.
- *      - if 'true', the Generator should only provide estimate()s for its Halide::Funcs,
+ *      - if 'false', the Generator should schedule its Funcs as it sees fit.
+ *      - if 'true', the Generator should only provide estimate()s for its Funcs,
  *        and not call any other scheduling methods.
  *  - 'machine_params' is only used if auto_schedule is true; it is ignored
  *    if auto_schedule is false. It provides details about the machine architecture
@@ -256,10 +256,10 @@
  *    schedule.
  *
  * Generators are added to a global registry to simplify AOT build mechanics; this
- * is done by simply using the ION_REGISTER_GENERATOR macro at global scope:
+ * is done by simply using the HALIDE_REGISTER_GENERATOR macro at global scope:
  *
  * \code
- *      ION_REGISTER_GENERATOR(ExampleGen, jit_example)
+ *      HALIDE_REGISTER_GENERATOR(ExampleGen, jit_example)
  * \endcode
  *
  * The registered name of the Generator is provided must match the same rules as
@@ -270,10 +270,10 @@
  * you can add it as an optional third argument:
  *
  * \code
- *      ION_REGISTER_GENERATOR(ExampleGen, jit_example, SomeNamespace::JitExampleStub)
+ *      HALIDE_REGISTER_GENERATOR(ExampleGen, jit_example, SomeNamespace::JitExampleStub)
  * \endcode
  *
- * Note that a Generator is always executed with a specific Halide::Target assigned to it,
+ * Note that a Generator is always executed with a specific Target assigned to it,
  * that you can access via the get_target() method. (You should *not* use the
  * global get_target_from_environment(), etc. methods provided in Target.h)
  *
@@ -284,6 +284,7 @@
  */
 
 #include <algorithm>
+#include <functional>
 #include <iterator>
 #include <limits>
 #include <memory>
@@ -296,26 +297,64 @@
 #include <vector>
 
 #include <Halide.h>
-// #include "ExternalCode.h"
-// #include "Func.h"
-// #include "ImageParam.h"
-// #include "Introspection.h"
-// #include "ObjectInstanceRegistry.h"
-// #include "Target.h"
 
 namespace Halide {
+
 template<typename T>
 class Buffer;
+
 } // namespace Halide
 
+// Declare type alias
 namespace ion {
 
+using Halide::Buffer;
+using Halide::Expr;
+using Halide::ExternFuncArgument;
+using Halide::ExternalCode;
+using Halide::Func;
+using Halide::FuncRef;
+using Halide::ImageParam;
+using Halide::LinkageType;
+using Halide::LoopLevel;
+using Halide::MachineParams;
+using Halide::Module;
+using Halide::OutputImageParam;
+using Halide::Realization;
+using Halide::Region;
+using Halide::Stage;
+using Halide::Target;
+using Halide::Type;
+using Halide::Var;
+using Halide::_;
+using Halide::cast;
+using Halide::type_of;
+
 namespace Internal {
 
+using Halide::Internal::Parameter;
+using Halide::Internal::index_sequence;
+using Halide::Internal::make_const;
+using Halide::Internal::make_index_sequence;
+
+namespace Introspection {
+
+using Halide::Internal::Introspection::get_introspection_helper;
+
+} // namespace Introspection
+} // namespace Internal
+} // namespace ion
+
+namespace ion {
+
+namespace Internal {
+
+void generator_test();
+
 /**
  * ValueTracker is an internal utility class that attempts to track and flag certain
  * obvious Stub-related errors at Halide compile time: it tracks the constraints set
- * on any Parameter-based argument (i.e., Input<Halide::Buffer> and Output<Halide::Buffer>) to
+ * on any Parameter-based argument (i.e., Input<Buffer> and Output<Buffer>) to
  * ensure that incompatible values aren't set.
  *
  * e.g.: if a Generator A requires stride[0] == 1,
@@ -340,21 +379,21 @@ namespace Internal {
  */
 class ValueTracker {
 private:
-    std::map<std::string, std::vector<std::vector<Halide::Expr>>> values_history;
+    std::map<std::string, std::vector<std::vector<Expr>>> values_history;
     const size_t max_unique_values;
 
 public:
     explicit ValueTracker(size_t max_unique_values = 2)
         : max_unique_values(max_unique_values) {
     }
-    void track_values(const std::string &name, const std::vector<Halide::Expr> &values);
+    void track_values(const std::string &name, const std::vector<Expr> &values);
 };
 
-std::vector<Halide::Expr> parameter_constraints(const Halide::Internal::Parameter &p);
+std::vector<Expr> parameter_constraints(const Parameter &p);
 
 template<typename T>
 HALIDE_NO_USER_CODE_INLINE std::string enum_to_string(const std::map<std::string, T> &enum_map, const T &t) {
-    for (auto key_value : enum_map) {
+    for (const auto &key_value : enum_map) {
         if (t == key_value.second) {
             return key_value.first;
         }
@@ -371,17 +410,17 @@ T enum_from_string(const std::map<std::string, T> &enum_map, const std::string &
 }
 
 extern const std::map<std::string, Halide::Type> &get_halide_type_enum_map();
-inline std::string halide_type_to_enum_string(const Halide::Type &t) {
+inline std::string halide_type_to_enum_string(const Type &t) {
     return enum_to_string(get_halide_type_enum_map(), t);
 }
 
-// Convert a Halide Halide::Type into a string representation of its C source.
+// Convert a Halide Type into a string representation of its C source.
 // e.g., Int(32) -> "Halide::Int(32)"
-std::string halide_type_to_c_source(const Halide::Type &t);
+std::string halide_type_to_c_source(const Type &t);
 
-// Convert a Halide Halide::Type into a string representation of its C Source.
+// Convert a Halide Type into a string representation of its C Source.
 // e.g., Int(32) -> "int32_t"
-std::string halide_type_to_c_type(const Halide::Type &t);
+std::string halide_type_to_c_type(const Type &t);
 
 /** generate_filter_main() is a convenient wrapper for GeneratorRegistry::create() +
  * compile_to_files(); it can be trivially wrapped by a "real" main() to produce a
@@ -422,7 +461,9 @@ class GeneratorParamBase {
     explicit GeneratorParamBase(const std::string &name);
     virtual ~GeneratorParamBase();
 
-    const std::string name;
+    inline const std::string &name() const {
+        return name_;
+    }
 
     // overload the set() function to call the right virtual method based on type.
     // This allows us to attempt to set a GeneratorParam via a
@@ -446,10 +487,10 @@ class GeneratorParamBase {
     HALIDE_GENERATOR_PARAM_TYPED_SETTER(uint64_t)
     HALIDE_GENERATOR_PARAM_TYPED_SETTER(float)
     HALIDE_GENERATOR_PARAM_TYPED_SETTER(double)
-    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Halide::Target)
-    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Halide::MachineParams)
-    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Halide::Type)
-    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Halide::LoopLevel)
+    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Target)
+    HALIDE_GENERATOR_PARAM_TYPED_SETTER(MachineParams)
+    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Type)
+    HALIDE_GENERATOR_PARAM_TYPED_SETTER(LoopLevel)
 
 #undef HALIDE_GENERATOR_PARAM_TYPED_SETTER
 
@@ -494,12 +535,7 @@ class GeneratorParamBase {
     void fail_wrong_type(const char *type);
 
 private:
-    // No copy
-    GeneratorParamBase(const GeneratorParamBase &) = delete;
-    void operator=(const GeneratorParamBase &) = delete;
-    // No move
-    GeneratorParamBase(GeneratorParamBase &&) = delete;
-    void operator=(GeneratorParamBase &&) = delete;
+    const std::string name_;
 
     // Generator which owns this GeneratorParam. Note that this will be null
     // initially; the GeneratorBase itself will set this field when it initially
@@ -507,6 +543,12 @@ class GeneratorParamBase {
     // appropriate for GeneratorParam<> to be declared outside of a Generator,
     // all reasonable non-testing code should expect this to be non-null.
     GeneratorBase *generator{nullptr};
+
+public:
+    GeneratorParamBase(const GeneratorParamBase &) = delete;
+    GeneratorParamBase &operator=(const GeneratorParamBase &) = delete;
+    GeneratorParamBase(GeneratorParamBase &&) = delete;
+    GeneratorParamBase &operator=(GeneratorParamBase &&) = delete;
 };
 
 // This is strictly some syntactic sugar to suppress certain compiler warnings.
@@ -541,8 +583,8 @@ class GeneratorParamImpl : public GeneratorParamBase {
         return this->value();
     }
 
-    operator Halide::Expr() const {
-        return Halide::Internal::make_const(Halide::type_of<T>(), this->value());
+    operator Expr() const {
+        return make_const(type_of<T>(), this->value());
     }
 
 #define HALIDE_GENERATOR_PARAM_TYPED_SETTER(TYPE)  \
@@ -561,10 +603,10 @@ class GeneratorParamImpl : public GeneratorParamBase {
     HALIDE_GENERATOR_PARAM_TYPED_SETTER(uint64_t)
     HALIDE_GENERATOR_PARAM_TYPED_SETTER(float)
     HALIDE_GENERATOR_PARAM_TYPED_SETTER(double)
-    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Halide::Target)
-    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Halide::MachineParams)
-    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Halide::Type)
-    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Halide::LoopLevel)
+    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Target)
+    HALIDE_GENERATOR_PARAM_TYPED_SETTER(MachineParams)
+    HALIDE_GENERATOR_PARAM_TYPED_SETTER(Type)
+    HALIDE_GENERATOR_PARAM_TYPED_SETTER(LoopLevel)
 
 #undef HALIDE_GENERATOR_PARAM_TYPED_SETTER
 
@@ -580,7 +622,7 @@ class GeneratorParamImpl : public GeneratorParamBase {
         value_ = new_value;
     }
 
-    // Needs to be protected to allow GeneratorParam<Halide::LoopLevel>::set() override
+    // Needs to be protected to allow GeneratorParam<LoopLevel>::set() override
     T value_;
 
 private:
@@ -628,7 +670,7 @@ class GeneratorParamImpl : public GeneratorParamBase {
 // Stubs for type-specific implementations of GeneratorParam, to avoid
 // many complex enable_if<> statements that were formerly spread through the
 // implementation. Note that not all of these need to be templated classes,
-// (e.g. for GeneratorParam_Target, T == Halide::Target always), but are declared
+// (e.g. for GeneratorParam_Target, T == Target always), but are declared
 // that way for symmetry of declaration.
 template<typename T>
 class GeneratorParam_Target : public GeneratorParamImpl<T> {
@@ -638,7 +680,7 @@ class GeneratorParam_Target : public GeneratorParamImpl<T> {
     }
 
     void set_from_string(const std::string &new_value_string) override {
-        this->set(Halide::Target(new_value_string));
+        this->set(Target(new_value_string));
     }
 
     std::string get_default_value() const override {
@@ -652,7 +694,7 @@ class GeneratorParam_Target : public GeneratorParamImpl<T> {
     }
 
     std::string get_c_type() const override {
-        return "Halide::Target";
+        return "Target";
     }
 };
 
@@ -664,7 +706,7 @@ class GeneratorParam_MachineParams : public GeneratorParamImpl<T> {
     }
 
     void set_from_string(const std::string &new_value_string) override {
-        this->set(Halide::MachineParams(new_value_string));
+        this->set(MachineParams(new_value_string));
     }
 
     std::string get_default_value() const override {
@@ -678,26 +720,26 @@ class GeneratorParam_MachineParams : public GeneratorParamImpl<T> {
     }
 
     std::string get_c_type() const override {
-        return "Halide::MachineParams";
+        return "MachineParams";
     }
 };
 
-class GeneratorParam_LoopLevel : public GeneratorParamImpl<Halide::LoopLevel> {
+class GeneratorParam_LoopLevel : public GeneratorParamImpl<LoopLevel> {
 public:
-    GeneratorParam_LoopLevel(const std::string &name, const Halide::LoopLevel &value)
-        : GeneratorParamImpl<Halide::LoopLevel>(name, value) {
+    GeneratorParam_LoopLevel(const std::string &name, const LoopLevel &value)
+        : GeneratorParamImpl<LoopLevel>(name, value) {
     }
 
-    using GeneratorParamImpl<Halide::LoopLevel>::set;
+    using GeneratorParamImpl<LoopLevel>::set;
 
-    void set(const Halide::LoopLevel &value) override {
-        // Don't call check_value_writable(): It's OK to set a Halide::LoopLevel after generate().
+    void set(const LoopLevel &value) override {
+        // Don't call check_value_writable(): It's OK to set a LoopLevel after generate().
         // check_value_writable();
 
         // This looks odd, but is deliberate:
 
         // First, mutate the existing contents to match the value passed in,
-        // so that any existing usage of the Halide::LoopLevel now uses the newer value.
+        // so that any existing usage of the LoopLevel now uses the newer value.
         // (Strictly speaking, this is really only necessary if this method
         // is called after generate(): before generate(), there is no usage
         // to be concerned with.)
@@ -712,30 +754,30 @@ class GeneratorParam_LoopLevel : public GeneratorParamImpl<Halide::LoopLevel> {
 
     void set_from_string(const std::string &new_value_string) override {
         if (new_value_string == "root") {
-            this->set(Halide::LoopLevel::root());
+            this->set(LoopLevel::root());
         } else if (new_value_string == "inlined") {
-            this->set(Halide::LoopLevel::inlined());
+            this->set(LoopLevel::inlined());
         } else {
-            user_error << "Unable to parse " << this->name << ": " << new_value_string;
+            user_error << "Unable to parse " << this->name() << ": " << new_value_string;
         }
     }
 
     std::string get_default_value() const override {
         // This is dodgy but safe in this case: we want to
-        // see what the value of our Halide::LoopLevel is *right now*,
+        // see what the value of our LoopLevel is *right now*,
         // so we make a copy and lock the copy so we can inspect it.
-        // (Note that ordinarily this is a bad idea, since Halide::LoopLevels
+        // (Note that ordinarily this is a bad idea, since LoopLevels
         // can be mutated later on; however, this method is only
-        // called by the Generator infrastructure, on Halide::LoopLevels that
+        // called by the Generator infrastructure, on LoopLevels that
         // will never be mutated, so this is really just an elaborate way
         // to avoid runtime assertions.)
-        Halide::LoopLevel copy;
+        LoopLevel copy;
         copy.set(this->value());
         copy.lock();
         if (copy.is_inlined()) {
-            return "Halide::LoopLevel::inlined()";
+            return "LoopLevel::inlined()";
         } else if (copy.is_root()) {
-            return "Halide::LoopLevel::root()";
+            return "LoopLevel::root()";
         } else {
             internal_error;
             return "";
@@ -748,7 +790,7 @@ class GeneratorParam_LoopLevel : public GeneratorParamImpl<Halide::LoopLevel> {
     }
 
     std::string get_c_type() const override {
-        return "Halide::LoopLevel";
+        return "LoopLevel";
     }
 
     bool is_looplevel_param() const override {
@@ -796,7 +838,7 @@ class GeneratorParam_Arithmetic : public GeneratorParamImpl<T> {
         if (std::is_same<T, float>::value) {
             // If the constant has no decimal point ("1")
             // we must append one before appending "f"
-            if (oss.str().find(".") == std::string::npos) {
+            if (oss.str().find('.') == std::string::npos) {
                 oss << ".";
             }
             oss << "f";
@@ -876,7 +918,7 @@ class GeneratorParam_Enum : public GeneratorParamImpl<T> {
     // define a "set" that takes our specific enum (but don't hide the inherited virtual functions)
     using GeneratorParamImpl<T>::set;
 
-    template<typename T2 = T, typename std::enable_if<!std::is_same<T2, Halide::Type>::value>::type * = nullptr>
+    template<typename T2 = T, typename std::enable_if<!std::is_same<T2, Type>::value>::type * = nullptr>
     void set(const T &e) {
         this->set_impl(e);
     }
@@ -888,20 +930,20 @@ class GeneratorParam_Enum : public GeneratorParamImpl<T> {
     }
 
     std::string call_to_string(const std::string &v) const override {
-        return "Enum_" + this->name + "_map().at(" + v + ")";
+        return "Enum_" + this->name() + "_map().at(" + v + ")";
     }
 
     std::string get_c_type() const override {
-        return "Enum_" + this->name;
+        return "Enum_" + this->name();
     }
 
     std::string get_default_value() const override {
-        return "Enum_" + this->name + "::" + enum_to_string(enum_map, this->value());
+        return "Enum_" + this->name() + "::" + enum_to_string(enum_map, this->value());
     }
 
     std::string get_type_decls() const override {
         std::ostringstream oss;
-        oss << "enum class Enum_" << this->name << " {\n";
+        oss << "enum class Enum_" << this->name() << " {\n";
         for (auto key_value : enum_map) {
             oss << "  " << key_value.first << ",\n";
         }
@@ -910,10 +952,10 @@ class GeneratorParam_Enum : public GeneratorParamImpl<T> {
 
         // TODO: since we generate the enums, we could probably just use a vector (or array!) rather than a map,
         // since we can ensure that the enum values are a nice tight range.
-        oss << "inline HALIDE_NO_USER_CODE_INLINE const std::map<Enum_" << this->name << ", std::string>& Enum_" << this->name << "_map() {\n";
-        oss << "  static const std::map<Enum_" << this->name << ", std::string> m = {\n";
+        oss << "inline HALIDE_NO_USER_CODE_INLINE const std::map<Enum_" << this->name() << ", std::string>& Enum_" << this->name() << "_map() {\n";
+        oss << "  static const std::map<Enum_" << this->name() << ", std::string> m = {\n";
         for (auto key_value : enum_map) {
-            oss << "    { Enum_" << this->name << "::" << key_value.first << ", \"" << key_value.first << "\"},\n";
+            oss << "    { Enum_" << this->name() << "::" << key_value.first << ", \"" << key_value.first << "\"},\n";
         }
         oss << "  };\n";
         oss << "  return m;\n";
@@ -975,11 +1017,11 @@ class GeneratorParam_String : public Internal::GeneratorParamImpl<T> {
 template<typename T>
 using GeneratorParamImplBase =
     typename select_type<
-        cond<std::is_same<T, Halide::Target>::value, GeneratorParam_Target<T>>,
-        cond<std::is_same<T, Halide::MachineParams>::value, GeneratorParam_MachineParams<T>>,
-        cond<std::is_same<T, Halide::LoopLevel>::value, GeneratorParam_LoopLevel>,
+        cond<std::is_same<T, Target>::value, GeneratorParam_Target<T>>,
+        cond<std::is_same<T, MachineParams>::value, GeneratorParam_MachineParams<T>>,
+        cond<std::is_same<T, LoopLevel>::value, GeneratorParam_LoopLevel>,
         cond<std::is_same<T, std::string>::value, GeneratorParam_String<T>>,
-        cond<std::is_same<T, Halide::Type>::value, GeneratorParam_Type<T>>,
+        cond<std::is_same<T, Type>::value, GeneratorParam_Type<T>>,
         cond<std::is_same<T, bool>::value, GeneratorParam_Bool<T>>,
         cond<std::is_arithmetic<T>::value, GeneratorParam_Arithmetic<T>>,
         cond<std::is_enum<T>::value, GeneratorParam_Enum<T>>>::type;
@@ -1049,10 +1091,6 @@ template<typename Other, typename T>
 auto operator+(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a + b) {
     return (T)a + b;
 }
-template<typename T>
-auto operator+(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a + (T)b) {
-    return (T)a + (T)b;
-}
 // @}
 
 /** Subtraction between GeneratorParam<T> and any type that supports operator- with T.
@@ -1066,17 +1104,8 @@ template<typename Other, typename T>
 auto operator-(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a - b) {
     return (T)a - b;
 }
-template<typename T>
-auto operator-(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a - (T)b) {
-    return (T)a - (T)b;
-}
 // @}
 
-template<typename T>
-auto operator-(const GeneratorParam<T> &a) -> decltype(-(T)a) {
-    return -(T)a;
-}
-
 /** Multiplication between GeneratorParam<T> and any type that supports operator* with T.
  * Returns type of underlying operator*. */
 // @{
@@ -1088,10 +1117,6 @@ template<typename Other, typename T>
 auto operator*(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a * b) {
     return (T)a * b;
 }
-template<typename T>
-auto operator*(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a * (T)b) {
-    return (T)a * (T)b;
-}
 // @}
 
 /** Division between GeneratorParam<T> and any type that supports operator/ with T.
@@ -1105,10 +1130,6 @@ template<typename Other, typename T>
 auto operator/(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a / b) {
     return (T)a / b;
 }
-template<typename T>
-auto operator/(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a / (T)b) {
-    return (T)a / (T)b;
-}
 // @}
 
 /** Modulo between GeneratorParam<T> and any type that supports operator% with T.
@@ -1122,10 +1143,6 @@ template<typename Other, typename T>
 auto operator%(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a % b) {
     return (T)a % b;
 }
-template<typename T>
-auto operator%(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a % (T)b) {
-    return (T)a % (T)b;
-}
 // @}
 
 /** Greater than comparison between GeneratorParam<T> and any type that supports operator> with T.
@@ -1139,10 +1156,6 @@ template<typename Other, typename T>
 auto operator>(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a > b) {
     return (T)a > b;
 }
-template<typename T>
-auto operator>(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a > (T)b) {
-    return (T)a > (T)b;
-}
 // @}
 
 /** Less than comparison between GeneratorParam<T> and any type that supports operator< with T.
@@ -1156,10 +1169,6 @@ template<typename Other, typename T>
 auto operator<(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a < b) {
     return (T)a < b;
 }
-template<typename T>
-auto operator<(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a < (T)b) {
-    return (T)a < (T)b;
-}
 // @}
 
 /** Greater than or equal comparison between GeneratorParam<T> and any type that supports operator>= with T.
@@ -1173,10 +1182,6 @@ template<typename Other, typename T>
 auto operator>=(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a >= b) {
     return (T)a >= b;
 }
-template<typename T>
-auto operator>=(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a >= (T)b) {
-    return (T)a >= (T)b;
-}
 // @}
 
 /** Less than or equal comparison between GeneratorParam<T> and any type that supports operator<= with T.
@@ -1190,10 +1195,6 @@ template<typename Other, typename T>
 auto operator<=(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a <= b) {
     return (T)a <= b;
 }
-template<typename T>
-auto operator<=(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a <= (T)b) {
-    return (T)a <= (T)b;
-}
 // @}
 
 /** Equality comparison between GeneratorParam<T> and any type that supports operator== with T.
@@ -1207,10 +1208,6 @@ template<typename Other, typename T>
 auto operator==(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a == b) {
     return (T)a == b;
 }
-template<typename T>
-auto operator==(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a == (T)b) {
-    return (T)a == (T)b;
-}
 // @}
 
 /** Inequality comparison between between GeneratorParam<T> and any type that supports operator!= with T.
@@ -1224,10 +1221,6 @@ template<typename Other, typename T>
 auto operator!=(const GeneratorParam<T> &a, const Other &b) -> decltype((T)a != b) {
     return (T)a != b;
 }
-template<typename T>
-auto operator!=(const GeneratorParam<T> &a, const GeneratorParam<T> &b) -> decltype((T)a != (T)b) {
-    return (T)a != (T)b;
-}
 // @}
 
 /** Logical and between between GeneratorParam<T> and any type that supports operator&& with T.
@@ -1338,12 +1331,12 @@ enum class IOKind { Scalar,
 
 /**
  * StubInputBuffer is the placeholder that a Stub uses when it requires
- * a Halide::Buffer for an input (rather than merely a Halide::Func or Halide::Expr). It is constructed
+ * a Buffer for an input (rather than merely a Func or Expr). It is constructed
  * to allow only two possible sorts of input:
- * -- Assignment of an Input<Halide::Buffer<>>, with compatible type and dimensions,
+ * -- Assignment of an Input<Buffer<>>, with compatible type and dimensions,
  * essentially allowing us to pipe a parameter from an enclosing Generator to an internal Stub.
- * -- Assignment of a Halide::Buffer<>, with compatible type and dimensions,
- * causing the Input<Halide::Buffer<>> to become a precompiled buffer in the generated code.
+ * -- Assignment of a Buffer<>, with compatible type and dimensions,
+ * causing the Input<Buffer<>> to become a precompiled buffer in the generated code.
  */
 template<typename T = void>
 class StubInputBuffer {
@@ -1351,21 +1344,22 @@ class StubInputBuffer {
     template<typename T2>
     friend class GeneratorInput_Buffer;
 
-    Halide::Internal::Parameter parameter_;
+    Parameter parameter_;
 
-    HALIDE_NO_USER_CODE_INLINE explicit StubInputBuffer(const Halide::Internal::Parameter &p)
+    HALIDE_NO_USER_CODE_INLINE explicit StubInputBuffer(const Parameter &p)
         : parameter_(p) {
         // Create an empty 1-element buffer with the right runtime typing and dimensions,
         // which we'll use only to pass to can_convert_from() to verify this
         // Parameter is compatible with our constraints.
-        Halide::Buffer<> other(p.type(), nullptr, std::vector<int>(p.dimensions(), 1));
-        internal_assert((Halide::Buffer<T>::can_convert_from(other)));
+        Buffer<> other(p.type(), nullptr, std::vector<int>(p.dimensions(), 1));
+        internal_assert((Buffer<T>::can_convert_from(other)));
     }
 
     template<typename T2>
-    HALIDE_NO_USER_CODE_INLINE static Halide::Internal::Parameter parameter_from_buffer(const Halide::Buffer<T2> &b) {
-        user_assert((Halide::Buffer<T>::can_convert_from(b)));
-        Halide::Internal::Parameter p(b.type(), true, b.dimensions());
+    HALIDE_NO_USER_CODE_INLINE static Parameter parameter_from_buffer(const Buffer<T2> &b) {
+        internal_assert(b.defined());
+        user_assert((Buffer<T>::can_convert_from(b)));
+        Parameter p(b.type(), true, b.dimensions());
         p.set_buffer(b);
         return p;
     }
@@ -1374,36 +1368,31 @@ class StubInputBuffer {
     StubInputBuffer() = default;
 
     // *not* explicit -- this ctor should only be used when you want
-    // to pass a literal Halide::Buffer<> for a Stub Input; this Halide::Buffer<> will be
+    // to pass a literal Buffer<> for a Stub Input; this Buffer<> will be
     // compiled into the Generator's product, rather than becoming
     // a runtime Parameter.
     template<typename T2>
-    StubInputBuffer(const Halide::Buffer<T2> &b)
+    StubInputBuffer(const Buffer<T2> &b)
         : parameter_(parameter_from_buffer(b)) {
     }
 };
 
 class StubOutputBufferBase {
 protected:
-    Halide::Func f;
+    Func f;
     std::shared_ptr<GeneratorBase> generator;
 
     void check_scheduled(const char *m) const;
-    Halide::Target get_target() const;
+    Target get_target() const;
 
-    explicit StubOutputBufferBase(const Halide::Func &f, std::shared_ptr<GeneratorBase> generator)
-        : f(f), generator(std::move(generator)) {
-    }
-    StubOutputBufferBase() = default;
+    StubOutputBufferBase();
+    explicit StubOutputBufferBase(const Func &f, const std::shared_ptr<GeneratorBase> &generator);
 
 public:
-    Halide::Realization realize(std::vector<int32_t> sizes) {
-        check_scheduled("realize");
-        return f.realize(std::move(sizes), get_target());
-    }
+    Realization realize(std::vector<int32_t> sizes);
 
     template<typename... Args>
-    Halide::Realization realize(Args &&... args) {
+    Realization realize(Args &&...args) {
         check_scheduled("realize");
         return f.realize(std::forward<Args>(args)..., get_target());
     }
@@ -1417,22 +1406,22 @@ class StubOutputBufferBase {
 
 /**
  * StubOutputBuffer is the placeholder that a Stub uses when it requires
- * a Halide::Buffer for an output (rather than merely a Halide::Func). It is constructed
+ * a Buffer for an output (rather than merely a Func). It is constructed
  * to allow only two possible sorts of things:
- * -- Assignment to an Output<Halide::Buffer<>>, with compatible type and dimensions,
+ * -- Assignment to an Output<Buffer<>>, with compatible type and dimensions,
  * essentially allowing us to pipe a parameter from the result of a Stub to an
  * enclosing Generator
- * -- Halide::Realization into a Halide::Buffer<>; this is useful only in JIT compilation modes
+ * -- Realization into a Buffer<>; this is useful only in JIT compilation modes
  * (and shouldn't be usable otherwise)
  *
- * It is deliberate that StubOutputBuffer is not (easily) convertible to Halide::Func.
+ * It is deliberate that StubOutputBuffer is not (easily) convertible to Func.
  */
 template<typename T = void>
 class StubOutputBuffer : public StubOutputBufferBase {
     template<typename T2>
     friend class GeneratorOutput_Buffer;
     friend class GeneratorStub;
-    explicit StubOutputBuffer(const Halide::Func &f, const std::shared_ptr<GeneratorBase> &generator)
+    explicit StubOutputBuffer(const Func &f, const std::shared_ptr<GeneratorBase> &generator)
         : StubOutputBufferBase(f, generator) {
     }
 
@@ -1447,9 +1436,9 @@ class StubOutputBuffer : public StubOutputBufferBase {
 class StubInput {
     const IOKind kind_;
     // Exactly one of the following fields should be defined:
-    const Halide::Internal::Parameter parameter_;
-    const Halide::Func func_;
-    const Halide::Expr expr_;
+    const Parameter parameter_;
+    const Func func_;
+    const Expr expr_;
 
 public:
     // *not* explicit.
@@ -1457,10 +1446,10 @@ class StubInput {
     StubInput(const StubInputBuffer<T2> &b)
         : kind_(IOKind::Buffer), parameter_(b.parameter_), func_(), expr_() {
     }
-    StubInput(const Halide::Func &f)
+    StubInput(const Func &f)
         : kind_(IOKind::Function), parameter_(), func_(f), expr_() {
     }
-    StubInput(const Halide::Expr &e)
+    StubInput(const Expr &e)
         : kind_(IOKind::Scalar), parameter_(), func_(), expr_(e) {
     }
 
@@ -1471,17 +1460,17 @@ class StubInput {
         return kind_;
     }
 
-    Halide::Internal::Parameter parameter() const {
+    Parameter parameter() const {
         internal_assert(kind_ == IOKind::Buffer);
         return parameter_;
     }
 
-    Halide::Func func() const {
+    Func func() const {
         internal_assert(kind_ == IOKind::Function);
         return func_;
     }
 
-    Halide::Expr expr() const {
+    Expr expr() const {
         internal_assert(kind_ == IOKind::Scalar);
         return expr_;
     }
@@ -1492,10 +1481,10 @@ class StubInput {
  * used directly by user code.
  *
  * Every GIOBase instance can be either a single value or an array-of-values;
- * each of these values can be an Halide::Expr or a Halide::Func. (Note that for an
+ * each of these values can be an Expr or a Func. (Note that for an
  * array-of-values, the types/dimensions of all values in the array must match.)
  *
- * A GIOBase can have multiple Halide::Types, in which case it represents a Tuple.
+ * A GIOBase can have multiple Types, in which case it represents a Tuple.
  * (Note that Tuples are currently only supported for GeneratorOutput, but
  * it is likely that GeneratorInput will be extended to support Tuple as well.)
  *
@@ -1515,22 +1504,26 @@ class GIOBase {
     IOKind kind() const;
 
     bool types_defined() const;
-    const std::vector<Halide::Type> &types() const;
-    Halide::Type type() const;
+    const std::vector<Type> &types() const;
+    Type type() const;
 
     bool dims_defined() const;
     int dims() const;
 
-    const std::vector<Halide::Func> &funcs() const;
-    const std::vector<Halide::Expr> &exprs() const;
+    const std::vector<Func> &funcs() const;
+    const std::vector<Expr> &exprs() const;
+
+    virtual ~GIOBase() = default;
 
-    virtual ~GIOBase();
+    void set_type(const Type &type);
+    void set_dimensions(int dims);
+    void set_array_size(int size);
 
 protected:
     GIOBase(size_t array_size,
             const std::string &name,
             IOKind kind,
-            const std::vector<Halide::Type> &types,
+            const std::vector<Type> &types,
             int dims);
 
     friend class GeneratorBase;
@@ -1541,12 +1534,12 @@ class GIOBase {
 
     const std::string name_;
     const IOKind kind_;
-    mutable std::vector<Halide::Type> types_;  // empty if type is unspecified
+    mutable std::vector<Type> types_;  // empty if type is unspecified
     mutable int dims_;                 // -1 if dim is unspecified
 
     // Exactly one of these will have nonzero length
-    std::vector<Halide::Func> funcs_;
-    std::vector<Halide::Expr> exprs_;
+    std::vector<Func> funcs_;
+    std::vector<Expr> exprs_;
 
     // Generator which owns this Input or Output. Note that this will be null
     // initially; the GeneratorBase itself will set this field when it initially
@@ -1560,7 +1553,7 @@ class GIOBase {
     virtual void verify_internals();
 
     void check_matching_array_size(size_t size) const;
-    void check_matching_types(const std::vector<Halide::Type> &t) const;
+    void check_matching_types(const std::vector<Type> &t) const;
     void check_matching_dims(int d) const;
 
     template<typename ElemType>
@@ -1576,21 +1569,20 @@ class GIOBase {
     template<typename T>
     friend class GeneratorParam_Synthetic;
 
-    // No copy
+public:
     GIOBase(const GIOBase &) = delete;
-    void operator=(const GIOBase &) = delete;
-    // No move
+    GIOBase &operator=(const GIOBase &) = delete;
     GIOBase(GIOBase &&) = delete;
-    void operator=(GIOBase &&) = delete;
+    GIOBase &operator=(GIOBase &&) = delete;
 };
 
 template<>
-inline const std::vector<Halide::Expr> &GIOBase::get_values<Halide::Expr>() const {
+inline const std::vector<Expr> &GIOBase::get_values<Expr>() const {
     return exprs();
 }
 
 template<>
-inline const std::vector<Halide::Func> &GIOBase::get_values<Halide::Func>() const {
+inline const std::vector<Func> &GIOBase::get_values<Func>() const {
     return funcs();
 }
 
@@ -1599,23 +1591,22 @@ class GeneratorInputBase : public GIOBase {
     GeneratorInputBase(size_t array_size,
                        const std::string &name,
                        IOKind kind,
-                       const std::vector<Halide::Type> &t,
+                       const std::vector<Type> &t,
                        int d);
 
-    GeneratorInputBase(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d);
+    GeneratorInputBase(const std::string &name, IOKind kind, const std::vector<Type> &t, int d);
 
     friend class GeneratorBase;
     friend class GeneratorParamInfo;
 
-    std::vector<Halide::Internal::Parameter> parameters_;
+    std::vector<Parameter> parameters_;
 
-    Halide::Internal::Parameter parameter() const;
+    Parameter parameter() const;
 
     void init_internals();
     void set_inputs(const std::vector<StubInput> &inputs);
 
     virtual void set_def_min_max();
-    virtual Halide::Expr get_def_expr() const;
 
     void verify_internals() override;
 
@@ -1629,8 +1620,8 @@ class GeneratorInputBase : public GIOBase {
         return "Input";
     }
 
-    void set_estimate_impl(const Halide::Var &var, const Halide::Expr &min, const Halide::Expr &extent);
-    void set_estimates_impl(const Halide::Region &estimates);
+    void set_estimate_impl(const Var &var, const Expr &min, const Expr &extent);
+    void set_estimates_impl(const Region &estimates);
 
 public:
     ~GeneratorInputBase() override;
@@ -1648,21 +1639,21 @@ class GeneratorInputImpl : public GeneratorInputBase {
     template<typename T2 = T, typename std::enable_if<
                                   // Only allow T2 not-an-array
                                   !std::is_array<T2>::value>::type * = nullptr>
-    GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+    GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
         : GeneratorInputBase(name, kind, t, d) {
     }
 
     template<typename T2 = T, typename std::enable_if<
                                   // Only allow T2[kSomeConst]
                                   std::is_array<T2>::value && std::rank<T2>::value == 1 && (std::extent<T2, 0>::value > 0)>::type * = nullptr>
-    GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+    GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
         : GeneratorInputBase(std::extent<T2, 0>::value, name, kind, t, d) {
     }
 
     template<typename T2 = T, typename std::enable_if<
                                   // Only allow T2[]
                                   std::is_array<T2>::value && std::rank<T2>::value == 1 && std::extent<T2, 0>::value == 0>::type * = nullptr>
-    GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+    GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
         : GeneratorInputBase(-1, name, kind, t, d) {
     }
 
@@ -1698,32 +1689,32 @@ class GeneratorInputImpl : public GeneratorInputBase {
     }
 };
 
-// When forwarding methods to Halide::ImageParam, Halide::Func, etc., we must take
+// When forwarding methods to ImageParam, Func, etc., we must take
 // care with the return types: many of the methods return a reference-to-self
-// (e.g., Halide::ImageParam&); since we create temporaries for most of these forwards,
+// (e.g., ImageParam&); since we create temporaries for most of these forwards,
 // returning a ref will crater because it refers to a now-defunct section of the
 // stack. Happily, simply removing the reference is solves this, since all of the
 // types in question satisfy the property of copies referring to the same underlying
 // structure (returning references is just an optimization). Since this is verbose
 // and used in several places, we'll use a helper macro:
-#define HALIDE_FORWARD_METHOD(Class, Method)                                                                                                         \
-    template<typename... Args>                                                                                                                       \
-    inline auto Method(Args &&... args)->typename std::remove_reference<decltype(std::declval<Class>().Method(std::forward<Args>(args)...))>::type { \
-        return this->template as<Class>().Method(std::forward<Args>(args)...);                                                                       \
+#define HALIDE_FORWARD_METHOD(Class, Method)                                                                                                        \
+    template<typename... Args>                                                                                                                      \
+    inline auto Method(Args &&...args)->typename std::remove_reference<decltype(std::declval<Class>().Method(std::forward<Args>(args)...))>::type { \
+        return this->template as<Class>().Method(std::forward<Args>(args)...);                                                                      \
     }
 
 #define HALIDE_FORWARD_METHOD_CONST(Class, Method)                                                                  \
     template<typename... Args>                                                                                      \
-    inline auto Method(Args &&... args) const->                                                                     \
+    inline auto Method(Args &&...args) const->                                                                      \
         typename std::remove_reference<decltype(std::declval<Class>().Method(std::forward<Args>(args)...))>::type { \
         this->check_gio_access();                                                                                   \
         return this->template as<Class>().Method(std::forward<Args>(args)...);                                      \
     }
 
 template<typename T>
-class GeneratorInput_Buffer : public GeneratorInputImpl<T, Halide::Func> {
+class GeneratorInput_Buffer : public GeneratorInputImpl<T, Func> {
 private:
-    using Super = GeneratorInputImpl<T, Halide::Func>;
+    using Super = GeneratorInputImpl<T, Func>;
 
 protected:
     using TBase = typename Super::TBase;
@@ -1749,28 +1740,28 @@ class GeneratorInput_Buffer : public GeneratorInputImpl<T, Halide::Func> {
 public:
     GeneratorInput_Buffer(const std::string &name)
         : Super(name, IOKind::Buffer,
-                TBase::has_static_halide_type ? std::vector<Halide::Type>{TBase::static_halide_type()} : std::vector<Halide::Type>{},
+                TBase::has_static_halide_type ? std::vector<Type>{TBase::static_halide_type()} : std::vector<Type>{},
                 -1) {
     }
 
-    GeneratorInput_Buffer(const std::string &name, const Halide::Type &t, int d = -1)
+    GeneratorInput_Buffer(const std::string &name, const Type &t, int d = -1)
         : Super(name, IOKind::Buffer, {t}, d) {
-        static_assert(!TBase::has_static_halide_type, "You can only specify a Halide::Type argument for Input<Halide::Buffer<T>> if T is void or omitted.");
+        static_assert(!TBase::has_static_halide_type, "You can only specify a Type argument for Input<Buffer<T>> if T is void or omitted.");
     }
 
     GeneratorInput_Buffer(const std::string &name, int d)
-        : Super(name, IOKind::Buffer, TBase::has_static_halide_type ? std::vector<Halide::Type>{TBase::static_halide_type()} : std::vector<Halide::Type>{}, d) {
+        : Super(name, IOKind::Buffer, TBase::has_static_halide_type ? std::vector<Type>{TBase::static_halide_type()} : std::vector<Type>{}, d) {
     }
 
     template<typename... Args>
-    Halide::Expr operator()(Args &&... args) const {
+    Expr operator()(Args &&...args) const {
         this->check_gio_access();
-        return Halide::Func(*this)(std::forward<Args>(args)...);
+        return Func(*this)(std::forward<Args>(args)...);
     }
 
-    Halide::Expr operator()(std::vector<Halide::Expr> args) const {
+    Expr operator()(std::vector<Expr> args) const {
         this->check_gio_access();
-        return Halide::Func(*this)(std::move(args));
+        return Func(*this)(std::move(args));
     }
 
     template<typename T2>
@@ -1779,52 +1770,47 @@ class GeneratorInput_Buffer : public GeneratorInputImpl<T, Halide::Func> {
         return StubInputBuffer<T2>(this->parameters_.at(0));
     }
 
-    operator Halide::Func() const {
+    operator Func() const {
         this->check_gio_access();
         return this->funcs().at(0);
     }
 
-    operator Halide::ExternFuncArgument() const {
+    operator ExternFuncArgument() const {
         this->check_gio_access();
-        return Halide::ExternFuncArgument(this->parameters_.at(0));
+        return ExternFuncArgument(this->parameters_.at(0));
     }
 
-    GeneratorInput_Buffer<T> &set_estimate(Halide::Var var, Halide::Expr min, Halide::Expr extent) {
+    GeneratorInput_Buffer<T> &set_estimate(Var var, Expr min, Expr extent) {
         this->check_gio_access();
         this->set_estimate_impl(var, min, extent);
         return *this;
     }
 
-    HALIDE_ATTRIBUTE_DEPRECATED("Use set_estimate() instead")
-    GeneratorInput_Buffer<T> &estimate(const Halide::Var &var, const Halide::Expr &min, const Halide::Expr &extent) {
-        return set_estimate(var, min, extent);
-    }
-
-    GeneratorInput_Buffer<T> &set_estimates(const Halide::Region &estimates) {
+    GeneratorInput_Buffer<T> &set_estimates(const Region &estimates) {
         this->check_gio_access();
         this->set_estimates_impl(estimates);
         return *this;
     }
 
-    Halide::Func in() {
+    Func in() {
         this->check_gio_access();
-        return Halide::Func(*this).in();
+        return Func(*this).in();
     }
 
-    Halide::Func in(const Halide::Func &other) {
+    Func in(const Func &other) {
         this->check_gio_access();
-        return Halide::Func(*this).in(other);
+        return Func(*this).in(other);
     }
 
-    Halide::Func in(const std::vector<Halide::Func> &others) {
+    Func in(const std::vector<Func> &others) {
         this->check_gio_access();
-        return Halide::Func(*this).in(others);
+        return Func(*this).in(others);
     }
 
-    operator Halide::ImageParam() const {
+    operator ImageParam() const {
         this->check_gio_access();
-        user_assert(!this->is_array()) << "Cannot convert an Input<Halide::Buffer<>[]> to an Halide::ImageParam; use an explicit subscript operator: " << this->name();
-        return Halide::ImageParam(this->parameters_.at(0), Halide::Func(*this));
+        user_assert(!this->is_array()) << "Cannot convert an Input<Buffer<>[]> to an ImageParam; use an explicit subscript operator: " << this->name();
+        return ImageParam(this->parameters_.at(0), Func(*this));
     }
 
     template<typename T2 = T, typename std::enable_if<std::is_array<T2>::value>::type * = nullptr>
@@ -1834,64 +1820,59 @@ class GeneratorInput_Buffer : public GeneratorInputImpl<T, Halide::Func> {
     }
 
     template<typename T2 = T, typename std::enable_if<std::is_array<T2>::value>::type * = nullptr>
-    Halide::ImageParam operator[](size_t i) const {
+    ImageParam operator[](size_t i) const {
         this->check_gio_access();
-        return Halide::ImageParam(this->parameters_.at(i), this->funcs().at(i));
+        return ImageParam(this->parameters_.at(i), this->funcs().at(i));
     }
 
     template<typename T2 = T, typename std::enable_if<std::is_array<T2>::value>::type * = nullptr>
-    Halide::ImageParam at(size_t i) const {
+    ImageParam at(size_t i) const {
         this->check_gio_access();
-        return Halide::ImageParam(this->parameters_.at(i), this->funcs().at(i));
+        return ImageParam(this->parameters_.at(i), this->funcs().at(i));
     }
 
     template<typename T2 = T, typename std::enable_if<std::is_array<T2>::value>::type * = nullptr>
-    typename std::vector<Halide::ImageParam>::const_iterator begin() const {
-        user_error << "Input<Halide::Buffer<>>::begin() is not supported.";
+    typename std::vector<ImageParam>::const_iterator begin() const {
+        user_error << "Input<Buffer<>>::begin() is not supported.";
         return {};
     }
 
     template<typename T2 = T, typename std::enable_if<std::is_array<T2>::value>::type * = nullptr>
-    typename std::vector<Halide::ImageParam>::const_iterator end() const {
-        user_error << "Input<Halide::Buffer<>>::end() is not supported.";
+    typename std::vector<ImageParam>::const_iterator end() const {
+        user_error << "Input<Buffer<>>::end() is not supported.";
         return {};
     }
 
-    /** Forward methods to the Halide::ImageParam. */
+    /** Forward methods to the ImageParam. */
     // @{
-    HALIDE_FORWARD_METHOD(Halide::ImageParam, dim)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, dim)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, host_alignment)
-    HALIDE_FORWARD_METHOD(Halide::ImageParam, set_host_alignment)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, dimensions)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, left)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, right)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, top)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, bottom)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, width)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, height)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, channels)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, trace_loads)
-    HALIDE_FORWARD_METHOD_CONST(Halide::ImageParam, add_trace_tag)
-
-#ifdef HALIDE_FOR_FPGA
-    HALIDE_FORWARD_METHOD(Halide::ImageParam, hls_interface)
-    HALIDE_FORWARD_METHOD(Halide::ImageParam, hls_bundle)
-    HALIDE_FORWARD_METHOD(Halide::ImageParam, hls_burst)
-#endif // HALIDE_FOR_FPGA
+    HALIDE_FORWARD_METHOD(ImageParam, dim)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, dim)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, host_alignment)
+    HALIDE_FORWARD_METHOD(ImageParam, set_host_alignment)
+    HALIDE_FORWARD_METHOD(ImageParam, store_in)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, dimensions)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, left)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, right)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, top)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, bottom)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, width)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, height)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, channels)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, trace_loads)
+    HALIDE_FORWARD_METHOD_CONST(ImageParam, add_trace_tag)
     // }@
 };
 
 template<typename T>
-class GeneratorInput_Func : public GeneratorInputImpl<T, Halide::Func> {
+class GeneratorInput_Func : public GeneratorInputImpl<T, Func> {
 private:
-    using Super = GeneratorInputImpl<T, Halide::Func>;
+    using Super = GeneratorInputImpl<T, Func>;
 
 protected:
     using TBase = typename Super::TBase;
 
     std::string get_c_type() const override {
-        return "Halide::Func";
+        return "Func";
     }
 
     template<typename T2>
@@ -1900,7 +1881,7 @@ class GeneratorInput_Func : public GeneratorInputImpl<T, Halide::Func> {
     }
 
 public:
-    GeneratorInput_Func(const std::string &name, const Halide::Type &t, int d)
+    GeneratorInput_Func(const std::string &name, const Type &t, int d)
         : Super(name, IOKind::Function, {t}, d) {
     }
 
@@ -1910,7 +1891,7 @@ class GeneratorInput_Func : public GeneratorInputImpl<T, Halide::Func> {
     }
 
     // unspecified dimension
-    GeneratorInput_Func(const std::string &name, const Halide::Type &t)
+    GeneratorInput_Func(const std::string &name, const Type &t)
         : Super(name, IOKind::Function, {t}, -1) {
     }
 
@@ -1919,7 +1900,7 @@ class GeneratorInput_Func : public GeneratorInputImpl<T, Halide::Func> {
         : Super(name, IOKind::Function, {}, -1) {
     }
 
-    GeneratorInput_Func(size_t array_size, const std::string &name, const Halide::Type &t, int d)
+    GeneratorInput_Func(size_t array_size, const std::string &name, const Type &t, int d)
         : Super(array_size, name, IOKind::Function, {t}, d) {
     }
 
@@ -1929,7 +1910,7 @@ class GeneratorInput_Func : public GeneratorInputImpl<T, Halide::Func> {
     }
 
     // unspecified dimension
-    GeneratorInput_Func(size_t array_size, const std::string &name, const Halide::Type &t)
+    GeneratorInput_Func(size_t array_size, const std::string &name, const Type &t)
         : Super(array_size, name, IOKind::Function, {t}, -1) {
     }
 
@@ -1939,160 +1920,186 @@ class GeneratorInput_Func : public GeneratorInputImpl<T, Halide::Func> {
     }
 
     template<typename... Args>
-    Halide::Expr operator()(Args &&... args) const {
+    Expr operator()(Args &&...args) const {
         this->check_gio_access();
         return this->funcs().at(0)(std::forward<Args>(args)...);
     }
 
-    Halide::Expr operator()(const std::vector<Halide::Expr> &args) const {
+    Expr operator()(const std::vector<Expr> &args) const {
         this->check_gio_access();
         return this->funcs().at(0)(args);
     }
 
-    operator Halide::Func() const {
+    operator Func() const {
         this->check_gio_access();
         return this->funcs().at(0);
     }
 
-    operator Halide::ExternFuncArgument() const {
+    operator ExternFuncArgument() const {
         this->check_gio_access();
-        return Halide::ExternFuncArgument(this->parameters_.at(0));
+        return ExternFuncArgument(this->parameters_.at(0));
     }
 
-    GeneratorInput_Func<T> &set_estimate(Halide::Var var, Halide::Expr min, Halide::Expr extent) {
+    GeneratorInput_Func<T> &set_estimate(Var var, Expr min, Expr extent) {
         this->check_gio_access();
         this->set_estimate_impl(var, min, extent);
         return *this;
     }
 
-    HALIDE_ATTRIBUTE_DEPRECATED("Use set_estimate() instead")
-    GeneratorInput_Func<T> &estimate(const Halide::Var &var, const Halide::Expr &min, const Halide::Expr &extent) {
-        return set_estimate(var, min, extent);
-    }
-
-    GeneratorInput_Func<T> &set_estimates(const Halide::Region &estimates) {
+    GeneratorInput_Func<T> &set_estimates(const Region &estimates) {
         this->check_gio_access();
         this->set_estimates_impl(estimates);
         return *this;
     }
 
-    Halide::Func in() {
+    Func in() {
         this->check_gio_access();
-        return Halide::Func(*this).in();
+        return Func(*this).in();
     }
 
-    Halide::Func in(const Halide::Func &other) {
+    Func in(const Func &other) {
         this->check_gio_access();
-        return Halide::Func(*this).in(other);
+        return Func(*this).in(other);
     }
 
-    Halide::Func in(const std::vector<Halide::Func> &others) {
+    Func in(const std::vector<Func> &others) {
         this->check_gio_access();
-        return Halide::Func(*this).in(others);
+        return Func(*this).in(others);
     }
 
-    /** Forward const methods to the underlying Halide::Func. (Non-const methods
-     * aren't available for Input<Halide::Func>.) */
+    /** Forward const methods to the underlying Func. (Non-const methods
+     * aren't available for Input<Func>.) */
     // @{
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, args)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, defined)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, has_update_definition)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, num_update_definitions)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, output_types)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, outputs)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, rvars)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, update_args)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, update_value)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, update_values)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, value)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, values)
-
-#ifdef HALIDE_FOR_FPGA
-    HALIDE_FORWARD_METHOD(Halide::Func, hls_interface)
-    HALIDE_FORWARD_METHOD(Halide::Func, hls_bundle)
-    HALIDE_FORWARD_METHOD(Halide::Func, hls_burst)
-#endif // HALIDE_FOR_FPGA
+    HALIDE_FORWARD_METHOD_CONST(Func, args)
+    HALIDE_FORWARD_METHOD_CONST(Func, defined)
+    HALIDE_FORWARD_METHOD_CONST(Func, has_update_definition)
+    HALIDE_FORWARD_METHOD_CONST(Func, num_update_definitions)
+    HALIDE_FORWARD_METHOD_CONST(Func, output_types)
+    HALIDE_FORWARD_METHOD_CONST(Func, outputs)
+    HALIDE_FORWARD_METHOD_CONST(Func, rvars)
+    HALIDE_FORWARD_METHOD_CONST(Func, update_args)
+    HALIDE_FORWARD_METHOD_CONST(Func, update_value)
+    HALIDE_FORWARD_METHOD_CONST(Func, update_values)
+    HALIDE_FORWARD_METHOD_CONST(Func, value)
+    HALIDE_FORWARD_METHOD_CONST(Func, values)
     // }@
 };
 
 template<typename T>
-class GeneratorInput_Scalar : public GeneratorInputImpl<T, Halide::Expr> {
+class GeneratorInput_DynamicScalar : public GeneratorInputImpl<T, Expr> {
 private:
-    using Super = GeneratorInputImpl<T, Halide::Expr>;
+    using Super = GeneratorInputImpl<T, Expr>;
+
+    static_assert(std::is_same<typename std::remove_all_extents<T>::type, Expr>::value, "GeneratorInput_DynamicScalar is only legal to use with T=Expr for now");
 
 protected:
-    using TBase = typename Super::TBase;
+    std::string get_c_type() const override {
+        return "Expr";
+    }
 
-    const TBase def_{TBase()};
-    const Halide::Expr def_expr_;
+public:
+    explicit GeneratorInput_DynamicScalar(const std::string &name)
+        : Super(name, IOKind::Scalar, {}, 0) {
+        user_assert(!std::is_array<T>::value) << "Input<Expr[]> is not allowed";
+    }
 
-protected:
-    Halide::Expr get_def_expr() const override {
-        return def_expr_;
+    /** You can use this Input as an expression in a halide
+     * function definition */
+    operator Expr() const {
+        this->check_gio_access();
+        return this->exprs().at(0);
+    }
+
+    /** Using an Input as the argument to an external stage treats it
+     * as an Expr */
+    operator ExternFuncArgument() const {
+        this->check_gio_access();
+        return ExternFuncArgument(this->exprs().at(0));
+    }
+
+    void set_estimate(const Expr &value) {
+        this->check_gio_access();
+        for (Parameter &p : this->parameters_) {
+            p.set_estimate(value);
+        }
     }
+};
+
+template<typename T>
+class GeneratorInput_Scalar : public GeneratorInputImpl<T, Expr> {
+private:
+    using Super = GeneratorInputImpl<T, Expr>;
+
+protected:
+    using TBase = typename Super::TBase;
+
+    const TBase def_{TBase()};
+    const Expr def_expr_;
 
     void set_def_min_max() override {
-        for (Halide::Internal::Parameter &p : this->parameters_) {
+        for (Parameter &p : this->parameters_) {
             p.set_scalar<TBase>(def_);
+            p.set_default_value(def_expr_);
         }
     }
 
     std::string get_c_type() const override {
-        return "Halide::Expr";
+        return "Expr";
     }
 
-    // Halide::Expr() doesn't accept a pointer type in its ctor; add a SFINAE adapter
+    // Expr() doesn't accept a pointer type in its ctor; add a SFINAE adapter
     // so that pointer (aka handle) Inputs will get cast to uint64.
     template<typename TBase2 = TBase, typename std::enable_if<!std::is_pointer<TBase2>::value>::type * = nullptr>
-    static Halide::Expr TBaseToExpr(const TBase2 &value) {
-        return Halide::Expr(value);
+    static Expr TBaseToExpr(const TBase2 &value) {
+        return cast<TBase>(Expr(value));
     }
 
     template<typename TBase2 = TBase, typename std::enable_if<std::is_pointer<TBase2>::value>::type * = nullptr>
-    static Halide::Expr TBaseToExpr(const TBase2 &value) {
-        return Halide::Expr((uint64_t)value);
+    static Expr TBaseToExpr(const TBase2 &value) {
+        user_assert(value == 0) << "Zero is the only legal default value for Inputs which are pointer types.\n";
+        return Expr();
     }
 
 public:
     explicit GeneratorInput_Scalar(const std::string &name)
-        : Super(name, IOKind::Scalar, {Halide::type_of<TBase>()}, 0), def_(static_cast<TBase>(0)), def_expr_(Halide::Expr()) {
+        : Super(name, IOKind::Scalar, {type_of<TBase>()}, 0), def_(static_cast<TBase>(0)), def_expr_(Expr()) {
     }
 
     GeneratorInput_Scalar(const std::string &name, const TBase &def)
-        : Super(name, IOKind::Scalar, {Halide::type_of<TBase>()}, 0), def_(def), def_expr_(TBaseToExpr(def)) {
+        : Super(name, IOKind::Scalar, {type_of<TBase>()}, 0), def_(def), def_expr_(TBaseToExpr(def)) {
     }
 
     GeneratorInput_Scalar(size_t array_size,
                           const std::string &name)
-        : Super(array_size, name, IOKind::Scalar, {Halide::type_of<TBase>()}, 0), def_(static_cast<TBase>(0)), def_expr_(Halide::Expr()) {
+        : Super(array_size, name, IOKind::Scalar, {type_of<TBase>()}, 0), def_(static_cast<TBase>(0)), def_expr_(Expr()) {
     }
 
     GeneratorInput_Scalar(size_t array_size,
                           const std::string &name,
                           const TBase &def)
-        : Super(array_size, name, IOKind::Scalar, {Halide::type_of<TBase>()}, 0), def_(def), def_expr_(TBaseToExpr(def)) {
+        : Super(array_size, name, IOKind::Scalar, {type_of<TBase>()}, 0), def_(def), def_expr_(TBaseToExpr(def)) {
     }
 
     /** You can use this Input as an expression in a halide
      * function definition */
-    operator Halide::Expr() const {
+    operator Expr() const {
         this->check_gio_access();
         return this->exprs().at(0);
     }
 
     /** Using an Input as the argument to an external stage treats it
-     * as an Halide::Expr */
-    operator Halide::ExternFuncArgument() const {
+     * as an Expr */
+    operator ExternFuncArgument() const {
         this->check_gio_access();
-        return Halide::ExternFuncArgument(this->exprs().at(0));
+        return ExternFuncArgument(this->exprs().at(0));
     }
 
     template<typename T2 = T, typename std::enable_if<std::is_pointer<T2>::value>::type * = nullptr>
     void set_estimate(const TBase &value) {
         this->check_gio_access();
         user_assert(value == nullptr) << "nullptr is the only valid estimate for Input<PointerType>";
-        Halide::Expr e = reinterpret(Halide::type_of<T2>(), Halide::cast<uint64_t>(0));
-        for (Halide::Internal::Parameter &p : this->parameters_) {
+        Expr e = reinterpret(type_of<T2>(), cast<uint64_t>(0));
+        for (Parameter &p : this->parameters_) {
             p.set_estimate(e);
         }
     }
@@ -2100,11 +2107,11 @@ class GeneratorInput_Scalar : public GeneratorInputImpl<T, Halide::Expr> {
     template<typename T2 = T, typename std::enable_if<!std::is_array<T2>::value && !std::is_pointer<T2>::value>::type * = nullptr>
     void set_estimate(const TBase &value) {
         this->check_gio_access();
-        Halide::Expr e = Halide::Expr(value);
+        Expr e = Expr(value);
         if (std::is_same<T2, bool>::value) {
-            e = Halide::cast<bool>(e);
+            e = cast<bool>(e);
         }
-        for (Halide::Internal::Parameter &p : this->parameters_) {
+        for (Parameter &p : this->parameters_) {
             p.set_estimate(e);
         }
     }
@@ -2112,9 +2119,9 @@ class GeneratorInput_Scalar : public GeneratorInputImpl<T, Halide::Expr> {
     template<typename T2 = T, typename std::enable_if<std::is_array<T2>::value>::type * = nullptr>
     void set_estimate(size_t index, const TBase &value) {
         this->check_gio_access();
-        Halide::Expr e = Halide::Expr(value);
+        Expr e = Expr(value);
         if (std::is_same<T2, bool>::value) {
-            e = Halide::cast<bool>(e);
+            e = cast<bool>(e);
         }
         this->parameters_.at(index).set_estimate(e);
     }
@@ -2128,39 +2135,42 @@ class GeneratorInput_Arithmetic : public GeneratorInput_Scalar<T> {
 protected:
     using TBase = typename Super::TBase;
 
-    const Halide::Expr min_, max_;
+    const Expr min_, max_;
 
-protected:
     void set_def_min_max() override {
         Super::set_def_min_max();
         // Don't set min/max for bool
         if (!std::is_same<TBase, bool>::value) {
-            for (Halide::Internal::Parameter &p : this->parameters_) {
-                if (min_.defined()) p.set_min_value(min_);
-                if (max_.defined()) p.set_max_value(max_);
+            for (Parameter &p : this->parameters_) {
+                if (min_.defined()) {
+                    p.set_min_value(min_);
+                }
+                if (max_.defined()) {
+                    p.set_max_value(max_);
+                }
             }
         }
     }
 
 public:
     explicit GeneratorInput_Arithmetic(const std::string &name)
-        : Super(name), min_(Halide::Expr()), max_(Halide::Expr()) {
+        : Super(name), min_(Expr()), max_(Expr()) {
     }
 
     GeneratorInput_Arithmetic(const std::string &name,
                               const TBase &def)
-        : Super(name, def), min_(Halide::Expr()), max_(Halide::Expr()) {
+        : Super(name, def), min_(Expr()), max_(Expr()) {
     }
 
     GeneratorInput_Arithmetic(size_t array_size,
                               const std::string &name)
-        : Super(array_size, name), min_(Halide::Expr()), max_(Halide::Expr()) {
+        : Super(array_size, name), min_(Expr()), max_(Expr()) {
     }
 
     GeneratorInput_Arithmetic(size_t array_size,
                               const std::string &name,
                               const TBase &def)
-        : Super(array_size, name, def), min_(Halide::Expr()), max_(Halide::Expr()) {
+        : Super(array_size, name, def), min_(Expr()), max_(Expr()) {
     }
 
     GeneratorInput_Arithmetic(const std::string &name,
@@ -2192,9 +2202,10 @@ template<typename T, typename TBase = typename std::remove_all_extents<T>::type>
 using GeneratorInputImplBase =
     typename select_type<
         cond<has_static_halide_type_method<TBase>::value, GeneratorInput_Buffer<T>>,
-        cond<std::is_same<TBase, Halide::Func>::value, GeneratorInput_Func<T>>,
+        cond<std::is_same<TBase, Func>::value, GeneratorInput_Func<T>>,
         cond<std::is_arithmetic<TBase>::value, GeneratorInput_Arithmetic<T>>,
-        cond<std::is_scalar<TBase>::value, GeneratorInput_Scalar<T>>>::type;
+        cond<std::is_scalar<TBase>::value, GeneratorInput_Scalar<T>>,
+        cond<std::is_same<TBase, Expr>::value, GeneratorInput_DynamicScalar<T>>>::type;
 
 }  // namespace Internal
 
@@ -2206,14 +2217,14 @@ class GeneratorInput : public Internal::GeneratorInputImplBase<T> {
 protected:
     using TBase = typename Super::TBase;
 
-    // Trick to avoid ambiguous ctor between Halide::Func-with-dim and int-with-default-value;
+    // Trick to avoid ambiguous ctor between Func-with-dim and int-with-default-value;
     // since we can't use std::enable_if on ctors, define the argument to be one that
-    // can only be properly resolved for TBase=Halide::Func.
+    // can only be properly resolved for TBase=Func.
     struct Unused;
     using IntIfNonScalar =
         typename Internal::select_type<
             Internal::cond<Internal::has_static_halide_type_method<TBase>::value, int>,
-            Internal::cond<std::is_same<TBase, Halide::Func>::value, int>,
+            Internal::cond<std::is_same<TBase, Func>::value, int>,
             Internal::cond<true, Unused>>::type;
 
 public:
@@ -2239,29 +2250,29 @@ class GeneratorInput : public Internal::GeneratorInputImplBase<T> {
         : Super(array_size, name, def, min, max) {
     }
 
-    GeneratorInput(const std::string &name, const Halide::Type &t, int d)
+    GeneratorInput(const std::string &name, const Type &t, int d)
         : Super(name, t, d) {
     }
 
-    GeneratorInput(const std::string &name, const Halide::Type &t)
+    GeneratorInput(const std::string &name, const Type &t)
         : Super(name, t) {
     }
 
-    // Avoid ambiguity between Halide::Func-with-dim and int-with-default
+    // Avoid ambiguity between Func-with-dim and int-with-default
     GeneratorInput(const std::string &name, IntIfNonScalar d)
         : Super(name, d) {
     }
 
-    GeneratorInput(size_t array_size, const std::string &name, const Halide::Type &t, int d)
+    GeneratorInput(size_t array_size, const std::string &name, const Type &t, int d)
         : Super(array_size, name, t, d) {
     }
 
-    GeneratorInput(size_t array_size, const std::string &name, const Halide::Type &t)
+    GeneratorInput(size_t array_size, const std::string &name, const Type &t)
         : Super(array_size, name, t) {
     }
 
-    // Avoid ambiguity between Halide::Func-with-dim and int-with-default
-    //template <typename T2 = T, typename std::enable_if<std::is_same<TBase, Halide::Func>::value>::type * = nullptr>
+    // Avoid ambiguity between Func-with-dim and int-with-default
+    //template <typename T2 = T, typename std::enable_if<std::is_same<TBase, Func>::value>::type * = nullptr>
     GeneratorInput(size_t array_size, const std::string &name, IntIfNonScalar d)
         : Super(array_size, name, d) {
     }
@@ -2275,88 +2286,71 @@ namespace Internal {
 
 class GeneratorOutputBase : public GIOBase {
 protected:
-    template<typename T2, typename std::enable_if<std::is_same<T2, Halide::Func>::value>::type * = nullptr>
+    template<typename T2, typename std::enable_if<std::is_same<T2, Func>::value>::type * = nullptr>
     HALIDE_NO_USER_CODE_INLINE T2 as() const {
-        static_assert(std::is_same<T2, Halide::Func>::value, "Only Halide::Func allowed here");
+        static_assert(std::is_same<T2, Func>::value, "Only Func allowed here");
         internal_assert(kind() != IOKind::Scalar);
         internal_assert(exprs_.empty());
-        user_assert(funcs_.size() == 1) << "Use [] to access individual Halide::Funcs in Output<Halide::Func[]>";
+        user_assert(funcs_.size() == 1) << "Use [] to access individual Funcs in Output<Func[]>";
         return funcs_[0];
     }
 
 public:
-    HALIDE_ATTRIBUTE_DEPRECATED("Use set_estimate() instead")
-    GeneratorOutputBase &estimate(const Halide::Var &var, const Halide::Expr &min, const Halide::Expr &extent) {
-        this->as<Halide::Func>().set_estimate(var, min, extent);
-        return *this;
-    }
-
-    /** Forward schedule-related methods to the underlying Halide::Func. */
+    /** Forward schedule-related methods to the underlying Func. */
     // @{
-    HALIDE_FORWARD_METHOD(Halide::Func, add_trace_tag)
-    HALIDE_FORWARD_METHOD(Halide::Func, align_bounds)
-    HALIDE_FORWARD_METHOD(Halide::Func, align_storage)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, args)
-    HALIDE_FORWARD_METHOD(Halide::Func, bound)
-    HALIDE_FORWARD_METHOD(Halide::Func, bound_extent)
-    HALIDE_FORWARD_METHOD(Halide::Func, compute_at)
-    HALIDE_FORWARD_METHOD(Halide::Func, compute_inline)
-    HALIDE_FORWARD_METHOD(Halide::Func, compute_root)
-    HALIDE_FORWARD_METHOD(Halide::Func, compute_with)
-    HALIDE_FORWARD_METHOD(Halide::Func, copy_to_device)
-    HALIDE_FORWARD_METHOD(Halide::Func, copy_to_host)
-    HALIDE_FORWARD_METHOD(Halide::Func, define_extern)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, defined)
-    HALIDE_FORWARD_METHOD(Halide::Func, fold_storage)
-    HALIDE_FORWARD_METHOD(Halide::Func, fuse)
-    HALIDE_FORWARD_METHOD(Halide::Func, glsl)
-    HALIDE_FORWARD_METHOD(Halide::Func, gpu)
-    HALIDE_FORWARD_METHOD(Halide::Func, gpu_blocks)
-    HALIDE_FORWARD_METHOD(Halide::Func, gpu_single_thread)
-    HALIDE_FORWARD_METHOD(Halide::Func, gpu_threads)
-    HALIDE_FORWARD_METHOD(Halide::Func, gpu_tile)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, has_update_definition)
-    HALIDE_FORWARD_METHOD(Halide::Func, hexagon)
-    HALIDE_FORWARD_METHOD(Halide::Func, in)
-    HALIDE_FORWARD_METHOD(Halide::Func, memoize)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, num_update_definitions)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, output_types)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, outputs)
-    HALIDE_FORWARD_METHOD(Halide::Func, parallel)
-    HALIDE_FORWARD_METHOD(Halide::Func, prefetch)
-    HALIDE_FORWARD_METHOD(Halide::Func, print_loop_nest)
-    HALIDE_FORWARD_METHOD(Halide::Func, rename)
-    HALIDE_FORWARD_METHOD(Halide::Func, reorder)
-    HALIDE_FORWARD_METHOD(Halide::Func, reorder_storage)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, rvars)
-    HALIDE_FORWARD_METHOD(Halide::Func, serial)
-    HALIDE_FORWARD_METHOD(Halide::Func, set_estimate)
-    HALIDE_FORWARD_METHOD(Halide::Func, shader)
-    HALIDE_FORWARD_METHOD(Halide::Func, specialize)
-    HALIDE_FORWARD_METHOD(Halide::Func, specialize_fail)
-    HALIDE_FORWARD_METHOD(Halide::Func, split)
-    HALIDE_FORWARD_METHOD(Halide::Func, store_at)
-    HALIDE_FORWARD_METHOD(Halide::Func, store_root)
-    HALIDE_FORWARD_METHOD(Halide::Func, tile)
-    HALIDE_FORWARD_METHOD(Halide::Func, trace_stores)
-    HALIDE_FORWARD_METHOD(Halide::Func, unroll)
-    HALIDE_FORWARD_METHOD(Halide::Func, update)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, update_args)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, update_value)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, update_values)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, value)
-    HALIDE_FORWARD_METHOD_CONST(Halide::Func, values)
-    HALIDE_FORWARD_METHOD(Halide::Func, vectorize)
-
-#ifdef HALIDE_FOR_FPGA
-    HALIDE_FORWARD_METHOD(Halide::Func, hls_interface)
-    HALIDE_FORWARD_METHOD(Halide::Func, hls_bundle)
-    HALIDE_FORWARD_METHOD(Halide::Func, hls_burst)
-
-    std::tuple<std::vector<Halide::Func>, std::vector<Halide::Func>> accelerate(const std::vector<Halide::Func> &inputs, const std::vector<Halide::Func> &outputs, Halide::Var x = Halide::Var::outermost(), bool dma_for_this = true) {
-        return this->as<Halide::Func>().accelerate(inputs, outputs, x, dma_for_this);
-    }
-#endif // HALIDE_FOR_FPGA
+    HALIDE_FORWARD_METHOD(Func, add_trace_tag)
+    HALIDE_FORWARD_METHOD(Func, align_bounds)
+    HALIDE_FORWARD_METHOD(Func, align_extent)
+    HALIDE_FORWARD_METHOD(Func, align_storage)
+    HALIDE_FORWARD_METHOD_CONST(Func, args)
+    HALIDE_FORWARD_METHOD(Func, bound)
+    HALIDE_FORWARD_METHOD(Func, bound_extent)
+    HALIDE_FORWARD_METHOD(Func, compute_at)
+    HALIDE_FORWARD_METHOD(Func, compute_inline)
+    HALIDE_FORWARD_METHOD(Func, compute_root)
+    HALIDE_FORWARD_METHOD(Func, compute_with)
+    HALIDE_FORWARD_METHOD(Func, copy_to_device)
+    HALIDE_FORWARD_METHOD(Func, copy_to_host)
+    HALIDE_FORWARD_METHOD(Func, define_extern)
+    HALIDE_FORWARD_METHOD_CONST(Func, defined)
+    HALIDE_FORWARD_METHOD(Func, fold_storage)
+    HALIDE_FORWARD_METHOD(Func, fuse)
+    HALIDE_FORWARD_METHOD(Func, gpu)
+    HALIDE_FORWARD_METHOD(Func, gpu_blocks)
+    HALIDE_FORWARD_METHOD(Func, gpu_single_thread)
+    HALIDE_FORWARD_METHOD(Func, gpu_threads)
+    HALIDE_FORWARD_METHOD(Func, gpu_tile)
+    HALIDE_FORWARD_METHOD_CONST(Func, has_update_definition)
+    HALIDE_FORWARD_METHOD(Func, hexagon)
+    HALIDE_FORWARD_METHOD(Func, in)
+    HALIDE_FORWARD_METHOD(Func, memoize)
+    HALIDE_FORWARD_METHOD_CONST(Func, num_update_definitions)
+    HALIDE_FORWARD_METHOD_CONST(Func, output_types)
+    HALIDE_FORWARD_METHOD_CONST(Func, outputs)
+    HALIDE_FORWARD_METHOD(Func, parallel)
+    HALIDE_FORWARD_METHOD(Func, prefetch)
+    HALIDE_FORWARD_METHOD(Func, print_loop_nest)
+    HALIDE_FORWARD_METHOD(Func, rename)
+    HALIDE_FORWARD_METHOD(Func, reorder)
+    HALIDE_FORWARD_METHOD(Func, reorder_storage)
+    HALIDE_FORWARD_METHOD_CONST(Func, rvars)
+    HALIDE_FORWARD_METHOD(Func, serial)
+    HALIDE_FORWARD_METHOD(Func, set_estimate)
+    HALIDE_FORWARD_METHOD(Func, specialize)
+    HALIDE_FORWARD_METHOD(Func, specialize_fail)
+    HALIDE_FORWARD_METHOD(Func, split)
+    HALIDE_FORWARD_METHOD(Func, store_at)
+    HALIDE_FORWARD_METHOD(Func, store_root)
+    HALIDE_FORWARD_METHOD(Func, tile)
+    HALIDE_FORWARD_METHOD(Func, trace_stores)
+    HALIDE_FORWARD_METHOD(Func, unroll)
+    HALIDE_FORWARD_METHOD(Func, update)
+    HALIDE_FORWARD_METHOD_CONST(Func, update_args)
+    HALIDE_FORWARD_METHOD_CONST(Func, update_value)
+    HALIDE_FORWARD_METHOD_CONST(Func, update_values)
+    HALIDE_FORWARD_METHOD_CONST(Func, value)
+    HALIDE_FORWARD_METHOD_CONST(Func, values)
+    HALIDE_FORWARD_METHOD(Func, vectorize)
     // }@
 
 #undef HALIDE_OUTPUT_FORWARD
@@ -2366,12 +2360,12 @@ class GeneratorOutputBase : public GIOBase {
     GeneratorOutputBase(size_t array_size,
                         const std::string &name,
                         IOKind kind,
-                        const std::vector<Halide::Type> &t,
+                        const std::vector<Type> &t,
                         int d);
 
     GeneratorOutputBase(const std::string &name,
                         IOKind kind,
-                        const std::vector<Halide::Type> &t,
+                        const std::vector<Type> &t,
                         int d);
 
     friend class GeneratorBase;
@@ -2381,7 +2375,7 @@ class GeneratorOutputBase : public GIOBase {
     void resize(size_t size);
 
     virtual std::string get_c_type() const {
-        return "Halide::Func";
+        return "Func";
     }
 
     void check_value_writable() const override;
@@ -2398,7 +2392,7 @@ template<typename T>
 class GeneratorOutputImpl : public GeneratorOutputBase {
 protected:
     using TBase = typename std::remove_all_extents<T>::type;
-    using ValueType = Halide::Func;
+    using ValueType = Func;
 
     bool is_array() const override {
         return std::is_array<T>::value;
@@ -2407,45 +2401,45 @@ class GeneratorOutputImpl : public GeneratorOutputBase {
     template<typename T2 = T, typename std::enable_if<
                                   // Only allow T2 not-an-array
                                   !std::is_array<T2>::value>::type * = nullptr>
-    GeneratorOutputImpl(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+    GeneratorOutputImpl(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
         : GeneratorOutputBase(name, kind, t, d) {
     }
 
     template<typename T2 = T, typename std::enable_if<
                                   // Only allow T2[kSomeConst]
                                   std::is_array<T2>::value && std::rank<T2>::value == 1 && (std::extent<T2, 0>::value > 0)>::type * = nullptr>
-    GeneratorOutputImpl(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+    GeneratorOutputImpl(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
         : GeneratorOutputBase(std::extent<T2, 0>::value, name, kind, t, d) {
     }
 
     template<typename T2 = T, typename std::enable_if<
                                   // Only allow T2[]
                                   std::is_array<T2>::value && std::rank<T2>::value == 1 && std::extent<T2, 0>::value == 0>::type * = nullptr>
-    GeneratorOutputImpl(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+    GeneratorOutputImpl(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
         : GeneratorOutputBase(-1, name, kind, t, d) {
     }
 
 public:
     template<typename... Args, typename T2 = T, typename std::enable_if<!std::is_array<T2>::value>::type * = nullptr>
-    Halide::FuncRef operator()(Args &&... args) const {
+    FuncRef operator()(Args &&...args) const {
         this->check_gio_access();
         return get_values<ValueType>().at(0)(std::forward<Args>(args)...);
     }
 
     template<typename ExprOrVar, typename T2 = T, typename std::enable_if<!std::is_array<T2>::value>::type * = nullptr>
-    Halide::FuncRef operator()(std::vector<ExprOrVar> args) const {
+    FuncRef operator()(std::vector<ExprOrVar> args) const {
         this->check_gio_access();
         return get_values<ValueType>().at(0)(args);
     }
 
     template<typename T2 = T, typename std::enable_if<!std::is_array<T2>::value>::type * = nullptr>
-    operator Halide::Func() const {
+    operator Func() const {
         this->check_gio_access();
         return get_values<ValueType>().at(0);
     }
 
     template<typename T2 = T, typename std::enable_if<!std::is_array<T2>::value>::type * = nullptr>
-    operator Halide::Stage() const {
+    operator Stage() const {
         this->check_gio_access();
         return get_values<ValueType>().at(0);
     }
@@ -2494,7 +2488,7 @@ class GeneratorOutput_Buffer : public GeneratorOutputImpl<T> {
 private:
     using Super = GeneratorOutputImpl<T>;
 
-    HALIDE_NO_USER_CODE_INLINE void assign_from_func(const Halide::Func &f) {
+    HALIDE_NO_USER_CODE_INLINE void assign_from_func(const Func &f) {
         this->check_value_writable();
 
         internal_assert(f.defined());
@@ -2502,30 +2496,30 @@ class GeneratorOutput_Buffer : public GeneratorOutputImpl<T> {
         if (this->types_defined()) {
             const auto &my_types = this->types();
             user_assert(my_types.size() == f.output_types().size())
-                << "Cannot assign Halide::Func \"" << f.name()
+                << "Cannot assign Func \"" << f.name()
                 << "\" to Output \"" << this->name() << "\"\n"
                 << "Output " << this->name()
                 << " is declared to have " << my_types.size() << " tuple elements"
-                << " but Halide::Func " << f.name()
+                << " but Func " << f.name()
                 << " has " << f.output_types().size() << " tuple elements.\n";
             for (size_t i = 0; i < my_types.size(); i++) {
                 user_assert(my_types[i] == f.output_types().at(i))
-                    << "Cannot assign Halide::Func \"" << f.name()
+                    << "Cannot assign Func \"" << f.name()
                     << "\" to Output \"" << this->name() << "\"\n"
                     << (my_types.size() > 1 ? "In tuple element " + std::to_string(i) + ", " : "")
                     << "Output " << this->name()
                     << " has declared type " << my_types[i]
-                    << " but Halide::Func " << f.name()
+                    << " but Func " << f.name()
                     << " has type " << f.output_types().at(i) << "\n";
             }
         }
         if (this->dims_defined()) {
             user_assert(f.dimensions() == this->dims())
-                << "Cannot assign Halide::Func \"" << f.name()
+                << "Cannot assign Func \"" << f.name()
                 << "\" to Output \"" << this->name() << "\"\n"
                 << "Output " << this->name()
                 << " has declared dimensionality " << this->dims()
-                << " but Halide::Func " << f.name()
+                << " but Func " << f.name()
                 << " has dimensionality " << f.dimensions() << "\n";
         }
 
@@ -2537,20 +2531,19 @@ class GeneratorOutput_Buffer : public GeneratorOutputImpl<T> {
 protected:
     using TBase = typename Super::TBase;
 
-    static std::vector<Halide::Type> my_types(const std::vector<Halide::Type> &t) {
+    static std::vector<Type> my_types(const std::vector<Type> &t) {
         if (TBase::has_static_halide_type) {
-            user_assert(t.empty()) << "Cannot pass a Halide::Type argument for an Output<Halide::Buffer> with a non-void static type\n";
-            return std::vector<Halide::Type>{TBase::static_halide_type()};
+            user_assert(t.empty()) << "Cannot pass a Type argument for an Output<Buffer> with a non-void static type\n";
+            return std::vector<Type>{TBase::static_halide_type()};
         }
         return t;
     }
 
-protected:
-    GeneratorOutput_Buffer(const std::string &name, const std::vector<Halide::Type> &t = {}, int d = -1)
+    GeneratorOutput_Buffer(const std::string &name, const std::vector<Type> &t = {}, int d = -1)
         : Super(name, IOKind::Buffer, my_types(t), d) {
     }
 
-    GeneratorOutput_Buffer(size_t array_size, const std::string &name, const std::vector<Halide::Type> &t = {}, int d = -1)
+    GeneratorOutput_Buffer(size_t array_size, const std::string &name, const std::vector<Type> &t = {}, int d = -1)
         : Super(array_size, name, IOKind::Buffer, my_types(t), d) {
     }
 
@@ -2564,30 +2557,30 @@ class GeneratorOutput_Buffer : public GeneratorOutputImpl<T> {
         }
     }
 
-    template<typename T2, typename std::enable_if<!std::is_same<T2, Halide::Func>::value>::type * = nullptr>
+    template<typename T2, typename std::enable_if<!std::is_same<T2, Func>::value>::type * = nullptr>
     HALIDE_NO_USER_CODE_INLINE T2 as() const {
         return (T2) * this;
     }
 
 public:
-    // Allow assignment from a Halide::Buffer<> to an Output<Halide::Buffer<>>;
+    // Allow assignment from a Buffer<> to an Output<Buffer<>>;
     // this allows us to use a statically-compiled buffer inside a Generator
     // to assign to an output.
     // TODO: This used to take the buffer as a const ref. This no longer works as
     // using it in a Pipeline might change the dev field so it is currently
     // not considered const. We should consider how this really ought to work.
     template<typename T2>
-    HALIDE_NO_USER_CODE_INLINE GeneratorOutput_Buffer<T> &operator=(Halide::Buffer<T2> &buffer) {
+    HALIDE_NO_USER_CODE_INLINE GeneratorOutput_Buffer<T> &operator=(Buffer<T2> &buffer) {
         this->check_gio_access();
         this->check_value_writable();
 
         user_assert(T::can_convert_from(buffer))
             << "Cannot assign to the Output \"" << this->name()
-            << "\": the expression is not convertible to the same Halide::Buffer type and/or dimensions.\n";
+            << "\": the expression is not convertible to the same Buffer type and/or dimensions.\n";
 
         if (this->types_defined()) {
-            user_assert(Halide::Type(buffer.type()) == this->type())
-                << "Output " << this->name() << " should have type=" << this->type() << " but saw type=" << Halide::Type(buffer.type()) << "\n";
+            user_assert(Type(buffer.type()) == this->type())
+                << "Output " << this->name() << " should have type=" << this->type() << " but saw type=" << Type(buffer.type()) << "\n";
         }
         if (this->dims_defined()) {
             user_assert(buffer.dimensions() == this->dims())
@@ -2596,12 +2589,12 @@ class GeneratorOutput_Buffer : public GeneratorOutputImpl<T> {
 
         internal_assert(this->exprs_.empty() && this->funcs_.size() == 1);
         user_assert(!this->funcs_.at(0).defined());
-        this->funcs_.at(0)(Halide::_) = buffer(Halide::_);
+        this->funcs_.at(0)(_) = buffer(_);
 
         return *this;
     }
 
-    // Allow assignment from a StubOutputBuffer to an Output<Halide::Buffer>;
+    // Allow assignment from a StubOutputBuffer to an Output<Buffer>;
     // this allows us to pipeline the results of a Stub to the results
     // of the enclosing Generator.
     template<typename T2>
@@ -2611,18 +2604,18 @@ class GeneratorOutput_Buffer : public GeneratorOutputImpl<T> {
         return *this;
     }
 
-    // Allow assignment from a Halide::Func to an Output<Halide::Buffer>;
-    // this allows us to use helper functions that return a plain Halide::Func
-    // to simply set the output(s) without needing a wrapper Halide::Func.
-    GeneratorOutput_Buffer<T> &operator=(const Halide::Func &f) {
+    // Allow assignment from a Func to an Output<Buffer>;
+    // this allows us to use helper functions that return a plain Func
+    // to simply set the output(s) without needing a wrapper Func.
+    GeneratorOutput_Buffer<T> &operator=(const Func &f) {
         this->check_gio_access();
         assign_from_func(f);
         return *this;
     }
 
-    operator Halide::OutputImageParam() const {
+    operator OutputImageParam() const {
         this->check_gio_access();
-        user_assert(!this->is_array()) << "Cannot convert an Output<Halide::Buffer<>[]> to an Halide::ImageParam; use an explicit subscript operator: " << this->name();
+        user_assert(!this->is_array()) << "Cannot convert an Output<Buffer<>[]> to an ImageParam; use an explicit subscript operator: " << this->name();
         internal_assert(this->exprs_.empty() && this->funcs_.size() == 1);
         return this->funcs_.at(0).output_buffer();
     }
@@ -2630,25 +2623,26 @@ class GeneratorOutput_Buffer : public GeneratorOutputImpl<T> {
     // 'perfect forwarding' won't work with initializer lists,
     // so hand-roll our own forwarding method for set_estimates,
     // rather than using HALIDE_FORWARD_METHOD.
-    GeneratorOutput_Buffer<T> &set_estimates(const Halide::Region &estimates) {
-        this->as<Halide::OutputImageParam>().set_estimates(estimates);
+    GeneratorOutput_Buffer<T> &set_estimates(const Region &estimates) {
+        this->as<OutputImageParam>().set_estimates(estimates);
         return *this;
     }
 
-    /** Forward methods to the Halide::OutputImageParam. */
+    /** Forward methods to the OutputImageParam. */
     // @{
-    HALIDE_FORWARD_METHOD(Halide::OutputImageParam, dim)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, dim)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, host_alignment)
-    HALIDE_FORWARD_METHOD(Halide::OutputImageParam, set_host_alignment)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, dimensions)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, left)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, right)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, top)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, bottom)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, width)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, height)
-    HALIDE_FORWARD_METHOD_CONST(Halide::OutputImageParam, channels)
+    HALIDE_FORWARD_METHOD(OutputImageParam, dim)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, dim)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, host_alignment)
+    HALIDE_FORWARD_METHOD(OutputImageParam, set_host_alignment)
+    HALIDE_FORWARD_METHOD(OutputImageParam, store_in)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, dimensions)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, left)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, right)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, top)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, bottom)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, width)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, height)
+    HALIDE_FORWARD_METHOD_CONST(OutputImageParam, channels)
     // }@
 };
 
@@ -2657,7 +2651,7 @@ class GeneratorOutput_Func : public GeneratorOutputImpl<T> {
 private:
     using Super = GeneratorOutputImpl<T>;
 
-    HALIDE_NO_USER_CODE_INLINE Halide::Func &get_assignable_func_ref(size_t i) {
+    HALIDE_NO_USER_CODE_INLINE Func &get_assignable_func_ref(size_t i) {
         internal_assert(this->exprs_.empty() && this->funcs_.size() > i);
         return this->funcs_.at(i);
     }
@@ -2665,65 +2659,59 @@ class GeneratorOutput_Func : public GeneratorOutputImpl<T> {
 protected:
     using TBase = typename Super::TBase;
 
-protected:
     GeneratorOutput_Func(const std::string &name)
-        : Super(name, IOKind::Function, std::vector<Halide::Type>{}, -1) {
+        : Super(name, IOKind::Function, std::vector<Type>{}, -1) {
     }
 
-    GeneratorOutput_Func(const std::string &name, const std::vector<Halide::Type> &t, int d = -1)
+    GeneratorOutput_Func(const std::string &name, const std::vector<Type> &t, int d = -1)
         : Super(name, IOKind::Function, t, d) {
     }
 
-    GeneratorOutput_Func(size_t array_size, const std::string &name, const std::vector<Halide::Type> &t, int d)
+    GeneratorOutput_Func(size_t array_size, const std::string &name, const std::vector<Type> &t, int d)
         : Super(array_size, name, IOKind::Function, t, d) {
     }
 
 public:
-    // Allow Output<Halide::Func> = Halide::Func
+    // Allow Output<Func> = Func
     template<typename T2 = T, typename std::enable_if<!std::is_array<T2>::value>::type * = nullptr>
-    GeneratorOutput_Func<T> &operator=(const Halide::Func &f) {
+    GeneratorOutput_Func<T> &operator=(const Func &f) {
         this->check_gio_access();
         this->check_value_writable();
 
-        // Don't bother verifying the Halide::Func type, dimensions, etc., here:
+        // Don't bother verifying the Func type, dimensions, etc., here:
         // That's done later, when we produce the pipeline.
         get_assignable_func_ref(0) = f;
         return *this;
     }
 
-    // Allow Output<Halide::Func[]> = Halide::Func
+    // Allow Output<Func[]> = Func
     template<typename T2 = T, typename std::enable_if<std::is_array<T2>::value>::type * = nullptr>
-    Halide::Func &operator[](size_t i) {
+    Func &operator[](size_t i) {
         this->check_gio_access();
         this->check_value_writable();
         return get_assignable_func_ref(i);
     }
 
-    // Allow Halide::Func = Output<Halide::Func[]>
+    // Allow Func = Output<Func[]>
     template<typename T2 = T, typename std::enable_if<std::is_array<T2>::value>::type * = nullptr>
-    const Halide::Func &operator[](size_t i) const {
+    const Func &operator[](size_t i) const {
         this->check_gio_access();
         return Super::operator[](i);
     }
 
-    GeneratorOutput_Func<T> &set_estimate(const Halide::Var &var, const Halide::Expr &min, const Halide::Expr &extent) {
+    GeneratorOutput_Func<T> &set_estimate(const Var &var, const Expr &min, const Expr &extent) {
         this->check_gio_access();
         internal_assert(this->exprs_.empty() && !this->funcs_.empty());
-        for (Halide::Func &f : this->funcs_) {
+        for (Func &f : this->funcs_) {
             f.set_estimate(var, min, extent);
         }
         return *this;
     }
 
-    HALIDE_ATTRIBUTE_DEPRECATED("Use set_estimate() instead")
-    GeneratorOutput_Func<T> &estimate(const Halide::Var &var, const Halide::Expr &min, const Halide::Expr &extent) {
-        return set_estimate(var, min, extent);
-    }
-
-    GeneratorOutput_Func<T> &set_estimates(const Halide::Region &estimates) {
+    GeneratorOutput_Func<T> &set_estimates(const Region &estimates) {
         this->check_gio_access();
         internal_assert(this->exprs_.empty() && !this->funcs_.empty());
-        for (Halide::Func &f : this->funcs_) {
+        for (Func &f : this->funcs_) {
             f.set_estimates(estimates);
         }
         return *this;
@@ -2738,13 +2726,12 @@ class GeneratorOutput_Arithmetic : public GeneratorOutputImpl<T> {
 protected:
     using TBase = typename Super::TBase;
 
-protected:
     explicit GeneratorOutput_Arithmetic(const std::string &name)
-        : Super(name, IOKind::Function, {Halide::type_of<TBase>()}, 0) {
+        : Super(name, IOKind::Function, {type_of<TBase>()}, 0) {
     }
 
     GeneratorOutput_Arithmetic(size_t array_size, const std::string &name)
-        : Super(array_size, name, IOKind::Function, {Halide::type_of<TBase>()}, 0) {
+        : Super(array_size, name, IOKind::Function, {type_of<TBase>()}, 0) {
     }
 };
 
@@ -2752,7 +2739,7 @@ template<typename T, typename TBase = typename std::remove_all_extents<T>::type>
 using GeneratorOutputImplBase =
     typename select_type<
         cond<has_static_halide_type_method<TBase>::value, GeneratorOutput_Buffer<T>>,
-        cond<std::is_same<TBase, Halide::Func>::value, GeneratorOutput_Func<T>>,
+        cond<std::is_same<TBase, Func>::value, GeneratorOutput_Func<T>>,
         cond<std::is_arithmetic<TBase>::value, GeneratorOutput_Arithmetic<T>>>::type;
 
 }  // namespace Internal
@@ -2782,11 +2769,11 @@ class GeneratorOutput : public Internal::GeneratorOutputImplBase<T> {
         : Super(name, {}, d) {
     }
 
-    GeneratorOutput(const std::string &name, const Halide::Type &t, int d)
+    GeneratorOutput(const std::string &name, const Type &t, int d)
         : Super(name, {t}, d) {
     }
 
-    GeneratorOutput(const std::string &name, const std::vector<Halide::Type> &t, int d)
+    GeneratorOutput(const std::string &name, const std::vector<Type> &t, int d)
         : Super(name, t, d) {
     }
 
@@ -2794,11 +2781,11 @@ class GeneratorOutput : public Internal::GeneratorOutputImplBase<T> {
         : Super(array_size, name, {}, d) {
     }
 
-    GeneratorOutput(size_t array_size, const std::string &name, const Halide::Type &t, int d)
+    GeneratorOutput(size_t array_size, const std::string &name, const Type &t, int d)
         : Super(array_size, name, {t}, d) {
     }
 
-    GeneratorOutput(size_t array_size, const std::string &name, const std::vector<Halide::Type> &t, int d)
+    GeneratorOutput(size_t array_size, const std::string &name, const std::vector<Type> &t, int d)
         : Super(array_size, name, t, d) {
     }
 
@@ -2806,7 +2793,7 @@ class GeneratorOutput : public Internal::GeneratorOutputImplBase<T> {
     // using it in a Pipeline might change the dev field so it is currently
     // not considered const. We should consider how this really ought to work.
     template<typename T2>
-    GeneratorOutput<T> &operator=(Halide::Buffer<T2> &buffer) {
+    GeneratorOutput<T> &operator=(Buffer<T2> &buffer) {
         Super::operator=(buffer);
         return *this;
     }
@@ -2817,7 +2804,7 @@ class GeneratorOutput : public Internal::GeneratorOutputImplBase<T> {
         return *this;
     }
 
-    GeneratorOutput<T> &operator=(const Halide::Func &f) {
+    GeneratorOutput<T> &operator=(const Func &f) {
         Super::operator=(f);
         return *this;
     }
@@ -2834,7 +2821,7 @@ T parse_scalar(const std::string &value) {
     return t;
 }
 
-std::vector<Halide::Type> parse_halide_type_list(const std::string &types);
+std::vector<Type> parse_halide_type_list(const std::string &types);
 
 enum class SyntheticParamType { Type,
                                 Dim,
@@ -2924,7 +2911,7 @@ class GeneratorStub;
  * Generators are compiled in a compatible way.
  *
  * If you are using this at "top level" (e.g. with the JIT), you can construct a GeneratorContext
- * with a Halide::Target:
+ * with a Target:
  * \code
  *   auto my_stub = MyStub(
  *       GeneratorContext(get_target_from_environment()),
@@ -2955,30 +2942,30 @@ class GeneratorStub;
  */
 class GeneratorContext {
 public:
-    using ExternsMap = std::map<std::string, Halide::ExternalCode>;
+    using ExternsMap = std::map<std::string, ExternalCode>;
 
-    explicit GeneratorContext(const Halide::Target &t,
+    explicit GeneratorContext(const Target &t,
                               bool auto_schedule = false,
-                              const Halide::MachineParams &machine_params = Halide::MachineParams::generic());
-    virtual ~GeneratorContext();
+                              const MachineParams &machine_params = MachineParams::generic());
+    virtual ~GeneratorContext() = default;
 
-    inline Halide::Target get_target() const {
+    inline Target get_target() const {
         return target;
     }
     inline bool get_auto_schedule() const {
         return auto_schedule;
     }
-    inline Halide::MachineParams get_machine_params() const {
+    inline MachineParams get_machine_params() const {
         return machine_params;
     }
 
-    /** Generators can register Halide::ExternalCode objects onto
+    /** Generators can register ExternalCode objects onto
      * themselves. The Generator infrastructure will arrange to have
-     * this Halide::ExternalCode appended to the Halide::Module that is finally
+     * this ExternalCode appended to the Module that is finally
      * compiled using the Generator. This allows encapsulating
      * functionality that depends on external libraries or handwritten
      * code for various targets. The name argument should match the
-     * name of the Halide::ExternalCode block and is used to ensure the same
+     * name of the ExternalCode block and is used to ensure the same
      * code block is not duplicated in the output. Halide does not do
      * anything other than to compare names for equality. To guarantee
      * uniqueness in public code, we suggest using a Java style
@@ -2997,21 +2984,21 @@ class GeneratorContext {
     }
 
     template<typename T, typename... Args>
-    inline std::unique_ptr<T> apply(const Args &... args) const {
+    inline std::unique_ptr<T> apply(const Args &...args) const {
         auto t = this->create<T>();
         t->apply(args...);
         return t;
     }
 
 protected:
-    GeneratorParam<Halide::Target> target;
+    GeneratorParam<Target> target;
     GeneratorParam<bool> auto_schedule;
-    GeneratorParam<Halide::MachineParams> machine_params;
+    GeneratorParam<MachineParams> machine_params;
     std::shared_ptr<ExternsMap> externs_map;
     std::shared_ptr<Internal::ValueTracker> value_tracker;
 
     GeneratorContext()
-        : GeneratorContext(Halide::Target()) {
+        : GeneratorContext(Target()) {
     }
 
     virtual void init_from_context(const ion::GeneratorContext &context);
@@ -3020,12 +3007,11 @@ class GeneratorContext {
         return value_tracker;
     }
 
-    // No copy
+public:
     GeneratorContext(const GeneratorContext &) = delete;
-    void operator=(const GeneratorContext &) = delete;
-    // No move
+    GeneratorContext &operator=(const GeneratorContext &) = delete;
     GeneratorContext(GeneratorContext &&) = delete;
-    void operator=(GeneratorContext &&) = delete;
+    GeneratorContext &operator=(GeneratorContext &&) = delete;
 };
 
 class NamesInterface {
@@ -3034,6 +3020,7 @@ class NamesInterface {
     // Import a consistent list of Halide names that can be used in
     // Halide generators without qualification.
     using Expr = Halide::Expr;
+    using EvictionKey = Halide::EvictionKey;
     using ExternFuncArgument = Halide::ExternFuncArgument;
     using Func = Halide::Func;
     using GeneratorContext = ion::GeneratorContext;
@@ -3051,10 +3038,10 @@ class NamesInterface {
     using Type = Halide::Type;
     using Var = Halide::Var;
     template<typename T>
-    static Halide::Expr cast(Halide::Expr e) {
+    static Expr cast(Expr e) {
         return Halide::cast<T>(e);
     }
-    static inline Halide::Expr cast(Halide::Type t, Halide::Expr e) {
+    static inline Expr cast(Halide::Type t, Expr e) {
         return Halide::cast(t, std::move(e));
     }
     template<typename T>
@@ -3087,11 +3074,10 @@ struct NoRealizations<> : std::true_type {};
 
 template<typename T, typename... Args>
 struct NoRealizations<T, Args...> {
-    static const bool value = !std::is_convertible<T, Halide::Realization>::value && NoRealizations<Args...>::value;
+    static const bool value = !std::is_convertible<T, Realization>::value && NoRealizations<Args...>::value;
 };
 
 class GeneratorStub;
-class SimpleGeneratorFactory;
 
 // Note that these functions must never return null:
 // if they cannot return a valid Generator, they must assert-fail.
@@ -3099,7 +3085,7 @@ using GeneratorFactory = std::function<std::unique_ptr<GeneratorBase>(const Gene
 
 struct StringOrLoopLevel {
     std::string string_value;
-    Halide::LoopLevel loop_level;
+    LoopLevel loop_level;
 
     StringOrLoopLevel() = default;
     /*not-explicit*/ StringOrLoopLevel(const char *s)
@@ -3108,7 +3094,7 @@ struct StringOrLoopLevel {
     /*not-explicit*/ StringOrLoopLevel(const std::string &s)
         : string_value(s) {
     }
-    /*not-explicit*/ StringOrLoopLevel(const Halide::LoopLevel &loop_level)
+    /*not-explicit*/ StringOrLoopLevel(const LoopLevel &loop_level)
         : loop_level(loop_level) {
     }
 };
@@ -3138,7 +3124,7 @@ class GeneratorParamInfo {
 public:
     friend class GeneratorBase;
 
-    GeneratorParamInfo(GeneratorBase *generator, const size_t size);
+    GeneratorParamInfo(GeneratorBase *generator, size_t size);
 
     const std::vector<Internal::GeneratorParamBase *> &generator_params() const {
         return filter_generator_params;
@@ -3172,10 +3158,10 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
 
     void emit_cpp_stub(const std::string &stub_file_path);
 
-    // Call build() and produce a Halide::Module for the result.
+    // Call build() and produce a Module for the result.
     // If function_name is empty, generator_name() will be used for the function.
-    Halide::Module build_module(const std::string &function_name = "",
-                        const Halide::LinkageType linkage_type = Halide::LinkageType::ExternalPlusMetadata);
+    Module build_module(const std::string &function_name = "",
+                        LinkageType linkage_type = LinkageType::ExternalPlusMetadata);
 
     /**
      * Build a module that is suitable for using for gradient descent calculation in TensorFlow or PyTorch.
@@ -3183,7 +3169,7 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
      * Essentially:
      *   - A new Pipeline is synthesized from the current Generator (according to the rules below)
      *   - The new Pipeline is autoscheduled (if autoscheduling is requested, but it would be odd not to do so)
-     *   - The Pipeline is compiled to a Halide::Module and returned
+     *   - The Pipeline is compiled to a Module and returned
      *
      * The new Pipeline is adjoint to the original; it has:
      *   - All the same inputs as the original, in the same order
@@ -3191,7 +3177,7 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
      *   - Followed by one output for each unique pairing of original-output + original-input.
      *     (For the common case of just one original-output, this amounts to being one output for each original-input.)
      */
-    Halide::Module build_gradient_module(const std::string &function_name);
+    Module build_gradient_module(const std::string &function_name);
 
     /**
      * set_inputs is a variadic wrapper around set_inputs_vector, which makes usage much simpler
@@ -3204,29 +3190,29 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
      * will assert-fail at Halide compile time.
      */
     template<typename... Args>
-    void set_inputs(const Args &... args) {
+    void set_inputs(const Args &...args) {
         // set_inputs_vector() checks this too, but checking it here allows build_inputs() to avoid out-of-range checks.
         GeneratorParamInfo &pi = this->param_info();
         user_assert(sizeof...(args) == pi.inputs().size())
             << "Expected exactly " << pi.inputs().size()
             << " inputs but got " << sizeof...(args) << "\n";
-        set_inputs_vector(build_inputs(std::forward_as_tuple<const Args &...>(args...), Halide::Internal::make_index_sequence<sizeof...(Args)>{}));
+        set_inputs_vector(build_inputs(std::forward_as_tuple<const Args &...>(args...), make_index_sequence<sizeof...(Args)>{}));
     }
 
-    Halide::Realization realize(std::vector<int32_t> sizes) {
+    Realization realize(std::vector<int32_t> sizes) {
         this->check_scheduled("realize");
         return get_pipeline().realize(std::move(sizes), get_target());
     }
 
-    // Only enable if none of the args are Halide::Realization; otherwise we can incorrectly
-    // select this method instead of the Halide::Realization-as-outparam variant
+    // Only enable if none of the args are Realization; otherwise we can incorrectly
+    // select this method instead of the Realization-as-outparam variant
     template<typename... Args, typename std::enable_if<NoRealizations<Args...>::value>::type * = nullptr>
-    Halide::Realization realize(Args &&... args) {
+    Realization realize(Args &&...args) {
         this->check_scheduled("realize");
         return get_pipeline().realize(std::forward<Args>(args)..., get_target());
     }
 
-    void realize(Halide::Realization r) {
+    void realize(Realization r) {
         this->check_scheduled("realize");
         get_pipeline().realize(r, get_target());
     }
@@ -3238,10 +3224,10 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
     // calling from generate() as long as all Outputs have been defined.)
     Pipeline get_pipeline();
 
-    // Create Input<Halide::Buffer> or Input<Halide::Func> with dynamic type
+    // Create Input<Buffer> or Input<Func> with dynamic type
     template<typename T,
              typename std::enable_if<!std::is_arithmetic<T>::value>::type * = nullptr>
-    GeneratorInput<T> *add_input(const std::string &name, const Halide::Type &t, int dimensions) {
+    GeneratorInput<T> *add_input(const std::string &name, const Type &t, int dimensions) {
         check_exact_phase(GeneratorBase::ConfigureCalled);
         auto *p = new GeneratorInput<T>(name, t, dimensions);
         p->generator = this;
@@ -3250,7 +3236,7 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         return p;
     }
 
-    // Create a Input<Halide::Buffer> or Input<Halide::Func> with compile-time type
+    // Create a Input<Buffer> or Input<Func> with compile-time type
     template<typename T,
              typename std::enable_if<T::has_static_halide_type>::type * = nullptr>
     GeneratorInput<T> *add_input(const std::string &name, int dimensions) {
@@ -3274,10 +3260,23 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         return p;
     }
 
-    // Create Output<Halide::Buffer> or Output<Halide::Func> with dynamic type
+    // Create Input<Expr> with dynamic type
+    template<typename T,
+             typename std::enable_if<std::is_same<T, Expr>::value>::type * = nullptr>
+    GeneratorInput<T> *add_input(const std::string &name, const Type &type) {
+        check_exact_phase(GeneratorBase::ConfigureCalled);
+        auto *p = new GeneratorInput<Expr>(name);
+        p->generator = this;
+        p->set_type(type);
+        param_info_ptr->owned_extras.push_back(std::unique_ptr<Internal::GIOBase>(p));
+        param_info_ptr->filter_inputs.push_back(p);
+        return p;
+    }
+
+    // Create Output<Buffer> or Output<Func> with dynamic type
     template<typename T,
              typename std::enable_if<!std::is_arithmetic<T>::value>::type * = nullptr>
-    GeneratorOutput<T> *add_output(const std::string &name, const Halide::Type &t, int dimensions) {
+    GeneratorOutput<T> *add_output(const std::string &name, const Type &t, int dimensions) {
         check_exact_phase(GeneratorBase::ConfigureCalled);
         auto *p = new GeneratorOutput<T>(name, t, dimensions);
         p->generator = this;
@@ -3286,7 +3285,7 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         return p;
     }
 
-    // Create a Output<Halide::Buffer> or Output<Halide::Func> with compile-time type
+    // Create a Output<Buffer> or Output<Func> with compile-time type
     template<typename T,
              typename std::enable_if<T::has_static_halide_type>::type * = nullptr>
     GeneratorOutput<T> *add_output(const std::string &name, int dimensions) {
@@ -3299,7 +3298,7 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
     }
 
     template<typename... Args>
-    HALIDE_NO_USER_CODE_INLINE void add_requirement(Halide::Expr condition, Args &&... args) {
+    HALIDE_NO_USER_CODE_INLINE void add_requirement(Expr condition, Args &&...args) {
         get_pipeline().add_requirement(condition, std::forward<Args>(args)...);
     }
 
@@ -3324,7 +3323,7 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
     }
 
     template<typename... Args, size_t... Indices>
-    std::vector<std::vector<StubInput>> build_inputs(const std::tuple<const Args &...> &t, Halide::Internal::index_sequence<Indices...>) {
+    std::vector<std::vector<StubInput>> build_inputs(const std::tuple<const Args &...> &t, index_sequence<Indices...>) {
         return {build_input(Indices, std::get<Indices>(t))...};
     }
 
@@ -3389,15 +3388,16 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
     void check_min_phase(Phase expected_phase) const;
     void advance_phase(Phase new_phase);
 
+    void ensure_configure_has_been_called();
+
 private:
-    friend void ::Halide::Internal::generator_test();
+    friend void ::ion::Internal::generator_test();
     friend class GeneratorParamBase;
     friend class GIOBase;
     friend class GeneratorInputBase;
     friend class GeneratorOutputBase;
     friend class GeneratorParamInfo;
     friend class GeneratorStub;
-    friend class SimpleGeneratorFactory;
     friend class StubOutputBufferBase;
 
     const size_t size;
@@ -3431,15 +3431,11 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
     void get_target_from_environment();
 
 public:
-    // Return the Output<Halide::Func> or Output<Halide::Buffer> with the given name,
-    // which must be a singular (non-array) Halide::Func or Halide::Buffer output.
-    // If no such name exists (or is non-array), assert; this method never returns an undefined Halide::Func.
-    Halide::Func get_output(const std::string &n);
-
-    // Return the Output<Halide::Func[]> with the given name, which must be an
-    // array-of-Halide::Func output. If no such name exists (or is non-array), assert;
-    // this method never returns undefined Halide::Funcs.
-    std::vector<Halide::Func> get_array_output(const std::string &n);
+    // Return the output with the given name.
+    // If the output is singular (a non-array), return a vector of size 1.
+    // If no such name exists (or is non-array), assert.
+    // This method never returns undefined Funcs.
+    std::vector<Func> get_outputs(const std::string &n);
 
 private:
     void set_inputs_vector(const std::vector<std::vector<StubInput>> &inputs);
@@ -3449,10 +3445,10 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
     static void check_input_kind(Internal::GeneratorInputBase *in, Internal::IOKind kind);
 
 public:
-    // Allow Halide::Buffer<> if:
-    // -- we are assigning it to an Input<Halide::Buffer<>> (with compatible type and dimensions),
-    // causing the Input<Halide::Buffer<>> to become a precompiled buffer in the generated code.
-    // -- we are assigningit to an Input<Halide::Func>, in which case we just Halide::Func-wrap the Halide::Buffer<>.
+    // Allow Buffer<> if:
+    // -- we are assigning it to an Input<Buffer<>> (with compatible type and dimensions),
+    // causing the Input<Buffer<>> to become a precompiled buffer in the generated code.
+    // -- we are assigningit to an Input<Func>, in which case we just Func-wrap the Buffer<>.
     template<typename T>
     std::vector<StubInput> build_input(size_t i, const Buffer<T> &arg) {
         auto *in = param_info().inputs().at(i);
@@ -3474,10 +3470,10 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         }
     }
 
-    // Allow Input<Halide::Buffer<>> if:
-    // -- we are assigning it to another Input<Halide::Buffer<>> (with compatible type and dimensions),
+    // Allow Input<Buffer<>> if:
+    // -- we are assigning it to another Input<Buffer<>> (with compatible type and dimensions),
     // allowing us to simply pipe a parameter from an enclosing Generator to the Invoker.
-    // -- we are assigningit to an Input<Halide::Func>, in which case we just Func-wrap the Input<Halide::Buffer<>>.
+    // -- we are assigningit to an Input<Func>, in which case we just Func-wrap the Input<Buffer<>>.
     template<typename T>
     std::vector<StubInput> build_input(size_t i, const GeneratorInput<Buffer<T>> &arg) {
         auto *in = param_info().inputs().at(i);
@@ -3497,8 +3493,8 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         }
     }
 
-    // Allow Halide::Func iff we are assigning it to an Input<Halide::Func> (with compatible type and dimensions).
-    std::vector<StubInput> build_input(size_t i, const Halide::Func &arg) {
+    // Allow Func iff we are assigning it to an Input<Func> (with compatible type and dimensions).
+    std::vector<StubInput> build_input(size_t i, const Func &arg) {
         auto *in = param_info().inputs().at(i);
         check_input_kind(in, Internal::IOKind::Function);
         check_input_is_singular(in);
@@ -3507,8 +3503,8 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         return {si};
     }
 
-    // Allow vector<Halide::Func> iff we are assigning it to an Input<Halide::Func[]> (with compatible type and dimensions).
-    std::vector<StubInput> build_input(size_t i, const std::vector<Halide::Func> &arg) {
+    // Allow vector<Func> iff we are assigning it to an Input<Func[]> (with compatible type and dimensions).
+    std::vector<StubInput> build_input(size_t i, const std::vector<Func> &arg) {
         auto *in = param_info().inputs().at(i);
         check_input_kind(in, Internal::IOKind::Function);
         check_input_is_array(in);
@@ -3521,8 +3517,8 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         return siv;
     }
 
-    // Halide::Expr must be Input<Scalar>.
-    std::vector<StubInput> build_input(size_t i, const Halide::Expr &arg) {
+    // Expr must be Input<Scalar>.
+    std::vector<StubInput> build_input(size_t i, const Expr &arg) {
         auto *in = param_info().inputs().at(i);
         check_input_kind(in, Internal::IOKind::Scalar);
         check_input_is_singular(in);
@@ -3531,7 +3527,7 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
     }
 
     // (Array form)
-    std::vector<StubInput> build_input(size_t i, const std::vector<Halide::Expr> &arg) {
+    std::vector<StubInput> build_input(size_t i, const std::vector<Expr> &arg) {
         auto *in = param_info().inputs().at(i);
         check_input_kind(in, Internal::IOKind::Scalar);
         check_input_is_array(in);
@@ -3543,16 +3539,16 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         return siv;
     }
 
-    // Any other type must be convertible to Halide::Expr and must be associated with an Input<Scalar>.
-    // Use is_arithmetic since some Halide::Expr conversions are explicit.
+    // Any other type must be convertible to Expr and must be associated with an Input<Scalar>.
+    // Use is_arithmetic since some Expr conversions are explicit.
     template<typename T,
              typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
     std::vector<StubInput> build_input(size_t i, const T &arg) {
         auto *in = param_info().inputs().at(i);
         check_input_kind(in, Internal::IOKind::Scalar);
         check_input_is_singular(in);
-        // We must use an explicit Halide::Expr() ctor to preserve the type
-        Halide::Expr e(arg);
+        // We must use an explicit Expr() ctor to preserve the type
+        Expr e(arg);
         StubInput si(e);
         return {si};
     }
@@ -3567,21 +3563,18 @@ class GeneratorBase : public NamesInterface, public GeneratorContext {
         std::vector<StubInput> siv;
         siv.reserve(arg.size());
         for (const auto &value : arg) {
-            // We must use an explicit Halide::Expr() ctor to preserve the type;
+            // We must use an explicit Expr() ctor to preserve the type;
             // otherwise, implicit conversions can downgrade (e.g.) float -> int
-            Halide::Expr e(value);
+            Expr e(value);
             siv.emplace_back(e);
         }
         return siv;
     }
 
-private:
-    // No copy
     GeneratorBase(const GeneratorBase &) = delete;
-    void operator=(const GeneratorBase &) = delete;
-    // No move
+    GeneratorBase &operator=(const GeneratorBase &) = delete;
     GeneratorBase(GeneratorBase &&that) = delete;
-    void operator=(GeneratorBase &&that) = delete;
+    GeneratorBase &operator=(GeneratorBase &&that) = delete;
 };
 
 class GeneratorRegistry {
@@ -3603,8 +3596,12 @@ class GeneratorRegistry {
     static GeneratorRegistry &get_registry();
 
     GeneratorRegistry() = default;
+
+public:
     GeneratorRegistry(const GeneratorRegistry &) = delete;
-    void operator=(const GeneratorRegistry &) = delete;
+    GeneratorRegistry &operator=(const GeneratorRegistry &) = delete;
+    GeneratorRegistry(GeneratorRegistry &&that) = delete;
+    GeneratorRegistry &operator=(GeneratorRegistry &&that) = delete;
 };
 
 }  // namespace Internal
@@ -3614,7 +3611,7 @@ class Generator : public Internal::GeneratorBase {
 protected:
     Generator()
         : Internal::GeneratorBase(sizeof(T),
-                                  Halide::Internal::Introspection::get_introspection_helper<T>()) {
+                                  Internal::Introspection::get_introspection_helper<T>()) {
     }
 
 public:
@@ -3639,14 +3636,8 @@ class Generator : public Internal::GeneratorBase {
     using Internal::GeneratorBase::create;
 
     template<typename... Args>
-    void apply(const Args &... args) {
-#ifndef _MSC_VER
-        // VS2015 apparently has some SFINAE issues, so this can inappropriately
-        // trigger there. (We'll still fail when generate() is called, just
-        // with a less-helpful error message.)
-        static_assert(has_generate_method<T>::value, "apply() is not supported for old-style Generators.");
-#endif
-        GeneratorBase::apply(args...);
+    void apply(const Args &...args) {
+        Internal::GeneratorBase::apply(args...);
     }
 
 private:
@@ -3711,7 +3702,10 @@ class Generator : public Internal::GeneratorBase {
     // have build() or configure()/generate()/schedule() methods.
 
     void call_configure_impl(double, double) {
-        // Called as a side effect for build()-method Generators; quietly do nothing.
+        pre_configure();
+        // Called as a side effect for build()-method Generators; quietly do nothing
+        // (except for pre_configure(), to advance the phase).
+        post_configure();
     }
 
     template<typename T2 = T,
@@ -3795,26 +3789,22 @@ class Generator : public Internal::GeneratorBase {
     }
 
 private:
-    friend void ::Halide::Internal::generator_test();
-    friend class Internal::SimpleGeneratorFactory;
-    friend void ::Halide::Internal::generator_test();
-    friend class ::Halide::GeneratorContext;
+    friend void ::ion::Internal::generator_test();
+    friend void ::ion::Internal::generator_test();
+    friend class ::ion::GeneratorContext;
 
-    // No copy
+public:
     Generator(const Generator &) = delete;
-    void operator=(const Generator &) = delete;
-    // No move
+    Generator &operator=(const Generator &) = delete;
     Generator(Generator &&that) = delete;
-    void operator=(Generator &&that) = delete;
+    Generator &operator=(Generator &&that) = delete;
 };
 
 namespace Internal {
 
 class RegisterGenerator {
 public:
-    RegisterGenerator(const char *registered_name, GeneratorFactory generator_factory) {
-        Internal::GeneratorRegistry::register_factory(registered_name, std::move(generator_factory));
-    }
+    RegisterGenerator(const char *registered_name, GeneratorFactory generator_factory);
 };
 
 class GeneratorStub : public NamesInterface {
@@ -3826,23 +3816,17 @@ class GeneratorStub : public NamesInterface {
                   const GeneratorFactory &generator_factory,
                   const GeneratorParamsMap &generator_params,
                   const std::vector<std::vector<Internal::StubInput>> &inputs);
-    std::vector<std::vector<Halide::Func>> generate(const GeneratorParamsMap &generator_params,
+    std::vector<std::vector<Func>> generate(const GeneratorParamsMap &generator_params,
                                             const std::vector<std::vector<Internal::StubInput>> &inputs);
 
     // Output(s)
-    // TODO: identify vars used
-    Halide::Func get_output(const std::string &n) const {
-        return generator->get_output(n);
-    }
-
-    template<typename T2>
-    T2 get_output_buffer(const std::string &n) const {
-        return T2(get_output(n), generator);
+    std::vector<Func> get_outputs(const std::string &n) const {
+        return generator->get_outputs(n);
     }
 
     template<typename T2>
-    std::vector<T2> get_array_output_buffer(const std::string &n) const {
-        auto v = generator->get_array_output(n);
+    std::vector<T2> get_output_buffers(const std::string &n) const {
+        auto v = generator->get_outputs(n);
         std::vector<T2> result;
         for (auto &o : v) {
             result.push_back(T2(o, generator));
@@ -3850,15 +3834,11 @@ class GeneratorStub : public NamesInterface {
         return result;
     }
 
-    std::vector<Halide::Func> get_array_output(const std::string &n) const {
-        return generator->get_array_output(n);
-    }
-
-    static std::vector<StubInput> to_stub_input_vector(const Halide::Expr &e) {
+    static std::vector<StubInput> to_stub_input_vector(const Expr &e) {
         return {StubInput(e)};
     }
 
-    static std::vector<StubInput> to_stub_input_vector(const Halide::Func &f) {
+    static std::vector<StubInput> to_stub_input_vector(const Func &f) {
         return {StubInput(f)};
     }
 
@@ -3890,12 +3870,12 @@ class GeneratorStub : public NamesInterface {
 // defeat our static_assert check; define a dummy type inside so we can
 // check for type aliasing injected by anonymous namespace usage
 namespace ion_register_generator {
-struct ion_global_ns;
+struct halide_global_ns;
 };
 
 #define _ION_REGISTER_GENERATOR_IMPL(GEN_CLASS_NAME, GEN_REGISTRY_NAME, FULLY_QUALIFIED_STUB_NAME)                               \
     namespace ion_register_generator {                                                                                           \
-    struct ion_global_ns;                                                                                                        \
+    struct halide_global_ns;                                                                                                        \
     namespace GEN_REGISTRY_NAME##_ns {                                                                                              \
         std::unique_ptr<ion::Internal::GeneratorBase> factory(const ion::GeneratorContext &context);                          \
         std::unique_ptr<ion::Internal::GeneratorBase> factory(const ion::GeneratorContext &context) {                         \
@@ -3904,7 +3884,7 @@ struct ion_global_ns;
     }                                                                                                                               \
     static auto reg_##GEN_REGISTRY_NAME = ion::Internal::RegisterGenerator(#GEN_REGISTRY_NAME, GEN_REGISTRY_NAME##_ns::factory); \
     }                                                                                                                               \
-    static_assert(std::is_same<::ion_register_generator::ion_global_ns, ion_register_generator::ion_global_ns>::value,  \
+    static_assert(std::is_same<::ion_register_generator::halide_global_ns, ion_register_generator::halide_global_ns>::value,  \
                   "ION_REGISTER_GENERATOR must be used at global scope");
 
 #define _ION_REGISTER_GENERATOR2(GEN_CLASS_NAME, GEN_REGISTRY_NAME) \
@@ -3956,7 +3936,7 @@ struct ion_global_ns;
 // pass it thru as-is, this is fine (and even MSVC's 'broken' __VA_ARGS__ should be OK here).
 #define ION_REGISTER_GENERATOR_ALIAS(GEN_REGISTRY_NAME, ORIGINAL_REGISTRY_NAME, ...)                                             \
     namespace ion_register_generator {                                                                                           \
-    struct ion_global_ns;                                                                                                        \
+    struct halide_global_ns;                                                                                                        \
     namespace ORIGINAL_REGISTRY_NAME##_ns {                                                                                         \
         std::unique_ptr<ion::Internal::GeneratorBase> factory(const ion::GeneratorContext &context);                          \
     }                                                                                                                               \
@@ -3970,8 +3950,7 @@ struct ion_global_ns;
     }                                                                                                                               \
     static auto reg_##GEN_REGISTRY_NAME = ion::Internal::RegisterGenerator(#GEN_REGISTRY_NAME, GEN_REGISTRY_NAME##_ns::factory); \
     }                                                                                                                               \
-    static_assert(std::is_same<::ion_register_generator::ion_global_ns, ion_register_generator::ion_global_ns>::value,  \
+    static_assert(std::is_same<::ion_register_generator::halide_global_ns, ion_register_generator::halide_global_ns>::value,  \
                   "ION_REGISTER_GENERATOR_ALIAS must be used at global scope");
 
-
 #endif  // ION_GENERATOR_H
diff --git a/src/builder.cc b/src/builder.cc
index 0d92babb..b903f426 100644
--- a/src/builder.cc
+++ b/src/builder.cc
@@ -201,7 +201,7 @@ std::vector<std::tuple<std::string, Halide::Func>> collect_unbound_outputs(const
                 // }
             } else {
                 if (!is_dereferenced(nodes, node_id, output->name())) {
-                    unbounds.push_back(std::make_tuple(output_name(node_id, output->name()), bb->get_output(output->name())));
+                    unbounds.push_back(std::make_tuple(output_name(node_id, output->name()), bb->get_outputs(output->name()).front()));
                 }
             }
         }
@@ -260,7 +260,7 @@ Halide::Pipeline Builder::build(const ion::PortMap& pm, std::vector<Halide::Buff
                         // This block should be executed when g.run is called with appropriate PortMap.
                         e = pm.get_param_expr(p.key());
                     } else {
-                        e = Halide::Param<void>(p.type(), p.key());
+                        e = p.expr();
                     }
                     args.push_back(bb->build_input(j, e));
                 } else if (k == Internal::IOKind::Function) {
@@ -277,7 +277,7 @@ Halide::Pipeline Builder::build(const ion::PortMap& pm, std::vector<Halide::Buff
                 }
             } else {
                 if (in->is_array()) {
-                    auto f_array = bbs[p.node_id()]->get_array_output(p.key());
+                    auto f_array = bbs[p.node_id()]->get_outputs(p.key());
                     if (in->kind() == Internal::IOKind::Scalar) {
                         std::vector<Halide::Expr> exprs;
                         for (auto &f : f_array) {
@@ -293,7 +293,7 @@ Halide::Pipeline Builder::build(const ion::PortMap& pm, std::vector<Halide::Buff
                         throw std::runtime_error("fixme");
                     }
                 } else {
-                    Halide::Func f = bbs[p.node_id()]->get_output(p.key());
+                    Halide::Func f = bbs[p.node_id()]->get_outputs(p.key()).front();
                     if (in->kind() == Internal::IOKind::Scalar) {
                         if (f.dimensions() != 0) {
                             throw std::runtime_error("Invalid port connection : " + in->name());
@@ -322,7 +322,7 @@ Halide::Pipeline Builder::build(const ion::PortMap& pm, std::vector<Halide::Buff
            for (auto info : bbs[node_id]->param_info().outputs()) {
                if (info->name() == port_key) {
                    if (info->is_array()) {
-                       auto fs = bbs[node_id]->get_array_output(port_key);
+                       auto fs = bbs[node_id]->get_outputs(port_key);
                        if (fs.size() != kv.second.size()) {
                            throw std::runtime_error("Invalid size of array : " + node_id + ", " + port_key);
                        }
@@ -331,7 +331,7 @@ Halide::Pipeline Builder::build(const ion::PortMap& pm, std::vector<Halide::Buff
                            outputs->push_back(kv.second[i]);
                        }
                    } else {
-                       auto f = bbs[node_id]->get_output(port_key);
+                       auto f = bbs[node_id]->get_outputs(port_key).front();
                        if (1 != kv.second.size()) {
                            throw std::runtime_error("Invalid size of array : " + node_id + ", " + port_key);
                        }
@@ -356,7 +356,7 @@ Halide::Pipeline Builder::build(const ion::PortMap& pm, std::vector<Halide::Buff
                 auto p = n.ports()[j];
 
                 if (!p.node_id().empty()) {
-                    for (const auto &f : bbs[p.node_id()]->get_array_output(p.key())) {
+                    for (const auto &f : bbs[p.node_id()]->get_outputs(p.key())) {
                         dereferenced[p.node_id()].emplace_back(f.name());
                     }
                 }
diff --git a/src/generator.cc b/src/generator.cc
index 23b8e139..25a872d5 100644
--- a/src/generator.cc
+++ b/src/generator.cc
@@ -1,4 +1,3 @@
-// Halide/src/Generator.cpp
 #include <cmath>
 #include <fstream>
 #include <unordered_map>
@@ -9,18 +8,89 @@
 #endif
 #ifdef _WIN32
 #include <windows.h>
-#else
-#include <dlfcn.h>
 #endif
 
+
 #include "ion/generator.h"
 
-#include "Halide.h"
+#include <Halide.h>
 
+// Declare type alias
 namespace ion {
 
-GeneratorContext::GeneratorContext(const Halide::Target &t, bool auto_schedule,
-                                   const Halide::MachineParams &machine_params)
+using Halide::Argument;
+using Halide::ArgumentEstimates;
+using Halide::AutoSchedulerResults;
+using Halide::BFloat;
+using Halide::Bool;
+using Halide::CompilerLoggerFactory;
+using Halide::Derivative;
+using Halide::Float;
+using Halide::Handle;
+using Halide::Int;
+using Halide::Output;
+using Halide::Pipeline;
+using Halide::RDom;
+using Halide::Range;
+using Halide::Tuple;
+using Halide::UInt;
+using Halide::get_host_target;
+using Halide::load_plugin;
+using Halide::max;
+using Halide::min;
+using Halide::operator!=;
+using Halide::operator%;
+using Halide::operator&&;
+using Halide::operator*;
+using Halide::operator*=;
+using Halide::operator+;
+using Halide::operator+=;
+using Halide::operator-;
+using Halide::operator-=;
+using Halide::operator/;
+using Halide::operator/=;
+using Halide::operator<;
+using Halide::operator<=;
+using Halide::operator==;
+using Halide::operator>;
+using Halide::operator>=;
+using Halide::operator||;
+
+namespace BoundaryConditions {
+
+using Halide::BoundaryConditions::constant_exterior;
+
+} // namespace BoundaryConditions
+
+namespace Internal {
+
+using Halide::Internal::Call;
+using Halide::Internal::CompilerLogger;
+using Halide::Internal::Indentation;
+using Halide::Internal::JSONCompilerLogger;
+using Halide::Internal::ObjectInstanceRegistry;
+using Halide::Internal::OutputInfo;
+using Halide::Internal::Variable;
+using Halide::Internal::debug;
+using Halide::Internal::extract_namespaces;
+using Halide::Internal::get_env_variable;
+using Halide::Internal::get_output_info;
+using Halide::Internal::make_zero;
+using Halide::Internal::replace_all;
+using Halide::Internal::split_string;
+
+namespace Introspection {
+
+
+} // namespace Introspection
+} // namespace Internal
+} // namespace ion
+
+
+namespace ion {
+
+GeneratorContext::GeneratorContext(const Target &t, bool auto_schedule,
+                                   const MachineParams &machine_params)
     : target("target", t),
       auto_schedule("auto_schedule", auto_schedule),
       machine_params("machine_params", machine_params),
@@ -28,10 +98,6 @@ GeneratorContext::GeneratorContext(const Halide::Target &t, bool auto_schedule,
       value_tracker(std::make_shared<Internal::ValueTracker>()) {
 }
 
-GeneratorContext::~GeneratorContext() {
-    // nothing
-}
-
 void GeneratorContext::init_from_context(const ion::GeneratorContext &context) {
     target.set(context.get_target());
     auto_schedule.set(context.get_auto_schedule());
@@ -62,11 +128,19 @@ bool is_alnum(char c) {
 // -- 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;
+    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;
+        if (!is_alnum(n[i])) {
+            return false;
+        }
+        if (n[i] == '_' && n[i - 1] == '_') {
+            return false;
+        }
     }
     return true;
 }
@@ -75,66 +149,66 @@ 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 = Halide::Internal::extract_namespaces(function_name, 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::map<Halide::Output, std::string> compute_output_files(const Halide::Target &target,
-                                                           const std::string &base_path,
-                                                           const std::set<Halide::Output> &outputs) {
-    std::map<Halide::Output, const Halide::Internal::OutputInfo> output_info = Halide::Internal::get_output_info(target);
+std::map<Output, std::string> compute_output_files(const Target &target,
+                                                   const std::string &base_path,
+                                                   const std::set<Output> &outputs) {
+    std::map<Output, const OutputInfo> output_info = get_output_info(target);
 
-    std::map<Halide::Output, std::string> output_files;
+    std::map<Output, std::string> output_files;
     for (auto o : outputs) {
         output_files[o] = base_path + output_info.at(o).extension;
     }
     return output_files;
 }
 
-Halide::Argument to_argument(const Halide::Internal::Parameter &param, const Halide::Expr &default_value) {
-    Halide::ArgumentEstimates argument_estimates = param.get_argument_estimates();
-    argument_estimates.scalar_def = default_value;
-    return Halide::Argument(param.name(),
-                    param.is_buffer() ? Halide::Argument::InputBuffer : Halide::Argument::InputScalar,
-                    param.type(), param.dimensions(), argument_estimates);
+Argument to_argument(const Internal::Parameter &param) {
+    return Argument(param.name(),
+                    param.is_buffer() ? Argument::InputBuffer : Argument::InputScalar,
+                    param.type(),
+                    param.dimensions(),
+                    param.get_argument_estimates());
 }
 
-Halide::Func make_param_func(const Halide::Internal::Parameter &p, const std::string &name) {
+Func make_param_func(const Parameter &p, const std::string &name) {
     internal_assert(p.is_buffer());
-    Halide::Func f(name + "_im");
+    Func f(name + "_im");
     auto b = p.buffer();
     if (b.defined()) {
-        // If the Halide::Internal::Parameter has an explicit BufferPtr set, bind directly to it
-        f(Halide::_) = b(Halide::_);
+        // If the Parameter has an explicit BufferPtr set, bind directly to it
+        f(_) = b(_);
     } else {
-        std::vector<Halide::Var> args;
-        std::vector<Halide::Expr> args_expr;
+        std::vector<Var> args;
+        std::vector<Expr> args_expr;
         for (int i = 0; i < p.dimensions(); ++i) {
-            Halide::Var v = Halide::Var::implicit(i);
+            Var v = Var::implicit(i);
             args.push_back(v);
             args_expr.push_back(v);
         }
-        f(args) = Halide::Internal::Call::make(p, args_expr);
+        f(args) = Internal::Call::make(p, args_expr);
     }
     return f;
 }
 
 }  // namespace
 
-std::vector<Halide::Type> parse_halide_type_list(const std::string &types) {
+std::vector<Type> parse_halide_type_list(const std::string &types) {
     const auto &e = get_halide_type_enum_map();
-    std::vector<Halide::Type> result;
-    for (auto t : Halide::Internal::split_string(types, ",")) {
+    std::vector<Type> result;
+    for (const auto &t : split_string(types, ",")) {
         auto it = e.find(t);
-        user_assert(it != e.end()) << "Halide::Type not found: " << 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<Halide::Expr> &values) {
-    std::vector<std::vector<Halide::Expr>> &history = values_history[name];
+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]});
@@ -149,14 +223,14 @@ void ValueTracker::track_values(const std::string &name, const std::vector<Halid
 
     // For each item, see if we have a new unique value
     for (size_t i = 0; i < values.size(); ++i) {
-        Halide::Expr oldval = history[i].back();
-        Halide::Expr newval = values[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()) {
-            // Halide::Expr::operator== doesn't work with undefined
+            // Expr::operator== doesn't work with undefined
             // values, but they are equal for our purposes here.
             continue;
         }
@@ -170,7 +244,7 @@ void ValueTracker::track_values(const std::string &name, const std::vector<Halid
             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]) {
+            for (const auto &e : history[i]) {
                 o << "    " << e << "\n";
             }
             user_error << o.str();
@@ -178,9 +252,9 @@ void ValueTracker::track_values(const std::string &name, const std::vector<Halid
     }
 }
 
-std::vector<Halide::Expr> parameter_constraints(const Halide::Internal::Parameter &p) {
+std::vector<Expr> parameter_constraints(const Parameter &p) {
     internal_assert(p.defined());
-    std::vector<Halide::Expr> values;
+    std::vector<Expr> values;
     values.emplace_back(p.host_alignment());
     if (p.is_buffer()) {
         for (int i = 0; i < p.dimensions(); ++i) {
@@ -209,7 +283,7 @@ class StubEmitter {
           generator_params(select_generator_params(generator_params)),
           inputs(inputs),
           outputs(outputs) {
-        namespaces = Halide::Internal::split_string(generator_stub_name, "::");
+        namespaces = split_string(generator_stub_name, "::");
         internal_assert(!namespaces.empty());
         if (namespaces[0].empty()) {
             // We have a name like ::foo::bar::baz; omit the first empty ns.
@@ -235,20 +309,24 @@ class StubEmitter {
 
     std::vector<Internal::GeneratorParamBase *> select_generator_params(const std::vector<Internal::GeneratorParamBase *> &in) {
         std::vector<Internal::GeneratorParamBase *> out;
-        for (auto p : in) {
+        for (auto *p : in) {
             // These are always propagated specially.
-            if (p->name == "target" ||
-                p->name == "auto_schedule" ||
-                p->name == "machine_params") continue;
-            if (p->is_synthetic_param()) continue;
+            if (p->name() == "target" ||
+                p->name() == "auto_schedule" ||
+                p->name() == "machine_params") {
+                continue;
+            }
+            if (p->is_synthetic_param()) {
+                continue;
+            }
             out.push_back(p);
         }
         return out;
     }
 
     /** Emit spaces according to the current indentation level */
-    Halide::Internal::Indentation get_indent() const {
-        return Halide::Internal::Indentation{indent_level};
+    Indentation get_indent() const {
+        return Indentation{indent_level};
     }
 
     void emit_inputs_struct();
@@ -261,8 +339,8 @@ void StubEmitter::emit_generator_params_struct() {
     stream << get_indent() << "struct " << name << " final {\n";
     indent_level++;
     if (!v.empty()) {
-        for (auto p : v) {
-            stream << get_indent() << p->get_c_type() << " " << p->name << "{ " << p->get_default_value() << " };\n";
+        for (auto *p : v) {
+            stream << get_indent() << p->get_c_type() << " " << p->name() << "{ " << p->get_default_value() << " };\n";
         }
         stream << "\n";
     }
@@ -274,16 +352,16 @@ void StubEmitter::emit_generator_params_struct() {
         stream << get_indent() << name << "(\n";
         indent_level++;
         std::string comma = "";
-        for (auto p : v) {
-            stream << get_indent() << comma << p->get_c_type() << " " << p->name << "\n";
+        for (auto *p : v) {
+            stream << get_indent() << comma << p->get_c_type() << " " << p->name() << "\n";
             comma = ", ";
         }
         indent_level--;
         stream << get_indent() << ") : \n";
         indent_level++;
         comma = "";
-        for (auto p : v) {
-            stream << get_indent() << comma << p->name << "(" << p->name << ")\n";
+        for (auto *p : v) {
+            stream << get_indent() << comma << p->name() << "(" << p->name() << ")\n";
             comma = ", ";
         }
         indent_level--;
@@ -297,12 +375,12 @@ void StubEmitter::emit_generator_params_struct() {
     stream << get_indent() << "return {\n";
     indent_level++;
     std::string comma = "";
-    for (auto p : v) {
-        stream << get_indent() << comma << "{\"" << p->name << "\", ";
+    for (auto *p : v) {
+        stream << get_indent() << comma << "{\"" << p->name() << "\", ";
         if (p->is_looplevel_param()) {
-            stream << p->name << "}\n";
+            stream << p->name() << "}\n";
         } else {
-            stream << p->call_to_string(p->name) << "}\n";
+            stream << p->call_to_string(p->name()) << "}\n";
         }
         comma = ", ";
     }
@@ -322,7 +400,7 @@ void StubEmitter::emit_inputs_struct() {
         std::string name;
     };
     std::vector<InInfo> in_info;
-    for (auto input : inputs) {
+    for (auto *input : inputs) {
         std::string c_type = input->get_c_type();
         if (input->is_array()) {
             c_type = "std::vector<" + c_type + ">";
@@ -333,7 +411,7 @@ void StubEmitter::emit_inputs_struct() {
     const std::string name = "Inputs";
     stream << get_indent() << "struct " << name << " final {\n";
     indent_level++;
-    for (auto in : in_info) {
+    for (const auto &in : in_info) {
         stream << get_indent() << in.c_type << " " << in.name << ";\n";
     }
     stream << "\n";
@@ -344,7 +422,7 @@ void StubEmitter::emit_inputs_struct() {
         stream << get_indent() << name << "(\n";
         indent_level++;
         std::string comma = "";
-        for (auto in : in_info) {
+        for (const auto &in : in_info) {
             stream << get_indent() << comma << "const " << in.c_type << "& " << in.name << "\n";
             comma = ", ";
         }
@@ -352,7 +430,7 @@ void StubEmitter::emit_inputs_struct() {
         stream << get_indent() << ") : \n";
         indent_level++;
         comma = "";
-        for (auto in : in_info) {
+        for (const auto &in : in_info) {
             stream << get_indent() << comma << in.name << "(" << in.name << ")\n";
             comma = ", ";
         }
@@ -386,25 +464,21 @@ void StubEmitter::emit() {
     };
     bool all_outputs_are_func = true;
     std::vector<OutputInfo> out_info;
-    for (auto output : outputs) {
+    for (auto *output : outputs) {
         std::string c_type = output->get_c_type();
-        std::string getter;
-        const bool is_func = (c_type == "Halide::Func");
-        if (output->is_array()) {
-            getter = is_func ? "get_array_output" : "get_array_output_buffer<" + c_type + ">";
-        } else {
-            getter = is_func ? "get_output" : "get_output_buffer<" + c_type + ">";
-        }
+        const bool is_func = (c_type == "Func");
+        std::string getter = is_func ? "get_outputs" : "get_output_buffers<" + c_type + ">";
+        std::string getter_suffix = output->is_array() ? "" : ".at(0)";
         out_info.push_back({output->name(),
                             output->is_array() ? "std::vector<" + c_type + ">" : c_type,
-                            getter + "(\"" + output->name() + "\")"});
-        if (c_type != "Halide::Func") {
+                            getter + "(\"" + output->name() + "\")" + getter_suffix});
+        if (c_type != "Func") {
             all_outputs_are_func = false;
         }
     }
 
     std::ostringstream guard;
-    guard << "ION_STUB";
+    guard << "HALIDE_STUB";
     for (const auto &ns : namespaces) {
         guard << "_" << ns;
     }
@@ -427,10 +501,10 @@ void StubEmitter::emit() {
     stream << get_indent() << "#include \"Halide.h\"\n";
     stream << "\n";
 
-    stream << "namespace ion_register_generator {\n";
+    stream << "namespace halide_register_generator {\n";
     stream << "namespace " << generator_registered_name << "_ns {\n";
-    stream << "extern std::unique_ptr<ion::Internal::GeneratorBase> factory(const ion::GeneratorContext& context);\n";
-    stream << "}  // namespace ion_register_generator\n";
+    stream << "extern std::unique_ptr<Halide::Internal::GeneratorBase> factory(const ion::GeneratorContext& context);\n";
+    stream << "}  // namespace halide_register_generator\n";
     stream << "}  // namespace " << generator_registered_name << "\n";
     stream << "\n";
 
@@ -441,11 +515,13 @@ void StubEmitter::emit() {
 
     for (auto *p : generator_params) {
         std::string decl = p->get_type_decls();
-        if (decl.empty()) continue;
+        if (decl.empty()) {
+            continue;
+        }
         stream << decl << "\n";
     }
 
-    stream << get_indent() << "class " << class_name << " final : public ion::NamesInterface {\n";
+    stream << get_indent() << "class " << class_name << " final : public Halide::NamesInterface {\n";
     stream << get_indent() << "public:\n";
     indent_level++;
 
@@ -460,14 +536,14 @@ void StubEmitter::emit() {
     }
 
     stream << "\n";
-    stream << get_indent() << "// The Halide::Target used\n";
-    stream << get_indent() << "Halide::Target target;\n";
+    stream << get_indent() << "// The Target used\n";
+    stream << get_indent() << "Target target;\n";
 
     if (out_info.size() == 1) {
         stream << "\n";
         if (all_outputs_are_func) {
             std::string name = out_info.at(0).name;
-            auto output = outputs[0];
+            auto *output = outputs[0];
             if (output->is_array()) {
                 stream << get_indent() << "operator std::vector<Halide::Func>() const {\n";
                 indent_level++;
@@ -487,10 +563,10 @@ void StubEmitter::emit() {
                 indent_level--;
                 stream << get_indent() << "}\n";
 
-                stream << get_indent() << "// operator operator()() overloads omitted because the sole Halide::Output is array-of-Func.\n";
+                stream << get_indent() << "// operator operator()() overloads omitted because the sole Output is array-of-Func.\n";
             } else {
                 // If there is exactly one output, add overloads
-                // for operator Halide::Func and operator().
+                // for operator Func and operator().
                 stream << get_indent() << "operator Halide::Func() const {\n";
                 indent_level++;
                 stream << get_indent() << "return " << name << ";\n";
@@ -514,13 +590,13 @@ void StubEmitter::emit() {
                 stream << get_indent() << "}\n";
             }
         } else {
-            stream << get_indent() << "// operator Halide::Func() and operator()() overloads omitted because the sole Halide::Output is not Halide::Func.\n";
+            stream << get_indent() << "// operator Func() and operator()() overloads omitted because the sole Output is not Func.\n";
         }
     }
 
     stream << "\n";
     if (all_outputs_are_func) {
-        stream << get_indent() << "Halide::s get_pipeline() const {\n";
+        stream << get_indent() << "Halide::Pipeline get_pipeline() const {\n";
         indent_level++;
         stream << get_indent() << "return Halide::Pipeline(std::vector<Halide::Func>{\n";
         indent_level++;
@@ -555,7 +631,7 @@ void StubEmitter::emit() {
         indent_level--;
         stream << get_indent() << "}\n";
     } else {
-        stream << get_indent() << "// get_pipeline() and realize() overloads omitted because some Outputs are not Halide::Func.\n";
+        stream << get_indent() << "// get_pipeline() and realize() overloads omitted because some Outputs are not Func.\n";
     }
 
     indent_level--;
@@ -571,7 +647,7 @@ void StubEmitter::emit() {
     stream << get_indent() << ")\n";
     stream << get_indent() << "{\n";
     indent_level++;
-    stream << get_indent() << "using Stub = ion::Internal::GeneratorStub;\n";
+    stream << get_indent() << "using Stub = Halide::Internal::GeneratorStub;\n";
     stream << get_indent() << "Stub stub(\n";
     indent_level++;
     stream << get_indent() << "context,\n";
@@ -615,10 +691,10 @@ void StubEmitter::emit() {
     stream << get_indent() << "}\n";
     stream << "\n";
 
-    stream << get_indent() << "// overload to allow Halide::Target instead of GeneratorContext.\n";
+    stream << get_indent() << "// overload to allow Target instead of GeneratorContext.\n";
     stream << get_indent() << "inline static Outputs generate(\n";
     indent_level++;
-    stream << get_indent() << "const Halide::Target& target,\n";
+    stream << get_indent() << "const Target& target,\n";
     stream << get_indent() << "const Inputs& inputs,\n";
     stream << get_indent() << "const GeneratorParams& generator_params = GeneratorParams()\n";
     indent_level--;
@@ -661,38 +737,34 @@ GeneratorStub::GeneratorStub(const GeneratorContext &context,
 // as a vector-of-size-1. This method is primarily useful for code that needs
 // to iterate through the outputs of unknown, arbitrary Generators (e.g.,
 // the Python bindings).
-std::vector<std::vector<Halide::Func>> GeneratorStub::generate(const GeneratorParamsMap &generator_params,
+std::vector<std::vector<Func>> GeneratorStub::generate(const GeneratorParamsMap &generator_params,
                                                        const std::vector<std::vector<Internal::StubInput>> &inputs) {
     generator->set_generator_param_values(generator_params);
-    generator->call_configure();
+    generator->ensure_configure_has_been_called();
     generator->set_inputs_vector(inputs);
-    Halide::Pipeline p = generator->build_pipeline();
+    Pipeline p = generator->build_pipeline();
 
-    std::vector<std::vector<Halide::Func>> v;
+    std::vector<std::vector<Func>> v;
     GeneratorParamInfo &pi = generator->param_info();
     if (!pi.outputs().empty()) {
         for (auto *output : pi.outputs()) {
-            const std::string &name = output->name();
-            if (output->is_array()) {
-                v.push_back(get_array_output(name));
-            } else {
-                v.push_back(std::vector<Halide::Func>{get_output(name)});
-            }
+            v.push_back(get_outputs(output->name()));
         }
     } else {
         // Generators with build() method can't have Output<>, hence can't have array outputs
-        for (auto output : p.outputs()) {
-            v.push_back(std::vector<Halide::Func>{output});
+        for (const auto &output : p.outputs()) {
+            v.push_back(std::vector<Func>{output});
         }
     }
     return v;
 }
 
 GeneratorStub::Names GeneratorStub::get_names() const {
+    generator->ensure_configure_has_been_called();
     auto &pi = generator->param_info();
     Names names;
     for (auto *o : pi.generator_params()) {
-        names.generator_params.push_back(o->name);
+        names.generator_params.push_back(o->name());
     }
     for (auto *o : pi.inputs()) {
         names.inputs.push_back(o->name());
@@ -703,22 +775,22 @@ GeneratorStub::Names GeneratorStub::get_names() const {
     return names;
 }
 
-const std::map<std::string, Halide::Type> &get_halide_type_enum_map() {
-    static const std::map<std::string, Halide::Type> halide_type_enum_map{
-        {"bool", Halide::Bool()},
-        {"int8", Halide::Int(8)},
-        {"int16", Halide::Int(16)},
-        {"int32", Halide::Int(32)},
-        {"uint8", Halide::UInt(8)},
-        {"uint16", Halide::UInt(16)},
-        {"uint32", Halide::UInt(32)},
-        {"float16", Halide::Float(16)},
-        {"float32", Halide::Float(32)},
-        {"float64", Halide::Float(64)}};
+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)},
+        {"float16", Float(16)},
+        {"float32", Float(32)},
+        {"float64", Float(64)}};
     return halide_type_enum_map;
 }
 
-std::string halide_type_to_c_source(const Halide::Type &t) {
+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"},
@@ -730,30 +802,30 @@ std::string halide_type_to_c_source(const Halide::Type &t) {
     return oss.str();
 }
 
-std::string halide_type_to_c_type(const Halide::Type &t) {
-    auto encode = [](const Halide::Type &t) -> int { return t.code() << 16 | t.bits(); };
+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(Halide::Int(8)), "int8_t"},
-        {encode(Halide::Int(16)), "int16_t"},
-        {encode(Halide::Int(32)), "int32_t"},
-        {encode(Halide::Int(64)), "int64_t"},
-        {encode(Halide::UInt(1)), "bool"},
-        {encode(Halide::UInt(8)), "uint8_t"},
-        {encode(Halide::UInt(16)), "uint16_t"},
-        {encode(Halide::UInt(32)), "uint32_t"},
-        {encode(Halide::UInt(64)), "uint64_t"},
-        {encode(Halide::BFloat(16)), "uint16_t"},  // TODO: see Issues #3709, #3967
-        {encode(Halide::Float(16)), "uint16_t"},   // TODO: see Issues #3709, #3967
-        {encode(Halide::Float(32)), "float"},
-        {encode(Halide::Float(64)), "double"},
-        {encode(Halide::Handle(64)), "void*"}};
+        {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(BFloat(16)), "uint16_t"},  // TODO: see Issues #3709, #3967
+        {encode(Float(16)), "uint16_t"},   // TODO: see Issues #3709, #3967
+        {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));
 }
 
-int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) {
+int generate_filter_main_inner(int argc, char **argv, std::ostream &error_output) {
     const char kUsage[] =
-        "gengen \n"
+        "gengen\n"
         "  [-g GENERATOR_NAME] [-f FUNCTION_NAME] [-o OUTPUT_DIR] [-r RUNTIME_NAME] [-d 1|0]\n"
         "  [-e EMIT_OPTIONS] [-n FILE_BASE_NAME] [-p PLUGIN_NAME] [-s AUTOSCHEDULER_NAME]\n"
         "       target=target-string[,target-string...] [generator_arg=value [...]]\n"
@@ -797,9 +869,9 @@ int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) {
 
     for (int i = 1; i < argc; ++i) {
         if (argv[i][0] != '-') {
-            std::vector<std::string> v = Halide::Internal::split_string(argv[i], "=");
+            std::vector<std::string> v = split_string(argv[i], "=");
             if (v.size() != 2 || v[0].empty() || v[1].empty()) {
-                cerr << kUsage;
+                error_output << kUsage;
                 return 1;
             }
             generator_args[v[0]] = v[1];
@@ -808,44 +880,44 @@ int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) {
         auto it = flags_info.find(argv[i]);
         if (it != flags_info.end()) {
             if (i + 1 >= argc) {
-                cerr << kUsage;
+                error_output << kUsage;
                 return 1;
             }
             it->second = argv[i + 1];
             ++i;
             continue;
         }
-        cerr << "Unknown flag: " << argv[i] << "\n";
-        cerr << kUsage;
+        error_output << "Unknown flag: " << argv[i] << "\n";
+        error_output << kUsage;
         return 1;
     }
 
     // It's possible that in the future loaded plugins might change
     // how arguments are parsed, so we handle those first.
-    for (const auto &lib : Halide::Internal::split_string(flags_info["-p"], ",")) {
+    for (const auto &lib : split_string(flags_info["-p"], ",")) {
         if (!lib.empty()) {
-            Halide::load_plugin(lib);
+            load_plugin(lib);
         }
     }
 
     if (flags_info["-d"] != "1" && flags_info["-d"] != "0") {
-        cerr << "-d must be 0 or 1\n";
-        cerr << kUsage;
+        error_output << "-d must be 0 or 1\n";
+        error_output << kUsage;
         return 1;
     }
     const int build_gradient_module = flags_info["-d"] == "1";
 
     std::string autoscheduler_name = flags_info["-s"];
     if (!autoscheduler_name.empty()) {
-        Halide::Pipeline::set_default_autoscheduler_name(autoscheduler_name);
+        Pipeline::set_default_autoscheduler_name(autoscheduler_name);
     }
 
     std::string runtime_name = flags_info["-r"];
 
     std::vector<std::string> generator_names = GeneratorRegistry::enumerate();
     if (generator_names.empty() && runtime_name.empty()) {
-        cerr << "No generators have been registered and not compiling a standalone runtime\n";
-        cerr << kUsage;
+        error_output << "No generators have been registered and not compiling a standalone runtime\n";
+        error_output << kUsage;
         return 1;
     }
 
@@ -853,13 +925,13 @@ int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) {
     if (generator_name.empty() && runtime_name.empty()) {
         // Require either -g or -r to be specified:
         // no longer infer the name when only one Generator is registered
-        cerr << "Either -g <name> or -r must be specified; available Generators are:\n";
+        error_output << "Either -g <name> or -r must be specified; available Generators are:\n";
         if (!generator_names.empty()) {
             for (const auto &name : generator_names) {
-                cerr << "    " << name << "\n";
+                error_output << "    " << name << "\n";
             }
         } else {
-            cerr << "    <none>\n";
+            error_output << "    <none>\n";
         }
         return 1;
     }
@@ -871,18 +943,30 @@ int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) {
     }
     std::string output_dir = flags_info["-o"];
     if (output_dir.empty()) {
-        cerr << "-o must always be specified.\n";
-        cerr << kUsage;
+        error_output << "-o must always be specified.\n";
+        error_output << kUsage;
         return 1;
     }
 
+    std::string emit_flags_string = flags_info["-e"];
+
+    // If HL_EXTRA_OUTPUTS is defined, assume it's extra outputs we want to generate
+    // (usually for temporary debugging purposes) and just tack it on to the -e contents.
+    std::string extra_outputs = get_env_variable("HL_EXTRA_OUTPUTS");
+    if (!extra_outputs.empty()) {
+        if (!emit_flags_string.empty()) {
+            emit_flags_string += ",";
+        }
+        emit_flags_string += extra_outputs;
+    }
+
     // It's ok to omit "target=" if we are generating *only* a cpp_stub
-    const std::vector<std::string> emit_flags = Halide::Internal::split_string(flags_info["-e"], ",");
+    const std::vector<std::string> emit_flags = split_string(emit_flags_string, ",");
     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 << "Halide::Target missing\n";
-            cerr << kUsage;
+            error_output << "Target missing\n";
+            error_output << kUsage;
             return 1;
         }
     }
@@ -890,49 +974,49 @@ int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) {
     // 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"];
 
-    auto target_strings = Halide::Internal::split_string(generator_args["target"].string_value, ",");
-    std::vector<Halide::Target> targets;
+    auto target_strings = split_string(generator_args["target"].string_value, ",");
+    std::vector<Target> targets;
     for (const auto &s : target_strings) {
         targets.emplace_back(s);
     }
 
     // extensions won't vary across multitarget output
-    std::map<Halide::Output, const Halide::Internal::OutputInfo> output_info = Halide::Internal::get_output_info(targets[0]);
+    std::map<Output, const OutputInfo> output_info = get_output_info(targets[0]);
 
-    std::set<Halide::Output> outputs;
+    std::set<Output> outputs;
     if (emit_flags.empty() || (emit_flags.size() == 1 && emit_flags[0].empty())) {
         // If omitted or empty, assume .a and .h and registration.cpp
-        outputs.insert(Halide::Output::c_header);
-        outputs.insert(Halide::Output::registration);
-        outputs.insert(Halide::Output::static_library);
+        outputs.insert(Output::c_header);
+        outputs.insert(Output::registration);
+        outputs.insert(Output::static_library);
     } else {
         // Build a reverse lookup table. Allow some legacy aliases on the command line,
         // to allow legacy build systems to work more easily.
-        std::map<std::string, Halide::Output> output_name_to_enum = {
-            {"cpp", Halide::Output::c_source},
-            {"h", Halide::Output::c_header},
-            {"html", Halide::Output::stmt_html},
-            {"o", Halide::Output::object},
-            {"py.c", Halide::Output::python_extension},
+        std::map<std::string, Output> output_name_to_enum = {
+            {"cpp", Output::c_source},
+            {"h", Output::c_header},
+            {"html", Output::stmt_html},
+            {"o", Output::object},
+            {"py.c", Output::python_extension},
         };
         for (const auto &it : output_info) {
             output_name_to_enum[it.second.name] = it.first;
         }
 
-        for (std::string opt : emit_flags) {
+        for (const std::string &opt : emit_flags) {
             auto it = output_name_to_enum.find(opt);
             if (it == output_name_to_enum.end()) {
-                cerr << "Unrecognized emit option: " << opt << " is not one of [";
+                error_output << "Unrecognized emit option: " << opt << " is not one of [";
                 auto end = output_info.cend();
                 auto last = std::prev(end);
                 for (auto iter = output_info.cbegin(); iter != end; ++iter) {
-                    cerr << iter->second.name;
+                    error_output << iter->second.name;
                     if (iter != last) {
-                        cerr << " ";
+                        error_output << " ";
                     }
                 }
-                cerr << "], ignoring.\n";
-                cerr << kUsage;
+                error_output << "], ignoring.\n";
+                error_output << kUsage;
                 return 1;
             }
             outputs.insert(it->second);
@@ -940,81 +1024,84 @@ int generate_filter_main_inner(int argc, char **argv, std::ostream &cerr) {
     }
 
     // Allow quick-n-dirty use of compiler logging via HL_DEBUG_COMPILER_LOGGER env var
-    const bool do_compiler_logging = outputs.count(Halide::Output::compiler_log) ||
-                                     (Halide::Internal::get_env_variable("HL_DEBUG_COMPILER_LOGGER") == "1");
+    const bool do_compiler_logging = outputs.count(Output::compiler_log) ||
+                                     (get_env_variable("HL_DEBUG_COMPILER_LOGGER") == "1");
 
-    const bool obfuscate_compiler_logging = Halide::Internal::get_env_variable("HL_OBFUSCATE_COMPILER_LOGGER") == "1";
+    const bool obfuscate_compiler_logging = get_env_variable("HL_OBFUSCATE_COMPILER_LOGGER") == "1";
 
-    const Halide::CompilerLoggerFactory no_compiler_logger_factory =
-        [](const std::string &, const Halide::Target &) -> std::unique_ptr<Halide::Internal::CompilerLogger> {
+    const CompilerLoggerFactory no_compiler_logger_factory =
+        [](const std::string &, const Target &) -> std::unique_ptr<CompilerLogger> {
         return nullptr;
     };
 
-    const Halide::CompilerLoggerFactory json_compiler_logger_factory =
-        [&](const std::string &function_name, const Halide::Target &target) -> std::unique_ptr<Halide::Internal::CompilerLogger> {
+    const CompilerLoggerFactory json_compiler_logger_factory =
+        [&](const std::string &function_name, const Target &target) -> std::unique_ptr<CompilerLogger> {
         // rebuild generator_args from the map so that they are always canonical
         std::string generator_args_string;
         std::string sep;
         for (const auto &it : generator_args) {
-            if (it.first == "target") continue;
-            std::string quote = it.second.string_value.find(" ") != std::string::npos ? "\\\"" : "";
+            if (it.first == "target") {
+                continue;
+            }
+            std::string quote = it.second.string_value.find(' ') != std::string::npos ? "\\\"" : "";
             generator_args_string += sep + it.first + "=" + quote + it.second.string_value + quote;
             sep = " ";
         }
-        std::unique_ptr<Halide::Internal::JSONCompilerLogger> t(new Halide::Internal::JSONCompilerLogger(
+        std::unique_ptr<JSONCompilerLogger> t(new JSONCompilerLogger(
             obfuscate_compiler_logging ? "" : generator_name,
             obfuscate_compiler_logging ? "" : function_name,
             obfuscate_compiler_logging ? "" : autoscheduler_name,
-            obfuscate_compiler_logging ? Halide::Target() : target,
+            obfuscate_compiler_logging ? Target() : target,
             obfuscate_compiler_logging ? "" : generator_args_string,
             obfuscate_compiler_logging));
         return t;
     };
 
-    const Halide::CompilerLoggerFactory compiler_logger_factory = do_compiler_logging ?
+    const CompilerLoggerFactory compiler_logger_factory = do_compiler_logging ?
                                                               json_compiler_logger_factory :
                                                               no_compiler_logger_factory;
 
     if (!runtime_name.empty()) {
         std::string base_path = compute_base_path(output_dir, runtime_name, "");
 
-        Halide::Target gcd_target = targets[0];
+        Target gcd_target = targets[0];
         for (size_t i = 1; i < targets.size(); i++) {
             if (!gcd_target.get_runtime_compatible_target(targets[i], gcd_target)) {
-                user_error << "Failed to find compatible runtime target for "
-                           << gcd_target.to_string()
-                           << " and "
-                           << targets[i].to_string() << "\n";
+                error_output << "Failed to find compatible runtime target for "
+                             << gcd_target.to_string()
+                             << " and "
+                             << targets[i].to_string() << "\n";
+                return -1;
             }
         }
 
         if (targets.size() > 1) {
-            Halide::Internal::debug(1) << "Building runtime for computed target: " << gcd_target.to_string() << "\n";
+            debug(1) << "Building runtime for computed target: " << gcd_target.to_string() << "\n";
         }
 
         auto output_files = compute_output_files(gcd_target, base_path, outputs);
-        // Runtime doesn't get to participate in the Halide::CompilerLogger party
+        // Runtime doesn't get to participate in the CompilerLogger party
         compile_standalone_runtime(output_files, gcd_target);
     }
 
     if (!generator_name.empty()) {
         std::string base_path = compute_base_path(output_dir, function_name, file_base_name);
-        Halide::Internal::debug(1) << "Generator " << generator_name << " has base_path " << base_path << "\n";
-        if (outputs.count(Halide::Output::cpp_stub)) {
-            // When generating cpp_stub, we ignore all generator args passed in, and supply a fake Halide::Target.
-            // (Halide::CompilerLogger is never enabled for cpp_stub, for now anyway.)
-            auto gen = GeneratorRegistry::create(generator_name, GeneratorContext(Halide::Target()));
-            auto stub_file_path = base_path + output_info[Halide::Output::cpp_stub].extension;
+        debug(1) << "Generator " << generator_name << " has base_path " << base_path << "\n";
+        if (outputs.count(Output::cpp_stub)) {
+            // When generating cpp_stub, we ignore all generator args passed in, and supply a fake Target.
+            // (CompilerLogger is never enabled for cpp_stub, for now anyway.)
+            auto gen = GeneratorRegistry::create(generator_name, GeneratorContext(Target()));
+            auto stub_file_path = base_path + output_info[Output::cpp_stub].extension;
             gen->emit_cpp_stub(stub_file_path);
         }
 
         // Don't bother with this if we're just emitting a cpp_stub.
         if (!stub_only) {
             auto output_files = compute_output_files(targets[0], base_path, outputs);
-            auto module_factory = [&generator_name, &generator_args, build_gradient_module](const std::string &name, const Halide::Target &target) -> Halide::Module {
+            auto module_factory = [&generator_name, &generator_args, build_gradient_module](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 Halide::Target.
+                // Must re-create each time since each instance will have a different Target.
                 auto gen = GeneratorRegistry::create(generator_name, GeneratorContext(target));
                 gen->set_generator_param_values(sub_generator_args);
                 return build_gradient_module ? gen->build_gradient_module(name) : gen->build_module(name);
@@ -1042,32 +1129,36 @@ int generate_filter_main(int argc, char **argv, std::ostream &cerr) {
 #endif
 
 GeneratorParamBase::GeneratorParamBase(const std::string &name)
-    : name(name) {
-    Halide::Internal::ObjectInstanceRegistry::register_instance(this, 0, Halide::Internal::ObjectInstanceRegistry::GeneratorParam,
+    : name_(name) {
+    ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorParam,
                                               this, nullptr);
 }
 
 GeneratorParamBase::~GeneratorParamBase() {
-    Halide::Internal::ObjectInstanceRegistry::unregister_instance(this);
+    ObjectInstanceRegistry::unregister_instance(this);
 }
 
 void GeneratorParamBase::check_value_readable() const {
     // These are always readable.
-    if (name == "target") return;
-    if (name == "auto_schedule") return;
-    if (name == "machine_params") return;
+    if (name() == "target" ||
+        name() == "auto_schedule" ||
+        name() == "machine_params") {
+        return;
+    }
     user_assert(generator && generator->phase >= GeneratorBase::ConfigureCalled)
-        << "The GeneratorParam \"" << name << "\" cannot be read before build() or configure()/generate() is called.\n";
+        << "The GeneratorParam \"" << name() << "\" cannot be read before build() or configure()/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;
-    user_assert(generator->phase < GeneratorBase::GenerateCalled) << "The GeneratorParam \"" << name << "\" cannot be written after build() or generate() is called.\n";
+    if (!generator) {
+        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";
+    user_error << "The GeneratorParam \"" << name() << "\" cannot be set with a value of type " << type << ".\n";
 }
 
 /* static */
@@ -1121,6 +1212,7 @@ std::vector<std::string> GeneratorRegistry::enumerate() {
     GeneratorRegistry &registry = get_registry();
     std::lock_guard<std::mutex> lock(registry.mutex);
     std::vector<std::string> result;
+    result.reserve(registry.factories.size());
     for (const auto &i : registry.factories) {
         result.push_back(i.first);
     }
@@ -1129,26 +1221,26 @@ std::vector<std::string> GeneratorRegistry::enumerate() {
 
 GeneratorBase::GeneratorBase(size_t size, const void *introspection_helper)
     : size(size) {
-    Halide::Internal::ObjectInstanceRegistry::register_instance(this, size, Halide::Internal::ObjectInstanceRegistry::Generator, this, introspection_helper);
+    ObjectInstanceRegistry::register_instance(this, size, ObjectInstanceRegistry::Generator, this, introspection_helper);
 }
 
 GeneratorBase::~GeneratorBase() {
-    Halide::Internal::ObjectInstanceRegistry::unregister_instance(this);
+    ObjectInstanceRegistry::unregister_instance(this);
 }
 
 GeneratorParamInfo::GeneratorParamInfo(GeneratorBase *generator, const size_t size) {
-    std::vector<void *> vf = Halide::Internal::ObjectInstanceRegistry::instances_in_range(
-        generator, size, Halide::Internal::ObjectInstanceRegistry::FilterParam);
+    std::vector<void *> vf = ObjectInstanceRegistry::instances_in_range(
+        generator, size, ObjectInstanceRegistry::FilterParam);
     user_assert(vf.empty()) << "ImageParam and Param<> are no longer allowed in Generators; use Input<> instead.";
 
     const auto add_synthetic_params = [this, generator](GIOBase *gio) {
         const std::string &n = gio->name();
         const std::string &gn = generator->generator_registered_name;
 
-        if (gio->kind() != IOKind::Scalar) {
-            owned_synthetic_params.push_back(GeneratorParam_Synthetic<Halide::Type>::make(generator, gn, n + ".type", *gio, SyntheticParamType::Type, gio->types_defined()));
-            filter_generator_params.push_back(owned_synthetic_params.back().get());
+        owned_synthetic_params.push_back(GeneratorParam_Synthetic<Type>::make(generator, gn, n + ".type", *gio, SyntheticParamType::Type, gio->types_defined()));
+        filter_generator_params.push_back(owned_synthetic_params.back().get());
 
+        if (gio->kind() != IOKind::Scalar) {
             owned_synthetic_params.push_back(GeneratorParam_Synthetic<int>::make(generator, gn, n + ".dim", *gio, SyntheticParamType::Dim, gio->dims_defined()));
             filter_generator_params.push_back(owned_synthetic_params.back().get());
         }
@@ -1158,10 +1250,10 @@ GeneratorParamInfo::GeneratorParamInfo(GeneratorBase *generator, const size_t si
         }
     };
 
-    std::vector<void *> vi = Halide::Internal::ObjectInstanceRegistry::instances_in_range(
-        generator, size, Halide::Internal::ObjectInstanceRegistry::GeneratorInput);
-    for (auto v : vi) {
-        auto input = static_cast<Internal::GeneratorInputBase *>(v);
+    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();
@@ -1172,10 +1264,10 @@ GeneratorParamInfo::GeneratorParamInfo(GeneratorBase *generator, const size_t si
         add_synthetic_params(input);
     }
 
-    std::vector<void *> vo = Halide::Internal::ObjectInstanceRegistry::instances_in_range(
-        generator, size, Halide::Internal::ObjectInstanceRegistry::GeneratorOutput);
-    for (auto v : vo) {
-        auto output = static_cast<Internal::GeneratorOutputBase *>(v);
+    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();
@@ -1186,14 +1278,14 @@ GeneratorParamInfo::GeneratorParamInfo(GeneratorBase *generator, const size_t si
         add_synthetic_params(output);
     }
 
-    std::vector<void *> vg = Halide::Internal::ObjectInstanceRegistry::instances_in_range(
-        generator, size, Halide::Internal::ObjectInstanceRegistry::GeneratorParam);
-    for (auto v : vg) {
-        auto param = static_cast<GeneratorParamBase *>(v);
+    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);
+        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;
         filter_generator_params.push_back(param);
@@ -1209,18 +1301,7 @@ GeneratorParamInfo &GeneratorBase::param_info() {
     return *param_info_ptr;
 }
 
-Halide::Func GeneratorBase::get_output(const std::string &n) {
-    check_min_phase(GenerateCalled);
-    auto *output = find_output_by_name(n);
-    // Call for the side-effect of asserting if the value isn't defined.
-    (void)output->array_size();
-    user_assert(!output->is_array() && output->funcs().size() == 1) << "Output " << n << " must be accessed via get_array_output()\n";
-    Halide::Func f = output->funcs().at(0);
-    user_assert(f.defined()) << "Output " << n << " was not defined.\n";
-    return f;
-}
-
-std::vector<Halide::Func> GeneratorBase::get_array_output(const std::string &n) {
+std::vector<Func> GeneratorBase::get_outputs(const std::string &n) {
     check_min_phase(GenerateCalled);
     auto *output = find_output_by_name(n);
     // Call for the side-effect of asserting if the value isn't defined.
@@ -1249,10 +1330,10 @@ void GeneratorBase::set_generator_param_values(const GeneratorParamsMap &params)
 
     std::unordered_map<std::string, Internal::GeneratorParamBase *> generator_params_by_name;
     for (auto *g : pi.generator_params()) {
-        generator_params_by_name[g->name] = g;
+        generator_params_by_name[g->name()] = g;
     }
 
-    for (auto &key_value : params) {
+    for (const auto &key_value : params) {
         auto gp = generator_params_by_name.find(key_value.first);
         user_assert(gp != generator_params_by_name.end())
             << "Generator " << generator_registered_name << " has no GeneratorParam named: " << key_value.first << "\n";
@@ -1298,7 +1379,7 @@ void GeneratorBase::set_inputs_vector(const std::vector<std::vector<StubInput>>
 
 void GeneratorBase::track_parameter_values(bool include_outputs) {
     GeneratorParamInfo &pi = param_info();
-    for (auto input : pi.inputs()) {
+    for (auto *input : pi.inputs()) {
         if (input->kind() == IOKind::Buffer) {
             internal_assert(!input->parameters_.empty());
             for (auto &p : input->parameters_) {
@@ -1308,14 +1389,14 @@ void GeneratorBase::track_parameter_values(bool include_outputs) {
         }
     }
     if (include_outputs) {
-        for (auto output : pi.outputs()) {
+        for (auto *output : pi.outputs()) {
             if (output->kind() == IOKind::Buffer) {
                 internal_assert(!output->funcs().empty());
-                for (auto &f : output->funcs()) {
+                for (const auto &f : output->funcs()) {
                     user_assert(f.defined()) << "Output " << output->name() << " is not fully defined.";
                     auto output_buffers = f.output_buffers();
                     for (auto &o : output_buffers) {
-                        Halide::Internal::Parameter p = o.parameter();
+                        Parameter p = o.parameter();
                         // This must use p.name(), *not* output->name()
                         get_value_tracker()->track_values(p.name(), ion::Internal::parameter_constraints(p));
                     }
@@ -1339,7 +1420,7 @@ void GeneratorBase::advance_phase(Phase new_phase) {
         internal_error << "Impossible";
         break;
     case ConfigureCalled:
-        internal_assert(phase == Created) << "pase is " << phase;
+        internal_assert(phase == Created);
         break;
     case InputsSet:
         internal_assert(phase == Created || phase == ConfigureCalled);
@@ -1355,6 +1436,13 @@ void GeneratorBase::advance_phase(Phase new_phase) {
     phase = new_phase;
 }
 
+void GeneratorBase::ensure_configure_has_been_called() {
+    if (phase < ConfigureCalled) {
+        call_configure();
+    }
+    check_min_phase(ConfigureCalled);
+}
+
 void GeneratorBase::pre_configure() {
     advance_phase(ConfigureCalled);
 }
@@ -1366,7 +1454,7 @@ void GeneratorBase::pre_generate() {
     advance_phase(GenerateCalled);
     GeneratorParamInfo &pi = param_info();
     user_assert(!pi.outputs().empty()) << "Must use Output<> with generate() method.";
-    user_assert(get_target() != Halide::Target()) << "The Generator target has not been set.";
+    user_assert(get_target() != Target()) << "The Generator target has not been set.";
 
     if (!inputs_set) {
         for (auto *input : pi.inputs()) {
@@ -1411,12 +1499,12 @@ void GeneratorBase::post_build() {
     track_parameter_values(true);
 }
 
-Halide::Pipeline GeneratorBase::get_pipeline() {
+Pipeline GeneratorBase::get_pipeline() {
     check_min_phase(GenerateCalled);
     if (!pipeline.defined()) {
         GeneratorParamInfo &pi = param_info();
         user_assert(!pi.outputs().empty()) << "Must use get_pipeline<> with Output<>.";
-        std::vector<Halide::Func> funcs;
+        std::vector<Func> funcs;
         for (auto *output : pi.outputs()) {
             for (const auto &f : output->funcs()) {
                 user_assert(f.defined()) << "Output \"" << f.name() << "\" was not defined.\n";
@@ -1430,8 +1518,8 @@ Halide::Pipeline GeneratorBase::get_pipeline() {
                                                                                  << "\" 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) {
-                        Halide::Type expected = output->types().at(i);
-                        Halide::Type actual = f.output_types()[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";
@@ -1440,29 +1528,29 @@ Halide::Pipeline GeneratorBase::get_pipeline() {
                 funcs.push_back(f);
             }
         }
-        pipeline = Halide::Pipeline(funcs);
+        pipeline = Pipeline(funcs);
     }
     return pipeline;
 }
 
-Halide::Module GeneratorBase::build_module(const std::string &function_name,
-                                   const Halide::LinkageType linkage_type) {
-    Halide::AutoSchedulerResults auto_schedule_results;
-    call_configure();
-    Halide::Pipeline pipeline = build_pipeline();
+Module GeneratorBase::build_module(const std::string &function_name,
+                                   const LinkageType linkage_type) {
+    AutoSchedulerResults auto_schedule_results;
+    ensure_configure_has_been_called();
+    Pipeline pipeline = build_pipeline();
     if (get_auto_schedule()) {
         auto_schedule_results = pipeline.auto_schedule(get_target(), get_machine_params());
     }
 
     const GeneratorParamInfo &pi = param_info();
-    std::vector<Halide::Argument> filter_arguments;
+    std::vector<Argument> filter_arguments;
     for (const auto *input : pi.inputs()) {
         for (const auto &p : input->parameters_) {
-            filter_arguments.push_back(to_argument(p, p.is_buffer() ? Halide::Expr() : input->get_def_expr()));
+            filter_arguments.push_back(to_argument(p));
         }
     }
 
-    Halide::Module result = pipeline.compile_to_module(filter_arguments, function_name, get_target(), linkage_type);
+    Module result = pipeline.compile_to_module(filter_arguments, function_name, get_target(), linkage_type);
     std::shared_ptr<ExternsMap> externs_map = get_externs_map();
     for (const auto &map_entry : *externs_map) {
         result.append(map_entry.second);
@@ -1485,19 +1573,19 @@ Halide::Module GeneratorBase::build_module(const std::string &function_name,
     return result;
 }
 
-Halide::Module GeneratorBase::build_gradient_module(const std::string &function_name) {
+Module GeneratorBase::build_gradient_module(const std::string &function_name) {
     constexpr int DBG = 1;
 
     // I doubt these ever need customizing; if they do, we can make them arguments to this function.
     const std::string grad_input_pattern = "_grad_loss_for_$OUT$";
     const std::string grad_output_pattern = "_grad_loss_$OUT$_wrt_$IN$";
-    const Halide::LinkageType linkage_type = Halide::LinkageType::ExternalPlusMetadata;
+    const LinkageType linkage_type = LinkageType::ExternalPlusMetadata;
 
     user_assert(!function_name.empty()) << "build_gradient_module(): function_name cannot be empty\n";
 
-    call_configure();
-    Halide::Pipeline original_pipeline = build_pipeline();
-    std::vector<Halide::Func> original_outputs = original_pipeline.outputs();
+    ensure_configure_has_been_called();
+    Pipeline original_pipeline = build_pipeline();
+    std::vector<Func> original_outputs = original_pipeline.outputs();
 
     // Construct the adjoint pipeline, which has:
     // - All the same inputs as the original, in the same order
@@ -1525,7 +1613,7 @@ Halide::Module GeneratorBase::build_gradient_module(const std::string &function_
         internal_assert(tuple_size == 1) << "Tuple Outputs are not yet supported by build_gradient_module";
     }
 
-    std::vector<Halide::Argument> gradient_inputs;
+    std::vector<Argument> gradient_inputs;
 
     // First: the original inputs. Note that scalar inputs remain scalar,
     // rather being promoted into zero-dimensional buffers.
@@ -1533,8 +1621,8 @@ Halide::Module GeneratorBase::build_gradient_module(const std::string &function_
         // There can be multiple Funcs/Parameters per input if the input is an Array
         internal_assert(input->parameters_.size() == input->funcs_.size());
         for (const auto &p : input->parameters_) {
-            gradient_inputs.push_back(to_argument(p, p.is_buffer() ? Halide::Expr() : input->get_def_expr()));
-            Halide::Internal::debug(DBG) << "    gradient copied input is: " << gradient_inputs.back().name << "\n";
+            gradient_inputs.push_back(to_argument(p));
+            debug(DBG) << "    gradient copied input is: " << gradient_inputs.back().name << "\n";
         }
     }
 
@@ -1546,15 +1634,15 @@ Halide::Module GeneratorBase::build_gradient_module(const std::string &function_
     std::vector<ImageParam> d_output_imageparams;
     for (const auto *output : pi.outputs()) {
         for (size_t i = 0; i < output->funcs().size(); ++i) {
-            const Halide::Func &f = output->funcs()[i];
+            const Func &f = output->funcs()[i];
             const std::string output_name = output->array_name(i);
             // output_name is something like "funcname_i"
-            const std::string grad_in_name = Halide::Internal::replace_all(grad_input_pattern, "$OUT$", output_name);
+            const std::string grad_in_name = replace_all(grad_input_pattern, "$OUT$", output_name);
             // TODO(srj): does it make sense for gradient to be a non-float type?
             // For now, assume it's always float32 (unless the output is already some float).
-            const Halide::Type grad_in_type = output->type().is_float() ? output->type() : Float(32);
+            const Type grad_in_type = output->type().is_float() ? output->type() : Float(32);
             const int grad_in_dimensions = f.dimensions();
-            const Halide::ArgumentEstimates grad_in_estimates = f.output_buffer().parameter().get_argument_estimates();
+            const ArgumentEstimates grad_in_estimates = f.output_buffer().parameter().get_argument_estimates();
             internal_assert((int)grad_in_estimates.buffer_estimates.size() == grad_in_dimensions);
 
             ImageParam d_im(grad_in_type, grad_in_dimensions, grad_in_name);
@@ -1563,25 +1651,25 @@ Halide::Module GeneratorBase::build_gradient_module(const std::string &function_
                 d_im.parameter().set_extent_constraint_estimate(d, grad_in_estimates.buffer_estimates[i].extent);
             }
             d_output_imageparams.push_back(d_im);
-            gradient_inputs.push_back(to_argument(d_im.parameter(), Halide::Expr()));
+            gradient_inputs.push_back(to_argument(d_im.parameter()));
 
-            Halide::Internal::debug(DBG) << "    gradient synthesized input is: " << gradient_inputs.back().name << "\n";
+            debug(DBG) << "    gradient synthesized input is: " << gradient_inputs.back().name << "\n";
         }
     }
 
     // Finally: define the output Func(s), one for each unique output/input pair.
     // Note that original_outputs.size() != pi.outputs().size() if any outputs are arrays.
     internal_assert(original_outputs.size() == d_output_imageparams.size());
-    std::vector<Halide::Func> gradient_outputs;
+    std::vector<Func> gradient_outputs;
     for (size_t i = 0; i < original_outputs.size(); ++i) {
-        const Halide::Func &original_output = original_outputs.at(i);
+        const Func &original_output = original_outputs.at(i);
         const ImageParam &d_output = d_output_imageparams.at(i);
-        Halide::Region bounds;
+        Region bounds;
         for (int i = 0; i < d_output.dimensions(); i++) {
             bounds.emplace_back(d_output.dim(i).min(), d_output.dim(i).extent());
         }
-        Halide::Func adjoint_func = Halide::BoundaryConditions::constant_exterior(d_output, Halide::Internal::make_zero(d_output.type()));
-        Halide::Derivative d = propagate_adjoints(original_output, adjoint_func, bounds);
+        Func adjoint_func = BoundaryConditions::constant_exterior(d_output, make_zero(d_output.type()));
+        Derivative d = propagate_adjoints(original_output, adjoint_func, bounds);
 
         const std::string &output_name = original_output.name();
         for (const auto *input : pi.inputs()) {
@@ -1590,46 +1678,46 @@ Halide::Module GeneratorBase::build_gradient_module(const std::string &function_
                 const auto &f = input->funcs_[i];
                 const auto &p = input->parameters_[i];
 
-                Halide::Func d_f = d(f);
+                Func d_f = d(f);
 
-                std::string grad_out_name = Halide::Internal::replace_all(Halide::Internal::replace_all(grad_output_pattern, "$OUT$", output_name), "$IN$", input_name);
+                std::string grad_out_name = replace_all(replace_all(grad_output_pattern, "$OUT$", output_name), "$IN$", input_name);
                 if (!d_f.defined()) {
                     grad_out_name = "_dummy" + grad_out_name;
                 }
 
-                Halide::Func d_out_wrt_in(grad_out_name);
+                Func d_out_wrt_in(grad_out_name);
                 if (d_f.defined()) {
                     d_out_wrt_in(Halide::_) = d_f(Halide::_);
                 } else {
-                    Halide::Internal::debug(DBG) << "    No Derivative found for output " << output_name << " wrt input " << input_name << "\n";
+                    debug(DBG) << "    No Derivative found for output " << output_name << " wrt input " << input_name << "\n";
                     // If there was no Derivative found, don't skip the output;
                     // just replace with a dummy Func that is all zeros. This ensures
-                    // that the signature of the Halide::Pipeline we produce is always predictable.
-                    std::vector<Halide::Var> vars;
+                    // that the signature of the Pipeline we produce is always predictable.
+                    std::vector<Var> vars;
                     for (int i = 0; i < d_output.dimensions(); i++) {
-                        vars.push_back(Halide::Var::implicit(i));
+                        vars.push_back(Var::implicit(i));
                     }
-                    d_out_wrt_in(vars) = Halide::Internal::make_zero(d_output.type());
+                    d_out_wrt_in(vars) = make_zero(d_output.type());
                 }
 
                 d_out_wrt_in.set_estimates(p.get_argument_estimates().buffer_estimates);
 
                 // Useful for debugging; ordinarily better to leave out
-                // Halide::Internal::debug(0) << "\n\n"
+                // debug(0) << "\n\n"
                 //          << "output:\n" << FuncWithDependencies(original_output) << "\n"
                 //          << "d_output:\n" << FuncWithDependencies(adjoint_func) << "\n"
                 //          << "input:\n" << FuncWithDependencies(f) << "\n"
                 //          << "d_out_wrt_in:\n" << FuncWithDependencies(d_out_wrt_in) << "\n";
 
                 gradient_outputs.push_back(d_out_wrt_in);
-                Halide::Internal::debug(DBG) << "    gradient output is: " << d_out_wrt_in.name() << "\n";
+                debug(DBG) << "    gradient output is: " << d_out_wrt_in.name() << "\n";
             }
         }
     }
 
-    Halide::Pipeline grad_pipeline = Halide::Pipeline(gradient_outputs);
+    Pipeline grad_pipeline = Pipeline(gradient_outputs);
 
-    Halide::AutoSchedulerResults auto_schedule_results;
+    AutoSchedulerResults auto_schedule_results;
     if (get_auto_schedule()) {
         auto_schedule_results = grad_pipeline.auto_schedule(get_target(), get_machine_params());
     } else {
@@ -1637,7 +1725,7 @@ Halide::Module GeneratorBase::build_gradient_module(const std::string &function_
                         "gradient module will be unscheduled; this is very unlikely to be what you want.\n";
     }
 
-    Halide::Module result = grad_pipeline.compile_to_module(gradient_inputs, function_name, get_target(), linkage_type);
+    Module result = grad_pipeline.compile_to_module(gradient_inputs, function_name, get_target(), linkage_type);
     user_assert(get_externs_map()->empty())
         << "Building a gradient-descent module for a Generator with ExternalCode is not supported.\n";
 
@@ -1649,7 +1737,7 @@ Halide::Module GeneratorBase::build_gradient_module(const std::string &function_
 void GeneratorBase::emit_cpp_stub(const std::string &stub_file_path) {
     user_assert(!generator_registered_name.empty() && !generator_stub_name.empty()) << "Generator has no name.\n";
     // Make sure we call configure() so that extra inputs/outputs are added as necessary.
-    call_configure();
+    ensure_configure_has_been_called();
     // StubEmitter will want to access the GP/SP values, so advance the phase to avoid assert-fails.
     advance_phase(GenerateCalled);
     advance_phase(ScheduleCalled);
@@ -1681,15 +1769,11 @@ void GeneratorBase::check_input_kind(Internal::GeneratorInputBase *in, Internal:
 GIOBase::GIOBase(size_t array_size,
                  const std::string &name,
                  IOKind kind,
-                 const std::vector<Halide::Type> &types,
+                 const std::vector<Type> &types,
                  int dims)
     : array_size_(array_size), name_(name), kind_(kind), types_(types), dims_(dims) {
 }
 
-GIOBase::~GIOBase() {
-    // nothing
-}
-
 bool GIOBase::array_size_defined() const {
     return array_size_ != -1;
 }
@@ -1717,9 +1801,9 @@ bool GIOBase::types_defined() const {
     return !types_.empty();
 }
 
-const std::vector<Halide::Type> &GIOBase::types() const {
+const std::vector<Type> &GIOBase::types() const {
     // If types aren't defined, but we have one Func that is,
-    // we probably just set an Output<Halide::Func> and should propagate the types.
+    // we probably just set an Output<Func> and should propagate the types.
     if (!types_defined()) {
         // use funcs_, not funcs(): the latter could give a much-less-helpful error message
         // in this case.
@@ -1728,23 +1812,41 @@ const std::vector<Halide::Type> &GIOBase::types() const {
             check_matching_types(f.at(0).output_types());
         }
     }
-    user_assert(types_defined()) << "Halide::Type is not defined for " << input_or_output() << " '" << name() << "'; you may need to specify '" << name() << ".type' as a GeneratorParam.\n";
+    user_assert(types_defined()) << "Type is not defined for " << input_or_output() << " '" << name() << "'; you may need to specify '" << name() << ".type' as a GeneratorParam, or call set_type() from the configure() method.\n";
     return types_;
 }
 
-Halide::Type GIOBase::type() const {
+Type GIOBase::type() const {
     const auto &t = types();
     internal_assert(t.size() == 1) << "Expected types_.size() == 1, saw " << t.size() << " for " << name() << "\n";
     return t.at(0);
 }
 
+void GIOBase::set_type(const Type &type) {
+    generator->check_exact_phase(GeneratorBase::ConfigureCalled);
+    user_assert(!types_defined()) << "set_type() may only be called on an Input or Output that has no type specified.";
+    types_ = {type};
+}
+
+void GIOBase::set_dimensions(int dims) {
+    generator->check_exact_phase(GeneratorBase::ConfigureCalled);
+    user_assert(!dims_defined()) << "set_dimensions() may only be called on an Input or Output that has no dimensionality specified.";
+    dims_ = dims;
+}
+
+void GIOBase::set_array_size(int size) {
+    generator->check_exact_phase(GeneratorBase::ConfigureCalled);
+    user_assert(!array_size_defined()) << "set_array_size() may only be called on an Input or Output that has no array size specified.";
+    array_size_ = size;
+}
+
 bool GIOBase::dims_defined() const {
     return dims_ != -1;
 }
 
 int GIOBase::dims() const {
     // If types aren't defined, but we have one Func that is,
-    // we probably just set an Output<Halide::Func> and should propagate the types.
+    // we probably just set an Output<Func> and should propagate the types.
     if (!dims_defined()) {
         // use funcs_, not funcs(): the latter could give a much-less-helpful error message
         // in this case.
@@ -1757,12 +1859,12 @@ int GIOBase::dims() const {
     return dims_;
 }
 
-const std::vector<Halide::Func> &GIOBase::funcs() const {
+const std::vector<Func> &GIOBase::funcs() const {
     internal_assert(funcs_.size() == array_size() && exprs_.empty());
     return funcs_;
 }
 
-const std::vector<Halide::Expr> &GIOBase::exprs() const {
+const std::vector<Expr> &GIOBase::exprs() const {
     internal_assert(exprs_.size() == array_size() && funcs_.empty());
     return exprs_;
 }
@@ -1771,7 +1873,7 @@ void GIOBase::verify_internals() {
     user_assert(dims_ >= 0) << "Generator Input/Output Dimensions must have positive values";
 
     if (kind() != IOKind::Scalar) {
-        for (const Halide::Func &f : funcs()) {
+        for (const Func &f : funcs()) {
             user_assert(f.defined()) << "Input/Output " << name() << " is not defined.\n";
             user_assert(f.dimensions() == dims())
                 << "Expected dimensions " << dims()
@@ -1791,7 +1893,7 @@ void GIOBase::verify_internals() {
                 << " for " << name() << "\n";
         }
     } else {
-        for (const Halide::Expr &e : exprs()) {
+        for (const Expr &e : exprs()) {
             user_assert(e.defined()) << "Input/Ouput " << name() << " is not defined.\n";
             user_assert(e.type() == type())
                 << "Expected type " << type()
@@ -1811,11 +1913,11 @@ std::string GIOBase::array_name(size_t i) const {
 
 // 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.
-void GIOBase::check_matching_types(const std::vector<Halide::Type> &t) const {
+void GIOBase::check_matching_types(const std::vector<Type> &t) const {
     if (types_defined()) {
-        user_assert(types().size() == t.size()) << "Halide::Type mismatch for " << name() << ": expected " << types().size() << " types but saw " << t.size();
+        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)) << "Halide::Type mismatch for " << name() << ": expected " << types().at(i) << " saw " << t.at(i);
+            user_assert(types().at(i) == t.at(i)) << "Type mismatch for " << name() << ": expected " << types().at(i) << " saw " << t.at(i);
         }
     } else {
         types_ = t;
@@ -1824,7 +1926,9 @@ void GIOBase::check_matching_types(const std::vector<Halide::Type> &t) const {
 
 void GIOBase::check_gio_access() const {
     // // Allow reading when no Generator is set, to avoid having to special-case ctor initing code
-    if (!generator) return;
+    if (!generator) {
+        return;
+    }
     user_assert(generator->phase > GeneratorBase::InputsSet)
         << "The " << input_or_output() << " \"" << name() << "\" cannot be examined before build() or generate() is called.\n";
 }
@@ -1851,19 +1955,19 @@ void GIOBase::check_matching_array_size(size_t size) const {
 GeneratorInputBase::GeneratorInputBase(size_t array_size,
                                        const std::string &name,
                                        IOKind kind,
-                                       const std::vector<Halide::Type> &t,
+                                       const std::vector<Type> &t,
                                        int d)
     : GIOBase(array_size, name, kind, t, d) {
-    Halide::Internal::ObjectInstanceRegistry::register_instance(this, 0, Halide::Internal::ObjectInstanceRegistry::GeneratorInput, this, nullptr);
+    ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorInput, this, nullptr);
 }
 
-GeneratorInputBase::GeneratorInputBase(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+GeneratorInputBase::GeneratorInputBase(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
     : GeneratorInputBase(1, name, kind, t, d) {
     // nothing
 }
 
 GeneratorInputBase::~GeneratorInputBase() {
-    Halide::Internal::ObjectInstanceRegistry::unregister_instance(this);
+    ObjectInstanceRegistry::unregister_instance(this);
 }
 
 void GeneratorInputBase::check_value_writable() const {
@@ -1875,11 +1979,7 @@ void GeneratorInputBase::set_def_min_max() {
     // nothing
 }
 
-Halide::Expr GeneratorInputBase::get_def_expr() const {
-    return Halide::Expr();
-}
-
-Halide::Internal::Parameter GeneratorInputBase::parameter() const {
+Parameter GeneratorInputBase::parameter() const {
     user_assert(!this->is_array()) << "Cannot call the parameter() method on Input<[]> " << name() << "; use an explicit subscript operator instead.";
     return parameters_.at(0);
 }
@@ -1909,7 +2009,7 @@ void GeneratorInputBase::init_internals() {
             internal_assert(dims() == p.dimensions());
             funcs_.push_back(make_param_func(p, name));
         } else {
-            Halide::Expr e = Halide::Internal::Variable::make(type(), name, p);
+            Expr e = Internal::Variable::make(type(), name, p);
             exprs_.push_back(e);
         }
     }
@@ -1943,7 +2043,7 @@ void GeneratorInputBase::set_inputs(const std::vector<StubInput> &inputs) {
             parameters_.push_back(p);
         } else {
             auto e = in.expr();
-            user_assert(e.defined()) << "The input for " << name() << " is an undefined Halide::Expr. Please define it.\n";
+            user_assert(e.defined()) << "The input for " << name() << " is an undefined Expr. Please define it.\n";
             check_matching_types({e.type()});
             check_matching_dims(0);
             exprs_.push_back(e);
@@ -1955,14 +2055,14 @@ void GeneratorInputBase::set_inputs(const std::vector<StubInput> &inputs) {
     verify_internals();
 }
 
-void GeneratorInputBase::set_estimate_impl(const Halide::Var &var, const Halide::Expr &min, const Halide::Expr &extent) {
+void GeneratorInputBase::set_estimate_impl(const Var &var, const Expr &min, const Expr &extent) {
     internal_assert(exprs_.empty() && !funcs_.empty() && parameters_.size() == funcs_.size());
     for (size_t i = 0; i < funcs_.size(); ++i) {
-        Halide::Func &f = funcs_[i];
+        Func &f = funcs_[i];
         f.set_estimate(var, min, extent);
         // Propagate the estimate into the Parameter as well, just in case
         // we end up compiling this for toplevel.
-        std::vector<Halide::Var> args = f.args();
+        std::vector<Var> args = f.args();
         int dim = -1;
         for (size_t a = 0; a < args.size(); ++a) {
             if (args[a].same_as(var)) {
@@ -1971,42 +2071,42 @@ void GeneratorInputBase::set_estimate_impl(const Halide::Var &var, const Halide:
             }
         }
         internal_assert(dim >= 0);
-        Halide::Internal::Parameter &p = parameters_[i];
+        Parameter &p = parameters_[i];
         p.set_min_constraint_estimate(dim, min);
         p.set_extent_constraint_estimate(dim, extent);
     }
 }
 
-void GeneratorInputBase::set_estimates_impl(const Halide::Region &estimates) {
+void GeneratorInputBase::set_estimates_impl(const Region &estimates) {
     internal_assert(exprs_.empty() && !funcs_.empty() && parameters_.size() == funcs_.size());
     for (size_t i = 0; i < funcs_.size(); ++i) {
-        Halide::Func &f = funcs_[i];
+        Func &f = funcs_[i];
         f.set_estimates(estimates);
         // Propagate the estimate into the Parameter as well, just in case
         // we end up compiling this for toplevel.
         for (size_t dim = 0; dim < estimates.size(); ++dim) {
-            Halide::Internal::Parameter &p = parameters_[i];
-            const Halide::Range &r = estimates[dim];
+            Parameter &p = parameters_[i];
+            const Range &r = estimates[dim];
             p.set_min_constraint_estimate(dim, r.min);
             p.set_extent_constraint_estimate(dim, r.extent);
         }
     }
 }
 
-GeneratorOutputBase::GeneratorOutputBase(size_t array_size, const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+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);
-    Halide::Internal::ObjectInstanceRegistry::register_instance(this, 0, Halide::Internal::ObjectInstanceRegistry::GeneratorOutput,
+    ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorOutput,
                                               this, nullptr);
 }
 
-GeneratorOutputBase::GeneratorOutputBase(const std::string &name, IOKind kind, const std::vector<Halide::Type> &t, int d)
+GeneratorOutputBase::GeneratorOutputBase(const std::string &name, IOKind kind, const std::vector<Type> &t, int d)
     : GeneratorOutputBase(1, name, kind, t, d) {
     // nothing
 }
 
 GeneratorOutputBase::~GeneratorOutputBase() {
-    Halide::Internal::ObjectInstanceRegistry::unregister_instance(this);
+    ObjectInstanceRegistry::unregister_instance(this);
 }
 
 void GeneratorOutputBase::check_value_writable() const {
@@ -2032,13 +2132,283 @@ void GeneratorOutputBase::resize(size_t size) {
     init_internals();
 }
 
+StubOutputBufferBase::StubOutputBufferBase() = default;
+
+StubOutputBufferBase::StubOutputBufferBase(const Func &f, const std::shared_ptr<GeneratorBase> &generator)
+    : f(f), generator(generator) {
+}
+
 void StubOutputBufferBase::check_scheduled(const char *m) const {
     generator->check_scheduled(m);
 }
 
-Halide::Target StubOutputBufferBase::get_target() const {
+Realization StubOutputBufferBase::realize(std::vector<int32_t> sizes) {
+    return f.realize(std::move(sizes), get_target());
+}
+
+Target StubOutputBufferBase::get_target() const {
     return generator->get_target();
 }
 
+RegisterGenerator::RegisterGenerator(const char *registered_name, GeneratorFactory generator_factory) {
+    Internal::GeneratorRegistry::register_factory(registered_name, std::move(generator_factory));
+}
+
+void generator_test() {
+    GeneratorContext context(get_host_target());
+
+    // 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};
+
+            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() {
+                // empty
+            }
+        };
+
+        Tester tester;
+        tester.init_from_context(context);
+        internal_assert(tester.phase == GeneratorBase::Created);
+
+        // Verify that calling GeneratorParam::set() works.
+        tester.gp0.set(1);
+
+        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.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.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 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};
+            GeneratorParam<uint8_t> gp_uint8{"gp_uint8", 65};
+            GeneratorParam<int8_t> gp_int8{"gp_int8", 66};
+            GeneratorParam<char> gp_char{"gp_char", 97};
+            GeneratorParam<signed char> gp_schar{"gp_schar", 98};
+            GeneratorParam<unsigned char> gp_uchar{"gp_uchar", 99};
+            GeneratorParam<bool> gp_bool{"gp_bool", true};
+
+            Input<int> input{"input"};
+
+            Func build() {
+                internal_assert(gp0 == 1);
+                internal_assert(gp1 == 2.f);
+                internal_assert(gp2 == (uint64_t)2);  // unchanged
+                internal_assert(gp_uint8 == 67);
+                internal_assert(gp_int8 == 68);
+                internal_assert(gp_bool == false);
+                internal_assert(gp_char == 107);
+                internal_assert(gp_schar == 108);
+                internal_assert(gp_uchar == 109);
+                Var x;
+                Func output;
+                output(x) = input + gp0;
+                return output;
+            }
+        };
+
+        Tester tester;
+        tester.init_from_context(context);
+        internal_assert(tester.phase == GeneratorBase::Created);
+
+        // Verify that calling GeneratorParam::set() works.
+        tester.gp0.set(1);
+
+        // 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);
+
+        // Verify that 8-bit non-boolean GP values are parsed as integers, not chars.
+        tester.gp_int8.set_from_string("68");
+        tester.gp_uint8.set_from_string("67");
+        tester.gp_char.set_from_string("107");
+        tester.gp_schar.set_from_string("108");
+        tester.gp_uchar.set_from_string("109");
+        tester.gp_bool.set_from_string("false");
+
+        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;
+        tester_instance.init_from_context(context);
+        // 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.dimensions() == 1);
+        internal_assert(im.dim(0).extent() == 1);
+        internal_assert(im(0) == 1475.25f) << "Expected 1475.25 but saw " << im(0);
+    }
+
+    // Verify that array inputs and outputs are typed correctly.
+    {
+        class Tester : public Generator<Tester> {
+        public:
+            Input<int[]> expr_array_input{"expr_array_input"};
+            Input<Func[]> func_array_input{"input_func_array"};
+            Input<Buffer<>[]> buffer_array_input { "buffer_array_input" };
+
+            Input<int[]> expr_array_output{"expr_array_output"};
+            Output<Func[]> func_array_output{"func_array_output"};
+            Output<Buffer<>[]> buffer_array_output { "buffer_array_output" };
+
+            void generate() {
+            }
+        };
+
+        Tester tester_instance;
+
+        static_assert(std::is_same<decltype(tester_instance.expr_array_input[0]), const Expr &>::value, "type mismatch");
+        static_assert(std::is_same<decltype(tester_instance.expr_array_output[0]), const Expr &>::value, "type mismatch");
+
+        static_assert(std::is_same<decltype(tester_instance.func_array_input[0]), const Func &>::value, "type mismatch");
+        static_assert(std::is_same<decltype(tester_instance.func_array_output[0]), Func &>::value, "type mismatch");
+
+        static_assert(std::is_same<decltype(tester_instance.buffer_array_input[0]), ImageParam>::value, "type mismatch");
+        static_assert(std::is_same<decltype(tester_instance.buffer_array_output[0]), const Func &>::value, "type mismatch");
+    }
+
+    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;
+    gp_tester.init_from_context(context);
+    // 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);
+
+    std::cout << "Generator test passed" << std::endl;
+}
+
 }  // namespace Internal
 }  // namespace ion
diff --git a/src/metadata.cc b/src/metadata.cc
index 19d733ba..0914781f 100644
--- a/src/metadata.cc
+++ b/src/metadata.cc
@@ -102,7 +102,7 @@ Metadata::Metadata(const std::string& n)
         auto dv = info->is_synthetic_param() ? "" : unquote(info->get_default_value());
         auto ctv = info->is_synthetic_param() ? "" : info->get_c_type();
         auto tdv = info->is_synthetic_param() ? "" : info->get_type_decls();
-        params.push_back(ParamMD(info->name, dv, ctv, tdv));
+        params.push_back(ParamMD(info->name(), dv, ctv, tdv));
     }
 }
 


Test patch:
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 8ec2aec2..dd5a7498 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -62,6 +62,9 @@ if (WITH_CUDA)
     add_dependencies(gpu-extern ion-bb-test gpu-extern-lib)
 endif()
 
+# Duplicate name test
+ion_jit(dup-port-name SRCS dup-port-name.cc)
+
 # Export test
 ion_jit(export SRCS export.cc)
 
diff --git a/test/complex_graph_jit.cc b/test/complex_graph_jit.cc
index 34edae7b..81e7be4b 100644
--- a/test/complex_graph_jit.cc
+++ b/test/complex_graph_jit.cc
@@ -11,8 +11,10 @@ int main()
     Port input{"input", t, 2}, width{"width", t}, height{"height", t};
     Param v0{"v", "0"};
     Param v1{"v", "1"};
+
     Builder b;
     b.set_target(Halide::get_host_target());
+
     Node n;
     n = b.add("test_inc_i32x2")(input).set_param(v1);
     n = b.add("test_branch")(n["output"], width, height);
diff --git a/test/dup-port-name.cc b/test/dup-port-name.cc
new file mode 100644
index 00000000..63118ab7
--- /dev/null
+++ b/test/dup-port-name.cc
@@ -0,0 +1,22 @@
+#include "ion/ion.h"
+
+#include "test-bb.h"
+
+using namespace ion;
+
+int main()
+{
+    Halide::Type t = Halide::type_of<int32_t>();
+    Port input{"input", t, 2}, width{"width", t}, height{"height", t};
+
+    Builder b;
+    b.set_target(Halide::get_target_from_environment());
+
+    Node n;
+    n = b.add("test_branch")(n["output"], width, height);
+    n = b.add("test_merge")(n["output0"], n["output1"], height);
+
+    b.compile("complex_graph");
+
+    return 0;
+}