From 18108334fa0674b439268d35ed73ea0334d69317 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 27 Sep 2016 16:10:11 -0700 Subject: [PATCH 01/18] Generator Enhancements --- HalideGenerator.cmake | 56 +- Makefile | 14 +- src/Generator.cpp | 920 +++++++++++++- src/Generator.h | 1174 +++++++++++++++++- src/ObjectInstanceRegistry.h | 2 + test/CMakeLists.txt | 70 +- test/generator/metadata_tester_aottest.cpp | 283 ++++- test/generator/metadata_tester_generator.cpp | 82 +- test/generator/pyramid_generator.cpp | 39 +- test/generator/stubtest_generator.cpp | 76 ++ test/generator/stubtest_jittest.cpp | 100 ++ test/generator/stubuser_aottest.cpp | 53 + test/generator/stubuser_generator.cpp | 52 + 13 files changed, 2760 insertions(+), 161 deletions(-) create mode 100644 test/generator/stubtest_generator.cpp create mode 100644 test/generator/stubtest_jittest.cpp create mode 100644 test/generator/stubuser_aottest.cpp create mode 100644 test/generator/stubuser_generator.cpp diff --git a/HalideGenerator.cmake b/HalideGenerator.cmake index 9c9e69a9572e..e4d99d9e03bb 100644 --- a/HalideGenerator.cmake +++ b/HalideGenerator.cmake @@ -67,7 +67,6 @@ endfunction() # GENERATOR_ARGS are optional extra arguments passed to the generator executable during # build. function(halide_add_aot_library AOT_LIBRARY_TARGET) - # Parse arguments set(options ) set(oneValueArgs GENERATOR_TARGET GENERATOR_NAME GENERATED_FUNCTION) @@ -132,13 +131,62 @@ function(halide_add_aot_library_dependency TARGET AOT_LIBRARY_TARGET) endfunction(halide_add_aot_library_dependency) function(halide_add_generator NAME) - set(options ) - set(oneValueArgs ) - set(multiValueArgs SRCS) + set(options WITH_STUB) + set(oneValueArgs STUB_GENERATOR_NAME) + set(multiValueArgs SRCS STUB_DEPS) cmake_parse_arguments(args "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) halide_project("${NAME}" "generator" "${CMAKE_SOURCE_DIR}/tools/GenGen.cpp" ${args_SRCS}) + + # Declare a stub library if requested. + if (${args_WITH_STUB}) + halide_add_generator_stub_library(STUB_GENERATOR_TARGET "${NAME}" + STUB_GENERATOR_NAME ${args_STUB_GENERATOR_NAME}) + endif() + + # Add any stub deps passed to us. + foreach(STUB ${args_STUB_DEPS}) + halide_add_generator_stub_dependency(TARGET ${NAME} STUB_GENERATOR_TARGET ${STUB}) + endforeach() endfunction(halide_add_generator) + +function(halide_add_generator_stub_library) + set(options ) + set(oneValueArgs STUB_GENERATOR_TARGET STUB_GENERATOR_NAME) + set(multiValueArgs ) + cmake_parse_arguments(args "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + halide_generator_genfiles_dir(${args_STUB_GENERATOR_TARGET} GENFILES_DIR) + + set(STUB_HDR "${GENFILES_DIR}/${args_STUB_GENERATOR_TARGET}.stub.h") + + set(GENERATOR_EXEC_ARGS "-o" "${GENFILES_DIR}" "-e" "cpp_stub") + if (NOT ${args_STUB_GENERATOR_NAME} STREQUAL "") + list(APPEND GENERATOR_EXEC_ARGS "-g" "${args_STUB_GENERATOR_NAME}") + endif() + + set(STUBGEN "${args_STUB_GENERATOR_TARGET}.exec_stub_generator") + halide_generator_add_exec_generator_target(${STUBGEN} + GENERATOR_TARGET ${args_STUB_GENERATOR_TARGET} + GENERATOR_ARGS "${GENERATOR_EXEC_ARGS}" + GENFILES_DIR ${GENFILES_DIR} + OUTPUTS "${STUB_HDR}" + ) + set_source_files_properties("${STUB_HDR}" PROPERTIES GENERATED TRUE) +endfunction(halide_add_generator_stub_library) + +function(halide_add_generator_stub_dependency) + # Parse arguments + set(options ) + set(oneValueArgs TARGET STUB_GENERATOR_TARGET) + set(multiValueArgs ) + cmake_parse_arguments(args "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + halide_generator_genfiles_dir(${args_STUB_GENERATOR_TARGET} GENFILES_DIR) + set(STUBGEN "${args_STUB_GENERATOR_TARGET}.exec_stub_generator") + add_dependencies("${args_TARGET}" ${STUBGEN}) + target_include_directories("${args_TARGET}" PRIVATE "${GENFILES_DIR}") +endfunction(halide_add_generator_stub_dependency) diff --git a/Makefile b/Makefile index 2f8129c01a8d..087bcc0607d6 100644 --- a/Makefile +++ b/Makefile @@ -891,6 +891,11 @@ $(FILTERS_DIR)/%.a: $(BIN_DIR)/%.generator $(FILTERS_DIR)/%.h: $(FILTERS_DIR)/%.a @echo $@ produced implicitly by $^ +$(FILTERS_DIR)/%.stub.h: $(BIN_DIR)/%.generator + @mkdir -p $(FILTERS_DIR) + @-mkdir -p $(TMP_DIR) + cd $(TMP_DIR); $(CURDIR)/$< -o $(CURDIR)/$(FILTERS_DIR) -e cpp_stub + # If we want to use a Generator with custom GeneratorParams, we need to write # custom rules: to pass the GeneratorParams, and to give a unique function and file name. $(FILTERS_DIR)/cxx_mangling.a: $(BIN_DIR)/cxx_mangling.generator @@ -923,12 +928,12 @@ $(FILTERS_DIR)/pyramid.a: $(BIN_DIR)/pyramid.generator $(FILTERS_DIR)/metadata_tester.a: $(BIN_DIR)/metadata_tester.generator @mkdir -p $(FILTERS_DIR) @-mkdir -p $(TMP_DIR) - cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-no_runtime + cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-no_runtime input_type=uint8 input_dim=3 output_type=float32 output_dim=3 array_count=2 $(FILTERS_DIR)/metadata_tester_ucon.a: $(BIN_DIR)/metadata_tester.generator @mkdir -p $(FILTERS_DIR) @-mkdir -p $(TMP_DIR) - cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester_ucon -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-user_context-no_runtime + cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester_ucon -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-user_context-no_runtime input_type=uint8 input_dim=3 output_type=float32 output_dim=3 array_count=2 $(BIN_DIR)/generator_aot_metadata_tester: $(FILTERS_DIR)/metadata_tester_ucon.a @@ -969,6 +974,11 @@ $(BIN_DIR)/generator_aot_tiled_blur: $(FILTERS_DIR)/tiled_blur_blur.a $(BIN_DIR)/generator_aot_tiled_blur_interleaved: $(FILTERS_DIR)/tiled_blur_blur_interleaved.a $(BIN_DIR)/generator_aot_cxx_mangling_define_extern: $(FILTERS_DIR)/cxx_mangling.a +$(BIN_DIR)/generator_jit_stubtest: $(FILTERS_DIR)/stubtest.stub.h $(BIN_DIR)/stubtest_generator.o + +$(BIN_DIR)/stubuser_generator.o: $(FILTERS_DIR)/stubtest.stub.h +$(BIN_DIR)/stubuser.generator: $(BIN_DIR)/stubtest_generator.o + # Usually, it's considered best practice to have one Generator per # .cpp file, with the generator-name and filename matching; # nested_externs_generators.cpp is a counterexample, and thus requires diff --git a/src/Generator.cpp b/src/Generator.cpp index fd4480d17518..5c0c07385244 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include "Generator.h" @@ -104,29 +106,473 @@ void compile_module_to_filter(const Module &m, m.compile(output_files); } +Argument to_argument(const Internal::Parameter ¶m) { + Expr def, min, max; + if (!param.is_buffer()) { + def = param.get_scalar_expr(); + min = param.get_min_value(); + max = param.get_max_value(); + } + return Argument(param.name(), + param.is_buffer() ? Argument::InputBuffer : Argument::InputScalar, + param.type(), param.dimensions(), def, min, max); +} + +std::pair rational_approximation(double d) { + if (std::isnan(d)) return {0, 0}; + if (!std::isfinite(d)) return {(d < 0) ? -1 : 1, 0}; + // TODO: fix this abomination to something more intelligent + const double kDenom = 1e9; + const int64_t num = (int64_t)(d * kDenom); + const int64_t den = (int64_t)kDenom; + user_assert(std::abs((double)num / (double)den - kDenom) <= kDenom) + << "The value " << d << " cannot be accurately approximated as a ratio\n"; + return { num, den }; +} + } // namespace -const std::map &get_halide_type_enum_map() { - static const std::map 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)}, - {"float32", Halide::Float(32)}, - {"float64", Halide::Float(64)} +class StubEmitter { +public: + StubEmitter(std::ostream &dest, + const std::string &fully_qualified_name, + const std::vector& generator_params, + const std::vector& inputs, + const std::vector& outputs) + : stream(dest), fully_qualified_name(fully_qualified_name), generator_params(filter_params(generator_params, false)), + schedule_params(filter_params(generator_params, true)), inputs(inputs), outputs(outputs) { + internal_assert(!outputs.empty()); + } + + void emit(); +private: + std::ostream &stream; + const std::string fully_qualified_name; + const std::vector generator_params; + const std::vector schedule_params; + const std::vector inputs; + const std::vector outputs; + int indent{0}; + + std::vector filter_params(const std::vector &in, + bool is_schedule_params) { + std::vector out; + for (auto p : in) { + if (p->name == "target") continue; + if (p->is_schedule_param() != is_schedule_params) continue; + out.push_back(p); + } + return out; + } + + /** Emit spaces according to the current indentation level */ + std::string ind(); + + void emit_params_struct(bool schedule_only); + void emit_inputs(); +}; + +std::string StubEmitter::ind() { + std::ostringstream o; + for (int i = 0; i < indent; i++) { + o << " "; + } + return o.str(); +} + +void StubEmitter::emit_params_struct(bool is_schedule_params) { + const auto &v = is_schedule_params ? schedule_params : generator_params; + std::string name = is_schedule_params ? "ScheduleParams" : "GeneratorParams"; + stream << ind() << "struct " << name << " {\n"; + indent++; + for (auto p : v) { + stream << ind() << p->get_c_type() << " " << p->name << "{ " << p->get_default_value() << " };\n"; + } + stream << "\n"; + + stream << ind() << "// default ctor\n"; + stream << ind() << name << "() {}\n"; + stream << "\n"; + + stream << ind() << "// ctor with inputs\n"; + stream << ind() << name << "(\n"; + indent++; + std::string comma = ""; + for (auto p : v) { + stream << ind() << comma << p->get_c_type() << " " << p->name << "\n"; + comma = ", "; + } + indent--; + stream << ind() << ") : \n"; + indent++; + comma = ""; + for (auto p : v) { + stream << ind() << comma << p->name << "(" << p->name << ")\n"; + comma = ", "; + } + indent--; + stream << ind() << "{\n"; + stream << ind() << "}\n"; + stream << "\n"; + + stream << ind() << "inline NO_INLINE std::map to_string_map() const {\n"; + indent++; + stream << ind() << "std::map m;\n"; + for (auto p : v) { + if (p->is_looplevel_param()) continue; + stream << ind() << "if (" << p->name << " != " << p->get_default_value() << ") " + << "m[\"" << p->name << "\"] = " << p->call_to_string(p->name) << ";\n"; + } + stream << ind() << "return m;\n"; + indent--; + stream << ind() << "}\n"; + + if (is_schedule_params) { + stream << "\n"; + stream << ind() << "inline NO_INLINE std::map to_looplevel_map() const {\n"; + indent++; + stream << ind() << "std::map m;\n"; + for (auto p : v) { + if (!p->is_looplevel_param()) continue; + stream << ind() << "if (" << p->name << " != " << p->get_default_value() << ") " + << "m[\"" << p->name << "\"] = " << p->name << ";\n"; + } + stream << ind() << "return m;\n"; + indent--; + stream << ind() << "}\n"; + } + + indent--; + stream << ind() << "};\n"; + stream << "\n"; +} + +void StubEmitter::emit_inputs() { + stream << ind() << "const Halide::GeneratorContext& context\n"; + for (auto input : inputs) { + std::string type(input->kind() == IOKind::Function ? "Halide::Func" : "Halide::Expr"); + if (input->is_array()) { + type = "const std::vector<" + type + ">&"; + } + stream << ind() << ", " << type << " " << input->name() << "\n"; + } +} + +void StubEmitter::emit() { + std::vector namespaces = split_string(fully_qualified_name, "::"); + internal_assert(namespaces.size() >= 2); + if (namespaces[0].empty()) { + // We have a name like ::foo::bar::baz; omit the first empty ns. + namespaces.erase(namespaces.begin()); + internal_assert(namespaces.size() >= 2); + } + const std::string class_name = namespaces.back(); + namespaces.pop_back(); + + struct OutputInfo { + std::string name; + std::string ctype; + std::string getter; + }; + std::vector out_info; + for (auto output : outputs) { + out_info.push_back({ + output->name(), + output->is_array() ? "std::vector" : "Halide::Func", + std::string(output->is_array() ? "get_output_vector" : "get_output") + "(\"" + output->name() + "\")" + }); + } + + std::ostringstream guard; + guard << "HALIDE_STUB"; + for (const auto &ns : namespaces) { + guard << "_" << ns; + } + guard << "_" << class_name; + + stream << ind() << "#ifndef " << guard.str() << "\n"; + stream << ind() << "#define " << guard.str() << "\n"; + stream << "\n"; + + stream << ind() << "/* MACHINE-GENERATED - DO NOT EDIT */\n"; + stream << "\n"; + + stream << ind() << "#include \n"; + stream << ind() << "#include \n"; + stream << ind() << "#include \n"; + stream << ind() << "#include \n"; + stream << ind() << "#include \n"; + stream << ind() << "#include \n"; + stream << "\n"; + stream << ind() << "#include \"Halide.h\"\n"; + stream << "\n"; + + for (const auto &ns : namespaces) { + stream << ind() << "namespace " << ns << " {\n"; + } + stream << "\n"; + + for (auto p : generator_params) { + std::string decl = p->get_type_decls(); + if (decl.empty()) continue; + stream << decl << "\n"; + } + + for (auto p : schedule_params) { + std::string decl = p->get_type_decls(); + if (decl.empty()) continue; + stream << decl << "\n"; + } + + stream << ind() << "class " << class_name << " final : public Halide::Internal::GeneratorStub {\n"; + stream << ind() << "public:\n"; + indent++; + + emit_params_struct(true); + emit_params_struct(false); + + stream << ind() << "// default ctor\n"; + stream << ind() << class_name << "() {}\n"; + stream << "\n"; + + stream << ind() << "// ctor with inputs\n"; + stream << ind() << class_name << "(\n"; + indent++; + emit_inputs(); + stream << ind() << ", const GeneratorParams& params = GeneratorParams()\n"; + indent--; + stream << ind() << ")\n"; + indent++; + stream << ind() << ": GeneratorStub(context, &factory, params.to_string_map(), {\n"; + indent++; + for (size_t i = 0; i < inputs.size(); ++i) { + stream << ind() << "Halide::Internal::to_func_or_expr_vector(" << inputs[i]->name() << ")"; + stream << ",\n"; + } + indent--; + stream << ind() << "})\n"; + for (const auto &out : out_info) { + stream << ind() << ", " << out.name << "(" << out.getter << ")\n"; + } + indent--; + stream << ind() << "{\n"; + stream << ind() << "}\n"; + stream << "\n"; + + stream << ind() << "// templated construction method with inputs\n"; + stream << ind() << "template<\n"; + std::string comma = ""; + indent++; + for (auto p : generator_params) { + std::string type = p->get_template_type(); + std::string value = p->get_template_value(); + if (type == "float" || type == "double") { + // floats and doubles can't be used as template value arguments; + // it turns out to be pretty uncommon use floating point types + // in GeneratorParams, but to avoid breaking these cases entirely, + // use std::ratio as an approximation for the default value. + auto ratio = rational_approximation(std::atof(value.c_str())); + stream << ind() << comma << "typename" << " " << p->name << " = std::ratio<" << ratio.first << ", " << ratio.second << ">\n"; + } else { + stream << ind() << comma << type << " " << p->name << " = " << value << "\n"; + } + comma = ", "; + } + indent--; + stream << ind() << ">\n"; + stream << ind() << "static " << class_name << " make(\n"; + indent++; + emit_inputs(); + indent--; + stream << ind() << ") {\n"; + indent++; + stream << ind() << "GeneratorParams gp(\n"; + indent++; + comma = ""; + for (auto p : generator_params) { + std::string type = p->get_template_type(); + if (type == "typename") { + stream << ind() << comma << "Halide::type_of<" << p->name << ">()\n"; + } else if (type == "float" || type == "double") { + stream << ind() << comma << "ratio_to_double<" << p->name << ">()\n"; + } else { + stream << ind() << comma << p->name << "\n"; + } + comma = ", "; + } + indent--; + stream << ind() << ");\n"; + stream << ind() << "return " << class_name << "(context, \n"; + indent++; + for (size_t i = 0; i < inputs.size(); ++i) { + stream << ind() << inputs[i]->name() << ",\n"; + } + stream << ind() << "gp);\n"; + indent--; + indent--; + stream << ind() << "}\n"; + stream << "\n"; + + stream << ind() << "// schedule method\n"; + stream << ind() << "void schedule(const ScheduleParams& params = ScheduleParams()) {\n"; + indent++; + stream << ind() << "GeneratorStub::schedule(params.to_string_map(), params.to_looplevel_map());\n"; + indent--; + stream << ind() << "}\n"; + stream << "\n"; + + stream << ind() << "// move constructor\n"; + stream << ind() << class_name << "("<< class_name << "&& that)\n"; + indent++; + stream << ind() << ": GeneratorStub(std::move(that))\n"; + for (const auto &out : out_info) { + stream << ind() << ", " << out.name << "(std::move(that." << out.name << "))\n"; + } + indent--; + stream << ind() << "{\n"; + stream << ind() << "}\n"; + stream << "\n"; + + stream << ind() << "// move assignment operator\n"; + stream << ind() << class_name << "& operator=("<< class_name << "&& that) {\n"; + indent++; + stream << ind() << "GeneratorStub::operator=(std::move(that));\n"; + for (const auto &out : out_info) { + stream << ind() << out.name << " = std::move(that." << out.name << ");\n"; + } + stream << ind() << "return *this;\n"; + indent--; + stream << ind() << "}\n"; + stream << "\n"; + + stream << ind() << "// Output(s)\n"; + stream << ind() << "// TODO: identify vars used\n"; + for (auto output : outputs) { + if (output->is_array()) { + stream << ind() << "std::vector " << output->name() << ";\n"; + } else { + stream << ind() << "Halide::Func " << output->name() << ";\n"; + } + } + stream << "\n"; + + stream << ind() << "~" << class_name << "() { if (has_generator()) verify(); }\n"; + stream << "\n"; + + indent--; + stream << ind() << "protected:\n"; + indent++; + stream << ind() << "void verify() {\n"; + indent++; + stream << ind() << "using Halide::Internal::verify_same_funcs;\n"; + for (const auto &out : out_info) { + stream << ind() << "verify_same_funcs(" << out.name << ", " << out.getter << ");\n"; + } + indent--; + stream << ind() << "}\n"; + stream << "\n"; + + + indent--; + stream << ind() << "private:\n"; + indent++; + stream << ind() << "static std::unique_ptr factory(const std::map& params) {\n"; + indent++; + stream << ind() << "return Halide::Internal::RegisterGeneratorAndStub<" << class_name << ">::create(params);\n"; + indent--; + stream << ind() << "};\n"; + stream << "\n"; + + indent--; + stream << ind() << "};\n"; + stream << "\n"; + + for (int i = (int)namespaces.size() - 1; i >= 0 ; --i) { + stream << ind() << "} // namespace " << namespaces[i] << "\n"; + } + stream << "\n"; + + stream << ind() << "#endif // " << guard.str() << "\n"; +} + +void verify_same_funcs(Func a, Func b) { + user_assert(a.function().get_contents().same_as(b.function().get_contents())) + << "Expected Func " << a.name() << " and " << b.name() << " to match.\n"; +} + +void verify_same_funcs(const std::vector& a, const std::vector& b) { + user_assert(a.size() == b.size()) << "Mismatch in Function vector length.\n"; + for (size_t i = 0; i < a.size(); ++i) { + verify_same_funcs(a[i], b[i]); + } +} + +const std::map &get_halide_type_enum_map() { + static const std::map halide_type_enum_map{ + {"bool", Bool()}, + {"int8", Int(8)}, + {"int16", Int(16)}, + {"int32", Int(32)}, + {"uint8", UInt(8)}, + {"uint16", UInt(16)}, + {"uint32", UInt(32)}, + {"float32", Float(32)}, + {"float64", Float(64)} }; return halide_type_enum_map; } +std::string halide_type_to_c_source(const Type &t) { + static const std::map m = { + { halide_type_int, "Int" }, + { halide_type_uint, "UInt" }, + { halide_type_float, "Float" }, + { halide_type_handle, "Handle" }, + }; + std::ostringstream oss; + oss << "Halide::" << m.at(t.code()) << "(" << t.bits() << + ")"; + return oss.str(); +} + +std::string halide_type_to_c_type(const Type &t) { + auto encode = [](const Type &t) -> int { return t.code() << 16 | t.bits(); }; + static const std::map m = { + { encode(Int(8)), "int8_t" }, + { encode(Int(16)), "int16_t" }, + { encode(Int(32)), "int32_t" }, + { encode(Int(64)), "int64_t" }, + { encode(UInt(1)), "bool" }, + { encode(UInt(8)), "uint8_t" }, + { encode(UInt(16)), "uint16_t" }, + { encode(UInt(32)), "uint32_t" }, + { encode(UInt(64)), "uint64_t" }, + { encode(Float(32)), "float" }, + { encode(Float(64)), "double" }, + { encode(Handle(64)), "void*" } + }; + return m.at(encode(t)); +} + +LoopLevel get_halide_undefined_looplevel() { + static LoopLevel undefined(Func("__undefined_looplevel_func"), Var("__undefined_looplevel_var")); + return undefined; +} + +const std::map &get_halide_looplevel_enum_map() { + static const std::map halide_looplevel_enum_map{ + {"root", LoopLevel::root()}, + {"undefined", get_halide_undefined_looplevel()}, + {"inline", LoopLevel()}, + }; + return halide_looplevel_enum_map; +} + int generate_filter_main(int argc, char **argv, std::ostream &cerr) { const char kUsage[] = "gengen [-g GENERATOR_NAME] [-f FUNCTION_NAME] [-o OUTPUT_DIR] [-r RUNTIME_NAME] [-e EMIT_OPTIONS] [-x EXTENSION_OPTIONS] [-n FILE_BASE_NAME] " "target=target-string[,target-string...] [generator_arg=value [...]]\n\n" " -e A comma separated list of files to emit. Accepted values are " - "[assembly, bitcode, cpp, h, html, o, static_library, stmt]. If omitted, default value is [static_library, h].\n" - " -x A comma separated list of file extension (or file-suffix) pairs to substitute during file naming, " + "[assembly, bitcode, cpp, h, html, o, static_library, stmt, cpp_stub]. If omitted, default value is [static_library, h].\n" + " -x A comma separated list of file extension pairs to substitute during file naming, " "in the form [.old=.new[,.old2=.new2]]\n"; std::map flags_info = { { "-f", "" }, @@ -196,11 +642,18 @@ int generate_filter_main(int argc, char **argv, std::ostream &cerr) { cerr << kUsage; return 1; } - if (generator_args.find("target") == generator_args.end()) { - cerr << "Target missing\n"; - cerr << kUsage; - return 1; + + // It's ok to omit "target=" if we are generating *only* a cpp_stub + const std::vector emit_flags = split_string(flags_info["-e"], ","); + const bool stub_only = (emit_flags.size() == 1 && emit_flags[0] == "cpp_stub"); + if (!stub_only) { + if (generator_args.find("target") == generator_args.end()) { + cerr << "Target missing\n"; + cerr << kUsage; + return 1; + } } + // it's OK for file_base_name to be empty: filename will be based on function name std::string file_base_name = flags_info["-n"]; @@ -208,7 +661,6 @@ int generate_filter_main(int argc, char **argv, std::ostream &cerr) { // Ensure all flags start as false. emit_options.emit_static_library = emit_options.emit_h = false; - std::vector emit_flags = split_string(flags_info["-e"], ","); if (emit_flags.empty() || (emit_flags.size() == 1 && emit_flags[0].empty())) { // If omitted or empty, assume .a and .h emit_options.emit_static_library = emit_options.emit_h = true; @@ -231,9 +683,11 @@ int generate_filter_main(int argc, char **argv, std::ostream &cerr) { emit_options.emit_h = true; } else if (opt == "static_library") { emit_options.emit_static_library = true; + } else if (opt == "cpp_stub") { + emit_options.emit_cpp_stub = true; } else if (!opt.empty()) { cerr << "Unrecognized emit option: " << opt - << " not one of [assembly, bitcode, cpp, h, html, o, static_library, stmt], ignoring.\n"; + << " not one of [assembly, bitcode, cpp, h, html, o, static_library, stmt, cpp_stub], ignoring.\n"; } } } @@ -271,26 +725,43 @@ int generate_filter_main(int argc, char **argv, std::ostream &cerr) { if (!generator_name.empty()) { std::string base_path = compute_base_path(output_dir, function_name, file_base_name); - Outputs output_files = compute_outputs(targets[0], base_path, emit_options); - auto module_producer = [&generator_name, &generator_args, &cerr] - (const std::string &name, const Target &target) -> Module { - auto sub_generator_args = generator_args; - sub_generator_args["target"] = target.to_string(); - // Must re-create each time since each instance will have a different Target - auto gen = GeneratorRegistry::create(generator_name, sub_generator_args); - if (gen == nullptr) { - cerr << "Unknown generator: " << generator_name << "\n"; - exit(1); - } - return gen->build_module(name); - }; - if (targets.size() > 1 || !emit_options.substitutions.empty()) { - compile_multitarget(function_name, output_files, targets, module_producer, emit_options.substitutions); - } else { - user_assert(emit_options.substitutions.empty()) << "substitutions not supported for single-target"; - // compile_multitarget() will fail if we request anything but library and/or header, - // so defer directly to Module::compile if there is a single target. - module_producer(function_name, targets[0]).compile(output_files); + debug(1) << "Generator " << generator_name << " has base_path " << base_path << "\n"; + if (emit_options.emit_cpp_stub) { + // When generating cpp_stub, we ignore all generator args passed in, and supply a fake Target. + std::map stub_generator_args; + stub_generator_args["target"] = Target().to_string(); + auto gen = GeneratorRegistry::create(generator_name, stub_generator_args); + if (gen == nullptr) { + cerr << "Unknown generator: " << generator_name << "\n"; + exit(1); + } + auto stub_file_path = base_path + get_extension(".stub.h", emit_options); + gen->emit_cpp_stub(stub_file_path); + } + + // Don't bother with this if we're just emitting a cpp_stub. + if (!stub_only) { + Outputs output_files = compute_outputs(targets[0], base_path, emit_options); + auto module_producer = [&generator_name, &generator_args, &cerr] + (const std::string &name, const Target &target) -> Module { + auto sub_generator_args = generator_args; + sub_generator_args["target"] = target.to_string(); + // Must re-create each time since each instance will have a different Target + auto gen = GeneratorRegistry::create(generator_name, sub_generator_args); + if (gen == nullptr) { + cerr << "Unknown generator: " << generator_name << "\n"; + exit(1); + } + return gen->build_module(name); + }; + if (targets.size() > 1 || !emit_options.substitutions.empty()) { + compile_multitarget(function_name, output_files, targets, module_producer, emit_options.substitutions); + } else { + user_assert(emit_options.substitutions.empty()) << "substitutions not supported for single-target"; + // compile_multitarget() will fail if we request anything but library and/or header, + // so defer directly to Module::compile if there is a single target. + module_producer(function_name, targets[0]).compile(output_files); + } } } @@ -313,7 +784,9 @@ GeneratorRegistry &GeneratorRegistry::get_registry() { /* static */ void GeneratorRegistry::register_factory(const std::string &name, std::unique_ptr factory) { - user_assert(is_valid_name(name)) << "Invalid Generator name: " << name; + for (auto n : split_string(name, "::")) { + user_assert(is_valid_name(n)) << "Invalid Generator name part: " << n; + } GeneratorRegistry ®istry = get_registry(); std::lock_guard lock(registry.mutex); internal_assert(registry.factories.find(name) == registry.factories.end()) @@ -336,7 +809,7 @@ std::unique_ptr GeneratorRegistry::create(const std::string &name GeneratorRegistry ®istry = get_registry(); std::lock_guard lock(registry.mutex); auto it = registry.factories.find(name); - user_assert(it != registry.factories.end()) << "Generator not found: " << name; + user_assert(it != registry.factories.end()) << "Generator not found: " << name << "\n"; return it->second->create(params); } @@ -351,44 +824,76 @@ std::vector GeneratorRegistry::enumerate() { return result; } -GeneratorBase::GeneratorBase(size_t size, const void *introspection_helper) : size(size), params_built(false) { +GeneratorBase::GeneratorBase(size_t size, const void *introspection_helper) + : size(size) { ObjectInstanceRegistry::register_instance(this, size, ObjectInstanceRegistry::Generator, this, introspection_helper); } -GeneratorBase::~GeneratorBase() { ObjectInstanceRegistry::unregister_instance(this); } +GeneratorBase::~GeneratorBase() { + ObjectInstanceRegistry::unregister_instance(this); +} void GeneratorBase::build_params(bool force) { if (force) { params_built = false; - filter_arguments.clear(); + filter_inputs.clear(); + filter_outputs.clear(); + filter_params.clear(); generator_params.clear(); } if (!params_built) { std::set names; std::vector vf = ObjectInstanceRegistry::instances_in_range( this, size, ObjectInstanceRegistry::FilterParam); - for (size_t i = 0; i < vf.size(); ++i) { - Parameter *param = static_cast(vf[i]); + for (auto v : vf) { + auto param = static_cast(v); internal_assert(param != nullptr); user_assert(param->is_explicit_name()) << "Params in Generators must have explicit names: " << param->name(); user_assert(is_valid_name(param->name())) << "Invalid Param name: " << param->name(); user_assert(!names.count(param->name())) << "Duplicate Param name: " << param->name(); names.insert(param->name()); - Expr def, min, max; - if (!param->is_buffer()) { - def = param->get_scalar_expr(); - min = param->get_min_value(); - max = param->get_max_value(); - } - filter_arguments.push_back(Argument(param->name(), - param->is_buffer() ? Argument::InputBuffer : Argument::InputScalar, - param->type(), param->dimensions(), def, min, max)); + filter_params.push_back(param); + } + + std::vector vi = ObjectInstanceRegistry::instances_in_range( + this, size, ObjectInstanceRegistry::GeneratorInput); + for (auto v : vi) { + auto input = static_cast(v); + internal_assert(input != nullptr); + user_assert(is_valid_name(input->name())) << "Invalid Input name: (" << input->name() << ")\n"; + user_assert(!names.count(input->name())) << "Duplicate Input name: " << input->name(); + names.insert(input->name()); + filter_inputs.push_back(input); + } + + std::vector vo = ObjectInstanceRegistry::instances_in_range( + this, size, ObjectInstanceRegistry::GeneratorOutput); + for (auto v : vo) { + auto output = static_cast(v); + internal_assert(output != nullptr); + user_assert(is_valid_name(output->name())) << "Invalid Output name: (" << output->name() << ")\n"; + user_assert(!names.count(output->name())) << "Duplicate Output name: " << output->name(); + names.insert(output->name()); + filter_outputs.push_back(output); + } + + if (filter_params.size() > 0 && filter_inputs.size() > 0) { + user_error << "Input<> may not be used with Param<> or ImageParam in Generators.\n"; + } + + if (filter_params.size() > 0 && filter_outputs.size() > 0) { + user_error << "Output<> may not be used with Param<> or ImageParam in Generators.\n"; + } + + if (filter_inputs.size() > 0 && filter_outputs.size() == 0) { + // This doesn't catch *every* possibility (since a Generator can have zero Inputs). + user_error << "Output<> must be used with Input<> in Generators.\n"; } std::vector vg = ObjectInstanceRegistry::instances_in_range( this, size, ObjectInstanceRegistry::GeneratorParam); - for (size_t i = 0; i < vg.size(); ++i) { - GeneratorParamBase *param = static_cast(vg[i]); + for (auto v : vg) { + auto param = static_cast(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; @@ -399,6 +904,43 @@ void GeneratorBase::build_params(bool force) { } } +Func GeneratorBase::get_first_output() { + build_params(); + return get_output(filter_outputs[0]->name()); +} + +Func GeneratorBase::get_output(const std::string &n) { + user_assert(generate_called) << "Must call generate() before accessing Generator outputs."; + // There usually are very few outputs, so a linear search is fine + build_params(); + for (auto output : filter_outputs) { + if (output->name() == n) { + user_assert(!output->is_array() && output->funcs().size() == 1) << "Output " << n << " must be accessed via get_output_vector()\n"; + Func f = output->funcs().at(0); + user_assert(f.defined()) << "Output " << n << " was not defined.\n"; + return f; + } + } + internal_error << "Output " << n << " not found.\n"; + return Func(); +} + +std::vector GeneratorBase::get_output_vector(const std::string &n) { + user_assert(generate_called) << "Must call generate() before accessing Generator outputs."; + // There usually are very few outputs, so a linear search is fine + build_params(); + for (auto output : filter_outputs) { + if (output->name() == n) { + for (const auto &f : output->funcs()) { + user_assert(f.defined()) << "Output " << n << " was not fully defined.\n"; + } + return output->funcs(); + } + } + internal_error << "Output " << n << " not found.\n"; + return {}; +} + void GeneratorBase::set_generator_param_values(const std::map ¶ms) { user_assert(!generator_params_set) << "set_generator_param_values() must be called at most once per Generator instance.\n"; build_params(); @@ -411,20 +953,280 @@ void GeneratorBase::set_generator_param_values(const std::mapsecond->is_schedule_param()); p->second->set_from_string(value); } generator_params_set = true; } +void GeneratorBase::set_schedule_param_values(const std::map ¶ms, + const std::map &looplevel_params) { + user_assert(!schedule_params_set) << "set_schedule_param_values() must be called at most once per Generator instance.\n"; + build_params(); + std::map m; + for (auto param : generator_params) { + m[param->name] = param; + } + for (auto key_value : params) { + const std::string &key = key_value.first; + const std::string &value = key_value.second; + auto p = m.find(key); + user_assert(p != m.end()) << "Generator has no GeneratorParam named: " << key; + // It's not OK to set non-schedule params here. + user_assert(p->second->is_schedule_param()) << "GeneratorParam cannot be specified for: " << key; + p->second->set_from_string(value); + } + for (auto key_value : looplevel_params) { + const std::string &key = key_value.first; + const LoopLevel &value = key_value.second; + auto p = m.find(key); + user_assert(p != m.end()) << "Generator has no GeneratorParam named: " << key; + user_assert(p->second->is_schedule_param()) << "LoopLevel param cannot be specified for: " << key; + static_cast *>(p->second)->set(value); + } + schedule_params_set = true; +} + +void GeneratorBase::set_inputs(const std::vector> &inputs) { + internal_assert(!inputs_set) << "set_inputs() must be called at most once per Generator instance.\n"; + build_params(); + user_assert(inputs.size() == filter_inputs.size()) + << "Expected exactly " << filter_inputs.size() + << " inputs but got " << inputs.size() << "\n"; + for (size_t i = 0; i < filter_inputs.size(); ++i) { + filter_inputs[i]->set_inputs(inputs[i]); + } + inputs_set = true; +} + +void GeneratorBase::init_inputs_and_outputs() { + if (!inputs_set) { + for (auto input : filter_inputs) { + input->init_internals(); + } + inputs_set = true; + } + for (auto output : filter_outputs) { + output->init_internals(); + } +} + +void GeneratorBase::pre_build() { + user_assert(filter_inputs.size() == 0) << "May not use build() method with Input<>."; + user_assert(filter_outputs.size() == 0) << "May not use build() method with Output<>."; +} + +void GeneratorBase::pre_generate() { + user_assert(filter_params.size() == 0) << "May not use generate() method with Param<> or ImageParam."; + user_assert(filter_outputs.size() > 0) << "Must use Output<> with generate() method."; + init_inputs_and_outputs(); +} + +Pipeline GeneratorBase::produce_pipeline() { + user_assert(filter_outputs.size() > 0) << "Must use produce_pipeline<> with Output<>."; + std::vector funcs; + for (auto output : filter_outputs) { + for (const auto &f : output->funcs()) { + user_assert(f.defined()) << "Output \"" << f.name() << "\" was not defined.\n"; + user_assert(f.dimensions() == output->dimensions()) << "Output \"" << f.name() + << "\" requires dimensions=" << output->dimensions() + << " but was defined as dimensions=" << f.dimensions() << ".\n"; + user_assert((int)f.outputs() == (int)output->type_size()) << "Output \"" << f.name() + << "\" requires a Tuple of size " << output->type_size() + << " but was defined as Tuple of size " << f.outputs() << ".\n"; + for (size_t i = 0; i < f.output_types().size(); ++i) { + Type expected = output->type_at(i); + Type actual = f.output_types()[i]; + user_assert(expected == actual) << "Output \"" << f.name() + << "\" requires type " << expected + << " but was defined as type " << actual << ".\n"; + } + funcs.push_back(f); + } + } + return Pipeline(funcs); +} + Module GeneratorBase::build_module(const std::string &function_name, const LoweredFunc::LinkageType linkage_type) { build_params(); Pipeline pipeline = build_pipeline(); - // Building the pipeline may mutate the params and imageparams, so force a rebuild. - build_params(true); + // Building the pipeline may mutate the Params/ImageParams (but not Inputs). + if (filter_params.size() > 0) { + build_params(true); + } + + std::vector filter_arguments; + for (auto param : filter_params) { + filter_arguments.push_back(to_argument(*param)); + } + for (auto input : filter_inputs) { + for (const auto &p : input->parameters_) { + filter_arguments.push_back(to_argument(p)); + } + } return pipeline.compile_to_module(filter_arguments, function_name, target, linkage_type); } +void GeneratorBase::emit_cpp_stub(const std::string &stub_file_path) { + user_assert(!cpp_stub_class_name.empty()) << "Generator has no cpp_stub class\n"; + build_params(); + std::ofstream file(stub_file_path); + StubEmitter emit(file, cpp_stub_class_name, generator_params, filter_inputs, filter_outputs); + emit.emit(); +} + +GIOBase::GIOBase(const ArraySizeArg &array_size, + const std::string &name, + IOKind kind, + const std::vector &types, + const DimensionArg &dimensions) + : array_size_(array_size), name_(name), kind_(kind), types_(types), dimensions_(dimensions) { + user_assert(array_size_.value() >= 0) << "Generator Input/Output Arrays must have positive size."; +} + +GIOBase::~GIOBase() { + // nothing +} + +void GIOBase::verify_internals() const { + user_assert(array_size_.value() >= 0) << "Generator Input/Output Arrays must have positive values"; + user_assert(dimensions_.value() >= 0) << "Generator Input/Output Dimensions must have positive values"; + + if (kind() == IOKind::Function) { + for (const Func &f : funcs()) { + user_assert(f.defined()) << "Input/Ouput " << name() << " is not defined.\n"; + user_assert(f.dimensions() == dimensions()) + << "Expected dimensions " << dimensions() + << " but got " << f.dimensions() + << " for " << name() << "\n"; + user_assert(f.outputs() == 1) + << "Expected outputs() == " << 1 + << " but got " << f.outputs() + << " for " << name() << "\n"; + user_assert(f.output_types().size() == 1) + << "Expected output_types().size() == " << 1 + << " but got " << f.outputs() + << " for " << name() << "\n"; + user_assert(f.output_types()[0] == type()) + << "Expected type " << type() + << " but got " << f.output_types()[0] + << " for " << name() << "\n"; + } + } else { + for (const Expr &e : exprs()) { + user_assert(e.defined()) << "Input/Ouput " << name() << " is not defined.\n"; + user_assert(e.type() == type()) + << "Expected type " << type() + << " but got " << e.type() + << " for " << name() << "\n"; + } + } +} + +std::string GIOBase::array_name(size_t i) const { + std::string n = name(); + if (is_array()) { + n += "_" + std::to_string(i); + } + return n; +} + +GeneratorInputBase::GeneratorInputBase(const ArraySizeArg &array_size, + const std::string &name, + IOKind kind, + const TypeArg &t, + const DimensionArg &d) + : GIOBase(array_size, name, kind, {t}, d) { + ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorInput, this, nullptr); +} + +GeneratorInputBase::~GeneratorInputBase() { + ObjectInstanceRegistry::unregister_instance(this); +} + +void GeneratorInputBase::init_parameters() { + parameters_.clear(); + for (size_t i = 0; i < array_size(); ++i) { + parameters_.emplace_back(type(), kind() == IOKind::Function, dimensions(), array_name(i), true, false); + } + set_def_min_max(); +} + +void GeneratorInputBase::verify_internals() const { + GIOBase::verify_internals(); + + const size_t expected = (kind() == IOKind::Function) ? funcs().size() : exprs().size(); + user_assert(parameters_.size() == expected) << "Expected parameters_.size() == " + << expected << ", saw " << parameters_.size() << " for " << name() << "\n"; +} + +void GeneratorInputBase::init_internals() { + init_parameters(); + + exprs_.clear(); + funcs_.clear(); + for (size_t i = 0; i < array_size(); ++i) { + if (kind() == IOKind::Function) { + std::vector args; + std::vector args_expr; + for (int i = 0; i < dimensions(); ++i) { + Var v = Var::implicit(i); + args.push_back(v); + args_expr.push_back(v); + } + Func f = Func(array_name(i) + "_im"); + f(args) = Internal::Call::make(parameters_[i], args_expr); + funcs_.push_back(f); + } else { + Expr e = Internal::Variable::make(type(), array_name(i), parameters_[i]); + exprs_.push_back(e); + } + } + + verify_internals(); +} + +void GeneratorInputBase::set_inputs(const std::vector &inputs) { + // must re-init parameters in case some GeneratorParams changed, since + // it can affect the expected length of parameters_. + init_parameters(); + + exprs_.clear(); + funcs_.clear(); + user_assert(inputs.size() == array_size()) << "Expected inputs.size() == " + << array_size() << ", saw " << inputs.size() << " for " << name() << "\n"; + for (const FuncOrExpr & i : inputs) { + user_assert(i.kind() == kind()) << "An input for " << name() << " is not of the expected kind.\n"; + if (kind() == IOKind::Function) { + funcs_.push_back(i.func()); + } else { + exprs_.push_back(i.expr()); + } + } + + verify_internals(); +} + +GeneratorOutputBase::GeneratorOutputBase(const ArraySizeArg &array_size, const std::string &name, const std::vector &t, const DimensionArg &d) + : GIOBase(array_size, name, IOKind::Function, t, d) { + ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorOutput, + this, nullptr); +} + +GeneratorOutputBase::~GeneratorOutputBase() { + ObjectInstanceRegistry::unregister_instance(this); +} + +void GeneratorOutputBase::init_internals() { + exprs_.clear(); + funcs_.clear(); + for (size_t i = 0; i < array_size(); ++i) { + funcs_.push_back(Func(array_name(i))); + } +} + void generator_test() { GeneratorParam gp("gp", 1); diff --git a/src/Generator.h b/src/Generator.h index 7e3bcd839ecf..5a74484f1609 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -114,7 +114,31 @@ NO_INLINE std::string enum_to_string(const std::map &enum_map, c return ""; } +template +T enum_from_string(const std::map &enum_map, const std::string& s) { + auto it = enum_map.find(s); + user_assert(it != enum_map.end()) << "Enumeration value not found: " << s << "\n"; + return it->second; +} + EXPORT extern const std::map &get_halide_type_enum_map(); +inline std::string halide_type_to_enum_string(const Type &t) { + return enum_to_string(get_halide_type_enum_map(), t); +} + +EXPORT extern Halide::LoopLevel get_halide_undefined_looplevel(); +EXPORT extern const std::map &get_halide_looplevel_enum_map(); +inline std::string halide_looplevel_to_enum_string(const LoopLevel &loop_level){ + return enum_to_string(get_halide_looplevel_enum_map(), loop_level); +} + +// 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 Type &t); + +// 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 Type &t); /** generate_filter_main() is a convenient wrapper for GeneratorRegistry::create() + * compile_to_files(); @@ -157,9 +181,36 @@ class GeneratorParamBase { protected: friend class GeneratorBase; + friend class StubEmitter; virtual void set_from_string(const std::string &value_string) = 0; virtual std::string to_string() const = 0; + virtual std::string call_to_string(const std::string &v) const = 0; + virtual std::string get_c_type() const = 0; + + virtual std::string get_type_decls() const { + return ""; + } + + virtual std::string get_default_value() const { + return to_string(); + } + + virtual std::string get_template_type() const { + return get_c_type(); + } + + virtual std::string get_template_value() const { + return get_default_value(); + } + + virtual bool is_schedule_param() const { + return false; + } + + virtual bool is_looplevel_param() const { + return false; + } private: explicit GeneratorParamBase(const GeneratorParamBase &) = delete; @@ -175,10 +226,15 @@ class GeneratorParamImpl : public GeneratorParamBase { operator T() const { return this->value(); } - operator Expr() const { return Internal::make_const(type_of(), this->value()); } + operator Expr() const { return make_const(type_of(), this->value()); } virtual void set(const T &new_value) { value_ = new_value; } +protected: + bool is_looplevel_param() const override { + return std::is_same::value; + } + private: T value_; }; @@ -192,12 +248,24 @@ template class GeneratorParam_Target : public GeneratorParamImpl { public: explicit GeneratorParam_Target(const std::string &name, const T &value) : GeneratorParamImpl(name, value) {} + void set_from_string(const std::string &new_value_string) override { this->set(Target(new_value_string)); } + std::string to_string() const override { return this->value().to_string(); } + + std::string call_to_string(const std::string &v) const override { + std::ostringstream oss; + oss << v << ".to_string()"; + return oss.str(); + } + + std::string get_c_type() const override { + return "Halide::Target"; + } }; template @@ -231,6 +299,28 @@ class GeneratorParam_Arithmetic : public GeneratorParamImpl { return oss.str(); } + std::string call_to_string(const std::string &v) const override { + std::ostringstream oss; + oss << "std::to_string(" << v << ")"; + return oss.str(); + } + + std::string get_c_type() const override { + std::ostringstream oss; + if (std::is_same::value) { + return "float"; + } else if (std::is_same::value) { + return "double"; + } else if (std::is_integral::value) { + if (std::is_unsigned::value) oss << 'u'; + oss << "int" << (sizeof(T) * 8) << "_t"; + return oss.str(); + } else { + user_error << "Unknown arithmetic type\n"; + return ""; + } + } + private: const T min, max; }; @@ -255,6 +345,16 @@ class GeneratorParam_Bool : public GeneratorParam_Arithmetic { std::string to_string() const override { return this->value() ? "true" : "false"; } + + std::string call_to_string(const std::string &v) const override { + std::ostringstream oss; + oss << "(" << v << ") ? \"true\" : \"false\""; + return oss.str(); + } + + std::string get_c_type() const override { + return "bool"; + } }; template @@ -270,7 +370,41 @@ class GeneratorParam_Enum : public GeneratorParamImpl { } std::string to_string() const override { - return Internal::enum_to_string(enum_map, this->value()); + return enum_to_string(enum_map, this->value()); + } + + std::string call_to_string(const std::string &v) const override { + return "Enum_" + this->name + "_map().at(" + v + ")"; + } + + std::string get_c_type() const override { + return "Enum_" + this->name; + } + + std::string get_default_value() const override { + 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"; + for (auto key_value : enum_map) { + oss << " " << key_value.first << ",\n"; + } + oss << "};\n"; + oss << "\n"; + + // 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 NO_INLINE const std::mapname << ", std::string>& Enum_" << this->name << "_map() {\n"; + oss << " static const std::mapname << ", std::string> m = {\n"; + for (auto key_value : enum_map) { + oss << " { Enum_" << this->name << "::" << key_value.first << ", \"" << key_value.first << "\"},\n"; + } + oss << " };\n"; + oss << " return m;\n"; + oss << "};\n"; + return oss.str(); } private: @@ -281,17 +415,73 @@ template class GeneratorParam_Type : public GeneratorParam_Enum { public: explicit GeneratorParam_Type(const std::string &name, const T &value) - : GeneratorParam_Enum(name, value, Internal::get_halide_type_enum_map()) {} + : GeneratorParam_Enum(name, value, get_halide_type_enum_map()) {} + + std::string call_to_string(const std::string &v) const override { + return "Halide::Internal::halide_type_to_enum_string(" + v + ")"; + } + + std::string get_c_type() const override { + return "Halide::Type"; + } + + std::string get_template_type() const override { + return "typename"; + } + + std::string get_template_value() const override { + return halide_type_to_c_type(this->value()); + } + + std::string get_default_value() const override { + return halide_type_to_c_source(this->value()); + } + + std::string get_type_decls() const override { + return ""; + } +}; + +template +class GeneratorParam_LoopLevel : public GeneratorParam_Enum { +public: + explicit GeneratorParam_LoopLevel(const std::string &name, const std::string &def) + : GeneratorParam_Enum(name, enum_from_string(get_halide_looplevel_enum_map(), def), get_halide_looplevel_enum_map()), def(def) {} + + std::string call_to_string(const std::string &v) const override { + std::ostringstream oss; + oss << "Halide::Internal::halide_looplevel_to_enum_string(" << v << ")"; + return oss.str(); + } + std::string get_c_type() const override { + return "Halide::LoopLevel"; + } + + std::string get_default_value() const override { + if (def == "undefined") return "Halide::Internal::get_halide_undefined_looplevel()"; + if (def == "root") return "Halide::LoopLevel::root()"; + if (def == "inline") return "Halide::LoopLevel()"; + user_error << "LoopLevel value " << def << " not found.\n"; + return ""; + } + + std::string get_type_decls() const override { + return ""; + } + +private: + const std::string def; }; template using GeneratorParamImplBase = - typename Internal::select_type< - Internal::cond::value, Internal::GeneratorParam_Target>, - Internal::cond::value, Internal::GeneratorParam_Type>, - Internal::cond::value, Internal::GeneratorParam_Bool>, - Internal::cond::value, Internal::GeneratorParam_Arithmetic>, - Internal::cond::value, Internal::GeneratorParam_Enum> + typename select_type< + cond::value, GeneratorParam_Target>, + cond::value, GeneratorParam_Type>, + cond::value, GeneratorParam_LoopLevel>, + cond::value, GeneratorParam_Bool>, + cond::value, GeneratorParam_Arithmetic>, + cond::value, GeneratorParam_Enum> >::type; } // namespace Internal @@ -300,7 +490,7 @@ using GeneratorParamImplBase = * of the Generator at code-generation time. GeneratorParams are commonly * specified in build files (e.g. Makefile) to customize the behavior of * a given Generator, thus they have a very constrained set of types to allow - * for efficient specification via command-line flags. A GeneratorParm can be: + * for efficient specification via command-line flags. A GeneratorParam can be: * - any float or int type. * - bool * - enum @@ -325,7 +515,7 @@ using GeneratorParamImplBase = * */ template - class GeneratorParam : public Internal::GeneratorParamImplBase { +class GeneratorParam : public Internal::GeneratorParamImplBase { public: GeneratorParam(const std::string &name, const T &value) : Internal::GeneratorParamImplBase(name, value) {} @@ -335,6 +525,25 @@ template GeneratorParam(const std::string &name, const T &value, const std::map &enum_map) : Internal::GeneratorParamImplBase(name, value, enum_map) {} + + GeneratorParam(const std::string &name, const std::string &value) + : Internal::GeneratorParamImplBase(name, value) {} +}; + +template +class ScheduleParam : public GeneratorParam { +public: + ScheduleParam(const std::string &name, const T &value) + : GeneratorParam(name, value) {} + + ScheduleParam(const std::string &name, const T &value, const T &min, const T &max) + : GeneratorParam(name, value, min, max) {} + + ScheduleParam(const std::string &name, const std::string &value) + : GeneratorParam(name, value) {} + +protected: + bool is_schedule_param() const override { return true; } }; /** Addition between GeneratorParam and any type that supports operator+ with T. @@ -505,6 +714,582 @@ auto max(const GeneratorParam &a, Other b) -> decltype(Internal::GeneratorMin template decltype(!(T)0) operator!(const GeneratorParam &a) { return !(T)a; } +namespace Internal { + +enum class IOKind { Scalar, Function }; + +class FuncOrExpr { + IOKind kind_; + Halide::Func func_; + Halide::Expr expr_; +public: + // *not* explicit + FuncOrExpr(const Func &f) : kind_(IOKind::Function), func_(f), expr_(Expr()) {} + FuncOrExpr(const Expr &e) : kind_(IOKind::Scalar), func_(Func()), expr_(e) {} + + IOKind kind() const { + return kind_; + } + + Func func() const { + internal_assert(kind_ == IOKind::Function) << "Expected Func, got Expr"; + return func_; + } + + Expr expr() const { + internal_assert(kind_ == IOKind::Scalar) << "Expected Expr, got Func"; + return expr_; + } +}; + +template +std::vector to_func_or_expr_vector(const T &t) { + return { FuncOrExpr(t) }; +} + +template +std::vector to_func_or_expr_vector(const std::vector &v) { + std::vector r; + for (auto f : v) r.push_back(f); + return r; +} + +void verify_same_funcs(Func a, Func b); +void verify_same_funcs(const std::vector& a, const std::vector& b); + +template +class ArgWithParam { + T value_; + const GeneratorParam * const param_; + +public: + // *not* explicit ctors + ArgWithParam(const T &value) : value_(value), param_(nullptr) {} + ArgWithParam(const GeneratorParam ¶m) : value_(param), param_(¶m) {} + + T value() const { return param_ ? *param_ : value_; } +}; + +template +struct ArgWithParamVector { + const std::vector> v; + // *not* explicit + ArgWithParamVector(const T &value) : v{ArgWithParam(value)} {} + ArgWithParamVector(const GeneratorParam ¶m) : v{ArgWithParam(param)} {} + ArgWithParamVector(std::initializer_list> t) : v(t) {} +}; + +using TypeArg = ArgWithParam; +using TypeArgVector = ArgWithParamVector; +using DimensionArg = ArgWithParam; +using ArraySizeArg = ArgWithParam; + +class GIOBase { +public: + size_t array_size() const { return (size_t) array_size_.value(); } + virtual bool is_array() const { internal_error << "Unimplemented"; return false; } + + const std::string &name() const { return name_; } + IOKind kind() const { return kind_; } + size_t type_size() const { return types_.size(); } + Type type_at(size_t i) const { + internal_assert(i < types_.size()); + return types_.at(i).value(); + } + Type type() const { + internal_assert(type_size() == 1) << "Expected type_size() == 1, saw " << type_size() << " for " << name() << "\n"; + return type_at(0); + } + int dimensions() const { return dimensions_.value(); } + + const std::vector &funcs() const { + internal_assert(funcs_.size() == array_size() && exprs_.empty()); + return funcs_; + } + + const std::vector &exprs() const { + internal_assert(exprs_.size() == array_size() && funcs_.empty()); + return exprs_; + } + +protected: + GIOBase(const ArraySizeArg &array_size, + const std::string &name, + IOKind kind, + const std::vector &types, + const DimensionArg &dimensions); + virtual ~GIOBase(); + + friend class GeneratorBase; + + ArraySizeArg array_size_; // always 1 if is_array() == false + + const std::string name_; + const IOKind kind_; + std::vector types_; + DimensionArg dimensions_; + + // Exactly one will have nonzero length + std::vector funcs_; + std::vector exprs_; + + std::string array_name(size_t i) const; + + virtual void verify_internals() const; + + template + const std::vector &get_values() const; + +private: + explicit GIOBase(const GIOBase &) = delete; + void operator=(const GIOBase &) = delete; +}; + +template<> +inline const std::vector &GIOBase::get_values() const { + return exprs(); +} + +template<> +inline const std::vector &GIOBase::get_values() const { + return funcs(); +} + +class GeneratorInputBase : public GIOBase { +protected: + GeneratorInputBase(const ArraySizeArg &array_size, + const std::string &name, + IOKind kind, + const TypeArg &t, + const DimensionArg &d); + + GeneratorInputBase(const std::string &name, IOKind kind, const TypeArg &t, const DimensionArg &d) + : GeneratorInputBase(ArraySizeArg(1), name, kind, t, d) {} + + ~GeneratorInputBase() override; + + friend class GeneratorBase; + + std::vector parameters_; + + void init_internals(); + void set_inputs(const std::vector &inputs); + + virtual void set_def_min_max() { + // nothing + } + + void verify_internals() const override; + +private: + void init_parameters(); +}; + + +template +class GeneratorInputImpl : public GeneratorInputBase { +protected: + using TBase = typename std::remove_all_extents::type; + + bool is_array() const override { + return std::is_array::value; + } + + template ::value + >::type * = nullptr> + GeneratorInputImpl(const std::string &name, IOKind kind, const TypeArg &t, const DimensionArg &d) + : GeneratorInputBase(name, kind, t, d) { + } + + template ::value && std::rank::value == 1 && (std::extent::value > 0) + >::type * = nullptr> + GeneratorInputImpl(const std::string &name, IOKind kind, const TypeArg &t, const DimensionArg &d) + : GeneratorInputBase(ArraySizeArg(std::extent::value), name, kind, t, d) { + } + + template ::value && std::rank::value == 1 && std::extent::value == 0 + >::type * = nullptr> + GeneratorInputImpl(const ArraySizeArg &array_size, const std::string &name, IOKind kind, const TypeArg &t, const DimensionArg &d) + : GeneratorInputBase(array_size, name, kind, t, d) { + } + +public: + template ::value>::type * = nullptr> + size_t size() const { + return get_values().size(); + } + + template ::value>::type * = nullptr> + ValueType operator[](size_t i) const { + return get_values()[i]; + } + + template ::value>::type * = nullptr> + ValueType at(size_t i) const { + return get_values().at(i); + } + + template ::value>::type * = nullptr> + typename std::vector::const_iterator begin() const { + return get_values().begin(); + } + + template ::value>::type * = nullptr> + typename std::vector::const_iterator end() const { + return get_values().end(); + } +}; + +template +class GeneratorInput_Func : public GeneratorInputImpl { +private: + using Super = GeneratorInputImpl; + +protected: + using TBase = typename Super::TBase; + +public: + GeneratorInput_Func(const std::string &name, const TypeArg &t, const DimensionArg &d) + : Super(name, IOKind::Function, t, d) { + } + + GeneratorInput_Func(const ArraySizeArg &array_size, const std::string &name, const TypeArg &t, const DimensionArg &d) + : Super(array_size, name, IOKind::Function, t, d) { + } + + template + Expr operator()(Args&&... args) const { + return this->funcs().at(0)(std::forward(args)...); + } + + Expr operator()(std::vector args) const { + return this->funcs().at(0)(args); + } + + operator class Func() const { + return this->funcs().at(0); + } +}; + + +template +class GeneratorInput_Scalar : public GeneratorInputImpl { +private: + using Super = GeneratorInputImpl; +protected: + using TBase = typename Super::TBase; + + const TBase def_{TBase()}; + +protected: + void set_def_min_max() override { + for (Parameter &p : this->parameters_) { + p.set_scalar(def_); + } + } + +public: + GeneratorInput_Scalar(const std::string &name, + const TBase &def) + : Super(name, IOKind::Scalar, type_of(), 0), def_(def) { + } + + GeneratorInput_Scalar(const ArraySizeArg &array_size, + const std::string &name, + const TBase &def) + : Super(array_size, name, IOKind::Scalar, type_of(), 0), def_(def) { + } + + /** You can use this Input as an expression in a halide + * function definition */ + operator Expr() const { + return this->exprs().at(0); + } + + /** Using an Input as the argument to an external stage treats it + * as an Expr */ + operator ExternFuncArgument() const { + return ExternFuncArgument(this->exprs().at(0)); + } + + +}; + +template +class GeneratorInput_Arithmetic : public GeneratorInput_Scalar { +private: + using Super = GeneratorInput_Scalar; +protected: + using TBase = typename Super::TBase; + + const Expr min_, max_; + +protected: + void set_def_min_max() override { + GeneratorInput_Scalar::set_def_min_max(); + // Don't set min/max for bool + if (!std::is_same::value) { + for (Parameter &p : this->parameters_) { + if (min_.defined()) p.set_min_value(min_); + if (max_.defined()) p.set_max_value(max_); + } + } + } + +public: + GeneratorInput_Arithmetic(const std::string &name, + const TBase &def) + : Super(name, def), min_(Expr()), max_(Expr()) { + } + + GeneratorInput_Arithmetic(const ArraySizeArg &array_size, + const std::string &name, + const TBase &def) + : Super(array_size, name, def), min_(Expr()), max_(Expr()) { + } + + GeneratorInput_Arithmetic(const std::string &name, + const TBase &def, + const TBase &min, + const TBase &max) + : Super(name, def), min_(min), max_(max) { + } + + GeneratorInput_Arithmetic(const ArraySizeArg &array_size, + const std::string &name, + const TBase &def, + const TBase &min, + const TBase &max) + : Super(array_size, name, def), min_(min), max_(max) { + } +}; + +template::type> +using GeneratorInputImplBase = + typename select_type< + cond::value, GeneratorInput_Func>, + cond::value, GeneratorInput_Arithmetic>, + cond::value, GeneratorInput_Scalar> + >::type; + +} // namespace Internal + +template +class GeneratorInput : public Internal::GeneratorInputImplBase { +private: + using Super = Internal::GeneratorInputImplBase; +protected: + using TBase = typename Super::TBase; + +public: + GeneratorInput(const std::string &name, + const TBase &def = static_cast(0)) + : Super(name, def) { + } + + GeneratorInput(const Internal::ArraySizeArg &array_size, const std::string &name, + const TBase &def = static_cast(0)) + : Super(array_size, name, def) { + } + + GeneratorInput(const std::string &name, + const TBase &def, const TBase &min, const TBase &max) + : Super(name, def, min, max) { + } + + GeneratorInput(const Internal::ArraySizeArg &array_size, const std::string &name, + const TBase &def, const TBase &min, const TBase &max) + : Super(array_size, name, def, min, max) { + } + + GeneratorInput(const std::string &name, + const Internal::TypeArg &t, const Internal::DimensionArg &d) + : Super(name, t, d) { + } + + GeneratorInput(const Internal::ArraySizeArg &array_size, const std::string &name, + const Internal::TypeArg &t, const Internal::DimensionArg &d) + : Super(array_size, name, t, d) { + } +}; + +namespace Internal { + +class GeneratorOutputBase : public GIOBase { +protected: + GeneratorOutputBase(const ArraySizeArg &array_size, + const std::string &name, + const std::vector &t, + const DimensionArg& d); + + GeneratorOutputBase(const std::string &name, + const std::vector &t, + const DimensionArg& d) + : GeneratorOutputBase(ArraySizeArg(1), name, t, d) {} + + ~GeneratorOutputBase() override; + + friend class GeneratorBase; + + void init_internals(); +}; + +template +class GeneratorOutputImpl : public GeneratorOutputBase { +protected: + using TBase = typename std::remove_all_extents::type; + using ValueType = Func; + + bool is_array() const override { + return std::is_array::value; + } + + template ::value + >::type * = nullptr> + GeneratorOutputImpl(const std::string &name, const TypeArgVector &t, const DimensionArg &d) + : GeneratorOutputBase(name, t.v, d) { + } + + template ::value && std::rank::value == 1 && (std::extent::value > 0) + >::type * = nullptr> + GeneratorOutputImpl(const std::string &name, const TypeArgVector &t, const DimensionArg &d) + : GeneratorOutputBase(ArraySizeArg(std::extent::value), name, t.v, d) { + } + + template ::value && std::rank::value == 1 && std::extent::value == 0 + >::type * = nullptr> + GeneratorOutputImpl(const ArraySizeArg &array_size, const std::string &name, const TypeArgVector &t, const DimensionArg &d) + : GeneratorOutputBase(array_size, name, t.v, d) { + } + +public: + template ::value>::type * = nullptr> + FuncRef operator()(Args&&... args) const { + return get_values().at(0)(std::forward(args)...); + } + + template ::value>::type * = nullptr> + FuncRef operator()(std::vector args) const { + return get_values().at(0)(args); + } + + template ::value>::type * = nullptr> + operator class Func() const { + return get_values().at(0); + } + + template ::value>::type * = nullptr> + size_t size() const { + return get_values().size(); + } + + template ::value>::type * = nullptr> + ValueType operator[](size_t i) const { + return get_values()[i]; + } + + template ::value>::type * = nullptr> + ValueType at(size_t i) const { + return get_values().at(i); + } + + template ::value>::type * = nullptr> + typename std::vector::const_iterator begin() const { + return get_values().begin(); + } + + template ::value>::type * = nullptr> + typename std::vector::const_iterator end() const { + return get_values().end(); + } +}; + +template +class GeneratorOutput_Func : public GeneratorOutputImpl { +private: + using Super = GeneratorOutputImpl; + +protected: + using TBase = typename Super::TBase; + +protected: + GeneratorOutput_Func(const std::string &name, const TypeArgVector &t, const DimensionArg &d) + : Super(name, t, d) { + } + + GeneratorOutput_Func(const ArraySizeArg &array_size, const std::string &name, const TypeArgVector &t, const DimensionArg &d) + : Super(array_size, name, t, d) { + } +}; + + +template +class GeneratorOutput_Arithmetic : public GeneratorOutputImpl { +private: + using Super = GeneratorOutputImpl; +protected: + using TBase = typename Super::TBase; + +protected: + explicit GeneratorOutput_Arithmetic(const std::string &name) + : Super(name, {type_of()}, 0) { + } + + GeneratorOutput_Arithmetic(const ArraySizeArg &array_size, const std::string &name) + : Super(array_size, name, {type_of()}, 0) { + } +}; + +template::type> +using GeneratorOutputImplBase = + typename select_type< + cond::value, GeneratorOutput_Func>, + cond::value, GeneratorOutput_Arithmetic> + >::type; + +} // namespace Internal + +template +class GeneratorOutput : public Internal::GeneratorOutputImplBase { +private: + using Super = Internal::GeneratorOutputImplBase; +protected: + using TBase = typename Super::TBase; + +public: + explicit GeneratorOutput(const std::string &name) + : Super(name) { + } + + explicit GeneratorOutput(const char *name) + : GeneratorOutput(std::string(name)) { + } + + GeneratorOutput(const Internal::ArraySizeArg &array_size, const std::string &name) + : Super(array_size, name) { + } + + GeneratorOutput(const std::string &name, const Internal::TypeArgVector &t, const Internal::DimensionArg &d) + : Super(name, t, d) { + } + + GeneratorOutput(const Internal::ArraySizeArg &array_size, const std::string &name, const Internal::TypeArgVector &t, const Internal::DimensionArg &d) + : Super(array_size, name, t, d) { + } +}; + class NamesInterface { // Names in this class are only intended for use in derived classes. protected: @@ -525,6 +1310,7 @@ class NamesInterface { template static Expr cast(Expr e) { return Halide::cast(e); } static inline Expr cast(Halide::Type t, Expr e) { return Halide::cast(t, e); } template using GeneratorParam = Halide::GeneratorParam; + template using ScheduleParam = Halide::ScheduleParam; template using Image = Halide::Image; template using Param = Halide::Param; static inline Type Bool(int lanes = 1) { return Halide::Bool(lanes); } @@ -533,14 +1319,31 @@ class NamesInterface { static inline Type UInt(int bits, int lanes = 1) { return Halide::UInt(bits, lanes); } }; +class GeneratorContext { +public: + virtual Target get_target() const = 0; +}; + +class JITGeneratorContext : public GeneratorContext { +public: + explicit JITGeneratorContext(const Target &t) : target(t) {} + Target get_target() const override { return target; } +private: + const Target target; +}; + namespace Internal { -class GeneratorBase : public NamesInterface { +class GeneratorStub; +class SimpleGeneratorFactory; +template class RegisterGeneratorAndStub; + +class GeneratorBase : public NamesInterface, public GeneratorContext { public: GeneratorParam target{ "target", Halide::get_host_target() }; struct EmitOptions { - bool emit_o, emit_h, emit_cpp, emit_assembly, emit_bitcode, emit_stmt, emit_stmt_html, emit_static_library; + bool emit_o, emit_h, emit_cpp, emit_assembly, emit_bitcode, emit_stmt, emit_stmt_html, emit_static_library, emit_cpp_stub; // This is an optional map used to replace the default extensions generated for // a file: if an key matches an output extension, emit those files with the // corresponding value instead (e.g., ".s" -> ".assembly_text"). This is @@ -550,15 +1353,18 @@ class GeneratorBase : public NamesInterface { std::map substitutions; EmitOptions() : emit_o(false), emit_h(true), emit_cpp(false), emit_assembly(false), - emit_bitcode(false), emit_stmt(false), emit_stmt_html(false), emit_static_library(true) {} + emit_bitcode(false), emit_stmt(false), emit_stmt_html(false), emit_static_library(true), emit_cpp_stub(false) {} }; EXPORT virtual ~GeneratorBase(); - Target get_target() const { return target; } + Target get_target() const override { return target; } EXPORT void set_generator_param_values(const std::map ¶ms); + EXPORT void set_schedule_param_values(const std::map ¶ms, + const std::map &looplevel_params); + /** Given a data type, return an estimate of the "natural" vector size * for that data type when compiling for the current target. */ int natural_vector_size(Halide::Type t) const { @@ -572,25 +1378,54 @@ class GeneratorBase : public NamesInterface { return get_target().natural_vector_size(); } + EXPORT void emit_cpp_stub(const std::string &stub_file_path); + // Call build() and produce a Module for the result. // If function_name is empty, generator_name() will be used for the function. EXPORT Module build_module(const std::string &function_name = "", const LoweredFunc::LinkageType linkage_type = LoweredFunc::External); + const GeneratorContext& context() const { return *this; } + protected: EXPORT GeneratorBase(size_t size, const void *introspection_helper); EXPORT virtual Pipeline build_pipeline() = 0; + EXPORT virtual void call_generate() = 0; + EXPORT virtual void call_schedule() = 0; + + EXPORT void pre_build(); + EXPORT void pre_generate(); + EXPORT Pipeline produce_pipeline(); + + template + using Input = GeneratorInput; + + template + using Output = GeneratorOutput; + + bool build_pipeline_called{false}; + bool generate_called{false}; + bool schedule_called{false}; private: - const size_t size; + friend class GeneratorStub; + friend class SimpleGeneratorFactory; + template friend class RegisterGeneratorAndStub; + const size_t size; + std::vector filter_params; + std::vector filter_inputs; + std::vector filter_outputs; std::vector generator_params; - std::vector filter_arguments; bool params_built{false}; bool generator_params_set{false}; + bool schedule_params_set{false}; + bool inputs_set{false}; + std::string cpp_stub_class_name; EXPORT void build_params(bool force = false); + EXPORT void init_inputs_and_outputs(); // Provide private, unimplemented, wrong-result-type methods here // so that Generators don't attempt to call the global methods @@ -599,6 +1434,17 @@ class GeneratorBase : public NamesInterface { void get_jit_target_from_environment(); void get_target_from_environment(); + EXPORT Func get_first_output(); + EXPORT Func get_output(const std::string &n); + EXPORT std::vector get_output_vector(const std::string &n); + + void set_cpp_stub_class_name(const std::string &n) { + internal_assert(cpp_stub_class_name.empty()); + cpp_stub_class_name = n; + } + + EXPORT void set_inputs(const std::vector> &inputs); + GeneratorBase(const GeneratorBase &) = delete; void operator=(const GeneratorBase &) = delete; }; @@ -609,12 +1455,33 @@ class GeneratorFactory { virtual std::unique_ptr create(const std::map ¶ms) const = 0; }; +typedef std::unique_ptr (*GeneratorCreateFunc)(); + +class SimpleGeneratorFactory : public GeneratorFactory { +public: + SimpleGeneratorFactory(GeneratorCreateFunc create_func, const std::string &cpp_stub_class_name) + : create_func(create_func), cpp_stub_class_name(cpp_stub_class_name) { + internal_assert(create_func != nullptr); + } + + std::unique_ptr create(const std::map ¶ms) const override { + auto g = create_func(); + internal_assert(g.get() != nullptr); + g->set_cpp_stub_class_name(cpp_stub_class_name); + g->set_generator_param_values(params); + return g; + } +private: + const GeneratorCreateFunc create_func; + const std::string cpp_stub_class_name; +}; + class GeneratorRegistry { public: - EXPORT static void register_factory(const std::string &name, - std::unique_ptr factory); + EXPORT static void register_factory(const std::string &name, std::unique_ptr factory); EXPORT static void unregister_factory(const std::string &name); EXPORT static std::vector enumerate(); + EXPORT static std::string get_cpp_stub_class_name(const std::string &name); EXPORT static std::unique_ptr create(const std::string &name, const std::map ¶ms); @@ -640,30 +1507,275 @@ template class Generator : public Internal::GeneratorBase { Generator() : Internal::GeneratorBase(sizeof(T), Internal::Introspection::get_introspection_helper()) {} + + static std::unique_ptr create() { + return std::unique_ptr(new T()); + } + +private: + + // Implementations for build_pipeline_impl(), specialized on whether we + // have build() or generate()/schedule() methods. + + // std::is_member_function_pointer will fail if there is no member of that name, + // so we use a little SFINAE to detect if there is a method-shaped member named 'schedule'. + template + struct type_sink { typedef void type; }; + + template + struct has_schedule_method : std::false_type {}; + + template + struct has_schedule_method().schedule())>::type> : std::true_type {}; + + template ::value>::type * = nullptr> + Pipeline build_pipeline_impl() { + internal_assert(!build_pipeline_called); + static_assert(!has_schedule_method::value, "The schedule() method is ignored if you define a build() method; use generate() instead."); + pre_build(); + Pipeline p = ((T *)this)->build(); + build_pipeline_called = true; + return p; + } + template ::value>::type * = nullptr> + Pipeline build_pipeline_impl() { + internal_assert(!build_pipeline_called); + ((T *)this)->call_generate_impl(); + ((T *)this)->call_schedule_impl(); + build_pipeline_called = true; + return produce_pipeline(); + } + + // Implementations for call_generate_impl(), specialized on whether we + // have build() or generate()/schedule() methods. + + template ::value>::type * = nullptr> + void call_generate_impl() { + user_error << "Unimplemented"; + } + + template ::value>::type * = nullptr> + void call_generate_impl() { + user_assert(!generate_called) << "You may not call the generate() method more than once per instance."; + typedef typename std::result_of::type GenerateRetType; + static_assert(std::is_void::value, "generate() must return void"); + pre_generate(); + ((T *)this)->generate(); + generate_called = true; + } + + // Implementations for call_schedule_impl(), specialized on whether we + // have build() or generate()/schedule() methods. + + template ::value>::type * = nullptr> + void call_schedule_impl() { + user_error << "Unimplemented"; + } + + template ::value>::type * = nullptr> + void call_schedule_impl() { + user_assert(generate_called) << "You must call the generate() method before calling the schedule() method."; + user_assert(!schedule_called) << "You may not call the schedule() method more than once per instance."; + typedef typename std::result_of::type ScheduleRetType; + static_assert(std::is_void::value, "schedule() must return void"); + ((T *)this)->schedule(); + schedule_called = true; + } + protected: Pipeline build_pipeline() override { - return ((T *)this)->build(); + return build_pipeline_impl(); + } + + void call_generate() override { + call_generate_impl(); } + + void call_schedule() override { + call_schedule_impl(); + } +private: + friend class Internal::SimpleGeneratorFactory; + + Generator(const Generator &) = delete; + void operator=(const Generator &) = delete; }; -template class RegisterGenerator { +namespace Internal { + +template +class RegisterGeneratorAndStub { private: - class TFactory : public Internal::GeneratorFactory { - public: - std::unique_ptr create(const std::map ¶ms) const override { - std::unique_ptr g(new T()); - g->set_generator_param_values(params); - return g; - } - }; + static GeneratorCreateFunc init_create_func(GeneratorCreateFunc f) { + // This is initialized on the first call; subsequent code flows return existing value + static GeneratorCreateFunc create_func_storage = f; + return create_func_storage; + } + + static const char *init_cpp_stub_class_name(const char *n) { + // This is initialized on the first call; subsequent code flows return existing value + static const char *init_cpp_stub_class_name_storage = n; + return init_cpp_stub_class_name_storage; + } public: - RegisterGenerator(const std::string &name) { - std::unique_ptr f(new TFactory()); + static std::unique_ptr create(const std::map ¶ms) { + GeneratorCreateFunc f = init_create_func(nullptr); + user_assert(f != nullptr) << "RegisterGeneratorAndStub was not initialized; this is probably a wrong value for cpp_stub_class_name.\n"; + auto g = f(); + g->set_cpp_stub_class_name(init_cpp_stub_class_name(nullptr)); + g->set_generator_param_values(params); + return g; + } + + RegisterGeneratorAndStub(GeneratorCreateFunc create_func, const char *registry_name, const char *cpp_stub_class_name) { + (void) init_create_func(create_func); + (void) init_cpp_stub_class_name(cpp_stub_class_name); + std::unique_ptr f(new Internal::SimpleGeneratorFactory(create_func, cpp_stub_class_name)); + Internal::GeneratorRegistry::register_factory(registry_name, std::move(f)); + } +}; + +} // namespace Internal + +template class RegisterGenerator { +public: + RegisterGenerator(const char* name) { + std::unique_ptr f(new Internal::SimpleGeneratorFactory(GeneratorClass::create, "")); Internal::GeneratorRegistry::register_factory(name, std::move(f)); } }; +namespace Internal { + +class GeneratorStub { +public: + // default ctor + GeneratorStub() {} + + // move constructor + GeneratorStub(GeneratorStub&& that) : generator(std::move(that.generator)) {} + + // move assignment operator + GeneratorStub& operator=(GeneratorStub&& that) { + generator = std::move(that.generator); + return *this; + } + + Target get_target() const { return generator->get_target(); } + + // schedule method + void schedule(const std::map &schedule_params, + const std::map &schedule_params_looplevels) { + generator->set_schedule_param_values(schedule_params, schedule_params_looplevels); + generator->call_schedule(); + } + + // Overloads for first output + operator Func() const { + return get_first_output(); + } + + template + FuncRef operator()(Args&&... args) const { + return get_first_output()(std::forward(args)...); + } + + template + FuncRef operator()(std::vector args) const { + return get_first_output()(args); + } + + Realization realize(std::vector sizes) { + check_scheduled("realize"); + return get_first_output().realize(sizes, get_target()); + } + + template + Realization realize(Args&&... args) { + check_scheduled("realize"); + return get_first_output().realize(std::forward(args)..., get_target()); + } + + template + void realize(Dst dst) { + check_scheduled("realize"); + get_first_output().realize(dst, get_target()); + } + + virtual ~GeneratorStub() {} + +protected: + typedef std::function(const std::map&)> GeneratorFactory; + + template + GeneratorStub(const GeneratorContext &context, + GeneratorFactory generator_factory, + const std::map &generator_params, + const std::vector> &inputs) { + generator = generator_factory(generator_params); + generator->target.set(context.get_target()); + generator->set_inputs(inputs); + generator->call_generate(); + } + + // Output(s) + // TODO: identify vars used + Func get_output(const std::string &n) { + return generator->get_output(n); + } + + std::vector get_output_vector(const std::string &n) { + return generator->get_output_vector(n); + } + + bool has_generator() const { + return generator != nullptr; + } + + template + static double ratio_to_double() { + return (double)Ratio::num / (double)Ratio::den; + } + +private: + std::shared_ptr generator; + + Func get_first_output() const { + return generator->get_first_output(); + } + void check_scheduled(const char* m) const { + user_assert(generator->schedule_called) << "Must call schedule() before calling " << m << "()"; + } + + explicit GeneratorStub(const GeneratorStub &) = delete; + void operator=(const GeneratorStub &) = delete; +}; + +} // namespace Internal + + } // namespace Halide +// Use a little variadic macro hacking to allow two or three arguments. +// This is suboptimal, but allows us more flexibility to mutate registration in +// the future with less impact on existing code. +#define _HALIDE_REGISTER_GENERATOR2(GEN_CLASS_NAME, GEN_REGISTRY_NAME) \ + Halide::RegisterGenerator(GEN_REGISTRY_NAME); + +#define _HALIDE_REGISTER_GENERATOR3(GEN_CLASS_NAME, GEN_REGISTRY_NAME, FULLY_QUALIFIED_STUB_NAME) \ + Halide::Internal::RegisterGeneratorAndStub<::FULLY_QUALIFIED_STUB_NAME>(GEN_CLASS_NAME::create, GEN_REGISTRY_NAME, #FULLY_QUALIFIED_STUB_NAME); + +#define _HALIDE_REGISTER_GENERATOR_CHOOSER(_1, _2, _3, NAME, ...) NAME + +#define HALIDE_REGISTER_GENERATOR(...) \ + _HALIDE_REGISTER_GENERATOR_CHOOSER(__VA_ARGS__, _HALIDE_REGISTER_GENERATOR3, _HALIDE_REGISTER_GENERATOR2)(__VA_ARGS__) + + #endif // HALIDE_GENERATOR_H_ diff --git a/src/ObjectInstanceRegistry.h b/src/ObjectInstanceRegistry.h index 847b277262a3..9e7a3883853d 100644 --- a/src/ObjectInstanceRegistry.h +++ b/src/ObjectInstanceRegistry.h @@ -25,6 +25,8 @@ class ObjectInstanceRegistry { Invalid, Generator, GeneratorParam, + GeneratorInput, + GeneratorOutput, FilterParam }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1372c8cc38e2..4a90634a1f67 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -67,24 +67,40 @@ if (WITH_TEST_RENDERSCRIPT) tests(renderscript) endif() +function(add_test_generator NAME) + set(options WITH_STUB) + set(oneValueArgs ) + set(multiValueArgs STUB_DEPS) + cmake_parse_arguments(args "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + # convert TRUE|FALSE -> WITH_STUB|emptystring + if (${args_WITH_STUB}) + set(args_WITH_STUB WITH_STUB) + else() + set(args_WITH_STUB ) + endif() + halide_add_generator("${NAME}.generator" + SRCS "${CMAKE_CURRENT_SOURCE_DIR}/generator/${NAME}_generator.cpp" + STUB_DEPS ${args_STUB_DEPS} + ${args_WITH_STUB}) +endfunction(add_test_generator) + function(halide_define_jit_test NAME) set(options ) set(oneValueArgs ) - set(multiValueArgs ) + set(multiValueArgs STUB_DEPS) cmake_parse_arguments(args "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) halide_project("generator_jit_${NAME}" "generator" "${CMAKE_CURRENT_SOURCE_DIR}/generator/${NAME}_jittest.cpp") + foreach(STUB_DEP ${args_STUB_DEPS}) + halide_add_generator_stub_dependency(TARGET "generator_jit_${NAME}" + STUB_GENERATOR_TARGET ${STUB_DEP}) + endforeach() endfunction(halide_define_jit_test) include(../HalideGenerator.cmake) -function(add_test_generator NAME) - halide_add_generator("${NAME}.generator" - SRCS "${CMAKE_CURRENT_SOURCE_DIR}/generator/${NAME}_generator.cpp") -endfunction(add_test_generator) - # Usage: # halide_define_aot_test(NAME # [GENERATED_FUNCTION make_image() { template void verify(const Image &input, const Image &output0, - const Image &output1) { + const Image &output1, + const Image &output_scalar, + const Image &output_array0, + const Image &output_array1) { + // Image doesn't allow for zero-dimensional buffers -- use 1-dimensional for now + if (output_scalar.dimensions() != 1 || output_scalar.width() != 1) { + fprintf(stderr, "output_scalar should be zero-dimensional\n"); + exit(-1); + } + if (output_scalar() != 1234.25f) { + fprintf(stderr, "output_scalar value is wrong (%f)\n", output_scalar(0)); + exit(-1); + } for (int x = 0; x < kSize; x++) { for (int y = 0; y < kSize; y++) { for (int c = 0; c < 3; c++) { @@ -144,6 +156,14 @@ void verify(const Image &input, fprintf(stderr, "img1[%d, %d, %d] = %f, expected %f\n", x, y, c, (double)actual1, (double)expected1); exit(-1); } + if (output_array0(x, y, c) != 1.5f) { + fprintf(stderr, "output_array0[%d, %d, %d] = %f, expected %f\n", x, y, c, output_array0(x, y, c), 1.5f); + exit(-1); + } + if (output_array1(x, y, c) != 3.0f) { + fprintf(stderr, "output_array1[%d, %d, %d] = %f, expected %f\n", x, y, c, output_array1(x, y, c), 3.0f); + exit(-1); + } } } } @@ -374,6 +394,168 @@ void check_metadata(const halide_filter_metadata_t &md, bool expect_ucon_at_0) { nullptr, nullptr, }, + { + "array_input_0", + halide_argument_kind_input_buffer, + 3, + halide_type_t(halide_type_uint, 8), + nullptr, + nullptr, + nullptr, + }, + { + "array_input_1", + halide_argument_kind_input_buffer, + 3, + halide_type_t(halide_type_uint, 8), + nullptr, + nullptr, + nullptr, + }, + { + "array2_input_0", + halide_argument_kind_input_buffer, + 3, + halide_type_t(halide_type_uint, 8), + nullptr, + nullptr, + nullptr, + }, + { + "array2_input_1", + halide_argument_kind_input_buffer, + 3, + halide_type_t(halide_type_uint, 8), + nullptr, + nullptr, + nullptr, + }, + { + "array_i8_0", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 8), + make_scalar(0), + nullptr, + nullptr, + }, + { + "array_i8_1", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 8), + make_scalar(0), + nullptr, + nullptr, + }, + { + "array2_i8_0", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 8), + make_scalar(0), + nullptr, + nullptr, + }, + { + "array2_i8_1", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 8), + make_scalar(0), + nullptr, + nullptr, + }, + { + "array_i16_0", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 16), + make_scalar(16), + nullptr, + nullptr, + }, + { + "array_i16_1", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 16), + make_scalar(16), + nullptr, + nullptr, + }, + { + "array2_i16_0", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 16), + make_scalar(16), + nullptr, + nullptr, + }, + { + "array2_i16_1", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 16), + make_scalar(16), + nullptr, + nullptr, + }, + { + "array_i32_0", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 32), + make_scalar(32), + make_scalar(-32), + make_scalar(127), + }, + { + "array_i32_1", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 32), + make_scalar(32), + make_scalar(-32), + make_scalar(127), + }, + { + "array2_i32_0", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 32), + make_scalar(32), + make_scalar(-32), + make_scalar(127), + }, + { + "array2_i32_1", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_int, 32), + make_scalar(32), + make_scalar(-32), + make_scalar(127), + }, + { + "array_h_0", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_handle, 64), + nullptr, + nullptr, + nullptr, + }, + { + "array_h_1", + halide_argument_kind_input_scalar, + 0, + halide_type_t(halide_type_handle, 64), + nullptr, + nullptr, + nullptr, + }, { "output.0", halide_argument_kind_output_buffer, @@ -391,6 +573,69 @@ void check_metadata(const halide_filter_metadata_t &md, bool expect_ucon_at_0) { nullptr, nullptr, nullptr, + }, + { + "output_scalar", + halide_argument_kind_output_buffer, + 0, + halide_type_t(halide_type_float, 32), + nullptr, + nullptr, + nullptr, + }, + { + "array_outputs_0", + halide_argument_kind_output_buffer, + 3, + halide_type_t(halide_type_float, 32), + nullptr, + nullptr, + nullptr, + }, + { + "array_outputs_1", + halide_argument_kind_output_buffer, + 3, + halide_type_t(halide_type_float, 32), + nullptr, + nullptr, + nullptr, + }, + { + "array_outputs2_0", + halide_argument_kind_output_buffer, + 3, + halide_type_t(halide_type_float, 32), + nullptr, + nullptr, + nullptr, + }, + { + "array_outputs2_1", + halide_argument_kind_output_buffer, + 3, + halide_type_t(halide_type_float, 32), + nullptr, + nullptr, + nullptr, + }, + { + "array_outputs3_0", + halide_argument_kind_output_buffer, + 0, + halide_type_t(halide_type_float, 32), + nullptr, + nullptr, + nullptr, + }, + { + "array_outputs3_1", + halide_argument_kind_output_buffer, + 0, + halide_type_t(halide_type_float, 32), + nullptr, + nullptr, + nullptr, } }; const int kExpectedArgumentCount = (int)sizeof(kExpectedArguments) / sizeof(kExpectedArguments[0]); @@ -419,6 +664,10 @@ int main(int argc, char **argv) { Image output0(kSize, kSize, 3); Image output1(kSize, kSize, 3); + Image output_scalar(1); // Image doesn't allow for zero-dimensional buffers + Image output_array[2] = {{kSize, kSize, 3}, {kSize, kSize, 3}}; + Image output_array2[2] = {{kSize, kSize, 3}, {kSize, kSize, 3}}; + Image output_array3[2] = {{1}, {1}}; result = metadata_tester( input, // Input @@ -434,7 +683,20 @@ int main(int argc, char **argv) { 0.f, // Input 0.0, // Input nullptr, // Input - output0, output1 // Output + input, input, // Input + input, input, // Input + 0, 0, // Input + 0, 0, // Input + 0, 0, // Input + 0, 0, // Input + 0, 0, // Input + 0, 0, // Input + nullptr, nullptr, // Input + output0, output1, // Output + output_scalar, // Output + output_array[0], output_array[1], // Output + output_array2[0], output_array2[1], // Output + output_array3[0], output_array3[1] // Output ); EXPECT_EQ(0, result); @@ -453,11 +715,24 @@ int main(int argc, char **argv) { 0.f, // Input 0.0, // Input nullptr, // Input - output0, output1 // Output + input, input, // Input + input, input, // Input + 0, 0, // Input + 0, 0, // Input + 0, 0, // Input + 0, 0, // Input + 0, 0, // Input + 0, 0, // Input + nullptr, nullptr, // Input + output0, output1, // Output + output_scalar, // Output + output_array[0], output_array[1], // Output + output_array2[0], output_array2[1], // Output + output_array3[0], output_array3[1] // Output ); EXPECT_EQ(0, result); - verify(input, output0, output1); + verify(input, output0, output1, output_scalar, output_array[0], output_array[1]); check_metadata(*metadata_tester_metadata(), false); if (!strcmp(metadata_tester_metadata()->name, "metadata_tester_metadata")) { diff --git a/test/generator/metadata_tester_generator.cpp b/test/generator/metadata_tester_generator.cpp index d83bb12a02ec..7023980ae1e8 100644 --- a/test/generator/metadata_tester_generator.cpp +++ b/test/generator/metadata_tester_generator.cpp @@ -7,35 +7,73 @@ enum class SomeEnum { Foo, class MetadataTester : public Halide::Generator { public: - GeneratorParam input_type{ "input_type", UInt(8) }; - GeneratorParam output_type{ "output_type", Float(32) }; - - ImageParam input{ UInt(8), 3, "input" }; - Param b{ "b", true }; - Param i8{ "i8", 8, -8, 127 }; - Param i16{ "i16", 16, -16, 127 }; - Param i32{ "i32", 32, -32, 127 }; - Param i64{ "i64", 64, -64, 127 }; - Param u8{ "u8", 80, 8, 255 }; - Param u16{ "u16", 160, 16, 2550 }; - Param u32{ "u32", 320, 32, 2550 }; - Param u64{ "u64", 640, 64, 2550 }; - Param f32{ "f32", 32.1234f, -3200.1234f, 3200.1234f }; - Param f64{ "f64", 64.25f, -6400.25f, 6400.25f }; - Param h{ "h", nullptr }; - - Func build() { - input = ImageParam(input_type, input.dimensions(), input.name()); + // Default values for all of these are deliberately wrong: + GeneratorParam input_type{ "input_type", Int(16) }; // must be overridden to UInt(8) + GeneratorParam input_dim{ "input_dim", 2 }; // must be overridden to 3 + GeneratorParam output_type{ "output_type", Int(16) }; // must be overridden to Float(32) + GeneratorParam output_dim{ "output_dim", 2 }; // must be overridden to 3 + GeneratorParam array_count{ "array_count", 32 }; // must be overridden to 2 + Input input{ "input", input_type, input_dim }; + Input b{ "b", true }; + Input i8{ "i8", 8, -8, 127 }; + Input i16{ "i16", 16, -16, 127 }; + Input i32{ "i32", 32, -32, 127 }; + Input i64{ "i64", 64, -64, 127 }; + Input u8{ "u8", 80, 8, 255 }; + Input u16{ "u16", 160, 16, 2550 }; + Input u32{ "u32", 320, 32, 2550 }; + Input u64{ "u64", 640, 64, 2550 }; + Input f32{ "f32", 32.1234f, -3200.1234f, 3200.1234f }; + Input f64{ "f64", 64.25f, -6400.25f, 6400.25f }; + Input h{ "h", nullptr }; + + Input array_input{ array_count, "array_input", input_type, input_dim }; + Input array2_input{ "array2_input", input_type, input_dim }; + Input array_i8{ array_count, "array_i8" }; + Input array2_i8{ "array2_i8" }; + Input array_i16{ array_count, "array_i16", 16 }; + Input array2_i16{ "array2_i16", 16 }; + Input array_i32{ array_count, "array_i32", 32, -32, 127 }; + Input array2_i32{ "array2_i32", 32, -32, 127 }; + Input array_h{ array_count, "array_h", nullptr }; + // array count of 0 means there are no inputs: for AOT, doesn't affect C call signature + // (Note that we can't use Func[0] for this, as some compilers don't properly distinguish + // between T[] and T[0].) + Input empty_inputs{ 0, "empty_inputs", Float(32), 3 }; + + Output output{ "output", {output_type, Float(32)}, output_dim }; + Output output_scalar{ "output_scalar" }; + Output array_outputs{ array_count, "array_outputs", Float(32), 3 }; + Output array_outputs2{ "array_outputs2", Float(32), 3 }; + Output array_outputs3{ "array_outputs3" }; + // array count of 0 means there are no outputs: for AOT, doesn't affect C call signature. + // (Note that we can't use Func[0] for this, as some compilers don't properly distinguish + // between T[] and T[0].) + Output empty_outputs{ 0, "empty_outputs", Float(32), 3 }; + + void generate() { Var x, y, c; + // These should both be zero; they are here to exercise the operator[] overloads + Expr zero1 = array_input[1](x, y, c) - array_input[0](x, y, c); + Expr zero2 = array_i32[1] - array_i32[0]; + Func f1, f2; - f1(x, y, c) = cast(output_type, input(x, y, c)); + f1(x, y, c) = cast(output_type, input(x, y, c) + zero1 + zero2); f2(x, y, c) = cast(f1(x, y, c) + 1); - Func output("output"); output(x, y, c) = Tuple(f1(x, y, c), f2(x, y, c)); - return output; + output_scalar() = 1234.25f; + for (size_t i = 0; i < array_outputs.size(); ++i) { + array_outputs[i](x, y, c) = (i + 1) * 1.5f; + array_outputs2[i](x, y, c) = (i + 1) * 1.5f; + array_outputs3[i]() = 42.f; + } + } + + void schedule() { + // empty } }; diff --git a/test/generator/pyramid_generator.cpp b/test/generator/pyramid_generator.cpp index c7a808651dc5..9a1fddf8febb 100644 --- a/test/generator/pyramid_generator.cpp +++ b/test/generator/pyramid_generator.cpp @@ -4,37 +4,40 @@ namespace { class Pyramid : public Halide::Generator { public: - ImageParam input{ Float(32), 2, "input" }; GeneratorParam levels{"levels", 1}; // deliberately wrong value, must be overridden to 10 - Var x, y; - - Func downsample(Func big) { - Func small; - small(x, y) = (big(2*x, 2*y) + - big(2*x+1, 2*y) + - big(2*x, 2*y+1) + - big(2*x+1, 2*y+1))/4; - return small; - } + Input input{ "input", Float(32), 2 }; - Pipeline build() { - std::vector pyramid(levels); - Func in = Halide::BoundaryConditions::repeat_edge(input); + Output pyramid{ levels, "pyramid", Float(32), 2 }; - pyramid[0](x, y) = in(x, y); + void generate() { + pyramid[0](x, y) = input(x, y); for (size_t i = 1; i < pyramid.size(); i++) { pyramid[i](x, y) = downsample(pyramid[i-1])(x, y); } + } + void schedule() { for (Func p : pyramid) { - p.compute_root().parallel(y); + // No need to specify compute_root() for outputs + p.parallel(y); // Vectorize if we're still wide enough at this level - p.specialize(p.output_buffer().width() >= 8).vectorize(x, 8); + const int v = natural_vector_size(); + p.specialize(p.output_buffer().width() >= v).vectorize(x, v); } + } + +private: + Var x, y; - return pyramid; + Func downsample(Func big) { + Func small; + small(x, y) = (big(2*x, 2*y) + + big(2*x+1, 2*y) + + big(2*x, 2*y+1) + + big(2*x+1, 2*y+1))/4; + return small; } }; diff --git a/test/generator/stubtest_generator.cpp b/test/generator/stubtest_generator.cpp new file mode 100644 index 000000000000..2fd7355cfc60 --- /dev/null +++ b/test/generator/stubtest_generator.cpp @@ -0,0 +1,76 @@ +#include "Halide.h" + +namespace { + +enum class BagType { Paper, Plastic }; + +class StubTest : public Halide::Generator { +public: + GeneratorParam input_type{ "input_type", UInt(8) }; + GeneratorParam output_type{ "output_type", Float(32) }; + GeneratorParam array_count{ "array_count", 2 }; + GeneratorParam float_param{ "float_param", 3.1415926535f }; + GeneratorParam bag_type{ "bag_type", + BagType::Paper, + { { "paper", BagType::Paper }, + { "plastic", BagType::Plastic } } }; + + ScheduleParam vectorize{ "vectorize", true }; + ScheduleParam intermediate_level{ "intermediate_level", "undefined" }; + + Input input{ array_count, "input", input_type, 3 }; + Input float_arg{ "float_arg", 1.0f, 0.0f, 100.0f }; + Input int_arg{ array_count, "int_arg", 1 }; + + Output f{"f", {input_type, output_type}, 3}; + Output g{ array_count, "g", Int(16), 2}; + + void generate() { + assert(array_count >= 1); + + // Gratuitous intermediate for the purpose of exercising + // ScheduleParam + intermediate(x, y, c) = input[0](x, y, c) * float_arg; + + f(x, y, c) = Tuple( + intermediate(x, y, c), + cast(output_type, intermediate(x, y, c) + int_arg[0])); + + for (size_t i = 0; i < input.size(); ++i) { + g[i](x, y) = cast(input[i](x, y, 0) + int_arg[i]); + } + } + + void schedule() { + intermediate.compute_at(intermediate_level); + if (vectorize) intermediate.vectorize(x, natural_vector_size()); + } + +private: + Var x{"x"}, y{"y"}, c{"c"}; + + Func intermediate{"intermediate"}; +}; + +} // namespace + +namespace StubNS1 { +namespace StubNS2 { + +// must forward-declare the name we want for the stub, inside the proper namespace(s). +// None of the namespace(s) may be anonymous (if you do, failures will occur at Halide +// compilation time). +class StubTest; + + +} // namespace StubNS2 +} // namespace StubNS1 + +namespace { + +// If the fully-qualified stub name specified for third argument hasn't been declared +// properly, a compile error will result. The fully-qualified name *must* have at least one +// namespace (i.e., a name at global scope is not acceptable). +auto register_me = HALIDE_REGISTER_GENERATOR(StubTest, "stubtest", StubNS1::StubNS2::StubTest); + +} // namespace diff --git a/test/generator/stubtest_jittest.cpp b/test/generator/stubtest_jittest.cpp new file mode 100644 index 000000000000..24cda506bf79 --- /dev/null +++ b/test/generator/stubtest_jittest.cpp @@ -0,0 +1,100 @@ +#include "Halide.h" + +#include "stubtest.stub.h" + +using Halide::Argument; +using Halide::Expr; +using Halide::Func; +using Halide::Image; +using Halide::LoopLevel; +using Halide::Var; +using StubNS1::StubNS2::StubTest; + +const int kSize = 32; + +Halide::Var x, y, c; + +template +Image MakeImage(int extra) { + Image im(kSize, kSize, 3); + for (int x = 0; x < kSize; x++) { + for (int y = 0; y < kSize; y++) { + for (int c = 0; c < 3; c++) { + im(x, y, c) = static_cast(x + y + c + extra); + } + } + } + return im; +} + +template +void verify(const Image &input, float float_arg, int int_arg, const Image &output) { + if (input.width() != output.width() || + input.height() != output.height()) { + fprintf(stderr, "size mismatch\n"); + exit(-1); + } + int channels = std::max(1, std::min(input.channels(), output.channels())); + for (int x = 0; x < output.width(); x++) { + for (int y = 0; y < output.height(); y++) { + for (int c = 0; c < channels; c++) { + const OutputType expected = static_cast(input(x, y, c) * float_arg + int_arg); + const OutputType actual = output(x, y, c); + if (expected != actual) { + fprintf(stderr, "img[%d, %d, %d] = %f, expected %f\n", x, y, c, (double)actual, (double)expected); + exit(-1); + } + } + } + } +} + +int main(int argc, char **argv) { + Halide::JITGeneratorContext context(Halide::get_target_from_environment()); + + constexpr int kArrayCount = 2; + + Image src[kArrayCount] = { + MakeImage(0), + MakeImage(1) + }; + + std::vector int_args = { 33, 66 }; + + // the Stub wants Expr, so make a conversion in place + std::vector int_args_expr(int_args.begin(), int_args.end()); + + // We statically know the types we want, so the templated construction method + // is most convenient. + auto gen = StubTest::make( + context, + { Func(src[0]), Func(src[1]) }, + 1.234f, + int_args_expr); + + StubTest::ScheduleParams sp; + // This generator default intermediate_level to "undefined", + // so we *must* specify something for it (else we'll crater at + // Halide compile time). We'll use this: + sp.intermediate_level = LoopLevel(gen.f, Var("y")); + // ...but any of the following would also be OK: + // sp.intermediate_level = LoopLevel::root(); + // sp.intermediate_level = LoopLevel(gen.f, Var("x")); + // sp.intermediate_level = LoopLevel(gen.f, Var("c")); + gen.schedule(sp); + + Halide::Realization f_realized = gen.realize(kSize, kSize, 3); + Image f0 = f_realized[0]; + Image f1 = f_realized[1]; + verify(src[0], 1.234f, 0, f0); + verify(src[0], 1.234f, 33, f1); + + for (int i = 0; i < kArrayCount; ++i) { + Halide::Realization g_realized = gen.g[i].realize(kSize, kSize, gen.get_target()); + Image g0 = g_realized; + verify(src[i], 1.0f, int_args[i], g0); + } + + printf("Success!\n"); + return 0; +} diff --git a/test/generator/stubuser_aottest.cpp b/test/generator/stubuser_aottest.cpp new file mode 100644 index 000000000000..3fe5a8b87d6a --- /dev/null +++ b/test/generator/stubuser_aottest.cpp @@ -0,0 +1,53 @@ +#include "HalideRuntime.h" +#include "HalideBuffer.h" + +#include "stubuser.h" + +using namespace Halide; + +const int kSize = 32; + +template +Image MakeImage() { + Image im(kSize, kSize, 3); + for (int x = 0; x < kSize; x++) { + for (int y = 0; y < kSize; y++) { + for (int c = 0; c < 3; c++) { + im(x, y, c) = static_cast(x + y + c); + } + } + } + return im; +} + +const float kFloatArg = 1.234f; +const int kIntArg = 33; +const float kOffset = 2.f; + +template +void verify(const Image &input, const Image &output) { + for (int x = 0; x < kSize; x++) { + for (int y = 0; y < kSize; y++) { + for (int c = 0; c < 3; c++) { + const OutputType expected = static_cast(input(x, y, c) * kFloatArg + kIntArg) + kOffset; + const OutputType actual = output(x, y, c); + if (expected != actual) { + fprintf(stderr, "img[%d, %d, %d] = %f, expected %f\n", x, y, c, (double)actual, (double)expected); + exit(-1); + } + } + } + } +} + +int main(int argc, char **argv) { + + Image input = MakeImage(); + Image output(kSize, kSize, 3); + + stubuser(input, output); + verify(input, output); + + printf("Success!\n"); + return 0; +} diff --git a/test/generator/stubuser_generator.cpp b/test/generator/stubuser_generator.cpp new file mode 100644 index 000000000000..adb04d261718 --- /dev/null +++ b/test/generator/stubuser_generator.cpp @@ -0,0 +1,52 @@ +#include "Halide.h" +#include "stubtest.stub.h" + +using StubNS1::StubNS2::StubTest; + +namespace { + +class StubUser : public Halide::Generator { +public: + GeneratorParam input_type{ "input_type", UInt(8) }; + GeneratorParam output_type{ "output_type", UInt(8) }; + GeneratorParam int_arg{ "int_arg", 33 }; + + Input input{ "input", input_type, 3 }; + + Output output{"output", output_type, 3}; + + void generate() { + + // Since we need to propagate types passed to us via our own GeneratorParams, + // we can't (easily) use the templated constructor; instead, pass on the + // values via the Stub's GeneratorParams struct. + StubTest::GeneratorParams gp; + gp.input_type = input_type; + gp.output_type = output_type; + // Override array_count to only expect 1 input and provide one output for g + gp.array_count = 1; + + Expr float_arg_expr(1.234f); + stub = StubTest(context(), { input }, float_arg_expr, { int_arg }, gp); + + const float kOffset = 2.f; + output(x, y, c) = cast(output_type, stub.f(x, y, c)[1] + kOffset); + } + + void schedule() { + const bool vectorize = true; + stub.schedule({ vectorize, LoopLevel(output, Var("y")) }); + } + +private: + Var x{"x"}, y{"y"}, c{"c"}; + StubTest stub; +}; + +// Note that HALIDE_REGISTER_GENERATOR() with just two args is functionally +// identical to the old HalideRegister<> syntax: no stub being defined, +// just AOT usage. (If you try to generate a stub for this class you'll +// fail with an error at generation time.) +auto register_me = HALIDE_REGISTER_GENERATOR(StubUser, "stubuser"); + +} // namespace From 77a0e97f173e42e8a9dcb16bd895c228efd23c3a Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 4 Oct 2016 12:19:26 -0700 Subject: [PATCH 02/18] Revise the Stub generator to use a struct for Inputs as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From offline discussion: it was suggested that Stubs should accept Inputs (aka Params) in struct form, similar to how Generator/ScheduleParams are handled; this allows for nicer symmetry, while still allowing inline “calls-style” declarations via C++11 aggregate initialization syntax. Take a look and see what you think. --- src/Generator.cpp | 77 ++++++++++++++++++--------- test/generator/stubtest_jittest.cpp | 9 ++-- test/generator/stubuser_generator.cpp | 11 +++- 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 0326b815e8e3..676e1be006e2 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -161,8 +161,8 @@ class StubEmitter { /** Emit spaces according to the current indentation level */ std::string ind(); + void emit_inputs_struct(); void emit_params_struct(bool schedule_only); - void emit_inputs(); }; std::string StubEmitter::ind() { @@ -176,18 +176,16 @@ std::string StubEmitter::ind() { void StubEmitter::emit_params_struct(bool is_schedule_params) { const auto &v = is_schedule_params ? schedule_params : generator_params; std::string name = is_schedule_params ? "ScheduleParams" : "GeneratorParams"; - stream << ind() << "struct " << name << " {\n"; + stream << ind() << "struct " << name << " final {\n"; indent++; for (auto p : v) { stream << ind() << p->get_c_type() << " " << p->name << "{ " << p->get_default_value() << " };\n"; } stream << "\n"; - stream << ind() << "// default ctor\n"; stream << ind() << name << "() {}\n"; stream << "\n"; - stream << ind() << "// ctor with inputs\n"; stream << ind() << name << "(\n"; indent++; std::string comma = ""; @@ -240,15 +238,53 @@ void StubEmitter::emit_params_struct(bool is_schedule_params) { stream << "\n"; } -void StubEmitter::emit_inputs() { - stream << ind() << "const Halide::GeneratorContext& context\n"; +void StubEmitter::emit_inputs_struct() { + struct InInfo { + std::string c_type; + std::string name; + }; + std::vector in_info; for (auto input : inputs) { - std::string type(input->kind() == IOKind::Function ? "Halide::Func" : "Halide::Expr"); + std::string c_type(input->kind() == IOKind::Function ? "Halide::Func" : "Halide::Expr"); if (input->is_array()) { - type = "const std::vector<" + type + ">&"; + c_type = "std::vector<" + c_type + ">"; } - stream << ind() << ", " << type << " " << input->name() << "\n"; + in_info.push_back({c_type, input->name()}); + } + + const std::string name = "Inputs"; + stream << ind() << "struct " << name << " final {\n"; + indent++; + for (auto in : in_info) { + stream << ind() << in.c_type << " " << in.name << ";\n"; } + stream << "\n"; + + stream << ind() << name << "() {}\n"; + stream << "\n"; + + stream << ind() << name << "(\n"; + indent++; + std::string comma = ""; + for (auto in : in_info) { + stream << ind() << comma << "const " << in.c_type << "& " << in.name << "\n"; + comma = ", "; + } + indent--; + stream << ind() << ") : \n"; + indent++; + comma = ""; + for (auto in : in_info) { + stream << ind() << comma << in.name << "(" << in.name << ")\n"; + comma = ", "; + } + indent--; + stream << ind() << "{\n"; + stream << ind() << "}\n"; + + indent--; + stream << ind() << "};\n"; + stream << "\n"; } void StubEmitter::emit() { @@ -321,25 +357,25 @@ void StubEmitter::emit() { stream << ind() << "public:\n"; indent++; + emit_inputs_struct(); emit_params_struct(true); emit_params_struct(false); - stream << ind() << "// default ctor\n"; stream << ind() << class_name << "() {}\n"; stream << "\n"; - stream << ind() << "// ctor with inputs\n"; stream << ind() << class_name << "(\n"; indent++; - emit_inputs(); - stream << ind() << ", const GeneratorParams& params = GeneratorParams()\n"; + stream << ind() << "const Halide::GeneratorContext& context,\n"; + stream << ind() << "const Inputs& inputs,\n"; + stream << ind() << "const GeneratorParams& params = GeneratorParams()\n"; indent--; stream << ind() << ")\n"; indent++; stream << ind() << ": GeneratorStub(context, &factory, params.to_string_map(), {\n"; indent++; for (size_t i = 0; i < inputs.size(); ++i) { - stream << ind() << "Halide::Internal::to_func_or_expr_vector(" << inputs[i]->name() << ")"; + stream << ind() << "Halide::Internal::to_func_or_expr_vector(inputs." << inputs[i]->name() << ")"; stream << ",\n"; } indent--; @@ -373,11 +409,7 @@ void StubEmitter::emit() { } indent--; stream << ind() << ">\n"; - stream << ind() << "static " << class_name << " make(\n"; - indent++; - emit_inputs(); - indent--; - stream << ind() << ") {\n"; + stream << ind() << "static " << class_name << " make(const Halide::GeneratorContext& context, const Inputs& inputs) {\n"; indent++; stream << ind() << "GeneratorParams gp(\n"; indent++; @@ -395,12 +427,7 @@ void StubEmitter::emit() { } indent--; stream << ind() << ");\n"; - stream << ind() << "return " << class_name << "(context, \n"; - indent++; - for (size_t i = 0; i < inputs.size(); ++i) { - stream << ind() << inputs[i]->name() << ",\n"; - } - stream << ind() << "gp);\n"; + stream << ind() << "return " << class_name << "(context, inputs, gp);\n"; indent--; indent--; stream << ind() << "}\n"; diff --git a/test/generator/stubtest_jittest.cpp b/test/generator/stubtest_jittest.cpp index 24cda506bf79..61d397e6aace 100644 --- a/test/generator/stubtest_jittest.cpp +++ b/test/generator/stubtest_jittest.cpp @@ -68,9 +68,12 @@ int main(int argc, char **argv) { // is most convenient. auto gen = StubTest::make( context, - { Func(src[0]), Func(src[1]) }, - 1.234f, - int_args_expr); + // Use aggregate-initialization syntax to fill in an Inputs struct. + { + { Func(src[0]), Func(src[1]) }, + 1.234f, + int_args_expr + }); StubTest::ScheduleParams sp; // This generator default intermediate_level to "undefined", diff --git a/test/generator/stubuser_generator.cpp b/test/generator/stubuser_generator.cpp index adb04d261718..ae2f93cd73d3 100644 --- a/test/generator/stubuser_generator.cpp +++ b/test/generator/stubuser_generator.cpp @@ -17,6 +17,14 @@ class StubUser : public Halide::Generator { void generate() { + // We'll explicit fill in the struct fields by name, just to show + // it as an option. (Alternately, we could fill it in by using + // C++11 aggregate-initialization syntax.) + StubTest::Inputs inputs; + inputs.input = { input }; + inputs.float_arg = 1.234f; + inputs.int_arg = { int_arg }; + // Since we need to propagate types passed to us via our own GeneratorParams, // we can't (easily) use the templated constructor; instead, pass on the // values via the Stub's GeneratorParams struct. @@ -26,8 +34,7 @@ class StubUser : public Halide::Generator { // Override array_count to only expect 1 input and provide one output for g gp.array_count = 1; - Expr float_arg_expr(1.234f); - stub = StubTest(context(), { input }, float_arg_expr, { int_arg }, gp); + stub = StubTest(context(), inputs, gp); const float kOffset = 2.f; output(x, y, c) = cast(output_type, stub.f(x, y, c)[1] + kOffset); From acd10e33e2a4e78de6ab2fb05ac436ae604872de Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 18 Oct 2016 10:17:38 -0700 Subject: [PATCH 03/18] Add inference for type-and-dim for Func, and size for Array --- Makefile | 20 +- src/Generator.cpp | 226 +++++++++++++-- src/Generator.h | 276 +++++++++++-------- test/CMakeLists.txt | 13 +- test/generator/metadata_tester_aottest.cpp | 33 +++ test/generator/metadata_tester_generator.cpp | 40 ++- test/generator/pyramid_generator.cpp | 4 +- test/generator/stubtest_aottest.cpp | 59 ++++ test/generator/stubtest_generator.cpp | 22 +- test/generator/stubtest_jittest.cpp | 6 +- test/generator/stubuser_generator.cpp | 20 +- 11 files changed, 518 insertions(+), 201 deletions(-) create mode 100644 test/generator/stubtest_aottest.cpp diff --git a/Makefile b/Makefile index 76752b22e8d9..890f1148b766 100644 --- a/Makefile +++ b/Makefile @@ -912,16 +912,18 @@ $(FILTERS_DIR)/pyramid.a: $(BIN_DIR)/pyramid.generator @-mkdir -p $(TMP_DIR) cd $(TMP_DIR); $(CURDIR)/$< -f pyramid -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET) levels=10 +MDTEST_GEN_ARGS=input.type=uint8 input.dim=3 output.type=float32,float32 output.dim=3 input_not_nod.type=uint8 input_not_nod.dim=3 input_nod.dim=3 input_not.type=uint8 array_input.size=2 array_i8.size=2 array_i16.size=2 array_i32.size=2 array_h.size=2 array_outputs.size=2 + # metadata_tester is built with and without user-context $(FILTERS_DIR)/metadata_tester.a: $(BIN_DIR)/metadata_tester.generator @mkdir -p $(FILTERS_DIR) @-mkdir -p $(TMP_DIR) - cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-no_runtime input_type=uint8 input_dim=3 output_type=float32 output_dim=3 array_count=2 + cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-no_runtime $(MDTEST_GEN_ARGS) $(FILTERS_DIR)/metadata_tester_ucon.a: $(BIN_DIR)/metadata_tester.generator @mkdir -p $(FILTERS_DIR) @-mkdir -p $(TMP_DIR) - cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester_ucon -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-user_context-no_runtime input_type=uint8 input_dim=3 output_type=float32 output_dim=3 array_count=2 + cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester_ucon -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-user_context-no_runtime $(MDTEST_GEN_ARGS) $(BIN_DIR)/generator_aot_metadata_tester: $(FILTERS_DIR)/metadata_tester_ucon.a @@ -967,6 +969,14 @@ $(BIN_DIR)/generator_jit_stubtest: $(FILTERS_DIR)/stubtest.stub.h $(BIN_DIR)/stu $(BIN_DIR)/stubuser_generator.o: $(FILTERS_DIR)/stubtest.stub.h $(BIN_DIR)/stubuser.generator: $(BIN_DIR)/stubtest_generator.o +# stubtest has input and output funcs with undefined types and array sizes; this is fine for stub +# usage (the types can be inferred), but for AOT compilation, we must make the types +# concrete via generator args. +$(FILTERS_DIR)/stubtest.a: $(BIN_DIR)/stubtest.generator + @mkdir -p $(FILTERS_DIR) + @-mkdir -p $(TMP_DIR) + cd $(TMP_DIR); $(CURDIR)/$< -f stubtest -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-no_runtime input.type=float32 input.size=2 int_arg.size=2 f.type=float32,float32 + # Usually, it's considered best practice to have one Generator per # .cpp file, with the generator-name and filename matching; # nested_externs_generators.cpp is a counterexample, and thus requires @@ -1156,14 +1166,14 @@ test_apps: $(LIB_DIR)/libHalide.a $(BIN_DIR)/libHalide.$(SHARED_EXT) $(INCLUDE_D cp -r $(ROOT_DIR)/tools .; \ fi make -C apps/bilateral_grid clean HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) - make -C apps/bilateral_grid out.png HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) + make -C apps/bilateral_grid bin/out.png HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) make -C apps/local_laplacian clean HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) make -C apps/local_laplacian out.png HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) make -C apps/interpolate clean HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) make -C apps/interpolate out.png HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) make -C apps/blur clean HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) - make -C apps/blur test HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) - apps/blur/test + make -C apps/blur bin/test HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) + apps/blur/bin/test make -C apps/wavelet clean HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) make -C apps/wavelet test HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) make -C apps/c_backend clean HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) diff --git a/src/Generator.cpp b/src/Generator.cpp index 676e1be006e2..1a3c6f9e8df9 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -123,6 +123,26 @@ std::pair rational_approximation(double d) { return { num, den }; } +std::vector parse_halide_type_list(const std::string &types) { + const auto &e = get_halide_type_enum_map(); + std::vector result; + for (auto t : split_string(types, ",")) { + auto it = e.find(t); + user_assert(it != e.end()) << "Type not found: " << t; + result.push_back(it->second); + } + return result; +} + +template +T parse_scalar(const std::string &value) { + std::istringstream iss(value); + T t; + iss >> t; + user_assert(!iss.fail() && iss.get() == EOF) << "Unable to parse: " << value; + return t; +} + } // namespace class StubEmitter { @@ -935,6 +955,7 @@ Func GeneratorBase::get_output(const std::string &n) { build_params(); for (auto output : filter_outputs) { if (output->name() == n) { + user_assert(output->array_size_defined()) << "Output " << n << " has no ArraySize defined.\n"; user_assert(!output->is_array() && output->funcs().size() == 1) << "Output " << n << " must be accessed via get_output_vector()\n"; Func f = output->funcs().at(0); user_assert(f.defined()) << "Output " << n << " was not defined.\n"; @@ -951,6 +972,7 @@ std::vector GeneratorBase::get_output_vector(const std::string &n) { build_params(); for (auto output : filter_outputs) { if (output->name() == n) { + user_assert(output->array_size_defined()) << "Output " << n << " has no ArraySize defined.\n"; for (const auto &f : output->funcs()) { user_assert(f.defined()) << "Output " << n << " was not fully defined.\n"; } @@ -964,18 +986,61 @@ std::vector GeneratorBase::get_output_vector(const std::string &n) { void GeneratorBase::set_generator_param_values(const std::map ¶ms) { user_assert(!generator_params_set) << "set_generator_param_values() must be called at most once per Generator instance.\n"; build_params(); - std::map m; - for (auto param : generator_params) { - m[param->name] = param; + std::map generator_param_names; + for (auto p : generator_params) { + generator_param_names[p->name] = p; + } + std::map type_names, dim_names, array_size_names; + for (auto i : filter_inputs) { + if (i->kind() == IOKind::Function) { + type_names[i->name() + ".type"] = i; + dim_names[i->name() + ".dim"] = i; + } + if (i->is_array()) { + array_size_names[i->name() + ".size"] = i; + } + } + for (auto o : filter_outputs) { + if (o->kind() == IOKind::Function) { + type_names[o->name() + ".type"] = o; + dim_names[o->name() + ".dim"] = o; + } + if (o->is_array()) { + array_size_names[o->name() + ".size"] = o; + } } for (auto key_value : params) { const std::string &key = key_value.first; const std::string &value = key_value.second; - auto p = m.find(key); - user_assert(p != m.end()) << "Generator has no GeneratorParam named: " << key; - // It's OK to set schedule params here. - // user_assert(!p->second->is_schedule_param()); - p->second->set_from_string(value); + { + auto p = generator_param_names.find(key); + if (p != generator_param_names.end()) { + p->second->set_from_string(value); + continue; + } + } + { + auto p = type_names.find(key); + if (p != type_names.end()) { + p->second->types_ = parse_halide_type_list(value); + continue; + } + } + { + auto p = dim_names.find(key); + if (p != dim_names.end()) { + p->second->dimensions_ = parse_scalar(value); + continue; + } + } + { + auto p = array_size_names.find(key); + if (p != array_size_names.end()) { + p->second->array_size_ = parse_scalar(value); + continue; + } + } + user_error << "Generator has no GeneratorParam named: " << key; } generator_params_set = true; } @@ -1052,11 +1117,11 @@ Pipeline GeneratorBase::produce_pipeline() { user_assert(f.dimensions() == output->dimensions()) << "Output \"" << f.name() << "\" requires dimensions=" << output->dimensions() << " but was defined as dimensions=" << f.dimensions() << ".\n"; - user_assert((int)f.outputs() == (int)output->type_size()) << "Output \"" << f.name() - << "\" requires a Tuple of size " << output->type_size() + user_assert((int)f.outputs() == (int)output->types().size()) << "Output \"" << f.name() + << "\" requires a Tuple of size " << output->types().size() << " but was defined as Tuple of size " << f.outputs() << ".\n"; for (size_t i = 0; i < f.output_types().size(); ++i) { - Type expected = output->type_at(i); + Type expected = output->types().at(i); Type actual = f.output_types()[i]; user_assert(expected == actual) << "Output \"" << f.name() << "\" requires type " << expected @@ -1097,22 +1162,76 @@ void GeneratorBase::emit_cpp_stub(const std::string &stub_file_path) { emit.emit(); } -GIOBase::GIOBase(const ArraySizeArg &array_size, +GIOBase::GIOBase(size_t array_size, const std::string &name, IOKind kind, - const std::vector &types, - const DimensionArg &dimensions) + const std::vector &types, + int dimensions) : array_size_(array_size), name_(name), kind_(kind), types_(types), dimensions_(dimensions) { - user_assert(array_size_.value() >= 0) << "Generator Input/Output Arrays must have positive size."; } GIOBase::~GIOBase() { // nothing } +bool GIOBase::array_size_defined() const { + return array_size_ != -1; +} + +size_t GIOBase::array_size() const { + internal_assert(array_size_defined()) << "ArraySize is unspecified for " << name() + << "; you need to explicit set it via the resize() method or by setting " + << name() << ".size = value in your build rules."; + return (size_t) array_size_; +} + +bool GIOBase::is_array() const { + internal_error << "Unimplemented"; return false; +} + +const std::string &GIOBase::name() const { + return name_; +} + +IOKind GIOBase::kind() const { + return kind_; +} + +bool GIOBase::types_defined() const { + return !types_.empty(); +} + +const std::vector &GIOBase::types() const { + internal_assert(types_defined()) << "Type is unspecified for " << name() << "\n"; + return types_; +} + +Type GIOBase::type() const { + internal_assert(types_.size() == 1) << "Expected types_.size() == 1, saw " << types_.size() << " for " << name() << "\n"; + return types_.at(0); +} + +bool GIOBase::dimensions_defined() const { + return dimensions_ != -1; +} + +int GIOBase::dimensions() const { + internal_assert(dimensions_defined()) << "Dimensions unspecified for " << name() << "\n"; + return dimensions_; +} + +const std::vector &GIOBase::funcs() const { + internal_assert(funcs_.size() == array_size() && exprs_.empty()); + return funcs_; +} + +const std::vector &GIOBase::exprs() const { + internal_assert(exprs_.size() == array_size() && funcs_.empty()); + return exprs_; +} + void GIOBase::verify_internals() const { - user_assert(array_size_.value() >= 0) << "Generator Input/Output Arrays must have positive values"; - user_assert(dimensions_.value() >= 0) << "Generator Input/Output Dimensions must have positive values"; + user_assert(dimensions_ >= 0) << "Generator Input/Output Dimensions must have positive values"; if (kind() == IOKind::Function) { for (const Func &f : funcs()) { @@ -1153,12 +1272,39 @@ std::string GIOBase::array_name(size_t i) const { return n; } -GeneratorInputBase::GeneratorInputBase(const ArraySizeArg &array_size, +// If our type(s) are defined, ensure it matches the ones passed in, asserting if not. +// If our type(s) are not defined, just set to the ones passed in. +// (Ditto for dims.) +void GIOBase::check_matching_type_and_dim(const std::vector &t, int d) { + if (types_defined()) { + user_assert(types().size() == t.size()) << "Type mismatch for " << name() << ": expected " << types().size() << " types but saw " << t.size(); + for (size_t i = 0; i < t.size(); ++i) { + user_assert(types().at(i) == t.at(i)) << "Type mismatch for " << name() << ": expected " << types().at(i) << " saw " << t.at(i); + } + } else { + types_ = t; + } + if (dimensions_defined()) { + user_assert(dimensions() == d) << "Dimensions mismatch for " << name() << ": expected " << dimensions() << " saw " << d; + } else { + dimensions_ = d; + } +} + +void GIOBase::check_matching_array_size(size_t size) { + if (array_size_defined()) { + user_assert(array_size() == size) << "ArraySize mismatch for " << name() << ": expected " << array_size() << " saw " << size; + } else { + array_size_ = size; + } +} + +GeneratorInputBase::GeneratorInputBase(size_t array_size, const std::string &name, IOKind kind, - const TypeArg &t, - const DimensionArg &d) - : GIOBase(array_size, name, kind, {t}, d) { + const std::vector &t, + int d) + : GIOBase(array_size, name, kind, t, d) { ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorInput, this, nullptr); } @@ -1183,6 +1329,10 @@ void GeneratorInputBase::verify_internals() const { } void GeneratorInputBase::init_internals() { + user_assert(array_size_defined()) << "ArraySize is not defined for Input " << name() << "; you may need to specify a GeneratorParam.\n"; + user_assert(types_defined()) << "Type is not defined for Input " << name() << "; you may need to specify a GeneratorParam.\n"; + user_assert(dimensions_defined()) << "Dimensions is not defined for Input " << name() << "; you may need to specify a GeneratorParam.\n"; + init_parameters(); exprs_.clear(); @@ -1209,27 +1359,28 @@ void GeneratorInputBase::init_internals() { } void GeneratorInputBase::set_inputs(const std::vector &inputs) { - // must re-init parameters in case some GeneratorParams changed, since - // it can affect the expected length of parameters_. - init_parameters(); - exprs_.clear(); funcs_.clear(); - user_assert(inputs.size() == array_size()) << "Expected inputs.size() == " - << array_size() << ", saw " << inputs.size() << " for " << name() << "\n"; + check_matching_array_size(inputs.size()); for (const FuncOrExpr & i : inputs) { user_assert(i.kind() == kind()) << "An input for " << name() << " is not of the expected kind.\n"; if (kind() == IOKind::Function) { + check_matching_type_and_dim(i.func().output_types(), i.func().dimensions()); funcs_.push_back(i.func()); } else { + check_matching_type_and_dim({i.expr().type()}, 0); exprs_.push_back(i.expr()); } } + // must re-init parameters in case some GeneratorParams changed, since + // it can affect the expected length of parameters_. + init_parameters(); + verify_internals(); } -GeneratorOutputBase::GeneratorOutputBase(const ArraySizeArg &array_size, const std::string &name, const std::vector &t, const DimensionArg &d) +GeneratorOutputBase::GeneratorOutputBase(size_t array_size, const std::string &name, const std::vector &t, int d) : GIOBase(array_size, name, IOKind::Function, t, d) { ObjectInstanceRegistry::register_instance(this, 0, ObjectInstanceRegistry::GeneratorOutput, this, nullptr); @@ -1240,13 +1391,28 @@ GeneratorOutputBase::~GeneratorOutputBase() { } void GeneratorOutputBase::init_internals() { + // user_assert(array_size_defined()) << "ArraySize is not defined for Output " << name() << "; you may need to specify a GeneratorParam.\n"; + // user_assert(types_defined()) << "Type is not defined for Output " << name() << "; you may need to specify a GeneratorParam.\n"; + // user_assert(dimensions_defined()) << "Dimensions is not defined for Output " << name() << "; you may need to specify a GeneratorParam.\n"; + exprs_.clear(); funcs_.clear(); - for (size_t i = 0; i < array_size(); ++i) { - funcs_.push_back(Func(array_name(i))); + if (array_size_defined()) { + for (size_t i = 0; i < array_size(); ++i) { + funcs_.push_back(Func(array_name(i))); + } } } +void GeneratorOutputBase::resize(size_t size) { + internal_assert(is_array()); + internal_assert(!array_size_defined()) << "You may only call " << name() + << ".resize() when then size is undefined\n"; + array_size_ = (int) size; + init_internals(); +} + + void generator_test() { GeneratorParam gp("gp", 1); diff --git a/src/Generator.h b/src/Generator.h index 5a74484f1609..ffa0add5a7a3 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -469,6 +469,10 @@ class GeneratorParam_LoopLevel : public GeneratorParam_Enum { return ""; } + bool defined() const { + return this->value() != get_halide_undefined_looplevel(); + } + private: const std::string def; }; @@ -750,84 +754,48 @@ std::vector to_func_or_expr_vector(const T &t) { template std::vector to_func_or_expr_vector(const std::vector &v) { std::vector r; - for (auto f : v) r.push_back(f); + std::copy(v.begin(), v.end(), std::back_inserter(r)); return r; } void verify_same_funcs(Func a, Func b); void verify_same_funcs(const std::vector& a, const std::vector& b); -template -class ArgWithParam { - T value_; - const GeneratorParam * const param_; - -public: - // *not* explicit ctors - ArgWithParam(const T &value) : value_(value), param_(nullptr) {} - ArgWithParam(const GeneratorParam ¶m) : value_(param), param_(¶m) {} - - T value() const { return param_ ? *param_ : value_; } -}; - -template -struct ArgWithParamVector { - const std::vector> v; - // *not* explicit - ArgWithParamVector(const T &value) : v{ArgWithParam(value)} {} - ArgWithParamVector(const GeneratorParam ¶m) : v{ArgWithParam(param)} {} - ArgWithParamVector(std::initializer_list> t) : v(t) {} -}; - -using TypeArg = ArgWithParam; -using TypeArgVector = ArgWithParamVector; -using DimensionArg = ArgWithParam; -using ArraySizeArg = ArgWithParam; - class GIOBase { public: - size_t array_size() const { return (size_t) array_size_.value(); } - virtual bool is_array() const { internal_error << "Unimplemented"; return false; } + bool array_size_defined() const; + size_t array_size() const; + virtual bool is_array() const; - const std::string &name() const { return name_; } - IOKind kind() const { return kind_; } - size_t type_size() const { return types_.size(); } - Type type_at(size_t i) const { - internal_assert(i < types_.size()); - return types_.at(i).value(); - } - Type type() const { - internal_assert(type_size() == 1) << "Expected type_size() == 1, saw " << type_size() << " for " << name() << "\n"; - return type_at(0); - } - int dimensions() const { return dimensions_.value(); } + const std::string &name() const; + IOKind kind() const; - const std::vector &funcs() const { - internal_assert(funcs_.size() == array_size() && exprs_.empty()); - return funcs_; - } + bool types_defined() const; + const std::vector &types() const; + Type type() const; - const std::vector &exprs() const { - internal_assert(exprs_.size() == array_size() && funcs_.empty()); - return exprs_; - } + bool dimensions_defined() const; + int dimensions() const; + + const std::vector &funcs() const; + const std::vector &exprs() const; protected: - GIOBase(const ArraySizeArg &array_size, + GIOBase(size_t array_size, const std::string &name, IOKind kind, - const std::vector &types, - const DimensionArg &dimensions); + const std::vector &types, + int dimensions); virtual ~GIOBase(); friend class GeneratorBase; - ArraySizeArg array_size_; // always 1 if is_array() == false + int array_size_; // always 1 if is_array() == false. -1 if is_array() == true but unspecified. const std::string name_; const IOKind kind_; - std::vector types_; - DimensionArg dimensions_; + std::vector types_; // empty if type is unspecified + int dimensions_; // -1 if dim is unspecified // Exactly one will have nonzero length std::vector funcs_; @@ -837,6 +805,9 @@ class GIOBase { virtual void verify_internals() const; + void check_matching_array_size(size_t size); + void check_matching_type_and_dim(const std::vector &t, int d); + template const std::vector &get_values() const; @@ -857,14 +828,14 @@ inline const std::vector &GIOBase::get_values() const { class GeneratorInputBase : public GIOBase { protected: - GeneratorInputBase(const ArraySizeArg &array_size, + GeneratorInputBase(size_t array_size, const std::string &name, IOKind kind, - const TypeArg &t, - const DimensionArg &d); + const std::vector &t, + int d); - GeneratorInputBase(const std::string &name, IOKind kind, const TypeArg &t, const DimensionArg &d) - : GeneratorInputBase(ArraySizeArg(1), name, kind, t, d) {} + GeneratorInputBase(const std::string &name, IOKind kind, const std::vector &t, int d) + : GeneratorInputBase(1, name, kind, t, d) {} ~GeneratorInputBase() override; @@ -899,7 +870,7 @@ class GeneratorInputImpl : public GeneratorInputBase { // Only allow T2 not-an-array !std::is_array::value >::type * = nullptr> - GeneratorInputImpl(const std::string &name, IOKind kind, const TypeArg &t, const DimensionArg &d) + GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector &t, int d) : GeneratorInputBase(name, kind, t, d) { } @@ -907,16 +878,16 @@ class GeneratorInputImpl : public GeneratorInputBase { // Only allow T2[kSomeConst] std::is_array::value && std::rank::value == 1 && (std::extent::value > 0) >::type * = nullptr> - GeneratorInputImpl(const std::string &name, IOKind kind, const TypeArg &t, const DimensionArg &d) - : GeneratorInputBase(ArraySizeArg(std::extent::value), name, kind, t, d) { + GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector &t, int d) + : GeneratorInputBase(std::extent::value, name, kind, t, d) { } template ::value && std::rank::value == 1 && std::extent::value == 0 >::type * = nullptr> - GeneratorInputImpl(const ArraySizeArg &array_size, const std::string &name, IOKind kind, const TypeArg &t, const DimensionArg &d) - : GeneratorInputBase(array_size, name, kind, t, d) { + GeneratorInputImpl(const std::string &name, IOKind kind, const std::vector &t, int d) + : GeneratorInputBase(-1, name, kind, t, d) { } public: @@ -955,12 +926,42 @@ class GeneratorInput_Func : public GeneratorInputImpl { using TBase = typename Super::TBase; public: - GeneratorInput_Func(const std::string &name, const TypeArg &t, const DimensionArg &d) - : Super(name, IOKind::Function, t, d) { + GeneratorInput_Func(const std::string &name, const Type &t, int d) + : Super(name, IOKind::Function, {t}, d) { + } + + // unspecified type + GeneratorInput_Func(const std::string &name, int d) + : Super(name, IOKind::Function, {}, d) { + } + + // unspecified dimension + GeneratorInput_Func(const std::string &name, const Type &t) + : Super(name, IOKind::Function, {t}, -1) { + } + + // unspecified type & dimension + GeneratorInput_Func(const std::string &name) + : Super(name, IOKind::Function, {}, -1) { + } + + GeneratorInput_Func(size_t array_size, const std::string &name, const Type &t, int d) + : Super(array_size, name, IOKind::Function, {t}, d) { + } + + // unspecified type + GeneratorInput_Func(size_t array_size, const std::string &name, int d) + : Super(array_size, name, IOKind::Function, {}, d) { + } + + // unspecified dimension + GeneratorInput_Func(size_t array_size, const std::string &name, const Type &t) + : Super(array_size, name, IOKind::Function, {t}, -1) { } - GeneratorInput_Func(const ArraySizeArg &array_size, const std::string &name, const TypeArg &t, const DimensionArg &d) - : Super(array_size, name, IOKind::Function, t, d) { + // unspecified type & dimension + GeneratorInput_Func(size_t array_size, const std::string &name) + : Super(array_size, name, IOKind::Function, {}, -1) { } template @@ -995,15 +996,15 @@ class GeneratorInput_Scalar : public GeneratorInputImpl { } public: - GeneratorInput_Scalar(const std::string &name, - const TBase &def) - : Super(name, IOKind::Scalar, type_of(), 0), def_(def) { + explicit GeneratorInput_Scalar(const std::string &name, + const TBase &def = static_cast(0)) + : Super(name, IOKind::Scalar, {type_of()}, 0), def_(def) { } - GeneratorInput_Scalar(const ArraySizeArg &array_size, + GeneratorInput_Scalar(size_t array_size, const std::string &name, - const TBase &def) - : Super(array_size, name, IOKind::Scalar, type_of(), 0), def_(def) { + const TBase &def = static_cast(0)) + : Super(array_size, name, IOKind::Scalar, {type_of()}, 0), def_(def) { } /** You can use this Input as an expression in a halide @@ -1043,14 +1044,14 @@ class GeneratorInput_Arithmetic : public GeneratorInput_Scalar { } public: - GeneratorInput_Arithmetic(const std::string &name, - const TBase &def) + explicit GeneratorInput_Arithmetic(const std::string &name, + const TBase &def = static_cast(0)) : Super(name, def), min_(Expr()), max_(Expr()) { } - GeneratorInput_Arithmetic(const ArraySizeArg &array_size, + GeneratorInput_Arithmetic(size_t array_size, const std::string &name, - const TBase &def) + const TBase &def = static_cast(0)) : Super(array_size, name, def), min_(Expr()), max_(Expr()) { } @@ -1061,7 +1062,7 @@ class GeneratorInput_Arithmetic : public GeneratorInput_Scalar { : Super(name, def), min_(min), max_(max) { } - GeneratorInput_Arithmetic(const ArraySizeArg &array_size, + GeneratorInput_Arithmetic(size_t array_size, const std::string &name, const TBase &def, const TBase &min, @@ -1087,14 +1088,26 @@ class GeneratorInput : public Internal::GeneratorInputImplBase { protected: using TBase = typename Super::TBase; + // 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=Func. + struct Unused; + using IntIfFunc = + typename Internal::select_type< + Internal::cond::value, int>, + Internal::cond + >::type; + public: - GeneratorInput(const std::string &name, - const TBase &def = static_cast(0)) + explicit GeneratorInput(const std::string &name) + : Super(name) { + } + + GeneratorInput(const std::string &name, const TBase &def) : Super(name, def) { } - GeneratorInput(const Internal::ArraySizeArg &array_size, const std::string &name, - const TBase &def = static_cast(0)) + GeneratorInput(size_t array_size, const std::string &name, const TBase &def) : Super(array_size, name, def) { } @@ -1103,41 +1116,64 @@ class GeneratorInput : public Internal::GeneratorInputImplBase { : Super(name, def, min, max) { } - GeneratorInput(const Internal::ArraySizeArg &array_size, const std::string &name, + GeneratorInput(size_t array_size, const std::string &name, const TBase &def, const TBase &min, const TBase &max) : Super(array_size, name, def, min, max) { } - GeneratorInput(const std::string &name, - const Internal::TypeArg &t, const Internal::DimensionArg &d) + GeneratorInput(const std::string &name, const Type &t, int d) : Super(name, t, d) { } - GeneratorInput(const Internal::ArraySizeArg &array_size, const std::string &name, - const Internal::TypeArg &t, const Internal::DimensionArg &d) + GeneratorInput(const std::string &name, const Type &t) + : Super(name, t) { + } + + // Avoid ambiguity between Func-with-dim and int-with-default + //template ::value>::type * = nullptr> + GeneratorInput(const std::string &name, IntIfFunc d) + : Super(name, 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 Type &t) + : Super(array_size, name, t) { + } + + // Avoid ambiguity between Func-with-dim and int-with-default + //template ::value>::type * = nullptr> + GeneratorInput(size_t array_size, const std::string &name, IntIfFunc d) + : Super(array_size, name, d) { + } + + GeneratorInput(size_t array_size, const std::string &name) + : Super(array_size, name) { + } }; namespace Internal { class GeneratorOutputBase : public GIOBase { protected: - GeneratorOutputBase(const ArraySizeArg &array_size, + GeneratorOutputBase(size_t array_size, const std::string &name, - const std::vector &t, - const DimensionArg& d); + const std::vector &t, + int d); GeneratorOutputBase(const std::string &name, - const std::vector &t, - const DimensionArg& d) - : GeneratorOutputBase(ArraySizeArg(1), name, t, d) {} + const std::vector &t, + int d) + : GeneratorOutputBase(1, name, t, d) {} ~GeneratorOutputBase() override; friend class GeneratorBase; void init_internals(); + void resize(size_t size); }; template @@ -1154,24 +1190,24 @@ class GeneratorOutputImpl : public GeneratorOutputBase { // Only allow T2 not-an-array !std::is_array::value >::type * = nullptr> - GeneratorOutputImpl(const std::string &name, const TypeArgVector &t, const DimensionArg &d) - : GeneratorOutputBase(name, t.v, d) { + GeneratorOutputImpl(const std::string &name, const std::vector &t, int d) + : GeneratorOutputBase(name, t, d) { } template ::value && std::rank::value == 1 && (std::extent::value > 0) >::type * = nullptr> - GeneratorOutputImpl(const std::string &name, const TypeArgVector &t, const DimensionArg &d) - : GeneratorOutputBase(ArraySizeArg(std::extent::value), name, t.v, d) { + GeneratorOutputImpl(const std::string &name, const std::vector &t, int d) + : GeneratorOutputBase(std::extent::value, name, t, d) { } template ::value && std::rank::value == 1 && std::extent::value == 0 >::type * = nullptr> - GeneratorOutputImpl(const ArraySizeArg &array_size, const std::string &name, const TypeArgVector &t, const DimensionArg &d) - : GeneratorOutputBase(array_size, name, t.v, d) { + GeneratorOutputImpl(const std::string &name, const std::vector &t, int d) + : GeneratorOutputBase(-1, name, t, d) { } public: @@ -1214,6 +1250,14 @@ class GeneratorOutputImpl : public GeneratorOutputBase { typename std::vector::const_iterator end() const { return get_values().end(); } + + template ::value && std::rank::value == 1 && std::extent::value == 0 + >::type * = nullptr> + void resize(size_t size) { + GeneratorOutputBase::resize(size); + } }; template @@ -1225,11 +1269,11 @@ class GeneratorOutput_Func : public GeneratorOutputImpl { using TBase = typename Super::TBase; protected: - GeneratorOutput_Func(const std::string &name, const TypeArgVector &t, const DimensionArg &d) + GeneratorOutput_Func(const std::string &name, const std::vector &t, int d) : Super(name, t, d) { } - GeneratorOutput_Func(const ArraySizeArg &array_size, const std::string &name, const TypeArgVector &t, const DimensionArg &d) + GeneratorOutput_Func(size_t array_size, const std::string &name, const std::vector &t, int d) : Super(array_size, name, t, d) { } }; @@ -1247,7 +1291,7 @@ class GeneratorOutput_Arithmetic : public GeneratorOutputImpl { : Super(name, {type_of()}, 0) { } - GeneratorOutput_Arithmetic(const ArraySizeArg &array_size, const std::string &name) + GeneratorOutput_Arithmetic(size_t array_size, const std::string &name) : Super(array_size, name, {type_of()}, 0) { } }; @@ -1277,15 +1321,31 @@ class GeneratorOutput : public Internal::GeneratorOutputImplBase { : GeneratorOutput(std::string(name)) { } - GeneratorOutput(const Internal::ArraySizeArg &array_size, const std::string &name) + GeneratorOutput(size_t array_size, const std::string &name) : Super(array_size, name) { } - GeneratorOutput(const std::string &name, const Internal::TypeArgVector &t, const Internal::DimensionArg &d) + GeneratorOutput(const std::string &name, int d) + : Super(name, {}, d) { + } + + GeneratorOutput(const std::string &name, const Type &t, int d) + : Super(name, {t}, d) { + } + + GeneratorOutput(const std::string &name, const std::vector &t, int d) : Super(name, t, d) { } - GeneratorOutput(const Internal::ArraySizeArg &array_size, const std::string &name, const Internal::TypeArgVector &t, const Internal::DimensionArg &d) + GeneratorOutput(size_t array_size, const std::string &name, int d) + : Super(array_size, name, {}, 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 &t, int d) : Super(array_size, name, t, d) { } }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f61445907121..5085633b427f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -262,14 +262,21 @@ if (WITH_TEST_GENERATORS) halide_define_aot_test(msan GENERATOR_HALIDE_TARGET host-msan) - # Tests that require additional dependencies + # stubtest has input and output funcs with undefined types; this is fine for stub + # usage (the types can be inferred), but for AOT compilation, we must make the types + # concrete via generator args. + halide_define_aot_test(stubtest + GENERATOR_ARGS input.type=float32 input.size=2 int_arg.size=2 f.type=float32,float32) + + # Tests that require additional dependencies, args, etc + set(MDTEST_GEN_ARGS input.type=uint8 input.dim=3 output.type=float32,float32 output.dim=3 input_not_nod.type=uint8 input_not_nod.dim=3 input_nod.dim=3 input_not.type=uint8 array_input.size=2 array_i8.size=2 array_i16.size=2 array_i32.size=2 array_h.size=2 array_outputs.size=2) halide_define_aot_test(metadata_tester - GENERATOR_ARGS input_type=uint8 input_dim=3 output_type=float32 output_dim=3 array_count=2) + GENERATOR_ARGS "${MDTEST_GEN_ARGS}") halide_add_aot_test_dependency(metadata_tester AOT_LIBRARY_TARGET metadata_tester_ucon GENERATED_FUNCTION metadata_tester_ucon GENERATOR_HALIDE_TARGET host-user_context - GENERATOR_ARGS input_type=uint8 input_dim=3 output_type=float32 output_dim=3 array_count=2) + GENERATOR_ARGS ${MDTEST_GEN_ARGS}) halide_define_aot_test(tiled_blur) halide_add_aot_test_dependency(tiled_blur diff --git a/test/generator/metadata_tester_aottest.cpp b/test/generator/metadata_tester_aottest.cpp index 46b406b1d1f1..e3518e2785cd 100644 --- a/test/generator/metadata_tester_aottest.cpp +++ b/test/generator/metadata_tester_aottest.cpp @@ -394,6 +394,33 @@ void check_metadata(const halide_filter_metadata_t &md, bool expect_ucon_at_0) { nullptr, nullptr, }, + { + "input_not_nod", + halide_argument_kind_input_buffer, + 3, + halide_type_t(halide_type_uint, 8), + nullptr, + nullptr, + nullptr, + }, + { + "input_nod", + halide_argument_kind_input_buffer, + 3, + halide_type_t(halide_type_uint, 8), + nullptr, + nullptr, + nullptr, + }, + { + "input_not", + halide_argument_kind_input_buffer, + 3, + halide_type_t(halide_type_uint, 8), + nullptr, + nullptr, + nullptr, + }, { "array_input_0", halide_argument_kind_input_buffer, @@ -683,6 +710,9 @@ int main(int argc, char **argv) { 0.f, // Input 0.0, // Input nullptr, // Input + input, // Input + input, // Input + input, // Input input, input, // Input input, input, // Input 0, 0, // Input @@ -715,6 +745,9 @@ int main(int argc, char **argv) { 0.f, // Input 0.0, // Input nullptr, // Input + input, // Input + input, // Input + input, // Input input, input, // Input input, input, // Input 0, 0, // Input diff --git a/test/generator/metadata_tester_generator.cpp b/test/generator/metadata_tester_generator.cpp index 7023980ae1e8..498334b09bde 100644 --- a/test/generator/metadata_tester_generator.cpp +++ b/test/generator/metadata_tester_generator.cpp @@ -7,14 +7,7 @@ enum class SomeEnum { Foo, class MetadataTester : public Halide::Generator { public: - // Default values for all of these are deliberately wrong: - GeneratorParam input_type{ "input_type", Int(16) }; // must be overridden to UInt(8) - GeneratorParam input_dim{ "input_dim", 2 }; // must be overridden to 3 - GeneratorParam output_type{ "output_type", Int(16) }; // must be overridden to Float(32) - GeneratorParam output_dim{ "output_dim", 2 }; // must be overridden to 3 - GeneratorParam array_count{ "array_count", 32 }; // must be overridden to 2 - - Input input{ "input", input_type, input_dim }; + Input input{ "input", Int(16), 2 }; // must be overridden to {UInt(8), 3} Input b{ "b", true }; Input i8{ "i8", 8, -8, 127 }; Input i16{ "i16", 16, -16, 127 }; @@ -27,30 +20,26 @@ class MetadataTester : public Halide::Generator { Input f32{ "f32", 32.1234f, -3200.1234f, 3200.1234f }; Input f64{ "f64", 64.25f, -6400.25f, 6400.25f }; Input h{ "h", nullptr }; + + Input input_not_nod{ "input_not_nod" }; // must specify type=uint8 dim=3 + Input input_nod{ "input_nod", UInt(8) }; // must specify type=uint8 dim=3 + Input input_not{ "input_not", 3 }; // must specify type=uint8 - Input array_input{ array_count, "array_input", input_type, input_dim }; - Input array2_input{ "array2_input", input_type, input_dim }; - Input array_i8{ array_count, "array_i8" }; + Input array_input{ "array_input", UInt(8), 3 }; // must specify size=2 + Input array2_input{ "array2_input", UInt(8), 3 }; + Input array_i8{ "array_i8" }; // must specify size=2 Input array2_i8{ "array2_i8" }; - Input array_i16{ array_count, "array_i16", 16 }; + Input array_i16{ "array_i16", 16 }; // must specify size=2 Input array2_i16{ "array2_i16", 16 }; - Input array_i32{ array_count, "array_i32", 32, -32, 127 }; + Input array_i32{ "array_i32", 32, -32, 127 }; // must specify size=2 Input array2_i32{ "array2_i32", 32, -32, 127 }; - Input array_h{ array_count, "array_h", nullptr }; - // array count of 0 means there are no inputs: for AOT, doesn't affect C call signature - // (Note that we can't use Func[0] for this, as some compilers don't properly distinguish - // between T[] and T[0].) - Input empty_inputs{ 0, "empty_inputs", Float(32), 3 }; + Input array_h{ "array_h", nullptr }; // must specify size=2 - Output output{ "output", {output_type, Float(32)}, output_dim }; + Output output{ "output", {Int(16), UInt(8)}, 2 }; // must be overridden to {{Float(32), Float(32)}, 3} Output output_scalar{ "output_scalar" }; - Output array_outputs{ array_count, "array_outputs", Float(32), 3 }; + Output array_outputs{ "array_outputs", Float(32), 3 }; // must specify size=2 Output array_outputs2{ "array_outputs2", Float(32), 3 }; Output array_outputs3{ "array_outputs3" }; - // array count of 0 means there are no outputs: for AOT, doesn't affect C call signature. - // (Note that we can't use Func[0] for this, as some compilers don't properly distinguish - // between T[] and T[0].) - Output empty_outputs{ 0, "empty_outputs", Float(32), 3 }; void generate() { Var x, y, c; @@ -59,6 +48,9 @@ class MetadataTester : public Halide::Generator { Expr zero1 = array_input[1](x, y, c) - array_input[0](x, y, c); Expr zero2 = array_i32[1] - array_i32[0]; + assert(output.types().size() == 2); + Type output_type = output.types().at(0); + Func f1, f2; f1(x, y, c) = cast(output_type, input(x, y, c) + zero1 + zero2); f2(x, y, c) = cast(f1(x, y, c) + 1); diff --git a/test/generator/pyramid_generator.cpp b/test/generator/pyramid_generator.cpp index 9a1fddf8febb..da3602057c28 100644 --- a/test/generator/pyramid_generator.cpp +++ b/test/generator/pyramid_generator.cpp @@ -8,9 +8,11 @@ class Pyramid : public Halide::Generator { Input input{ "input", Float(32), 2 }; - Output pyramid{ levels, "pyramid", Float(32), 2 }; + Output pyramid{ "pyramid", Float(32), 2 }; void generate() { + pyramid.resize(levels); + pyramid[0](x, y) = input(x, y); for (size_t i = 1; i < pyramid.size(); i++) { diff --git a/test/generator/stubtest_aottest.cpp b/test/generator/stubtest_aottest.cpp new file mode 100644 index 000000000000..3fabd05e3637 --- /dev/null +++ b/test/generator/stubtest_aottest.cpp @@ -0,0 +1,59 @@ +#include "HalideRuntime.h" +#include "HalideBuffer.h" +#include "stubtest.h" + +using Halide::Image; + +const int kSize = 32; + +template +Image MakeImage(int extra) { + Image im(kSize, kSize, 3); + for (int x = 0; x < kSize; x++) { + for (int y = 0; y < kSize; y++) { + for (int c = 0; c < 3; c++) { + im(x, y, c) = static_cast(x + y + c + extra); + } + } + } + return im; +} + +template +void verify(const Image &input, float float_arg, int int_arg, const Image &output) { + if (input.width() != output.width() || + input.height() != output.height()) { + fprintf(stderr, "size mismatch: %dx%d vs %dx%d\n",input.width(),input.height(),output.width(),output.height()); + exit(-1); + } + int channels = std::max(1, std::min(input.channels(), output.channels())); + for (int x = 0; x < output.width(); x++) { + for (int y = 0; y < output.height(); y++) { + for (int c = 0; c < channels; c++) { + const OutputType expected = static_cast(input(x, y, c) * float_arg + int_arg); + const OutputType actual = output(x, y, c); + if (expected != actual) { + fprintf(stderr, "img[%d, %d, %d] = %f, expected %f (input = %f)\n", x, y, c, (double)actual, (double)expected, (double)input(x, y, c)); + exit(-1); + } + } + } + } +} + +int main(int argc, char **argv) { + Image in0 = MakeImage(0); + Image in1 = MakeImage(1); + Image f0(kSize, kSize, 3), f1(kSize, kSize, 3); + Image g0(kSize, kSize), g1(kSize, kSize); + + stubtest(in0, in1, 1.234f, 33, 66, f0, f1, g0, g1); + + verify(in0, 1.234f, 0, f0); + verify(in0, 1.234f, 33, f1); + verify(in0, 1.0f, 33, g0); + verify(in1, 1.0f, 66, g1); + + printf("Success!\n"); + return 0; +} diff --git a/test/generator/stubtest_generator.cpp b/test/generator/stubtest_generator.cpp index 2fd7355cfc60..f3f98d4b86e6 100644 --- a/test/generator/stubtest_generator.cpp +++ b/test/generator/stubtest_generator.cpp @@ -6,9 +6,6 @@ enum class BagType { Paper, Plastic }; class StubTest : public Halide::Generator { public: - GeneratorParam input_type{ "input_type", UInt(8) }; - GeneratorParam output_type{ "output_type", Float(32) }; - GeneratorParam array_count{ "array_count", 2 }; GeneratorParam float_param{ "float_param", 3.1415926535f }; GeneratorParam bag_type{ "bag_type", BagType::Paper, @@ -18,31 +15,34 @@ class StubTest : public Halide::Generator { ScheduleParam vectorize{ "vectorize", true }; ScheduleParam intermediate_level{ "intermediate_level", "undefined" }; - Input input{ array_count, "input", input_type, 3 }; + Input input{ "input", 3 }; // require a 3-dimensional Func but leave Type and ArraySize unspecified Input float_arg{ "float_arg", 1.0f, 0.0f, 100.0f }; - Input int_arg{ array_count, "int_arg", 1 }; + Input int_arg{ "int_arg", 1 }; // leave ArraySize unspecified - Output f{"f", {input_type, output_type}, 3}; - Output g{ array_count, "g", Int(16), 2}; + Output f{"f", 3}; // require a 3-dimensional Func but leave Type(s) unspecified + Output g{ "g", Int(16), 2}; // leave ArraySize unspecified void generate() { - assert(array_count >= 1); - // Gratuitous intermediate for the purpose of exercising // ScheduleParam intermediate(x, y, c) = input[0](x, y, c) * float_arg; f(x, y, c) = Tuple( intermediate(x, y, c), - cast(output_type, intermediate(x, y, c) + int_arg[0])); + intermediate(x, y, c) + int_arg[0]); + g.resize(input.size()); for (size_t i = 0; i < input.size(); ++i) { g[i](x, y) = cast(input[i](x, y, 0) + int_arg[i]); } } void schedule() { - intermediate.compute_at(intermediate_level); + if (intermediate_level.defined()) { + intermediate.compute_at(intermediate_level); + } else { + intermediate.compute_at(f, x); + } if (vectorize) intermediate.vectorize(x, natural_vector_size()); } diff --git a/test/generator/stubtest_jittest.cpp b/test/generator/stubtest_jittest.cpp index 61d397e6aace..0b12820fd0ee 100644 --- a/test/generator/stubtest_jittest.cpp +++ b/test/generator/stubtest_jittest.cpp @@ -66,7 +66,7 @@ int main(int argc, char **argv) { // We statically know the types we want, so the templated construction method // is most convenient. - auto gen = StubTest::make( + auto gen = StubTest::make<>( context, // Use aggregate-initialization syntax to fill in an Inputs struct. { @@ -76,7 +76,7 @@ int main(int argc, char **argv) { }); StubTest::ScheduleParams sp; - // This generator default intermediate_level to "undefined", + // This generator defaults intermediate_level to "undefined", // so we *must* specify something for it (else we'll crater at // Halide compile time). We'll use this: sp.intermediate_level = LoopLevel(gen.f, Var("y")); @@ -88,7 +88,7 @@ int main(int argc, char **argv) { Halide::Realization f_realized = gen.realize(kSize, kSize, 3); Image f0 = f_realized[0]; - Image f1 = f_realized[1]; + Image f1 = f_realized[1]; verify(src[0], 1.234f, 0, f0); verify(src[0], 1.234f, 33, f1); diff --git a/test/generator/stubuser_generator.cpp b/test/generator/stubuser_generator.cpp index ae2f93cd73d3..55d3607573d4 100644 --- a/test/generator/stubuser_generator.cpp +++ b/test/generator/stubuser_generator.cpp @@ -7,13 +7,10 @@ namespace { class StubUser : public Halide::Generator { public: - GeneratorParam input_type{ "input_type", UInt(8) }; - GeneratorParam output_type{ "output_type", UInt(8) }; GeneratorParam int_arg{ "int_arg", 33 }; - Input input{ "input", input_type, 3 }; - - Output output{"output", output_type, 3}; + Input input{ "input", UInt(8), 3 }; + Output output{"output", UInt(8), 3}; void generate() { @@ -25,19 +22,10 @@ class StubUser : public Halide::Generator { inputs.float_arg = 1.234f; inputs.int_arg = { int_arg }; - // Since we need to propagate types passed to us via our own GeneratorParams, - // we can't (easily) use the templated constructor; instead, pass on the - // values via the Stub's GeneratorParams struct. - StubTest::GeneratorParams gp; - gp.input_type = input_type; - gp.output_type = output_type; - // Override array_count to only expect 1 input and provide one output for g - gp.array_count = 1; - - stub = StubTest(context(), inputs, gp); + stub = StubTest(context(), inputs); const float kOffset = 2.f; - output(x, y, c) = cast(output_type, stub.f(x, y, c)[1] + kOffset); + output(x, y, c) = cast(stub.f(x, y, c)[1] + kOffset); } void schedule() { From b590b6c0fbea7867b9c1c829e963dc18fa69f776 Mon Sep 17 00:00:00 2001 From: Andrew Adams Date: Tue, 18 Oct 2016 11:58:56 -0700 Subject: [PATCH 04/18] Update Generator.cpp Fancier method for finding a good rational --- src/Generator.cpp | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 1a3c6f9e8df9..854c3bf689e7 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -111,16 +111,45 @@ Argument to_argument(const Internal::Parameter ¶m) { param.type(), param.dimensions(), def, min, max); } +namespace { +std::pair rational_approximation_helper(double d, int max_depth) { + int64_t int_part = floor(d); + double float_part = d - int_part; + if (max_depth == 0 || float_part == 0.0) { + return {int_part, 1}; + } else { + auto r = rational_approximation_helper(1.0/float_part, max_depth - 1); + std::swap(r.first, r.second); + if (mul_would_overflow(64, int_part, r.second) || + add_would_overflow(64, r.first, int_part * r.second)) { + return {0, 0}; + } + r.first += int_part * r.second; + return r; + } +} +} + std::pair rational_approximation(double d) { - if (std::isnan(d)) return {0, 0}; - if (!std::isfinite(d)) return {(d < 0) ? -1 : 1, 0}; - // TODO: fix this abomination to something more intelligent - const double kDenom = 1e9; - const int64_t num = (int64_t)(d * kDenom); - const int64_t den = (int64_t)kDenom; - user_assert(std::abs((double)num / (double)den - kDenom) <= kDenom) - << "The value " << d << " cannot be accurately approximated as a ratio\n"; - return { num, den }; + // The most accurate rationals to approximate a real come from + // truncating its continued fraction representation. We want the + // largest continued fraction possible, but at some point they'll + // overflow our rational type, and because they're evaluated + // backwards it's not easy to stop at the point which will not + // trigger overflow. Use binary search to find the right depth. + std::pair best {0, 0}; + int lo = 0, hi = 64; + while (lo + 1 < hi) { + int mid = (lo + hi)/2; + auto next = rational_approximation_helper(d, mid); + if (next.first == 0 && next.second == 0) { + hi = mid; + } else { + lo = mid; + best = next; + } + } + return best; } std::vector parse_halide_type_list(const std::string &types) { From 406fe3bfebb22c955b9cf7a07ad6df3069a31289 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 18 Oct 2016 13:50:14 -0700 Subject: [PATCH 05/18] Fix compilation error in rational_approximation_helper --- src/Generator.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 854c3bf689e7..1a531e2be444 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -112,8 +112,9 @@ Argument to_argument(const Internal::Parameter ¶m) { } namespace { + std::pair rational_approximation_helper(double d, int max_depth) { - int64_t int_part = floor(d); + int64_t int_part = std::floor(d); double float_part = d - int_part; if (max_depth == 0 || float_part == 0.0) { return {int_part, 1}; @@ -128,7 +129,8 @@ std::pair rational_approximation_helper(double d, int max_dept return r; } } -} + +} // namespace std::pair rational_approximation(double d) { // The most accurate rationals to approximate a real come from From 7f28b718fdc75ad9a8ccad8ec3f490bf2d14f1af Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 18 Oct 2016 13:54:56 -0700 Subject: [PATCH 06/18] rename make_image --- test/generator/paramtest_jittest.cpp | 4 ++-- test/generator/stubtest_aottest.cpp | 6 +++--- test/generator/stubtest_jittest.cpp | 10 ++++------ test/generator/stubuser_aottest.cpp | 4 ++-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/test/generator/paramtest_jittest.cpp b/test/generator/paramtest_jittest.cpp index 1ad3fe376ee9..04b67a1de211 100644 --- a/test/generator/paramtest_jittest.cpp +++ b/test/generator/paramtest_jittest.cpp @@ -10,7 +10,7 @@ using Halide::Image; const int kSize = 32; template -Image MakeImage() { +Image make_image() { Image im(kSize, kSize, 3); for (int x = 0; x < kSize; x++) { for (int y = 0; y < kSize; y++) { @@ -78,7 +78,7 @@ int main(int argc, char **argv) { // the input (otherwise we'll get a buffer type mismatch error). Halide::Pipeline p = gen.build(); - Image src = MakeImage(); + Image src = make_image(); gen.input.set(src); gen.float_arg.set(1.234f); gen.int_arg.set(33); diff --git a/test/generator/stubtest_aottest.cpp b/test/generator/stubtest_aottest.cpp index 3fabd05e3637..3705b7106829 100644 --- a/test/generator/stubtest_aottest.cpp +++ b/test/generator/stubtest_aottest.cpp @@ -7,7 +7,7 @@ using Halide::Image; const int kSize = 32; template -Image MakeImage(int extra) { +Image make_image(int extra) { Image im(kSize, kSize, 3); for (int x = 0; x < kSize; x++) { for (int y = 0; y < kSize; y++) { @@ -42,8 +42,8 @@ void verify(const Image &input, float float_arg, int int_arg, const I } int main(int argc, char **argv) { - Image in0 = MakeImage(0); - Image in1 = MakeImage(1); + Image in0 = make_image(0); + Image in1 = make_image(1); Image f0(kSize, kSize, 3), f1(kSize, kSize, 3); Image g0(kSize, kSize), g1(kSize, kSize); diff --git a/test/generator/stubtest_jittest.cpp b/test/generator/stubtest_jittest.cpp index 0b12820fd0ee..075d856e1000 100644 --- a/test/generator/stubtest_jittest.cpp +++ b/test/generator/stubtest_jittest.cpp @@ -15,7 +15,7 @@ const int kSize = 32; Halide::Var x, y, c; template -Image MakeImage(int extra) { +Image make_image(int extra) { Image im(kSize, kSize, 3); for (int x = 0; x < kSize; x++) { for (int y = 0; y < kSize; y++) { @@ -55,8 +55,8 @@ int main(int argc, char **argv) { constexpr int kArrayCount = 2; Image src[kArrayCount] = { - MakeImage(0), - MakeImage(1) + make_image(0), + make_image(1) }; std::vector int_args = { 33, 66 }; @@ -64,9 +64,7 @@ int main(int argc, char **argv) { // the Stub wants Expr, so make a conversion in place std::vector int_args_expr(int_args.begin(), int_args.end()); - // We statically know the types we want, so the templated construction method - // is most convenient. - auto gen = StubTest::make<>( + auto gen = StubTest( context, // Use aggregate-initialization syntax to fill in an Inputs struct. { diff --git a/test/generator/stubuser_aottest.cpp b/test/generator/stubuser_aottest.cpp index 3fe5a8b87d6a..b46fb42418ec 100644 --- a/test/generator/stubuser_aottest.cpp +++ b/test/generator/stubuser_aottest.cpp @@ -8,7 +8,7 @@ using namespace Halide; const int kSize = 32; template -Image MakeImage() { +Image make_image() { Image im(kSize, kSize, 3); for (int x = 0; x < kSize; x++) { for (int y = 0; y < kSize; y++) { @@ -42,7 +42,7 @@ void verify(const Image &input, const Image &output) { int main(int argc, char **argv) { - Image input = MakeImage(); + Image input = make_image(); Image output(kSize, kSize, 3); stubuser(input, output); From 76dc2dcd25fa9a2b79f56c1e58e685bebab7a33a Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 18 Oct 2016 14:10:33 -0700 Subject: [PATCH 07/18] rework HALIDE_REGISTER_GENERATOR macro to avoid "auto register_me = " --- src/Generator.h | 4 ++-- test/generator/nested_externs_generator.cpp | 8 ++++---- test/generator/stubtest_generator.cpp | 2 +- test/generator/stubuser_generator.cpp | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Generator.h b/src/Generator.h index ffa0add5a7a3..960d3fbca1a1 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -1827,10 +1827,10 @@ class GeneratorStub { // This is suboptimal, but allows us more flexibility to mutate registration in // the future with less impact on existing code. #define _HALIDE_REGISTER_GENERATOR2(GEN_CLASS_NAME, GEN_REGISTRY_NAME) \ - Halide::RegisterGenerator(GEN_REGISTRY_NAME); + namespace ns_reg_gen { static auto reg_##GEN_CLASS_NAME = Halide::RegisterGenerator(GEN_REGISTRY_NAME); } #define _HALIDE_REGISTER_GENERATOR3(GEN_CLASS_NAME, GEN_REGISTRY_NAME, FULLY_QUALIFIED_STUB_NAME) \ - Halide::Internal::RegisterGeneratorAndStub<::FULLY_QUALIFIED_STUB_NAME>(GEN_CLASS_NAME::create, GEN_REGISTRY_NAME, #FULLY_QUALIFIED_STUB_NAME); + namespace ns_reg_gen { static auto reg_##GEN_CLASS_NAME = Halide::Internal::RegisterGeneratorAndStub<::FULLY_QUALIFIED_STUB_NAME>(GEN_CLASS_NAME::create, GEN_REGISTRY_NAME, #FULLY_QUALIFIED_STUB_NAME); } #define _HALIDE_REGISTER_GENERATOR_CHOOSER(_1, _2, _3, NAME, ...) NAME diff --git a/test/generator/nested_externs_generator.cpp b/test/generator/nested_externs_generator.cpp index 90259923f1e6..85f52cb44c17 100644 --- a/test/generator/nested_externs_generator.cpp +++ b/test/generator/nested_externs_generator.cpp @@ -118,9 +118,9 @@ class NestedExternsRoot : public Generator { } }; -RegisterGenerator register_combine_gen{"nested_externs_combine"}; -RegisterGenerator register_inner_gen{"nested_externs_inner"}; -RegisterGenerator register_leaf_gen{"nested_externs_leaf"}; -RegisterGenerator register_root_gen{"nested_externs_root"}; +HALIDE_REGISTER_GENERATOR(NestedExternsCombine, "nested_externs_combine"); +HALIDE_REGISTER_GENERATOR(NestedExternsInner, "nested_externs_inner"); +HALIDE_REGISTER_GENERATOR(NestedExternsLeaf, "nested_externs_leaf"); +HALIDE_REGISTER_GENERATOR(NestedExternsRoot, "nested_externs_root"); } // namespace diff --git a/test/generator/stubtest_generator.cpp b/test/generator/stubtest_generator.cpp index f3f98d4b86e6..28fc5aef9b9e 100644 --- a/test/generator/stubtest_generator.cpp +++ b/test/generator/stubtest_generator.cpp @@ -71,6 +71,6 @@ namespace { // If the fully-qualified stub name specified for third argument hasn't been declared // properly, a compile error will result. The fully-qualified name *must* have at least one // namespace (i.e., a name at global scope is not acceptable). -auto register_me = HALIDE_REGISTER_GENERATOR(StubTest, "stubtest", StubNS1::StubNS2::StubTest); +HALIDE_REGISTER_GENERATOR(StubTest, "stubtest", StubNS1::StubNS2::StubTest) } // namespace diff --git a/test/generator/stubuser_generator.cpp b/test/generator/stubuser_generator.cpp index 55d3607573d4..e969a93ec93f 100644 --- a/test/generator/stubuser_generator.cpp +++ b/test/generator/stubuser_generator.cpp @@ -39,9 +39,9 @@ class StubUser : public Halide::Generator { }; // Note that HALIDE_REGISTER_GENERATOR() with just two args is functionally -// identical to the old HalideRegister<> syntax: no stub being defined, +// identical to the old Halide::RegisterGenerator<> syntax: no stub being defined, // just AOT usage. (If you try to generate a stub for this class you'll // fail with an error at generation time.) -auto register_me = HALIDE_REGISTER_GENERATOR(StubUser, "stubuser"); +HALIDE_REGISTER_GENERATOR(StubUser, "stubuser") } // namespace From 19b1fb83843d06ab27cd1acf9d3bdb45b95f3c54 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Wed, 19 Oct 2016 15:27:59 -0700 Subject: [PATCH 08/18] rational_approximation tweaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit — add tests — special-case non-finite numbers and negative numbers to get predictable results --- src/Generator.cpp | 71 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 1a531e2be444..a1fe42b49a5a 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -111,28 +111,33 @@ Argument to_argument(const Internal::Parameter ¶m) { param.type(), param.dimensions(), def, min, max); } -namespace { - std::pair rational_approximation_helper(double d, int max_depth) { - int64_t int_part = std::floor(d); - double float_part = d - int_part; + const int64_t int_part = static_cast(std::floor(d)); + const double float_part = d - int_part; if (max_depth == 0 || float_part == 0.0) { return {int_part, 1}; - } else { - auto r = rational_approximation_helper(1.0/float_part, max_depth - 1); - std::swap(r.first, r.second); - if (mul_would_overflow(64, int_part, r.second) || - add_would_overflow(64, r.first, int_part * r.second)) { - return {0, 0}; - } - r.first += int_part * r.second; - return r; } -} -} // namespace + const auto r = rational_approximation_helper(1.0/float_part, max_depth - 1); + const int64_t num = r.second; + const int64_t den = r.first; + if (mul_would_overflow(64, int_part, den) || + add_would_overflow(64, num, int_part * den)) { + return {0, 0}; + } + + return {num + int_part * den, den}; +} std::pair rational_approximation(double d) { + // Special-case non-finite numbers. + if (std::isnan(d)) return {0, 0}; + + const int64_t sign = (d < 0) ? -1 : 1; + if (!std::isfinite(d)) return {sign, 0}; + + d = std::abs(d); + // The most accurate rationals to approximate a real come from // truncating its continued fraction representation. We want the // largest continued fraction possible, but at some point they'll @@ -151,7 +156,8 @@ std::pair rational_approximation(double d) { best = next; } } - return best; + + return {best.first * sign, best.second}; } std::vector parse_halide_type_list(const std::string &types) { @@ -1464,6 +1470,39 @@ void generator_test() { // Verify that Tuple parameter-pack variants can convert GeneratorParam to Expr Tuple t(gp, gp, gp); + + // Test rational_approximation + auto check_ratio = [](double d, std::pair expected) { + auto actual = rational_approximation(d); + internal_assert(actual == expected) + << "rational_approximation(" << d << ") failed:" + << " expected " << expected.first << "/" << expected.second + << " actual " << actual.first << "/" << actual.second << "\n"; + }; + + // deliberately use fractional values that are exactly representable so that + // we minimize testing variation across compilers + const double kFrac1 = 1234.125; + const double kFrac2 = 123412341234.125; + const double kFrac3 = 1.0/65536.0; + + check_ratio(0.0, {0, 1}); + check_ratio(1.0, {1, 1}); + check_ratio(2.0, {2, 1}); + check_ratio(kFrac1, {9873, 8}); + check_ratio(kFrac2, {987298729873, 8}); + check_ratio(kFrac3, {1, 65536}); + + check_ratio(-0.0, {0, 1}); + check_ratio(-1.0, {-1, 1}); + check_ratio(-2.0, {-2, 1}); + check_ratio(-kFrac1, {-9873, 8}); + check_ratio(-kFrac2, {-987298729873, 8}); + check_ratio(-kFrac3, {-1, 65536}); + + check_ratio(NAN, {0, 0}); + check_ratio(INFINITY, {1, 0}); + check_ratio(-INFINITY, {-1, 0}); } } // namespace Internal From d585b049d1c08e0b223a5f609abd8d0a27407735 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Wed, 19 Oct 2016 16:00:25 -0700 Subject: [PATCH 09/18] Add NamesInterface to GeneratorStub This allows us to remove lots of Halide:: noise from generated code --- src/Generator.cpp | 23 ++++---- src/Generator.h | 55 ++++++++++---------- test/generator/metadata_tester_generator.cpp | 5 +- 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index a1fe42b49a5a..badbf64dbff6 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -277,9 +277,9 @@ void StubEmitter::emit_params_struct(bool is_schedule_params) { if (is_schedule_params) { stream << "\n"; - stream << ind() << "inline NO_INLINE std::map to_looplevel_map() const {\n"; + stream << ind() << "inline NO_INLINE std::map to_looplevel_map() const {\n"; indent++; - stream << ind() << "std::map m;\n"; + stream << ind() << "std::map m;\n"; for (auto p : v) { if (!p->is_looplevel_param()) continue; stream << ind() << "if (" << p->name << " != " << p->get_default_value() << ") " @@ -302,7 +302,7 @@ void StubEmitter::emit_inputs_struct() { }; std::vector in_info; for (auto input : inputs) { - std::string c_type(input->kind() == IOKind::Function ? "Halide::Func" : "Halide::Expr"); + std::string c_type(input->kind() == IOKind::Function ? "Func" : "Expr"); if (input->is_array()) { c_type = "std::vector<" + c_type + ">"; } @@ -364,7 +364,7 @@ void StubEmitter::emit() { for (auto output : outputs) { out_info.push_back({ output->name(), - output->is_array() ? "std::vector" : "Halide::Func", + output->is_array() ? "std::vector" : "Func", std::string(output->is_array() ? "get_output_vector" : "get_output") + "(\"" + output->name() + "\")" }); } @@ -423,7 +423,7 @@ void StubEmitter::emit() { stream << ind() << class_name << "(\n"; indent++; - stream << ind() << "const Halide::GeneratorContext& context,\n"; + stream << ind() << "const GeneratorContext& context,\n"; stream << ind() << "const Inputs& inputs,\n"; stream << ind() << "const GeneratorParams& params = GeneratorParams()\n"; indent--; @@ -432,7 +432,7 @@ void StubEmitter::emit() { stream << ind() << ": GeneratorStub(context, &factory, params.to_string_map(), {\n"; indent++; for (size_t i = 0; i < inputs.size(); ++i) { - stream << ind() << "Halide::Internal::to_func_or_expr_vector(inputs." << inputs[i]->name() << ")"; + stream << ind() << "to_func_or_expr_vector(inputs." << inputs[i]->name() << ")"; stream << ",\n"; } indent--; @@ -466,7 +466,7 @@ void StubEmitter::emit() { } indent--; stream << ind() << ">\n"; - stream << ind() << "static " << class_name << " make(const Halide::GeneratorContext& context, const Inputs& inputs) {\n"; + stream << ind() << "static " << class_name << " make(const GeneratorContext& context, const Inputs& inputs) {\n"; indent++; stream << ind() << "GeneratorParams gp(\n"; indent++; @@ -526,9 +526,9 @@ void StubEmitter::emit() { stream << ind() << "// TODO: identify vars used\n"; for (auto output : outputs) { if (output->is_array()) { - stream << ind() << "std::vector " << output->name() << ";\n"; + stream << ind() << "std::vector " << output->name() << ";\n"; } else { - stream << ind() << "Halide::Func " << output->name() << ";\n"; + stream << ind() << "Func " << output->name() << ";\n"; } } stream << "\n"; @@ -541,7 +541,6 @@ void StubEmitter::emit() { indent++; stream << ind() << "void verify() {\n"; indent++; - stream << ind() << "using Halide::Internal::verify_same_funcs;\n"; for (const auto &out : out_info) { stream << ind() << "verify_same_funcs(" << out.name << ", " << out.getter << ");\n"; } @@ -572,12 +571,12 @@ void StubEmitter::emit() { stream << ind() << "#endif // " << guard.str() << "\n"; } -void verify_same_funcs(Func a, Func b) { +void GeneratorStub::verify_same_funcs(Func a, Func b) { user_assert(a.function().get_contents().same_as(b.function().get_contents())) << "Expected Func " << a.name() << " and " << b.name() << " to match.\n"; } -void verify_same_funcs(const std::vector& a, const std::vector& b) { +void GeneratorStub::verify_same_funcs(const std::vector& a, const std::vector& b) { user_assert(a.size() == b.size()) << "Mismatch in Function vector length.\n"; for (size_t i = 0; i < a.size(); ++i) { verify_same_funcs(a[i], b[i]); diff --git a/src/Generator.h b/src/Generator.h index 960d3fbca1a1..9be3a5181b7f 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -264,7 +264,7 @@ class GeneratorParam_Target : public GeneratorParamImpl { } std::string get_c_type() const override { - return "Halide::Target"; + return "Target"; } }; @@ -422,7 +422,7 @@ class GeneratorParam_Type : public GeneratorParam_Enum { } std::string get_c_type() const override { - return "Halide::Type"; + return "Type"; } std::string get_template_type() const override { @@ -454,13 +454,13 @@ class GeneratorParam_LoopLevel : public GeneratorParam_Enum { return oss.str(); } std::string get_c_type() const override { - return "Halide::LoopLevel"; + return "LoopLevel"; } std::string get_default_value() const override { if (def == "undefined") return "Halide::Internal::get_halide_undefined_looplevel()"; - if (def == "root") return "Halide::LoopLevel::root()"; - if (def == "inline") return "Halide::LoopLevel()"; + if (def == "root") return "LoopLevel::root()"; + if (def == "inline") return "LoopLevel()"; user_error << "LoopLevel value " << def << " not found.\n"; return ""; } @@ -746,21 +746,6 @@ class FuncOrExpr { } }; -template -std::vector to_func_or_expr_vector(const T &t) { - return { FuncOrExpr(t) }; -} - -template -std::vector to_func_or_expr_vector(const std::vector &v) { - std::vector r; - std::copy(v.begin(), v.end(), std::back_inserter(r)); - return r; -} - -void verify_same_funcs(Func a, Func b); -void verify_same_funcs(const std::vector& a, const std::vector& b); - class GIOBase { public: bool array_size_defined() const; @@ -1350,6 +1335,11 @@ class GeneratorOutput : public Internal::GeneratorOutputImplBase { } }; +class GeneratorContext { +public: + virtual Target get_target() const = 0; +}; + class NamesInterface { // Names in this class are only intended for use in derived classes. protected: @@ -1358,6 +1348,7 @@ class NamesInterface { using Expr = Halide::Expr; using ExternFuncArgument = Halide::ExternFuncArgument; using Func = Halide::Func; + using GeneratorContext = Halide::GeneratorContext; using ImageParam = Halide::ImageParam; using LoopLevel = Halide::LoopLevel; using Pipeline = Halide::Pipeline; @@ -1379,11 +1370,6 @@ class NamesInterface { static inline Type UInt(int bits, int lanes = 1) { return Halide::UInt(bits, lanes); } }; -class GeneratorContext { -public: - virtual Target get_target() const = 0; -}; - class JITGeneratorContext : public GeneratorContext { public: explicit JITGeneratorContext(const Target &t) : target(t) {} @@ -1445,7 +1431,7 @@ class GeneratorBase : public NamesInterface, public GeneratorContext { EXPORT Module build_module(const std::string &function_name = "", const LoweredFunc::LinkageType linkage_type = LoweredFunc::External); - const GeneratorContext& context() const { return *this; } + const Halide::GeneratorContext& context() const { return *this; } protected: EXPORT GeneratorBase(size_t size, const void *introspection_helper); @@ -1714,7 +1700,7 @@ template class RegisterGenerator { namespace Internal { -class GeneratorStub { +class GeneratorStub : public NamesInterface { public: // default ctor GeneratorStub() {} @@ -1804,6 +1790,21 @@ class GeneratorStub { return (double)Ratio::num / (double)Ratio::den; } + template + static std::vector to_func_or_expr_vector(const T &t) { + return { FuncOrExpr(t) }; + } + + template + static std::vector to_func_or_expr_vector(const std::vector &v) { + std::vector r; + std::copy(v.begin(), v.end(), std::back_inserter(r)); + return r; + } + + void verify_same_funcs(Func a, Func b); + void verify_same_funcs(const std::vector& a, const std::vector& b); + private: std::shared_ptr generator; diff --git a/test/generator/metadata_tester_generator.cpp b/test/generator/metadata_tester_generator.cpp index 498334b09bde..8ac6e91d6847 100644 --- a/test/generator/metadata_tester_generator.cpp +++ b/test/generator/metadata_tester_generator.cpp @@ -69,6 +69,7 @@ class MetadataTester : public Halide::Generator { } }; -Halide::RegisterGenerator register_MetadataTester{ "metadata_tester" }; - } // namespace + +namespace Halide { namespace Internal { class MetadataTesterStub; }} +HALIDE_REGISTER_GENERATOR(MetadataTester, "metadata_tester", Halide::Internal::MetadataTesterStub) From 4c009d195d30ab1252183e260fb79747ae14af32 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Wed, 19 Oct 2016 16:05:54 -0700 Subject: [PATCH 10/18] Omit certain ctors in stubs when there are no GP/SP present --- src/Generator.cpp | 130 ++++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 62 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index badbf64dbff6..60ec94e6960b 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -235,33 +235,37 @@ void StubEmitter::emit_params_struct(bool is_schedule_params) { std::string name = is_schedule_params ? "ScheduleParams" : "GeneratorParams"; stream << ind() << "struct " << name << " final {\n"; indent++; - for (auto p : v) { - stream << ind() << p->get_c_type() << " " << p->name << "{ " << p->get_default_value() << " };\n"; + if (!v.empty()) { + for (auto p : v) { + stream << ind() << p->get_c_type() << " " << p->name << "{ " << p->get_default_value() << " };\n"; + } + stream << "\n"; } - stream << "\n"; stream << ind() << name << "() {}\n"; stream << "\n"; - stream << ind() << name << "(\n"; - indent++; - std::string comma = ""; - for (auto p : v) { - stream << ind() << comma << p->get_c_type() << " " << p->name << "\n"; - comma = ", "; - } - indent--; - stream << ind() << ") : \n"; - indent++; - comma = ""; - for (auto p : v) { - stream << ind() << comma << p->name << "(" << p->name << ")\n"; - comma = ", "; + if (!v.empty()) { + stream << ind() << name << "(\n"; + indent++; + std::string comma = ""; + for (auto p : v) { + stream << ind() << comma << p->get_c_type() << " " << p->name << "\n"; + comma = ", "; + } + indent--; + stream << ind() << ") : \n"; + indent++; + comma = ""; + for (auto p : v) { + stream << ind() << comma << p->name << "(" << p->name << ")\n"; + comma = ", "; + } + indent--; + stream << ind() << "{\n"; + stream << ind() << "}\n"; + stream << "\n"; } - indent--; - stream << ind() << "{\n"; - stream << ind() << "}\n"; - stream << "\n"; stream << ind() << "inline NO_INLINE std::map to_string_map() const {\n"; indent++; @@ -445,50 +449,52 @@ void StubEmitter::emit() { stream << ind() << "}\n"; stream << "\n"; - stream << ind() << "// templated construction method with inputs\n"; - stream << ind() << "template<\n"; - std::string comma = ""; - indent++; - for (auto p : generator_params) { - std::string type = p->get_template_type(); - std::string value = p->get_template_value(); - if (type == "float" || type == "double") { - // floats and doubles can't be used as template value arguments; - // it turns out to be pretty uncommon use floating point types - // in GeneratorParams, but to avoid breaking these cases entirely, - // use std::ratio as an approximation for the default value. - auto ratio = rational_approximation(std::atof(value.c_str())); - stream << ind() << comma << "typename" << " " << p->name << " = std::ratio<" << ratio.first << ", " << ratio.second << ">\n"; - } else { - stream << ind() << comma << type << " " << p->name << " = " << value << "\n"; + if (!generator_params.empty()) { + stream << ind() << "// templated construction method with inputs\n"; + stream << ind() << "template<\n"; + std::string comma = ""; + indent++; + for (auto p : generator_params) { + std::string type = p->get_template_type(); + std::string value = p->get_template_value(); + if (type == "float" || type == "double") { + // floats and doubles can't be used as template value arguments; + // it turns out to be pretty uncommon use floating point types + // in GeneratorParams, but to avoid breaking these cases entirely, + // use std::ratio as an approximation for the default value. + auto ratio = rational_approximation(std::atof(value.c_str())); + stream << ind() << comma << "typename" << " " << p->name << " = std::ratio<" << ratio.first << ", " << ratio.second << ">\n"; + } else { + stream << ind() << comma << type << " " << p->name << " = " << value << "\n"; + } + comma = ", "; } - comma = ", "; - } - indent--; - stream << ind() << ">\n"; - stream << ind() << "static " << class_name << " make(const GeneratorContext& context, const Inputs& inputs) {\n"; - indent++; - stream << ind() << "GeneratorParams gp(\n"; - indent++; - comma = ""; - for (auto p : generator_params) { - std::string type = p->get_template_type(); - if (type == "typename") { - stream << ind() << comma << "Halide::type_of<" << p->name << ">()\n"; - } else if (type == "float" || type == "double") { - stream << ind() << comma << "ratio_to_double<" << p->name << ">()\n"; - } else { - stream << ind() << comma << p->name << "\n"; + indent--; + stream << ind() << ">\n"; + stream << ind() << "static " << class_name << " make(const GeneratorContext& context, const Inputs& inputs) {\n"; + indent++; + stream << ind() << "GeneratorParams gp(\n"; + indent++; + comma = ""; + for (auto p : generator_params) { + std::string type = p->get_template_type(); + if (type == "typename") { + stream << ind() << comma << "Halide::type_of<" << p->name << ">()\n"; + } else if (type == "float" || type == "double") { + stream << ind() << comma << "ratio_to_double<" << p->name << ">()\n"; + } else { + stream << ind() << comma << p->name << "\n"; + } + comma = ", "; } - comma = ", "; + indent--; + stream << ind() << ");\n"; + stream << ind() << "return " << class_name << "(context, inputs, gp);\n"; + indent--; + indent--; + stream << ind() << "}\n"; + stream << "\n"; } - indent--; - stream << ind() << ");\n"; - stream << ind() << "return " << class_name << "(context, inputs, gp);\n"; - indent--; - indent--; - stream << ind() << "}\n"; - stream << "\n"; stream << ind() << "// schedule method\n"; stream << ind() << "void schedule(const ScheduleParams& params = ScheduleParams()) {\n"; From 8abacb513b09ca581fe0b1fcd7131a16b17752f8 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Wed, 19 Oct 2016 17:03:37 -0700 Subject: [PATCH 11/18] Use args().at() for output Vars --- test/generator/stubtest_jittest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/generator/stubtest_jittest.cpp b/test/generator/stubtest_jittest.cpp index 075d856e1000..ac221c646c6f 100644 --- a/test/generator/stubtest_jittest.cpp +++ b/test/generator/stubtest_jittest.cpp @@ -77,7 +77,7 @@ int main(int argc, char **argv) { // This generator defaults intermediate_level to "undefined", // so we *must* specify something for it (else we'll crater at // Halide compile time). We'll use this: - sp.intermediate_level = LoopLevel(gen.f, Var("y")); + sp.intermediate_level = LoopLevel(gen.f, gen.f.args().at(1)); // ...but any of the following would also be OK: // sp.intermediate_level = LoopLevel::root(); // sp.intermediate_level = LoopLevel(gen.f, Var("x")); From d18d21031f33f191327271898c73b1a1e3c0810a Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 24 Oct 2016 13:55:07 -0700 Subject: [PATCH 12/18] Use *this instead of context() in StubUser --- test/generator/stubuser_generator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/generator/stubuser_generator.cpp b/test/generator/stubuser_generator.cpp index e969a93ec93f..b3c9ab944205 100644 --- a/test/generator/stubuser_generator.cpp +++ b/test/generator/stubuser_generator.cpp @@ -22,7 +22,7 @@ class StubUser : public Halide::Generator { inputs.float_arg = 1.234f; inputs.int_arg = { int_arg }; - stub = StubTest(context(), inputs); + stub = StubTest(*this, inputs); const float kOffset = 2.f; output(x, y, c) = cast(stub.f(x, y, c)[1] + kOffset); From 5c6fcbcc57800fed5809fca29cf111ab4cd6b5e0 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 24 Oct 2016 14:01:12 -0700 Subject: [PATCH 13/18] Minor nomenclature cleanup --- Makefile | 8 +- src/Generator.cpp | 298 +++++++++++++++++++++++----------------------- 2 files changed, 154 insertions(+), 152 deletions(-) diff --git a/Makefile b/Makefile index 439514d4ab8c..47dbf746ec2b 100644 --- a/Makefile +++ b/Makefile @@ -912,18 +912,20 @@ $(FILTERS_DIR)/pyramid.a: $(BIN_DIR)/pyramid.generator @-mkdir -p $(TMP_DIR) cd $(TMP_DIR); $(CURDIR)/$< -f pyramid -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET) levels=10 -MDTEST_GEN_ARGS=input.type=uint8 input.dim=3 output.type=float32,float32 output.dim=3 input_not_nod.type=uint8 input_not_nod.dim=3 input_nod.dim=3 input_not.type=uint8 array_input.size=2 array_i8.size=2 array_i16.size=2 array_i32.size=2 array_h.size=2 array_outputs.size=2 +METADATA_TESTER_GENERATOR_ARGS=input.type=uint8 input.dim=3 output.type=float32,float32 output.dim=3 \ + input_not_nod.type=uint8 input_not_nod.dim=3 input_nod.dim=3 input_not.type=uint8 array_input.size=2 \ + array_i8.size=2 array_i16.size=2 array_i32.size=2 array_h.size=2 array_outputs.size=2 # metadata_tester is built with and without user-context $(FILTERS_DIR)/metadata_tester.a: $(BIN_DIR)/metadata_tester.generator @mkdir -p $(FILTERS_DIR) @-mkdir -p $(TMP_DIR) - cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-no_runtime $(MDTEST_GEN_ARGS) + cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-no_runtime $(METADATA_TESTER_GENERATOR_ARGS) $(FILTERS_DIR)/metadata_tester_ucon.a: $(BIN_DIR)/metadata_tester.generator @mkdir -p $(FILTERS_DIR) @-mkdir -p $(TMP_DIR) - cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester_ucon -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-user_context-no_runtime $(MDTEST_GEN_ARGS) + cd $(TMP_DIR); $(CURDIR)/$< -f metadata_tester_ucon -o $(CURDIR)/$(FILTERS_DIR) target=$(HL_TARGET)-user_context-no_runtime $(METADATA_TESTER_GENERATOR_ARGS) $(BIN_DIR)/generator_aot_metadata_tester: $(FILTERS_DIR)/metadata_tester_ucon.a diff --git a/src/Generator.cpp b/src/Generator.cpp index 60ec94e6960b..94683a8bf64e 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -202,7 +202,7 @@ class StubEmitter { const std::vector schedule_params; const std::vector inputs; const std::vector outputs; - int indent{0}; + int indent_level{0}; std::vector filter_params(const std::vector &in, bool is_schedule_params) { @@ -216,15 +216,15 @@ class StubEmitter { } /** Emit spaces according to the current indentation level */ - std::string ind(); + std::string indent(); void emit_inputs_struct(); void emit_params_struct(bool schedule_only); }; -std::string StubEmitter::ind() { +std::string StubEmitter::indent() { std::ostringstream o; - for (int i = 0; i < indent; i++) { + for (int i = 0; i < indent_level; i++) { o << " "; } return o.str(); @@ -233,69 +233,69 @@ std::string StubEmitter::ind() { void StubEmitter::emit_params_struct(bool is_schedule_params) { const auto &v = is_schedule_params ? schedule_params : generator_params; std::string name = is_schedule_params ? "ScheduleParams" : "GeneratorParams"; - stream << ind() << "struct " << name << " final {\n"; - indent++; + stream << indent() << "struct " << name << " final {\n"; + indent_level++; if (!v.empty()) { for (auto p : v) { - stream << ind() << p->get_c_type() << " " << p->name << "{ " << p->get_default_value() << " };\n"; + stream << indent() << p->get_c_type() << " " << p->name << "{ " << p->get_default_value() << " };\n"; } stream << "\n"; } - stream << ind() << name << "() {}\n"; + stream << indent() << name << "() {}\n"; stream << "\n"; if (!v.empty()) { - stream << ind() << name << "(\n"; - indent++; + stream << indent() << name << "(\n"; + indent_level++; std::string comma = ""; for (auto p : v) { - stream << ind() << comma << p->get_c_type() << " " << p->name << "\n"; + stream << indent() << comma << p->get_c_type() << " " << p->name << "\n"; comma = ", "; } - indent--; - stream << ind() << ") : \n"; - indent++; + indent_level--; + stream << indent() << ") : \n"; + indent_level++; comma = ""; for (auto p : v) { - stream << ind() << comma << p->name << "(" << p->name << ")\n"; + stream << indent() << comma << p->name << "(" << p->name << ")\n"; comma = ", "; } - indent--; - stream << ind() << "{\n"; - stream << ind() << "}\n"; + indent_level--; + stream << indent() << "{\n"; + stream << indent() << "}\n"; stream << "\n"; } - stream << ind() << "inline NO_INLINE std::map to_string_map() const {\n"; - indent++; - stream << ind() << "std::map m;\n"; + stream << indent() << "inline NO_INLINE std::map to_string_map() const {\n"; + indent_level++; + stream << indent() << "std::map m;\n"; for (auto p : v) { if (p->is_looplevel_param()) continue; - stream << ind() << "if (" << p->name << " != " << p->get_default_value() << ") " + stream << indent() << "if (" << p->name << " != " << p->get_default_value() << ") " << "m[\"" << p->name << "\"] = " << p->call_to_string(p->name) << ";\n"; } - stream << ind() << "return m;\n"; - indent--; - stream << ind() << "}\n"; + stream << indent() << "return m;\n"; + indent_level--; + stream << indent() << "}\n"; if (is_schedule_params) { stream << "\n"; - stream << ind() << "inline NO_INLINE std::map to_looplevel_map() const {\n"; - indent++; - stream << ind() << "std::map m;\n"; + stream << indent() << "inline NO_INLINE std::map to_looplevel_map() const {\n"; + indent_level++; + stream << indent() << "std::map m;\n"; for (auto p : v) { if (!p->is_looplevel_param()) continue; - stream << ind() << "if (" << p->name << " != " << p->get_default_value() << ") " + stream << indent() << "if (" << p->name << " != " << p->get_default_value() << ") " << "m[\"" << p->name << "\"] = " << p->name << ";\n"; } - stream << ind() << "return m;\n"; - indent--; - stream << ind() << "}\n"; + stream << indent() << "return m;\n"; + indent_level--; + stream << indent() << "}\n"; } - indent--; - stream << ind() << "};\n"; + indent_level--; + stream << indent() << "};\n"; stream << "\n"; } @@ -314,37 +314,37 @@ void StubEmitter::emit_inputs_struct() { } const std::string name = "Inputs"; - stream << ind() << "struct " << name << " final {\n"; - indent++; + stream << indent() << "struct " << name << " final {\n"; + indent_level++; for (auto in : in_info) { - stream << ind() << in.c_type << " " << in.name << ";\n"; + stream << indent() << in.c_type << " " << in.name << ";\n"; } stream << "\n"; - stream << ind() << name << "() {}\n"; + stream << indent() << name << "() {}\n"; stream << "\n"; - stream << ind() << name << "(\n"; - indent++; + stream << indent() << name << "(\n"; + indent_level++; std::string comma = ""; for (auto in : in_info) { - stream << ind() << comma << "const " << in.c_type << "& " << in.name << "\n"; + stream << indent() << comma << "const " << in.c_type << "& " << in.name << "\n"; comma = ", "; } - indent--; - stream << ind() << ") : \n"; - indent++; + indent_level--; + stream << indent() << ") : \n"; + indent_level++; comma = ""; for (auto in : in_info) { - stream << ind() << comma << in.name << "(" << in.name << ")\n"; + stream << indent() << comma << in.name << "(" << in.name << ")\n"; comma = ", "; } - indent--; - stream << ind() << "{\n"; - stream << ind() << "}\n"; + indent_level--; + stream << indent() << "{\n"; + stream << indent() << "}\n"; - indent--; - stream << ind() << "};\n"; + indent_level--; + stream << indent() << "};\n"; stream << "\n"; } @@ -380,25 +380,25 @@ void StubEmitter::emit() { } guard << "_" << class_name; - stream << ind() << "#ifndef " << guard.str() << "\n"; - stream << ind() << "#define " << guard.str() << "\n"; + stream << indent() << "#ifndef " << guard.str() << "\n"; + stream << indent() << "#define " << guard.str() << "\n"; stream << "\n"; - stream << ind() << "/* MACHINE-GENERATED - DO NOT EDIT */\n"; + stream << indent() << "/* MACHINE-GENERATED - DO NOT EDIT */\n"; stream << "\n"; - stream << ind() << "#include \n"; - stream << ind() << "#include \n"; - stream << ind() << "#include \n"; - stream << ind() << "#include \n"; - stream << ind() << "#include \n"; - stream << ind() << "#include \n"; + stream << indent() << "#include \n"; + stream << indent() << "#include \n"; + stream << indent() << "#include \n"; + stream << indent() << "#include \n"; + stream << indent() << "#include \n"; + stream << indent() << "#include \n"; stream << "\n"; - stream << ind() << "#include \"Halide.h\"\n"; + stream << indent() << "#include \"Halide.h\"\n"; stream << "\n"; for (const auto &ns : namespaces) { - stream << ind() << "namespace " << ns << " {\n"; + stream << indent() << "namespace " << ns << " {\n"; } stream << "\n"; @@ -414,46 +414,46 @@ void StubEmitter::emit() { stream << decl << "\n"; } - stream << ind() << "class " << class_name << " final : public Halide::Internal::GeneratorStub {\n"; - stream << ind() << "public:\n"; - indent++; + stream << indent() << "class " << class_name << " final : public Halide::Internal::GeneratorStub {\n"; + stream << indent() << "public:\n"; + indent_level++; emit_inputs_struct(); emit_params_struct(true); emit_params_struct(false); - stream << ind() << class_name << "() {}\n"; + stream << indent() << class_name << "() {}\n"; stream << "\n"; - stream << ind() << class_name << "(\n"; - indent++; - stream << ind() << "const GeneratorContext& context,\n"; - stream << ind() << "const Inputs& inputs,\n"; - stream << ind() << "const GeneratorParams& params = GeneratorParams()\n"; - indent--; - stream << ind() << ")\n"; - indent++; - stream << ind() << ": GeneratorStub(context, &factory, params.to_string_map(), {\n"; - indent++; + stream << indent() << class_name << "(\n"; + indent_level++; + stream << indent() << "const GeneratorContext& context,\n"; + stream << indent() << "const Inputs& inputs,\n"; + stream << indent() << "const GeneratorParams& params = GeneratorParams()\n"; + indent_level--; + stream << indent() << ")\n"; + indent_level++; + stream << indent() << ": GeneratorStub(context, &factory, params.to_string_map(), {\n"; + indent_level++; for (size_t i = 0; i < inputs.size(); ++i) { - stream << ind() << "to_func_or_expr_vector(inputs." << inputs[i]->name() << ")"; + stream << indent() << "to_func_or_expr_vector(inputs." << inputs[i]->name() << ")"; stream << ",\n"; } - indent--; - stream << ind() << "})\n"; + indent_level--; + stream << indent() << "})\n"; for (const auto &out : out_info) { - stream << ind() << ", " << out.name << "(" << out.getter << ")\n"; + stream << indent() << ", " << out.name << "(" << out.getter << ")\n"; } - indent--; - stream << ind() << "{\n"; - stream << ind() << "}\n"; + indent_level--; + stream << indent() << "{\n"; + stream << indent() << "}\n"; stream << "\n"; if (!generator_params.empty()) { - stream << ind() << "// templated construction method with inputs\n"; - stream << ind() << "template<\n"; + stream << indent() << "// templated construction method with inputs\n"; + stream << indent() << "template<\n"; std::string comma = ""; - indent++; + indent_level++; for (auto p : generator_params) { std::string type = p->get_template_type(); std::string value = p->get_template_value(); @@ -463,118 +463,118 @@ void StubEmitter::emit() { // in GeneratorParams, but to avoid breaking these cases entirely, // use std::ratio as an approximation for the default value. auto ratio = rational_approximation(std::atof(value.c_str())); - stream << ind() << comma << "typename" << " " << p->name << " = std::ratio<" << ratio.first << ", " << ratio.second << ">\n"; + stream << indent() << comma << "typename" << " " << p->name << " = std::ratio<" << ratio.first << ", " << ratio.second << ">\n"; } else { - stream << ind() << comma << type << " " << p->name << " = " << value << "\n"; + stream << indent() << comma << type << " " << p->name << " = " << value << "\n"; } comma = ", "; } - indent--; - stream << ind() << ">\n"; - stream << ind() << "static " << class_name << " make(const GeneratorContext& context, const Inputs& inputs) {\n"; - indent++; - stream << ind() << "GeneratorParams gp(\n"; - indent++; + indent_level--; + stream << indent() << ">\n"; + stream << indent() << "static " << class_name << " make(const GeneratorContext& context, const Inputs& inputs) {\n"; + indent_level++; + stream << indent() << "GeneratorParams gp(\n"; + indent_level++; comma = ""; for (auto p : generator_params) { std::string type = p->get_template_type(); if (type == "typename") { - stream << ind() << comma << "Halide::type_of<" << p->name << ">()\n"; + stream << indent() << comma << "Halide::type_of<" << p->name << ">()\n"; } else if (type == "float" || type == "double") { - stream << ind() << comma << "ratio_to_double<" << p->name << ">()\n"; + stream << indent() << comma << "ratio_to_double<" << p->name << ">()\n"; } else { - stream << ind() << comma << p->name << "\n"; + stream << indent() << comma << p->name << "\n"; } comma = ", "; } - indent--; - stream << ind() << ");\n"; - stream << ind() << "return " << class_name << "(context, inputs, gp);\n"; - indent--; - indent--; - stream << ind() << "}\n"; + indent_level--; + stream << indent() << ");\n"; + stream << indent() << "return " << class_name << "(context, inputs, gp);\n"; + indent_level--; + indent_level--; + stream << indent() << "}\n"; stream << "\n"; } - stream << ind() << "// schedule method\n"; - stream << ind() << "void schedule(const ScheduleParams& params = ScheduleParams()) {\n"; - indent++; - stream << ind() << "GeneratorStub::schedule(params.to_string_map(), params.to_looplevel_map());\n"; - indent--; - stream << ind() << "}\n"; + stream << indent() << "// schedule method\n"; + stream << indent() << "void schedule(const ScheduleParams& params = ScheduleParams()) {\n"; + indent_level++; + stream << indent() << "GeneratorStub::schedule(params.to_string_map(), params.to_looplevel_map());\n"; + indent_level--; + stream << indent() << "}\n"; stream << "\n"; - stream << ind() << "// move constructor\n"; - stream << ind() << class_name << "("<< class_name << "&& that)\n"; - indent++; - stream << ind() << ": GeneratorStub(std::move(that))\n"; + stream << indent() << "// move constructor\n"; + stream << indent() << class_name << "("<< class_name << "&& that)\n"; + indent_level++; + stream << indent() << ": GeneratorStub(std::move(that))\n"; for (const auto &out : out_info) { - stream << ind() << ", " << out.name << "(std::move(that." << out.name << "))\n"; + stream << indent() << ", " << out.name << "(std::move(that." << out.name << "))\n"; } - indent--; - stream << ind() << "{\n"; - stream << ind() << "}\n"; + indent_level--; + stream << indent() << "{\n"; + stream << indent() << "}\n"; stream << "\n"; - stream << ind() << "// move assignment operator\n"; - stream << ind() << class_name << "& operator=("<< class_name << "&& that) {\n"; - indent++; - stream << ind() << "GeneratorStub::operator=(std::move(that));\n"; + stream << indent() << "// move assignment operator\n"; + stream << indent() << class_name << "& operator=("<< class_name << "&& that) {\n"; + indent_level++; + stream << indent() << "GeneratorStub::operator=(std::move(that));\n"; for (const auto &out : out_info) { - stream << ind() << out.name << " = std::move(that." << out.name << ");\n"; + stream << indent() << out.name << " = std::move(that." << out.name << ");\n"; } - stream << ind() << "return *this;\n"; - indent--; - stream << ind() << "}\n"; + stream << indent() << "return *this;\n"; + indent_level--; + stream << indent() << "}\n"; stream << "\n"; - stream << ind() << "// Output(s)\n"; - stream << ind() << "// TODO: identify vars used\n"; + stream << indent() << "// Output(s)\n"; + stream << indent() << "// TODO: identify vars used\n"; for (auto output : outputs) { if (output->is_array()) { - stream << ind() << "std::vector " << output->name() << ";\n"; + stream << indent() << "std::vector " << output->name() << ";\n"; } else { - stream << ind() << "Func " << output->name() << ";\n"; + stream << indent() << "Func " << output->name() << ";\n"; } } stream << "\n"; - stream << ind() << "~" << class_name << "() { if (has_generator()) verify(); }\n"; + stream << indent() << "~" << class_name << "() { if (has_generator()) verify(); }\n"; stream << "\n"; - indent--; - stream << ind() << "protected:\n"; - indent++; - stream << ind() << "void verify() {\n"; - indent++; + indent_level--; + stream << indent() << "protected:\n"; + indent_level++; + stream << indent() << "void verify() {\n"; + indent_level++; for (const auto &out : out_info) { - stream << ind() << "verify_same_funcs(" << out.name << ", " << out.getter << ");\n"; + stream << indent() << "verify_same_funcs(" << out.name << ", " << out.getter << ");\n"; } - indent--; - stream << ind() << "}\n"; + indent_level--; + stream << indent() << "}\n"; stream << "\n"; - indent--; - stream << ind() << "private:\n"; - indent++; - stream << ind() << "static std::unique_ptr factory(const std::map& params) {\n"; - indent++; - stream << ind() << "return Halide::Internal::RegisterGeneratorAndStub<" << class_name << ">::create(params);\n"; - indent--; - stream << ind() << "};\n"; + indent_level--; + stream << indent() << "private:\n"; + indent_level++; + stream << indent() << "static std::unique_ptr factory(const std::map& params) {\n"; + indent_level++; + stream << indent() << "return Halide::Internal::RegisterGeneratorAndStub<" << class_name << ">::create(params);\n"; + indent_level--; + stream << indent() << "};\n"; stream << "\n"; - indent--; - stream << ind() << "};\n"; + indent_level--; + stream << indent() << "};\n"; stream << "\n"; for (int i = (int)namespaces.size() - 1; i >= 0 ; --i) { - stream << ind() << "} // namespace " << namespaces[i] << "\n"; + stream << indent() << "} // namespace " << namespaces[i] << "\n"; } stream << "\n"; - stream << ind() << "#endif // " << guard.str() << "\n"; + stream << indent() << "#endif // " << guard.str() << "\n"; } void GeneratorStub::verify_same_funcs(Func a, Func b) { From db33afef334f9efdafd3525b7d074444a76c0c4f Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 24 Oct 2016 14:15:46 -0700 Subject: [PATCH 14/18] Tweak GeneratorContext API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit — remove Generator::context() — allow Stubs to accept a context by either pointer or ref --- src/Generator.cpp | 27 +++++++++++++++++++++++++-- src/Generator.h | 16 ++++------------ test/generator/stubtest_jittest.cpp | 7 +++---- test/generator/stubuser_generator.cpp | 2 +- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 94683a8bf64e..701e3527ff1c 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -427,7 +427,7 @@ void StubEmitter::emit() { stream << indent() << class_name << "(\n"; indent_level++; - stream << indent() << "const GeneratorContext& context,\n"; + stream << indent() << "const GeneratorContext* context,\n"; stream << indent() << "const Inputs& inputs,\n"; stream << indent() << "const GeneratorParams& params = GeneratorParams()\n"; indent_level--; @@ -449,6 +449,18 @@ void StubEmitter::emit() { stream << indent() << "}\n"; stream << "\n"; + stream << indent() << "// delegating ctor to allow GeneratorContext-ref\n"; + stream << indent() << class_name << "(\n"; + indent_level++; + stream << indent() << "const GeneratorContext& context,\n"; + stream << indent() << "const Inputs& inputs,\n"; + stream << indent() << "const GeneratorParams& params = GeneratorParams()\n"; + indent_level--; + stream << indent() << ")\n"; + indent_level++; + stream << indent() << ": " << class_name << "(&context, inputs, params) {}\n"; + stream << "\n"; + if (!generator_params.empty()) { stream << indent() << "// templated construction method with inputs\n"; stream << indent() << "template<\n"; @@ -471,7 +483,7 @@ void StubEmitter::emit() { } indent_level--; stream << indent() << ">\n"; - stream << indent() << "static " << class_name << " make(const GeneratorContext& context, const Inputs& inputs) {\n"; + stream << indent() << "static " << class_name << " make(const GeneratorContext* context, const Inputs& inputs) {\n"; indent_level++; stream << indent() << "GeneratorParams gp(\n"; indent_level++; @@ -577,6 +589,17 @@ void StubEmitter::emit() { stream << indent() << "#endif // " << guard.str() << "\n"; } +GeneratorStub::GeneratorStub(const GeneratorContext *context, + GeneratorFactory generator_factory, + const std::map &generator_params, + const std::vector> &inputs) { + user_assert(context != nullptr) << "Context may not be null"; + generator = generator_factory(generator_params); + generator->target.set(context->get_target()); + generator->set_inputs(inputs); + generator->call_generate(); +} + void GeneratorStub::verify_same_funcs(Func a, Func b) { user_assert(a.function().get_contents().same_as(b.function().get_contents())) << "Expected Func " << a.name() << " and " << b.name() << " to match.\n"; diff --git a/src/Generator.h b/src/Generator.h index 9be3a5181b7f..2999024240b8 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -1431,8 +1431,6 @@ class GeneratorBase : public NamesInterface, public GeneratorContext { EXPORT Module build_module(const std::string &function_name = "", const LoweredFunc::LinkageType linkage_type = LoweredFunc::External); - const Halide::GeneratorContext& context() const { return *this; } - protected: EXPORT GeneratorBase(size_t size, const void *introspection_helper); @@ -1760,16 +1758,10 @@ class GeneratorStub : public NamesInterface { protected: typedef std::function(const std::map&)> GeneratorFactory; - template - GeneratorStub(const GeneratorContext &context, - GeneratorFactory generator_factory, - const std::map &generator_params, - const std::vector> &inputs) { - generator = generator_factory(generator_params); - generator->target.set(context.get_target()); - generator->set_inputs(inputs); - generator->call_generate(); - } + GeneratorStub(const GeneratorContext *context, + GeneratorFactory generator_factory, + const std::map &generator_params, + const std::vector> &inputs); // Output(s) // TODO: identify vars used diff --git a/test/generator/stubtest_jittest.cpp b/test/generator/stubtest_jittest.cpp index ac221c646c6f..b39991de9557 100644 --- a/test/generator/stubtest_jittest.cpp +++ b/test/generator/stubtest_jittest.cpp @@ -6,6 +6,7 @@ using Halide::Argument; using Halide::Expr; using Halide::Func; using Halide::Image; +using Halide::JITGeneratorContext; using Halide::LoopLevel; using Halide::Var; using StubNS1::StubNS2::StubTest; @@ -49,9 +50,7 @@ void verify(const Image &input, float float_arg, int int_arg, const I } } -int main(int argc, char **argv) { - Halide::JITGeneratorContext context(Halide::get_target_from_environment()); - +int main(int argc, char **argv) { constexpr int kArrayCount = 2; Image src[kArrayCount] = { @@ -65,7 +64,7 @@ int main(int argc, char **argv) { std::vector int_args_expr(int_args.begin(), int_args.end()); auto gen = StubTest( - context, + JITGeneratorContext(Halide::get_target_from_environment()), // Use aggregate-initialization syntax to fill in an Inputs struct. { { Func(src[0]), Func(src[1]) }, diff --git a/test/generator/stubuser_generator.cpp b/test/generator/stubuser_generator.cpp index b3c9ab944205..b49d84519bec 100644 --- a/test/generator/stubuser_generator.cpp +++ b/test/generator/stubuser_generator.cpp @@ -22,7 +22,7 @@ class StubUser : public Halide::Generator { inputs.float_arg = 1.234f; inputs.int_arg = { int_arg }; - stub = StubTest(*this, inputs); + stub = StubTest(this, inputs); const float kOffset = 2.f; output(x, y, c) = cast(stub.f(x, y, c)[1] + kOffset); From cecfed4c85babf7b818319efe3501e10801960ec Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 24 Oct 2016 15:17:28 -0700 Subject: [PATCH 15/18] Add doc comments, EXPORTS, etc --- src/Generator.h | 164 +++++++++++++++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 58 deletions(-) diff --git a/src/Generator.h b/src/Generator.h index 2999024240b8..a318abe19878 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -134,16 +134,15 @@ inline std::string halide_looplevel_to_enum_string(const LoopLevel &loop_level){ // 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 Type &t); +EXPORT std::string halide_type_to_c_source(const Type &t); // 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 Type &t); +EXPORT 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 command-line utility - * for ahead-of-time filter compilation. */ + * compile_to_files(); it can be trivially wrapped by a "real" main() to produce a + * command-line utility for ahead-of-time filter compilation. */ EXPORT int generate_filter_main(int argc, char **argv, std::ostream &cerr); // select_type<> is to std::conditional as switch is to if: @@ -220,7 +219,7 @@ class GeneratorParamBase { template class GeneratorParamImpl : public GeneratorParamBase { public: - explicit GeneratorParamImpl(const std::string &name, const T &value) : GeneratorParamBase(name), value_(value) {} + GeneratorParamImpl(const std::string &name, const T &value) : GeneratorParamBase(name), value_(value) {} T value() const { return value_; } @@ -247,7 +246,7 @@ class GeneratorParamImpl : public GeneratorParamBase { template class GeneratorParam_Target : public GeneratorParamImpl { public: - explicit GeneratorParam_Target(const std::string &name, const T &value) : GeneratorParamImpl(name, value) {} + GeneratorParam_Target(const std::string &name, const T &value) : GeneratorParamImpl(name, value) {} void set_from_string(const std::string &new_value_string) override { this->set(Target(new_value_string)); @@ -271,10 +270,10 @@ class GeneratorParam_Target : public GeneratorParamImpl { template class GeneratorParam_Arithmetic : public GeneratorParamImpl { public: - explicit GeneratorParam_Arithmetic(const std::string &name, - const T &value, - const T &min = std::numeric_limits::lowest(), - const T &max = std::numeric_limits::max()) + GeneratorParam_Arithmetic(const std::string &name, + const T &value, + const T &min = std::numeric_limits::lowest(), + const T &max = std::numeric_limits::max()) : GeneratorParamImpl(name, value), min(min), max(max) { // call set() to ensure value is clamped to min/max this->set(value); @@ -328,7 +327,7 @@ class GeneratorParam_Arithmetic : public GeneratorParamImpl { template class GeneratorParam_Bool : public GeneratorParam_Arithmetic { public: - explicit GeneratorParam_Bool(const std::string &name, const T &value) : GeneratorParam_Arithmetic(name, value) {} + GeneratorParam_Bool(const std::string &name, const T &value) : GeneratorParam_Arithmetic(name, value) {} void set_from_string(const std::string &new_value_string) override { bool v = false; @@ -360,7 +359,7 @@ class GeneratorParam_Bool : public GeneratorParam_Arithmetic { template class GeneratorParam_Enum : public GeneratorParamImpl { public: - explicit GeneratorParam_Enum(const std::string &name, const T &value, const std::map &enum_map) + GeneratorParam_Enum(const std::string &name, const T &value, const std::map &enum_map) : GeneratorParamImpl(name, value), enum_map(enum_map) {} void set_from_string(const std::string &new_value_string) override { @@ -414,7 +413,7 @@ class GeneratorParam_Enum : public GeneratorParamImpl { template class GeneratorParam_Type : public GeneratorParam_Enum { public: - explicit GeneratorParam_Type(const std::string &name, const T &value) + GeneratorParam_Type(const std::string &name, const T &value) : GeneratorParam_Enum(name, value, get_halide_type_enum_map()) {} std::string call_to_string(const std::string &v) const override { @@ -445,7 +444,7 @@ class GeneratorParam_Type : public GeneratorParam_Enum { template class GeneratorParam_LoopLevel : public GeneratorParam_Enum { public: - explicit GeneratorParam_LoopLevel(const std::string &name, const std::string &def) + GeneratorParam_LoopLevel(const std::string &name, const std::string &def) : GeneratorParam_Enum(name, enum_from_string(get_halide_looplevel_enum_map(), def), get_halide_looplevel_enum_map()), def(def) {} std::string call_to_string(const std::string &v) const override { @@ -534,6 +533,14 @@ class GeneratorParam : public Internal::GeneratorParamImplBase { : Internal::GeneratorParamImplBase(name, value) {} }; +/** ScheduleParam is similar to a GeneratorParam, with two important differences: + * + * (1) ScheduleParams are intended for use only within a Generator's schedule() + * method (if any); if a Generator has no schedule() method, it should also have no + * ScheduleParams + * + * (2) ScheduleParam can represent a LoopLevel, while GeneratorParam cannot. + */ template class ScheduleParam : public GeneratorParam { public: @@ -722,10 +729,14 @@ namespace Internal { enum class IOKind { Scalar, Function }; +// This is a union class that allows for convenient initialization of Stub Inputs +// via C++11 initializer-list syntax; it is only used in situations where the +// downstream consumer will be able to explicitly check that each value is +// of the expected/required kind. class FuncOrExpr { - IOKind kind_; - Halide::Func func_; - Halide::Expr expr_; + const IOKind kind_; + const Halide::Func func_; + const Halide::Expr expr_; public: // *not* explicit FuncOrExpr(const Func &f) : kind_(IOKind::Function), func_(f), expr_(Expr()) {} @@ -746,52 +757,71 @@ class FuncOrExpr { } }; +/** GIOBase is the base class for all GeneratorInput<> and GeneratorOutput<> + * instantiations; it is not part of the public API and should never be + * 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 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 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.) + * + * The array-size, type(s), and dimensions can all be left "unspecified" at + * creation time, in which case they may assume values provided by a Stub. + * (It is important to note that attempting to use a GIOBase with unspecified + * values will assert-fail; you must ensure that all unspecified values are + * filled in prior to use.) + */ class GIOBase { public: - bool array_size_defined() const; - size_t array_size() const; - virtual bool is_array() const; + EXPORT bool array_size_defined() const; + EXPORT size_t array_size() const; + EXPORT virtual bool is_array() const; - const std::string &name() const; - IOKind kind() const; + EXPORT const std::string &name() const; + EXPORT IOKind kind() const; - bool types_defined() const; - const std::vector &types() const; - Type type() const; + EXPORT bool types_defined() const; + EXPORT const std::vector &types() const; + EXPORT Type type() const; - bool dimensions_defined() const; - int dimensions() const; + EXPORT bool dimensions_defined() const; + EXPORT int dimensions() const; - const std::vector &funcs() const; - const std::vector &exprs() const; + EXPORT const std::vector &funcs() const; + EXPORT const std::vector &exprs() const; protected: - GIOBase(size_t array_size, + EXPORT GIOBase(size_t array_size, const std::string &name, IOKind kind, const std::vector &types, int dimensions); - virtual ~GIOBase(); + EXPORT virtual ~GIOBase(); friend class GeneratorBase; - int array_size_; // always 1 if is_array() == false. -1 if is_array() == true but unspecified. + int array_size_; // always 1 if is_array() == false. + // -1 if is_array() == true but unspecified. const std::string name_; const IOKind kind_; std::vector types_; // empty if type is unspecified int dimensions_; // -1 if dim is unspecified - // Exactly one will have nonzero length + // Exactly one of these will have nonzero length std::vector funcs_; std::vector exprs_; - std::string array_name(size_t i) const; + EXPORT std::string array_name(size_t i) const; - virtual void verify_internals() const; + EXPORT virtual void verify_internals() const; - void check_matching_array_size(size_t size); - void check_matching_type_and_dim(const std::vector &t, int d); + EXPORT void check_matching_array_size(size_t size); + EXPORT void check_matching_type_and_dim(const std::vector &t, int d); template const std::vector &get_values() const; @@ -813,7 +843,7 @@ inline const std::vector &GIOBase::get_values() const { class GeneratorInputBase : public GIOBase { protected: - GeneratorInputBase(size_t array_size, + EXPORT GeneratorInputBase(size_t array_size, const std::string &name, IOKind kind, const std::vector &t, @@ -822,23 +852,23 @@ class GeneratorInputBase : public GIOBase { GeneratorInputBase(const std::string &name, IOKind kind, const std::vector &t, int d) : GeneratorInputBase(1, name, kind, t, d) {} - ~GeneratorInputBase() override; + EXPORT ~GeneratorInputBase() override; friend class GeneratorBase; std::vector parameters_; - void init_internals(); - void set_inputs(const std::vector &inputs); + EXPORT void init_internals(); + EXPORT void set_inputs(const std::vector &inputs); virtual void set_def_min_max() { // nothing } - void verify_internals() const override; + EXPORT void verify_internals() const override; private: - void init_parameters(); + EXPORT void init_parameters(); }; @@ -1115,7 +1145,6 @@ class GeneratorInput : public Internal::GeneratorInputImplBase { } // Avoid ambiguity between Func-with-dim and int-with-default - //template ::value>::type * = nullptr> GeneratorInput(const std::string &name, IntIfFunc d) : Super(name, d) { } @@ -1143,7 +1172,7 @@ namespace Internal { class GeneratorOutputBase : public GIOBase { protected: - GeneratorOutputBase(size_t array_size, + EXPORT GeneratorOutputBase(size_t array_size, const std::string &name, const std::vector &t, int d); @@ -1153,12 +1182,12 @@ class GeneratorOutputBase : public GIOBase { int d) : GeneratorOutputBase(1, name, t, d) {} - ~GeneratorOutputBase() override; + EXPORT ~GeneratorOutputBase() override; friend class GeneratorBase; - void init_internals(); - void resize(size_t size); + EXPORT void init_internals(); + EXPORT void resize(size_t size); }; template @@ -1335,11 +1364,38 @@ class GeneratorOutput : public Internal::GeneratorOutputImplBase { } }; +/** GeneratorContext is an abstract interface that is used when constructing a Generator Stub; + * it is used to allow the outer context (typically, either a Generator or "top-level" code) + * to specify certain information to the inner context to ensure that inner and outer + * Generators are compiled in a compatible way; at present, this is used to propagate + * the outer Target to the inner Generator. */ class GeneratorContext { public: virtual Target get_target() const = 0; }; +/** JITGeneratorContext is a utility implementation of GeneratorContext that + * is intended for use when using Generator Stubs with the JIT; it simply + * allows you to wrap a specific Target in a GeneratorContext for use with a stub, + * often in conjunction with the Halide::get_target_from_environment() call: + * + * \code + * auto my_stub = MyStub( + * JITGeneratorContext(get_target_from_environment()), + * // inputs + * { ... }, + * // generator params + * { ... } + * ); + */ +class JITGeneratorContext : public GeneratorContext { +public: + explicit JITGeneratorContext(const Target &t) : target(t) {} + Target get_target() const override { return target; } +private: + const Target target; +}; + class NamesInterface { // Names in this class are only intended for use in derived classes. protected: @@ -1370,14 +1426,6 @@ class NamesInterface { static inline Type UInt(int bits, int lanes = 1) { return Halide::UInt(bits, lanes); } }; -class JITGeneratorContext : public GeneratorContext { -public: - explicit JITGeneratorContext(const Target &t) : target(t) {} - Target get_target() const override { return target; } -private: - const Target target; -}; - namespace Internal { class GeneratorStub; @@ -1758,7 +1806,7 @@ class GeneratorStub : public NamesInterface { protected: typedef std::function(const std::map&)> GeneratorFactory; - GeneratorStub(const GeneratorContext *context, + EXPORT GeneratorStub(const GeneratorContext *context, GeneratorFactory generator_factory, const std::map &generator_params, const std::vector> &inputs); From a8861d883fa2cd7a77f3dbc0779d8605c5816168 Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Mon, 24 Oct 2016 16:53:22 -0700 Subject: [PATCH 16/18] Add link to https://github.com/halide/Halide/wiki/Generator-Enhancements --- src/Generator.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Generator.h b/src/Generator.h index a318abe19878..205d8a05928e 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -83,6 +83,10 @@ * Your build() method will usually return a Func. If you have a * pipeline that outputs multiple Funcs, you can also return a * Pipeline object. + * + * There are newer enhancements to Generator that are not yet documented + * here; for more information, see + * https://github.com/halide/Halide/wiki/Generator-Enhancements */ #include From 70ded22db5d25266fcdd8e83c6a892735f63903a Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 25 Oct 2016 15:10:03 -0700 Subject: [PATCH 17/18] Fixes for MSVC issues --- src/Generator.cpp | 4 ++ src/Generator.h | 67 +++++++++++++-------- test/generator/nested_externs_generator.cpp | 8 +-- test/generator/stubtest_aottest.cpp | 2 + 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 701e3527ff1c..9b50a1500485 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -1377,6 +1377,10 @@ GeneratorInputBase::~GeneratorInputBase() { ObjectInstanceRegistry::unregister_instance(this); } +void GeneratorInputBase::set_def_min_max() { + // nothing +} + void GeneratorInputBase::init_parameters() { parameters_.clear(); for (size_t i = 0; i < array_size(); ++i) { diff --git a/src/Generator.h b/src/Generator.h index 205d8a05928e..c9adade395e9 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -90,6 +90,7 @@ */ #include +#include #include #include #include @@ -299,6 +300,9 @@ class GeneratorParam_Arithmetic : public GeneratorParamImpl { std::string to_string() const override { std::ostringstream oss; oss << this->value(); + if (std::is_same::value) { + oss << "f"; + } return oss.str(); } @@ -800,10 +804,10 @@ class GIOBase { protected: EXPORT GIOBase(size_t array_size, - const std::string &name, - IOKind kind, - const std::vector &types, - int dimensions); + const std::string &name, + IOKind kind, + const std::vector &types, + int dimensions); EXPORT virtual ~GIOBase(); friend class GeneratorBase; @@ -853,7 +857,7 @@ class GeneratorInputBase : public GIOBase { const std::vector &t, int d); - GeneratorInputBase(const std::string &name, IOKind kind, const std::vector &t, int d) + EXPORT GeneratorInputBase(const std::string &name, IOKind kind, const std::vector &t, int d) : GeneratorInputBase(1, name, kind, t, d) {} EXPORT ~GeneratorInputBase() override; @@ -865,9 +869,7 @@ class GeneratorInputBase : public GIOBase { EXPORT void init_internals(); EXPORT void set_inputs(const std::vector &inputs); - virtual void set_def_min_max() { - // nothing - } + EXPORT virtual void set_def_min_max(); EXPORT void verify_internals() const override; @@ -992,7 +994,7 @@ class GeneratorInput_Func : public GeneratorInputImpl { return this->funcs().at(0)(args); } - operator class Func() const { + operator Func() const { return this->funcs().at(0); } }; @@ -1181,9 +1183,9 @@ class GeneratorOutputBase : public GIOBase { const std::vector &t, int d); - GeneratorOutputBase(const std::string &name, - const std::vector &t, - int d) + EXPORT GeneratorOutputBase(const std::string &name, + const std::vector &t, + int d) : GeneratorOutputBase(1, name, t, d) {} EXPORT ~GeneratorOutputBase() override; @@ -1240,7 +1242,7 @@ class GeneratorOutputImpl : public GeneratorOutputBase { } template ::value>::type * = nullptr> - operator class Func() const { + operator Func() const { return get_values().at(0); } @@ -1614,10 +1616,16 @@ template class Generator : public Internal::GeneratorBase { // have build() or generate()/schedule() methods. // std::is_member_function_pointer will fail if there is no member of that name, - // so we use a little SFINAE to detect if there is a method-shaped member named 'schedule'. + // so we use a little SFINAE to detect if there are method-shaped members. template struct type_sink { typedef void type; }; + template + struct has_generate_method : std::false_type {}; + + template + struct has_generate_method().generate())>::type> : std::true_type {}; + template struct has_schedule_method : std::false_type {}; @@ -1625,7 +1633,7 @@ template class Generator : public Internal::GeneratorBase { struct has_schedule_method().schedule())>::type> : std::true_type {}; template ::value>::type * = nullptr> + typename std::enable_if::value>::type * = nullptr> Pipeline build_pipeline_impl() { internal_assert(!build_pipeline_called); static_assert(!has_schedule_method::value, "The schedule() method is ignored if you define a build() method; use generate() instead."); @@ -1635,7 +1643,7 @@ template class Generator : public Internal::GeneratorBase { return p; } template ::value>::type * = nullptr> + typename std::enable_if::value>::type * = nullptr> Pipeline build_pipeline_impl() { internal_assert(!build_pipeline_called); ((T *)this)->call_generate_impl(); @@ -1648,13 +1656,13 @@ template class Generator : public Internal::GeneratorBase { // have build() or generate()/schedule() methods. template ::value>::type * = nullptr> + typename std::enable_if::value>::type * = nullptr> void call_generate_impl() { user_error << "Unimplemented"; } template ::value>::type * = nullptr> + typename std::enable_if::value>::type * = nullptr> void call_generate_impl() { user_assert(!generate_called) << "You may not call the generate() method more than once per instance."; typedef typename std::result_of::type GenerateRetType; @@ -1668,13 +1676,13 @@ template class Generator : public Internal::GeneratorBase { // have build() or generate()/schedule() methods. template ::value>::type * = nullptr> + typename std::enable_if::value>::type * = nullptr> void call_schedule_impl() { user_error << "Unimplemented"; } template ::value>::type * = nullptr> + typename std::enable_if::value>::type * = nullptr> void call_schedule_impl() { user_assert(generate_called) << "You must call the generate() method before calling the schedule() method."; user_assert(!schedule_called) << "You may not call the schedule() method more than once per instance."; @@ -1686,15 +1694,15 @@ template class Generator : public Internal::GeneratorBase { protected: Pipeline build_pipeline() override { - return build_pipeline_impl(); + return this->build_pipeline_impl(); } void call_generate() override { - call_generate_impl(); + this->call_generate_impl(); } void call_schedule() override { - call_schedule_impl(); + this->call_schedule_impl(); } private: friend class Internal::SimpleGeneratorFactory; @@ -1846,8 +1854,8 @@ class GeneratorStub : public NamesInterface { return r; } - void verify_same_funcs(Func a, Func b); - void verify_same_funcs(const std::vector& a, const std::vector& b); + EXPORT void verify_same_funcs(Func a, Func b); + EXPORT void verify_same_funcs(const std::vector& a, const std::vector& b); private: std::shared_ptr generator; @@ -1879,8 +1887,15 @@ class GeneratorStub : public NamesInterface { #define _HALIDE_REGISTER_GENERATOR_CHOOSER(_1, _2, _3, NAME, ...) NAME +#ifdef _MSC_VER +// MSVC flagrantly ignores the spec for __VA_ARGS__; this is an ugly but effective workaround +#define _HALIDE_VARIADIC_MACRO(MACRO, TUPLE) MACRO TUPLE +#define HALIDE_REGISTER_GENERATOR(...) \ + _HALIDE_VARIADIC_MACRO(_HALIDE_REGISTER_GENERATOR_CHOOSER, (__VA_ARGS__, _HALIDE_REGISTER_GENERATOR3, _HALIDE_REGISTER_GENERATOR2, DUMMY))(__VA_ARGS__) +#else #define HALIDE_REGISTER_GENERATOR(...) \ - _HALIDE_REGISTER_GENERATOR_CHOOSER(__VA_ARGS__, _HALIDE_REGISTER_GENERATOR3, _HALIDE_REGISTER_GENERATOR2)(__VA_ARGS__) + _HALIDE_REGISTER_GENERATOR_CHOOSER(__VA_ARGS__, _HALIDE_REGISTER_GENERATOR3, _HALIDE_REGISTER_GENERATOR2, DUMMY)(__VA_ARGS__) +#endif #endif // HALIDE_GENERATOR_H_ diff --git a/test/generator/nested_externs_generator.cpp b/test/generator/nested_externs_generator.cpp index 85f52cb44c17..49eec0050ca2 100644 --- a/test/generator/nested_externs_generator.cpp +++ b/test/generator/nested_externs_generator.cpp @@ -118,9 +118,9 @@ class NestedExternsRoot : public Generator { } }; -HALIDE_REGISTER_GENERATOR(NestedExternsCombine, "nested_externs_combine"); -HALIDE_REGISTER_GENERATOR(NestedExternsInner, "nested_externs_inner"); -HALIDE_REGISTER_GENERATOR(NestedExternsLeaf, "nested_externs_leaf"); -HALIDE_REGISTER_GENERATOR(NestedExternsRoot, "nested_externs_root"); +HALIDE_REGISTER_GENERATOR(NestedExternsCombine, "nested_externs_combine") +HALIDE_REGISTER_GENERATOR(NestedExternsInner, "nested_externs_inner") +HALIDE_REGISTER_GENERATOR(NestedExternsLeaf, "nested_externs_leaf") +HALIDE_REGISTER_GENERATOR(NestedExternsRoot, "nested_externs_root") } // namespace diff --git a/test/generator/stubtest_aottest.cpp b/test/generator/stubtest_aottest.cpp index 3705b7106829..5365a82d7f8d 100644 --- a/test/generator/stubtest_aottest.cpp +++ b/test/generator/stubtest_aottest.cpp @@ -1,3 +1,5 @@ +#include + #include "HalideRuntime.h" #include "HalideBuffer.h" #include "stubtest.h" From 31957667bd8f7a602457f097343d3346619dffed Mon Sep 17 00:00:00 2001 From: Steven Johnson Date: Tue, 25 Oct 2016 17:01:57 -0700 Subject: [PATCH 18/18] Rewrite Generator doxygen comment --- src/Generator.h | 258 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 180 insertions(+), 78 deletions(-) diff --git a/src/Generator.h b/src/Generator.h index c9adade395e9..15ec35fe66d3 100644 --- a/src/Generator.h +++ b/src/Generator.h @@ -3,90 +3,192 @@ /** \file * - * 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. - * - * A Generator automatically detects the run-time parameters (Param/ImageParams) - * associated with the Func and (for AOT code) produces a function signature - * with the correct params in the correct order. - * - * A Generator can also be customized via compile-time parameters (GeneratorParams), - * which affect code generation. - * - * GeneratorParams, ImageParams, and Params are (by convention) - * always public and always declared at the top of the Generator class, - * in the order - * - * GeneratorParam(s) - * ImageParam(s) - * Param(s) - * - * Preferred style is to use C++11 in-class initialization style, e.g. + * 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. + * + * A Generator explicitly declares the Inputs and Outputs associated for a given + * pipeline, and separates the code for constructing the outputs from the code from + * scheduling them. For instance: + * * \code - * GeneratorParam magic{"magic", 42}; + * class Blur : public Generator { + * public: + * Input input{"input", UInt(16), 2}; + * Output 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; + * output(x, y) = blur(x, y); + * } + * void schedule() { + * blur_y.split(y, y, yi, 8).parallel(y).vectorize(x, 8); + * blur_x.store_at(blur_y, y).compute_at(blur_y, yi).vectorize(x, 8); + * } + * private: + * Var x, y, xi, yi; + * Func blur_x, blur_y; + * }; * \endcode - * - * Note that the ImageParams/Params will appear in the C function - * call in the order they are declared. (GeneratorParams are always - * referenced by name, not position, so their order is irrelevant.) - * - * All Param variants declared as Generator members must have explicit - * names, and all such names must match the regex [A-Za-z][A-Za-z_0-9]* - * (i.e., essentially a C/C++ variable name, with some extra restrictions - * on underscore use). By convention, the name should match the member-variable name. - * - * Generators are usually added to a global registry to simplify AOT build mechanics; - * this is done by simply defining an instance of RegisterGenerator at static - * scope: + * + * Halide can compile a Generator into the correct pipeline by introspecting these + * values and constructing an appropriate signature based on them. + * + * A Generator must provide implementations of two methods: + * + * - generate(), which must fill in all Output Func(s), but should not do any + * scheduling + * - schedule(), which should do scheduling for any intermediate and + * output Funcs + * + * Inputs can be any C++ scalar type: + * * \code - * RegisterGenerator register_jit_example{"jit_example"}; + * Input radius{"radius"}; + * Input increment{"increment"}; * \endcode - * - * The registered name of the Generator is provided as an argument - * (which must match the same rules as Param names, above). - * - * (If you are jitting, you may not need to bother registering your Generator, - * but it's considered best practice to always do so anyway.) - * - * Most Generator classes will only need to provide a build() method - * that the base class will call, and perhaps declare a Param and/or - * GeneratorParam: - * + * + * An Input 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 - * class XorImage : public Generator { - * public: - * GeneratorParam channels{"channels", 3}; - * ImageParam input{UInt(8), 3, "input"}; - * Param mask{"mask"}; - * - * Func build() { - * Var x, y, c; - * Func f; - * f(x, y, c) = input(x, y, c) ^ mask; - * f.bound(c, 0, bound).reorder(c, x, y).unroll(c); - * return f; - * } - * }; - * RegisterGenerator reg_xor{"xor_image"}; + * Input input{"input", Float(32), 2}; * \endcode - * - * By default, this code schedules itself for 3-channel (RGB) images; - * by changing the value of the "channels" GeneratorParam before calling - * build() we can produce code suited for different channel counts. - * - * 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) - * - * Your build() method will usually return a Func. If you have a - * pipeline that outputs multiple Funcs, you can also return a - * Pipeline object. - * - * There are newer enhancements to Generator that are not yet documented - * here; for more information, see - * https://github.com/halide/Halide/wiki/Generator-Enhancements + * + * You can optionally make the type and/or dimensions of Input 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 input{ "input", 3 }; // require 3-dimensional Func, + * // but leave Type unspecified + * \endcode + * + * A Generator must explicitly list the output(s) it produces: + * + * \code + * Output output{"output", Float(32), 2}; + * \endcode + * + * You can specify an output that returns a Tuple by specifying a list of Types: + * + * \code + * class Tupler : Generator { + * Input input{"input", Int(32), 2}; + * Output output{"output", {Float(32), UInt(8)}, 2}; + * void generate() { + * Var x, y; + * Expr a = cast(input(x, y)); + * Expr b = cast(input(x, y)); + * output(x, y) = Tuple(a, b); + * } + * }; + * \endcode + * + * You can also specify Output for any scalar type (except for Handle types); + * 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 sum{"sum"}; // equivalent to Output {"sum", Float(32), 0} + * \endcode + * + * As with Input, you can optionally make the type and/or dimensions of an + * Output 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 + * as the type parameter: + * + * \code + * // Takes exactly 3 images and outputs exactly 3 sums. + * class SumRowsAndColumns : Generator { + * Input inputs{"inputs", Float(32), 2}; + * Input extents{"extents"}; + * Output sums{"sums", Float(32), 1}; + * void generate() { + * assert(inputs.size() == sums.size()); + * // assume all inputs are same extent + * 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; + * sums[i]() += inputs[i](r.x, r.y); + * } + * } + * }; + * + * You can also leave array size unspecified, in which case it will be inferred + * from the input vector, or (optionally) explicitly specified via a resize() + * method: + * + * \code + * class Pyramid : public Generator { + * public: + * GeneratorParam levels{"levels", 10}; + * Input input{ "input", Float(32), 2 }; + * Output pyramid{ "pyramid", Float(32), 2 }; + * void generate() { + * pyramid.resize(levels); + * pyramid[0](x, y) = input(x, y); + * for (int i = 1; i < pyramid.size(); i++) { + * pyramid[i](x, y) = (pyramid[i-1](2*x, 2*y) + + * pyramid[i-1](2*x+1, 2*y) + + * pyramid[i-1](2*x, 2*y+1) + + * pyramid[i-1](2*x+1, 2*y+1))/4; + * } + * } + * }; + * \endcode + * + * A Generator can also be customized via compile-time parameters (GeneratorParams + * or ScheduleParams), which affect code generation. While a GeneratorParam can + * be used from anywhere inside a Generator (either the generate() or + * schedule() method), ScheduleParam should be accessed only within the + * schedule() method. (This is not currently a compile-time error but may become + * one in the future.) + * + * GeneratorParams, ScheduleParams, Inputs, and Outputs are (by convention) always + * public and always declared at the top of the Generator class, in the order + * + * \code + * GeneratorParam(s) + * ScheduleParam(s) + * Input(s) + * Input(s) + * Output(s) + * \endcode + * + * Note that the Inputs and Outputs will appear in the C function call in the order + * they are declared. All Input and Output are represented as buffer_t; + * all other Input<> are the appropriate C++ scalar type. (GeneratorParams are + * always referenced by name, not position, so their order is irrelevant.) + * + * All Inputs and Outputs must have explicit names, and all such names must match + * the regex [A-Za-z][A-Za-z_0-9]* (i.e., essentially a C/C++ variable name, with + * some extra restrictions on underscore use). By convention, the name should match + * the member-variable name. + * + * Generators are added to a global registry to simplify AOT build mechanics; this + * is done by simply using the HALIDE_REGISTER_GENERATOR macro at global scope: + * + * \code + * HALIDE_REGISTER_GENERATOR(ExampleGen, "jit_example") + * \endcode + * + * The registered name of the Generator is provided must match the same rules as + * Input names, above. + * + * 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) + * + * (Note that there are older variations of Generator that differ from what's + * documented above; these are still supported but not described here. See + * https://github.com/halide/Halide/wiki/Old-Generator-Documentation for + * more information.) */ #include