Skip to content

Commit 375a151

Browse files
steven-johnsonardier
authored andcommitted
Rework Python Extension C++ code (again) (halide#7010)
* Rework Python Extension C++ code (again) My previous effort was too clever for itself: while it worked for Halide's build systems, some other build systems (e.g. Blaze) are much more finicky about the C++ files you build Python extensions from, and re-using the same C++ files with different preprocessor settings turns out to be too problematic there, for reasons that aren't important here. Anyway, the important part here was to rework so that (1) All the C++ source files needed are compiled exactly once (2) All the C++ source files needed can be compiled with the same set of preprocessor definitions To that end, I have extended GenGen's `-r` flag to allow using `-e python_extension`; this emits the bare module-registration code by itself. So now, we generate the Python Extension code as before, but define HALIDE_PYTHON_EXTENSION_OMIT_MODULE_DEFINITION to defeat the standalone module registration for each one, then also compile in the new 'standalone' registration, with HALIDE_PYTHON_EXTENSION_MODULE and HALIDE_PYTHON_EXTENSION_FUNCTIONS defined to fill in the blanks. Also, a little drive-by cleanup in CodeGen_C to make extern "C" blocks more findable, and some restructuring in PyExtGen. * Update user_context_generator.cpp * Dummy source file * IF NOT EXISTS before file(WRITE)
1 parent a2f65ce commit 375a151

File tree

4 files changed

+180
-153
lines changed

4 files changed

+180
-153
lines changed

cmake/HalideGeneratorHelpers.cmake

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,12 @@ function(add_halide_generator TARGET)
100100
"#include \"Halide.h\"\n"
101101
"HALIDE_GENERATOR_PYSTUB(${GEN_NAME}, ${MODULE_NAME})\n")
102102

103-
file(WRITE
104-
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.py_stub_generated.cpp"
105-
"${stub_text}")
103+
set(stub_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.${GEN_NAME}.${MODULE_NAME}.py_stub_generated.cpp")
104+
if (NOT EXISTS "${stub_file}")
105+
file(WRITE "${stub_file}" "${stub_text}")
106+
endif()
106107

107-
Python3_add_library(${TARGET}_pystub MODULE WITH_SOABI "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.py_stub_generated.cpp" ${ARG_SOURCES})
108+
Python3_add_library(${TARGET}_pystub MODULE WITH_SOABI "${stub_file}" ${ARG_SOURCES})
108109
set_target_properties(${TARGET}_pystub PROPERTIES
109110
CXX_VISIBILITY_PRESET hidden
110111
VISIBILITY_INLINES_HIDDEN ON
@@ -454,30 +455,22 @@ function(add_halide_python_extension_library TARGET)
454455
message(FATAL_ERROR "${TARGET} requires all libraries to use the same Halide Runtime, but saw ${len}: ${runtimes}")
455456
endif ()
456457

457-
# Module def code is the same in all of them, so arbitrarily choose the first
458-
list(GET pycpps 0 module_definition_cpp)
459-
add_library(${TARGET}_module_definition OBJECT ${module_definition_cpp})
460-
set_target_properties(${TARGET}_module_definition PROPERTIES
461-
CXX_VISIBILITY_PRESET hidden
462-
VISIBILITY_INLINES_HIDDEN ON
463-
POSITION_INDEPENDENT_CODE ON)
464-
target_link_libraries(${TARGET}_module_definition PRIVATE Halide::Runtime Python3::Module)
465-
466-
list(GET ARG_HALIDE_LIBRARIES 0 module_definition_lib)
467-
add_dependencies(${TARGET}_module_definition ${module_definition_lib})
468-
469-
# Compile it with the right preprocessor definitions to provide the module defs,
470-
# but not the function implementations
471-
target_compile_definitions(${TARGET}_module_definition PRIVATE
472-
HALIDE_PYTHON_EXTENSION_OMIT_FUNCTION_DEFINITIONS
473-
HALIDE_PYTHON_EXTENSION_MODULE=${ARG_MODULE_NAME}
474-
"HALIDE_PYTHON_EXTENSION_FUNCTIONS=${function_names}")
458+
set(pyext_runtime_name ${TARGET}_module_definition)
459+
set(pyext_module_definition_src "${CMAKE_CURRENT_BINARY_DIR}/${pyext_runtime_name}.py.cpp")
460+
_Halide_gengen_ensure()
461+
add_custom_command(OUTPUT ${pyext_module_definition_src}
462+
COMMAND _Halide_gengen -r "${pyext_runtime_name}" -e python_extension -o "${CMAKE_CURRENT_BINARY_DIR}" target=host
463+
DEPENDS _Halide_gengen
464+
VERBATIM)
475465

476-
# Now compile all the pycpps to build the function implementations (but not the module def)
477-
Python3_add_library(${TARGET} MODULE WITH_SOABI ${pycpps} $<TARGET_OBJECTS:${TARGET}_module_definition>)
466+
Python3_add_library(${TARGET} MODULE WITH_SOABI ${pycpps} ${pyext_module_definition_src})
478467
target_link_libraries(${TARGET} PRIVATE ${ARG_HALIDE_LIBRARIES})
479468
target_compile_definitions(${TARGET} PRIVATE
480-
HALIDE_PYTHON_EXTENSION_OMIT_MODULE_DEFINITION)
469+
# Skip the default module-definition code in each file
470+
HALIDE_PYTHON_EXTENSION_OMIT_MODULE_DEFINITION
471+
# Gotta explicitly specify the module name and function(s) for this mode
472+
HALIDE_PYTHON_EXTENSION_MODULE_NAME=${ARG_MODULE_NAME}
473+
"HALIDE_PYTHON_EXTENSION_FUNCTIONS=${function_names}")
481474
set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME ${ARG_MODULE_NAME})
482475
_Halide_target_export_single_symbol(${TARGET} "PyInit_${ARG_MODULE_NAME}")
483476
endfunction()
@@ -534,12 +527,8 @@ function(add_halide_runtime RT)
534527
# so that GCD calculation doesn't get confused.
535528
list(TRANSFORM ARG_TARGETS APPEND "-no_runtime")
536529

537-
# Create a Generator that is GenGen.cpp and nothing else; all it can do is generate a runtime.
538-
if (NOT TARGET _Halide_gengen)
539-
add_executable(_Halide_gengen )
540-
target_link_libraries(_Halide_gengen PRIVATE Halide::Generator)
541-
_Halide_place_dll(_Halide_gengen)
542-
endif ()
530+
# Ensure _Halide_gengen is defined
531+
_Halide_gengen_ensure()
543532

544533
_Halide_get_platform_details(
545534
is_crosscompiling
@@ -668,15 +657,38 @@ function(_Halide_fix_xcode TARGET)
668657
endfunction()
669658

670659
function(_Halide_target_export_single_symbol TARGET SYMBOL)
671-
file(WRITE
672-
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.ldscript.apple"
673-
"_${SYMBOL}\n")
674-
file(WRITE
675-
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.ldscript"
676-
"{ global: ${SYMBOL}; local: *; };\n")
660+
if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.${SYMBOL}.ldscript.apple")
661+
file(WRITE
662+
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.${SYMBOL}.ldscript.apple"
663+
"_${SYMBOL}\n")
664+
endif()
665+
if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.${SYMBOL}.ldscript")
666+
file(WRITE
667+
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.${SYMBOL}.ldscript"
668+
"{ global: ${SYMBOL}; local: *; };\n")
669+
endif ()
677670
target_export_script(
678671
${TARGET}
679-
APPLE_LD "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.ldscript.apple"
680-
GNU_LD "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.ldscript"
672+
APPLE_LD "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.${SYMBOL}.ldscript.apple"
673+
GNU_LD "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.${SYMBOL}.ldscript"
681674
)
682675
endfunction()
676+
677+
function(_Halide_gengen_ensure)
678+
# Create a Generator that is GenGen.cpp and nothing else; all it can do is generate a runtime.
679+
if (NOT TARGET _Halide_gengen)
680+
681+
# add_executable requires at least one source file for some
682+
# configs (e.g. Xcode), because, uh, reasons, so we'll create
683+
# an empty one here to satisfy it
684+
set(empty "${CMAKE_CURRENT_BINARY_DIR}/_Halide_gengen.empty.cpp")
685+
if (NOT EXISTS "${empty}")
686+
file(WRITE "${empty}" "/* nothing */\n")
687+
endif ()
688+
689+
add_executable(_Halide_gengen "${empty}")
690+
target_link_libraries(_Halide_gengen PRIVATE Halide::Generator)
691+
_Halide_place_dll(_Halide_gengen)
692+
endif ()
693+
endfunction()
694+

src/CodeGen_C.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,14 +1272,20 @@ class NativeVectorOps {
12721272

12731273
void CodeGen_C::set_name_mangling_mode(NameMangling mode) {
12741274
if (extern_c_open && mode != NameMangling::C) {
1275-
stream << "\n#ifdef __cplusplus\n";
1276-
stream << "} // extern \"C\"\n";
1277-
stream << "#endif\n\n";
1275+
stream << R"INLINE_CODE(
1276+
#ifdef __cplusplus
1277+
} // extern "C"
1278+
#endif
1279+
1280+
)INLINE_CODE";
12781281
extern_c_open = false;
12791282
} else if (!extern_c_open && mode == NameMangling::C) {
1280-
stream << "\n#ifdef __cplusplus\n";
1281-
stream << "extern \"C\" {\n";
1282-
stream << "#endif\n\n";
1283+
stream << R"INLINE_CODE(
1284+
#ifdef __cplusplus
1285+
extern "C" {
1286+
#endif
1287+
1288+
)INLINE_CODE";
12831289
extern_c_open = true;
12841290
}
12851291
}

src/Module.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,10 @@ std::map<OutputFileType, std::string> compile_standalone_runtime(const std::map<
729729
// For runtime, it only makes sense to output object files or static_library, so ignore
730730
// everything else.
731731
std::map<OutputFileType, std::string> actual_outputs;
732-
for (auto key : {OutputFileType::object, OutputFileType::static_library}) {
732+
// If the python_extension output is specified, we'll generate just the module-registration code,
733+
// with no functions at all. This is useful when gluing together multiple Halide functions
734+
// into the same Python extension.
735+
for (auto key : {OutputFileType::object, OutputFileType::static_library, OutputFileType::python_extension}) {
733736
auto it = output_files.find(key);
734737
if (it != output_files.end()) {
735738
actual_outputs[key] = it->second;

0 commit comments

Comments
 (0)