diff --git a/cmake/BLTMacros.cmake b/cmake/BLTMacros.cmake index 3f06c49e4..21fa10e1b 100644 --- a/cmake/BLTMacros.cmake +++ b/cmake/BLTMacros.cmake @@ -136,21 +136,59 @@ endmacro(blt_register_library) ## HEADERS [header1 [header2 ...]] ## INCLUDES [dir1 [dir2 ...]] ## DEFINES [define1 [define2 ...]] -## DEPENDS_ON [dep1 ...] +## DEPENDS_ON [dep1 ...] ## OUTPUT_NAME [name] ## OUTPUT_DIR [dir] ## SHARED [TRUE | FALSE] ## OBJECT [TRUE | FALSE] ## CLEAR_PREFIX [TRUE | FALSE] -## FOLDER [name]) +## FOLDER [name] +## RDC [TRUE | FALSE] (HIP-only) +## RDC_SOURCES [src1 [src2 ...]] (HIP-only) +## EARLY_RDC [TRUE | FALSE] (HIP-only) +## EARLY_RDC_SUFFIX [suffix] (default: "_earlyrdc")) ## ## Adds a library target, called , to be built from the given sources. +## +## HIP Early RDC behavior: +## - When RDC is TRUE and EARLY_RDC is FALSE: +## * A host RDC static lib _host is built from RDC_SOURCES (or all SOURCES by default) +## with -fgpu-rdc and configured with DEPENDS_ON/INCLUDES. +## * The base target links _host transitively. +## - When EARLY_RDC is TRUE and RDC_SOURCES is provided as a non-empty strict subset: +## * A host RDC static lib _host is built from RDC_SOURCES with -fgpu-rdc. +## * erdc.sh is run on the host RDC archive to produce uber.o, creating _device. +## * The base target links _device transitively. The generated +## device archive replaces the host input archive because uber.o contains the host code. +## - When EARLY_RDC is TRUE and RDC_SOURCES is omitted or equals all SOURCES ("full-RDC"): +## * The base target is an INTERFACE library. A private host RDC archive is built from +## SOURCES for erdc.sh, but is not propagated to consumers. +## * erdc.sh consumes that host archive to produce uber.o, creating only +## _device. +## * The base target links _device transitively, so consumers do not +## receive both the host input archive and the generated device archive. +## +## Exported variables for downstream use (uppercase ): +## - _TARGETS: base + optional early RDC targets +## * Full-RDC: [, _device] +## * Partial-RDC: [, _host, _device] +## - _INTERFACE_TARGETS: subset of INTERFACE libraries (base may be INTERFACE only for header-only libs) +## - _NONINTERFACE_TARGETS: subset of STATIC/SHARED/OBJECT targets +## - _EARLY_RDC_HOST_TARGET: +## * Full-RDC: resolves to the private _host input archive +## * Partial-RDC: resolves to _host +## - _EARLY_RDC_DEVICE_TARGET: resolves to _device when EARLY_RDC artifacts are created, +## otherwise resolves to . +## +## Notes: +## - Header-only libraries remain INTERFACE and do not participate in EARLY_RDC. +## - EARLY_RDC_* options are effective only when BLT_ENABLE_HIP=ON. ##------------------------------------------------------------------------------ macro(blt_add_library) set(options) - set(singleValueArgs NAME OUTPUT_NAME OUTPUT_DIR SHARED OBJECT CLEAR_PREFIX FOLDER) - set(multiValueArgs SOURCES HEADERS INCLUDES DEFINES DEPENDS_ON) + set(singleValueArgs NAME OUTPUT_NAME OUTPUT_DIR SHARED OBJECT CLEAR_PREFIX FOLDER RDC EARLY_RDC EARLY_RDC_SUFFIX) + set(multiValueArgs SOURCES HEADERS INCLUDES DEFINES DEPENDS_ON RDC_SOURCES) # parse the arguments cmake_parse_arguments(arg @@ -175,6 +213,15 @@ macro(blt_add_library) endif() endif() + # Early RDC defaults + if(NOT DEFINED arg_EARLY_RDC_SUFFIX) + if(DEFINED BLT_EARLY_RDC_SUFFIX) + set(arg_EARLY_RDC_SUFFIX "${BLT_EARLY_RDC_SUFFIX}") + else() + set(arg_EARLY_RDC_SUFFIX "_earlyrdc") + endif() + endif() + if ( arg_SOURCES ) # Determine type of library to build. STATIC by default and OBJECT takes # precedence over global BUILD_SHARED_LIBS variable. @@ -196,7 +243,67 @@ macro(blt_add_library) set(_lib_type "STATIC") endif() - add_library( ${arg_NAME} ${_lib_type} ${arg_SOURCES} ${arg_HEADERS} ) + # HIP RDC / EARLY_RDC partitioning: + # - RDC sources are compiled into _host with -fgpu-rdc + # - When EARLY_RDC is enabled, those same sources are used to generate _device via erdc.sh + set(_normal_sources ${arg_SOURCES}) + set(_rdc_sources) + set(_erdc_sources) + set(_use_early_rdc FALSE) + set(_use_rdc FALSE) + if(BLT_ENABLE_HIP AND (DEFINED arg_EARLY_RDC AND arg_EARLY_RDC)) + set(_use_early_rdc TRUE) + endif() + if(BLT_ENABLE_HIP AND ((DEFINED arg_RDC AND arg_RDC) OR _use_early_rdc)) + set(_use_rdc TRUE) + endif() + if(_use_rdc) + if(DEFINED arg_RDC_SOURCES AND arg_RDC_SOURCES) + set(_rdc_sources ${arg_RDC_SOURCES}) + else() + set(_rdc_sources ${arg_SOURCES}) + endif() + foreach(_s ${_rdc_sources}) + list(REMOVE_ITEM _normal_sources ${_s}) + endforeach() + if(_use_early_rdc) + set(_erdc_sources ${_rdc_sources}) + endif() + endif() + + # Flag when all sources are assigned to EARLY_RDC + set(_erdc_full FALSE) + if(_use_early_rdc AND _erdc_sources AND NOT _normal_sources) + set(_erdc_full TRUE) + endif() + + # Create the public base target. For full EARLY_RDC, the host archive is an + # implementation detail of the generated device archive and must not also be + # linked by consumers. + set(_base_is_interface FALSE) + if(_normal_sources) + add_library( ${arg_NAME} ${_lib_type} ${_normal_sources} ${arg_HEADERS} ) + else() + # All sources assigned to RDC (and not EARLY_RDC): avoid compiling everything twice by + # making the base an INTERFACE library that links to _host. + if(_use_rdc AND NOT _use_early_rdc) + set(_base_is_interface TRUE) + if( ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0" ) + add_library( ${arg_NAME} INTERFACE ${arg_HEADERS} ) + else() + add_library( ${arg_NAME} INTERFACE ) + endif() + elseif(_use_early_rdc) + set(_base_is_interface TRUE) + if( ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0" ) + add_library( ${arg_NAME} INTERFACE ${arg_HEADERS} ) + else() + add_library( ${arg_NAME} INTERFACE ) + endif() + else() + add_library( ${arg_NAME} ${_lib_type} ${arg_SOURCES} ${arg_HEADERS} ) + endif() + endif() if (BLT_ENABLE_CUDA AND NOT BLT_ENABLE_CLANG_CUDA) blt_setup_cuda_target( @@ -205,17 +312,30 @@ macro(blt_add_library) DEPENDS_ON ${arg_DEPENDS_ON} LIBRARY_TYPE ${_lib_type}) endif() - + if(BLT_ENABLE_HIP) - blt_setup_hip_target( - NAME ${arg_NAME} - SOURCES ${arg_SOURCES} - DEPENDS_ON ${arg_DEPENDS_ON}) + # Configure HIP language on sources + if(_normal_sources) + blt_setup_hip_target( + NAME ${arg_NAME} + SOURCES ${_normal_sources} + DEPENDS_ON ${arg_DEPENDS_ON}) + else() + if(_base_is_interface) + message(STATUS "[BLT][HIP] Skipping blt_setup_hip_target for ${arg_NAME}: no non-RDC HIP sources to configure") + else() + blt_setup_hip_target( + NAME ${arg_NAME} + SOURCES ${_normal_sources} + DEPENDS_ON ${arg_DEPENDS_ON}) + endif() + endif() endif() else() # # Header-only library support # + set(_base_is_interface TRUE) if( ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0" ) # Adding headers here allows them to show up in IDE projects but is not # necessary for building @@ -240,21 +360,66 @@ macro(blt_add_library) target_include_directories(${arg_NAME} PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) endif() - blt_setup_target( NAME ${arg_NAME} - DEPENDS_ON ${arg_DEPENDS_ON} - OBJECT ${arg_OBJECT}) + set(_blt_object FALSE) + set(_arg_object ${arg_OBJECT}) + if(DEFINED arg_OBJECT AND arg_OBJECT AND NOT _base_is_interface) + set(_blt_object TRUE) + endif() + blt_setup_target(NAME ${arg_NAME} + DEPENDS_ON ${arg_DEPENDS_ON} + OBJECT ${_blt_object}) + # Full EARLY_RDC libraries use the private host target created below as the + # erdc.sh input, so there is no base archive to compile with RDC here. + if(_erdc_full AND NOT _base_is_interface) + target_compile_options(${arg_NAME} PRIVATE $<$:-fgpu-rdc>) + endif() + set(arg_OBJECT ${_arg_object}) + unset(_blt_object) + + # If RDC is enabled without EARLY_RDC, create the host RDC archive target and link it transitively. + set(_blt_rdc_host_target) + if(_use_rdc AND NOT _use_early_rdc AND _rdc_sources) + # blt_setup_target() and blt_setup_hip_target() are macros that use the same + # cmake_parse_arguments() prefix ("arg") and can overwrite arg_NAME. + # Save/restore the base target name so we don't accidentally link the host + # target to itself. + set(_blt_base_target_name ${arg_NAME}) + set(_blt_rdc_host_target ${_blt_base_target_name}${arg_EARLY_RDC_SUFFIX}_host) +# message(FATAL_ERROR "${_blt_rdc_host_target} STATIC SOURCES ${_rdc_sources} DEPEND_ON ${arg_DEPENDS_ON}") + add_library(${_blt_rdc_host_target} STATIC ${_rdc_sources} ${arg_HEADERS}) + blt_setup_target(NAME ${_blt_rdc_host_target} + DEPENDS_ON ${arg_DEPENDS_ON} + OBJECT ${arg_OBJECT}) + blt_setup_hip_target(NAME ${_blt_rdc_host_target} SOURCES ${_rdc_sources} DEPENDS_ON ${arg_DEPENDS_ON}) + target_compile_options(${_blt_rdc_host_target} PRIVATE $<$:-fgpu-rdc>) + set(arg_NAME ${_blt_base_target_name}) + if(_base_is_interface) + target_link_libraries(${arg_NAME} INTERFACE ${_blt_rdc_host_target}) + else() + target_link_libraries(${arg_NAME} PUBLIC ${_blt_rdc_host_target}) + endif() + endif() if ( arg_INCLUDES ) - if (NOT arg_SOURCES ) - # Header only + if (_base_is_interface) target_include_directories(${arg_NAME} INTERFACE ${arg_INCLUDES}) else() target_include_directories(${arg_NAME} PUBLIC ${arg_INCLUDES}) endif() + if(_blt_rdc_host_target) + target_include_directories(${_blt_rdc_host_target} PUBLIC ${arg_INCLUDES}) + endif() endif() if ( arg_DEFINES ) - target_compile_definitions(${arg_NAME} PUBLIC ${arg_DEFINES}) + if (_base_is_interface) + target_compile_definitions(${arg_NAME} INTERFACE ${arg_DEFINES}) + else() + target_compile_definitions(${arg_NAME} PUBLIC ${arg_DEFINES}) + endif() + if(_blt_rdc_host_target) + target_compile_definitions(${_blt_rdc_host_target} PUBLIC ${arg_DEFINES}) + endif() endif() if ( arg_OUTPUT_DIR ) @@ -277,12 +442,81 @@ macro(blt_add_library) blt_set_target_folder(TARGET ${arg_NAME} FOLDER "${arg_FOLDER}") endif() - if ( arg_SOURCES ) + if ( arg_SOURCES AND NOT _base_is_interface ) # Don't clean header-only libraries because you would have to handle # the white-list of properties that are allowed blt_clean_target(TARGET ${arg_NAME}) endif() + # Create early RDC archive and imported target if requested + # TODO: not sure if the _erdc_sources is needed in this conditional - might be causing issue for FULL_RDC true + if( BLT_ENABLE_HIP AND _use_early_rdc AND _erdc_sources ) + blt_setup_hip_early_rdc_target( + NAME ${arg_NAME} + DEPENDS_ON ${arg_DEPENDS_ON} + INCLUDES ${arg_INCLUDES} + RDC_SOURCES ${_erdc_sources} + HEADERS ${arg_HEADERS} + SUFFIX ${arg_EARLY_RDC_SUFFIX} + OBJECT ${arg_OBJECT} + INTERFACE ${_base_is_interface} + # A full-RDC library has an interface base target and therefore uses + # the private host archive path in blt_setup_hip_early_rdc_target(). + FULL_RDC FALSE) + endif() + + # Provide variables describing the created library targets for downstream use. + # - _TARGETS: all created targets (base + optional early RDC host/device) + # - _INTERFACE_TARGETS: subset of targets that are INTERFACE libraries + # - _NONINTERFACE_TARGETS: subset of targets that are non-INTERFACE (STATIC/SHARED/OBJECT) + # - _EARLY_RDC_HOST_TARGET and _EARLY_RDC_DEVICE_TARGET: if early RDC was created + set(_blt_created_lib_targets ${arg_NAME}) + if(BLT_ENABLE_HIP AND _use_early_rdc AND _erdc_sources) + if(_erdc_full) + list(APPEND _blt_created_lib_targets ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_device) + else() + list(APPEND _blt_created_lib_targets ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_host ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_device) + endif() + elseif(_blt_rdc_host_target) + list(APPEND _blt_created_lib_targets ${_blt_rdc_host_target}) + endif() + string(TOUPPER ${arg_NAME} _blt_uppercase_name) + + set(${_blt_uppercase_name}_TARGETS ${_blt_created_lib_targets}) + + # Classify targets into interface vs non-interface + set(_blt_interface_targets) + set(_blt_noninterface_targets) + if(_base_is_interface) + list(APPEND _blt_interface_targets ${arg_NAME}) + else() + list(APPEND _blt_noninterface_targets ${arg_NAME}) + endif() + if(BLT_ENABLE_HIP AND _use_early_rdc AND _erdc_sources) + # Early RDC targets are STATIC libraries + if(_erdc_full) + list(APPEND _blt_noninterface_targets ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_device) + else() + list(APPEND _blt_noninterface_targets ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_host ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_device) + endif() + elseif(_blt_rdc_host_target) + list(APPEND _blt_noninterface_targets ${_blt_rdc_host_target}) + endif() + set(${_blt_uppercase_name}_INTERFACE_TARGETS ${_blt_interface_targets}) + set(${_blt_uppercase_name}_NONINTERFACE_TARGETS ${_blt_noninterface_targets}) + + # Provide direct refs for each created target + if(BLT_ENABLE_HIP AND _use_early_rdc AND _erdc_sources) + set(${_blt_uppercase_name}_EARLY_RDC_HOST_TARGET ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_host) + set(${_blt_uppercase_name}_EARLY_RDC_DEVICE_TARGET ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_device) + elseif(_blt_rdc_host_target) + set(${_blt_uppercase_name}_EARLY_RDC_HOST_TARGET ${_blt_rdc_host_target}) + set(${_blt_uppercase_name}_EARLY_RDC_DEVICE_TARGET ${arg_NAME}) + else() + set(${_blt_uppercase_name}_EARLY_RDC_HOST_TARGET ${arg_NAME}) + set(${_blt_uppercase_name}_EARLY_RDC_DEVICE_TARGET ${arg_NAME}) + endif() + endmacro(blt_add_library) @@ -295,15 +529,37 @@ endmacro(blt_add_library) ## DEPENDS_ON [dep1 [dep2 ...]] ## OUTPUT_DIR [dir] ## OUTPUT_NAME [name] -## FOLDER [name]) +## FOLDER [name] +## RDC [TRUE | FALSE] (HIP-only) +## RDC_SOURCES [src1 [src2 ...]] (HIP-only) +## EARLY_RDC [TRUE | FALSE] (HIP-only) +## EARLY_RDC_SUFFIX [suffix] (default: "_earlyrdc")) ## ## Adds an executable target, called , to be built from the given sources. +## +## HIP Early RDC behavior: +## - RDC (and EARLY_RDC) is only supported for the "partial-RDC" case where RDC_SOURCES +## is a non-empty strict subset of SOURCES: +## * A host RDC static lib _host is built from RDC_SOURCES with -fgpu-rdc. +## * When EARLY_RDC is TRUE, erdc.sh is run on the host RDC archive to produce uber.o, +## creating _device and linking it transitively from the base executable. +## * The base executable's non-RDC sources are compiled without -fgpu-rdc; its final +## link remains configured for HIP RDC. +## - "Full-RDC" executables (all SOURCES requiring RDC) are not supported by this +## macro; at least one non-RDC compilation unit is assumed to exist. +## +## Exported variables for downstream use (uppercase ): +## - _TARGETS: base executable + optional early RDC library targets +## * Partial-RDC: [, _host, _device] +## - _EXEC_TARGETS: the executable target(s) (currently just ) +## - _LIB_TARGETS: any associated library targets created by this macro +## * Partial-RDC: [_host, _device] ##------------------------------------------------------------------------------ macro(blt_add_executable) set(options ) - set(singleValueArgs NAME OUTPUT_DIR OUTPUT_NAME FOLDER) - set(multiValueArgs HEADERS SOURCES INCLUDES DEFINES DEPENDS_ON) + set(singleValueArgs NAME OUTPUT_DIR OUTPUT_NAME FOLDER RDC EARLY_RDC EARLY_RDC_SUFFIX) + set(multiValueArgs HEADERS SOURCES INCLUDES DEFINES DEPENDS_ON RDC_SOURCES) # Parse the arguments to the macro cmake_parse_arguments(arg @@ -318,7 +574,66 @@ macro(blt_add_executable) message(FATAL_ERROR "blt_add_executable(NAME ${arg_NAME} ...) given with no sources") endif() - add_executable( ${arg_NAME} ${arg_SOURCES} ${arg_HEADERS}) + # Early RDC defaults + if(NOT DEFINED arg_EARLY_RDC_SUFFIX) + if(DEFINED BLT_EARLY_RDC_SUFFIX) + set(arg_EARLY_RDC_SUFFIX "${BLT_EARLY_RDC_SUFFIX}") + else() + set(arg_EARLY_RDC_SUFFIX "_earlyrdc") + endif() + endif() + + # Partition sources if RDC/EARLY_RDC is enabled (HIP-only). + # For executables we only support the partial-RDC case where RDC_SOURCES + # is a non-empty strict subset of SOURCES. + set(_normal_sources ${arg_SOURCES}) + set(_target_sources ${_normal_sources}) + set(_rdc_sources) + set(_erdc_sources) + set(_use_early_rdc FALSE) + set(_use_rdc FALSE) + if(BLT_ENABLE_HIP AND (DEFINED arg_EARLY_RDC AND arg_EARLY_RDC)) + set(_use_early_rdc TRUE) + set(_use_rdc TRUE) + endif() + if(BLT_ENABLE_HIP AND (DEFINED arg_RDC AND arg_RDC)) + set(_use_rdc TRUE) + endif() + + + if(_use_rdc) + if(NOT (DEFINED arg_RDC_SOURCES AND arg_RDC_SOURCES)) + set(_flag_only TRUE) + else() + set(_flag_only FALSE) + endif() + + # Exclude RDC_SOURCES from normal sources + foreach(_s ${arg_RDC_SOURCES}) + list(REMOVE_ITEM _normal_sources ${_s}) + list(APPEND _rdc_sources ${_s}) + endforeach() + + if(NOT _rdc_sources AND NOT _flag_only) + message(FATAL_ERROR + "blt_add_executable(NAME ${arg_NAME} ... RDC TRUE) was given " + "RDC_SOURCES that do not match any of the SOURCES.") + endif() + + if(NOT _normal_sources) + # set to rdc + set(_target_sources ${_rdc_sources}) + else() + set(_target_sources ${_normal_sources}) + endif() + + if(_use_early_rdc) + set(_erdc_sources ${_rdc_sources}) + endif() + endif() + + # Create the base executable from the 'normal' sources + add_executable( ${arg_NAME} ${_target_sources} ${arg_HEADERS}) if (BLT_ENABLE_CUDA AND NOT BLT_ENABLE_CLANG_CUDA) blt_setup_cuda_target( @@ -330,7 +645,7 @@ macro(blt_add_executable) if(BLT_ENABLE_HIP) blt_setup_hip_target( NAME ${arg_NAME} - SOURCES ${arg_SOURCES} + SOURCES ${_target_sources} DEPENDS_ON ${arg_DEPENDS_ON}) endif() @@ -342,10 +657,84 @@ macro(blt_add_executable) set_target_properties( ${arg_NAME} PROPERTIES LINKER_LANGUAGE Fortran ) target_include_directories(${arg_NAME} PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) endif() - + blt_setup_target(NAME ${arg_NAME} - DEPENDS_ON ${arg_DEPENDS_ON} + DEPENDS_ON ${arg_DEPENDS_ON} OBJECT FALSE) + + # Configure the final executable for HIP RDC linking. In the partial + # EARLY_RDC case, normal sources must remain non-RDC; the early-RDC host + # target receives -fgpu-rdc below in blt_setup_hip_early_rdc_target(). + if (BLT_ENABLE_HIP AND _use_rdc) + set(_blt_exe_target_name ${arg_NAME}) + if (NOT (_use_early_rdc AND _erdc_sources AND _normal_sources)) + target_compile_options(${_blt_exe_target_name} PRIVATE $<$:-fgpu-rdc>) + endif() + target_link_options(${_blt_exe_target_name} PRIVATE -fgpu-rdc) + set_target_properties(${_blt_exe_target_name} PROPERTIES LINKER_LANGUAGE HIP) + endif() + set(_blt_rdc_host_target) + if(BLT_ENABLE_HIP AND _use_rdc AND NOT _use_early_rdc AND _rdc_sources) + # blt_setup_target() and blt_setup_hip_target() are macros that use the same + # cmake_parse_arguments() prefix ("arg") and can overwrite arg_NAME. + # Save/restore the executable name so we don't accidentally link the host + # target to itself. + set(_blt_exe_target_name ${arg_NAME}) + set(_blt_rdc_host_target ${_blt_exe_target_name}${arg_EARLY_RDC_SUFFIX}_host) + add_library(${_blt_rdc_host_target} STATIC ${_rdc_sources} ${arg_HEADERS}) + blt_setup_target(NAME ${_blt_rdc_host_target} + DEPENDS_ON ${arg_DEPENDS_ON} + OBJECT FALSE) + blt_setup_hip_target(NAME ${_blt_rdc_host_target} SOURCES ${_rdc_sources} DEPENDS_ON ${arg_DEPENDS_ON}) + target_compile_options(${_blt_rdc_host_target} PRIVATE $<$:-fgpu-rdc>) + if(arg_INCLUDES) + target_include_directories(${_blt_rdc_host_target} PUBLIC ${arg_INCLUDES}) + endif() + if(arg_DEFINES) + target_compile_definitions(${_blt_rdc_host_target} PUBLIC ${arg_DEFINES}) + endif() + set(arg_NAME ${_blt_exe_target_name}) + target_link_libraries(${arg_NAME} PRIVATE ${_blt_rdc_host_target}) + add_dependencies(${arg_NAME} ${_blt_rdc_host_target}) + endif() + + # Create early RDC archive and imported target if requested + if( BLT_ENABLE_HIP AND _use_early_rdc AND _erdc_sources ) + blt_setup_hip_early_rdc_target( + NAME ${arg_NAME} + DEPENDS_ON ${arg_DEPENDS_ON} + INCLUDES ${arg_INCLUDES} + RDC_SOURCES ${_erdc_sources} + HEADERS ${arg_HEADERS} + SUFFIX ${arg_EARLY_RDC_SUFFIX} + OBJECT FALSE + INTERFACE FALSE) + endif() + + # Provide variables describing the created executable and any associated library targets. + # - _TARGETS: all created targets (base executable + optional early RDC host/device libs) + # - _EXEC_TARGETS: executable target(s) (currently just the base executable) + # - _LIB_TARGETS: any library targets created by this macro (e.g. early RDC host/device libs) + set(_blt_exec_targets ${arg_NAME}) + set(_blt_lib_targets) + + if(BLT_ENABLE_HIP AND _use_early_rdc AND _erdc_sources) + list(APPEND _blt_lib_targets ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_host ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_device) + list(APPEND _blt_exec_targets ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_host ${arg_NAME}${arg_EARLY_RDC_SUFFIX}_device) + elseif(_blt_rdc_host_target) + list(APPEND _blt_lib_targets ${_blt_rdc_host_target}) + list(APPEND _blt_exec_targets ${_blt_rdc_host_target}) + endif() + + string(TOUPPER ${arg_NAME} _blt_exe_uppercase_name) + set(${_blt_exe_uppercase_name}_TARGETS ${_blt_exec_targets}) + set(${_blt_exe_uppercase_name}_EXEC_TARGETS ${arg_NAME}) + set(${_blt_exe_uppercase_name}_LIB_TARGETS ${_blt_lib_targets}) + + # add any generated libraries as a dependency to the executable + foreach(tgt ${${_blt_exe_uppercase_name}_LIB_TARGETS}) + add_dependencies(${arg_NAME} ${tgt}) + endforeach() # Override the linker language with INTERFACE_BLT_LINKER_LANGUAGE_OVERRIDE, if applicable # Will have just been populated by blt_setup_target @@ -383,7 +772,6 @@ macro(blt_add_executable) endif() blt_clean_target(TARGET ${arg_NAME}) - endmacro(blt_add_executable) diff --git a/cmake/BLTPrivateMacros.cmake b/cmake/BLTPrivateMacros.cmake index 3df3addf2..282d24d35 100644 --- a/cmake/BLTPrivateMacros.cmake +++ b/cmake/BLTPrivateMacros.cmake @@ -399,6 +399,199 @@ macro(blt_setup_hip_target) endmacro(blt_setup_hip_target) +##------------------------------------------------------------------------------ +## blt_setup_hip_early_rdc_target(NAME +## RDC_SOURCES +## DEPENDS_ON +## INCLUDES +## HEADERS +## SUFFIX +## OBJECT +## FULL_RDC ) +## +## When FULL_RDC is FALSE (default): +## - Creates a static host RDC library _host from RDC_SOURCES (+HEADERS), +## compiled with -fgpu-rdc and configured with DEPENDS_ON/INCLUDES. +## - Runs erdc.sh on _host to produce a single "uber" device object +## and wraps it in the static library _device. +## - Adds _host as a build dependency of NAME and links NAME +## INTERFACE to _device. The generated device archive replaces +## the host input archive for consumers because uber.o contains the host code. +## +## When FULL_RDC is TRUE: +## - Compiles NAME itself with -fgpu-rdc (using INCLUDES), +## then runs erdc.sh on NAME’s archive to produce the "uber" device object +## wrapped in _device. +## - Links NAME INTERFACE to _device only (no separate host RDC library). +## +## In both modes: +## - _device is a static library containing the early-RDC "uber" +## device object, with LINKER_LANGUAGE set to CXX, and is linked INTERFACE +## from NAME so consumers automatically receive the device code. +##------------------------------------------------------------------------------ +macro(blt_setup_hip_early_rdc_target) + + set(options) + set(singleValueArgs NAME SUFFIX OBJECT INTERFACE FULL_RDC) + set(multiValueArgs RDC_SOURCES DEPENDS_ON INCLUDES HEADERS) + + cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT DEFINED arg_NAME) + message(FATAL_ERROR "blt_setup_hip_early_rdc_target requires NAME") + endif() + if(NOT DEFINED arg_RDC_SOURCES AND NOT DEFINED FULL_RDC AND NOT FULL_RDC) + message(FATAL_ERROR "blt_setup_hip_early_rdc_target requires RDC_SOURCES unless FULL_RDC is set to TRUE") + endif() + + if(NOT DEFINED arg_SUFFIX) + if(DEFINED BLT_EARLY_RDC_SUFFIX) + set(arg_SUFFIX "${BLT_EARLY_RDC_SUFFIX}") + else() + set(arg_SUFFIX "_earlyrdc") + endif() + endif() + message(STATUS "[BLT] Configuring HIP early RDC for '${arg_NAME}' with suffix '${arg_SUFFIX}'") + + + # Determine ROCm Arch flags + # ARCH_FLAGS from CMAKE_HIP_ARCHITECTURES + if(DEFINED BLT_HIP_ARCH_FLAGS) + set(_erdc_arch_flags "${BLT_HIP_ARCH_FLAGS}") + elseif(DEFINED CMAKE_HIP_ARCHITECTURES) + set(_erdc_arch_flags "") + foreach(_t ${CMAKE_HIP_ARCHITECTURES}) + if (_erdc_arch_flags) + set(_erdc_arch_flags "${_erdc_arch_flags} --offload-arch=${_t}") + else() + set(_erdc_arch_flags "--offload-arch=${_t}") + endif() + endforeach() + else() + set(_erdc_arch_flags "") + endif() + message(STATUS "[BLT] HIP arch flags='${_erdc_arch_flags}'") + + # the call to blt_setup* will override arg_NAME, remember it for later use + set(_erdc_arg_name ${arg_NAME} ) + if(NOT arg_FULL_RDC) + # Create host RDC library from RDC_SOURCES and compile with -fgpu-rdc + set(_erdc_host "${arg_NAME}${arg_SUFFIX}_host") + add_library( ${_erdc_host} STATIC ${arg_RDC_SOURCES} ${arg_HEADERS}) + blt_setup_target(NAME ${_erdc_host} + DEPENDS_ON ${arg_DEPENDS_ON} + OBJECT ${arg_OBJECT}) + blt_setup_hip_target(NAME ${_erdc_host} SOURCES ${arg_RDC_SOURCES} DEPENDS_ON ${arg_DEPENDS_ON}) + target_include_directories(${_erdc_host} PUBLIC ${arg_INCLUDES}) + target_compile_options(${_erdc_host} PRIVATE $<$:-fgpu-rdc>) + else() + # FULL_RDC: compile base target with HIP RDC flags, use its archive as input to erdc.sh + target_include_directories(${arg_NAME} PUBLIC ${arg_INCLUDES}) + target_compile_options(${arg_NAME} PRIVATE $<$:-fgpu-rdc>) + endif() + # restore arg_NAME to what it was before the above calls + set(arg_NAME ${_erdc_arg_name}) + + if(NOT arg_FULL_RDC) + # The host archive is private to the EARLY-RDC transformation, but its + # sources must see the same usage requirements as the public base target. + # Use generator expressions so requirements added after this macro call + # (for example, feature compile definitions) are also reflected here. + target_include_directories(${_erdc_host} PRIVATE + $) + target_compile_definitions(${_erdc_host} PRIVATE + $) + endif() + + if(NOT arg_FULL_RDC) + # add _erdc_host as a dependency to arg_NAME, to ensure it gets built + add_dependencies(${arg_NAME} ${_erdc_host}) + endif() + + # Paths + set(_erdc_build_dir "${CMAKE_CURRENT_BINARY_DIR}/${arg_NAME}_erdc") + file(MAKE_DIRECTORY "${_erdc_build_dir}") + + # Select erdc input: host RDC lib (partial) or base target archive (full) + if(arg_FULL_RDC) + set(_erdc_input "$") + set(_erdc_input_depends ${arg_NAME}) + else() + set(_erdc_input "$") + set(_erdc_input_depends ${_erdc_host}) + endif() + + # Add extra input archives to erdc.sh from DEPENDS_ON. + # If a dependency exposes an "*${arg_SUFFIX}_host" target in its link interface, include it. + set(_erdc_extra_input_targets) + foreach(_dep ${arg_DEPENDS_ON}) + if(TARGET ${_dep}) + get_target_property(_dep_iface_libs ${_dep} INTERFACE_LINK_LIBRARIES) + if(_dep_iface_libs) + set(_has_erdc_device FALSE) + foreach(_iface ${_dep_iface_libs}) + if(TARGET ${_iface} AND "${_iface}" MATCHES "${arg_SUFFIX}_device$") + set(_has_erdc_device TRUE) + break() + endif() + endforeach() + + if(NOT _has_erdc_device) + foreach(_iface ${_dep_iface_libs}) + if(TARGET ${_iface} AND "${_iface}" MATCHES "${arg_SUFFIX}_host$") + list(APPEND _erdc_extra_input_targets ${_iface}) + endif() + endforeach() + endif() + endif() + endif() + endforeach() + + if(_erdc_extra_input_targets) + list(REMOVE_DUPLICATES _erdc_extra_input_targets) + if(NOT arg_FULL_RDC) + list(REMOVE_ITEM _erdc_extra_input_targets ${_erdc_host}) + endif() + list(REMOVE_ITEM _erdc_extra_input_targets ${arg_NAME}) + endif() + + set(_erdc_extra_inputs) + foreach(_tgt ${_erdc_extra_input_targets}) + list(APPEND _erdc_extra_inputs "$") + endforeach() + + # The erdc.sh script will take a static library and produce uber.o; we will use that as the source + # for the ${arg_NAME}${arg_SUFFIX} target + set(_erdc_output_obj "${_erdc_build_dir}/uber.o") + message(STATUS "[BLT] Early RDC build dir='${_erdc_build_dir}' input='${_erdc_input}' output='${_erdc_output_obj}'") + + add_custom_command( + COMMAND ${CMAKE_COMMAND} -E env ROCM_PATH=${ROCM_PATH} ARCH_FLAGS="${_erdc_arch_flags}" bash ${BLT_ROOT_DIR}/scripts/erdc.sh ${_erdc_input} ${_erdc_extra_inputs} -t ${_erdc_build_dir}/tmp --keep-temp --verbose + OUTPUT ${_erdc_output_obj} + DEPENDS ${_erdc_input_depends} ${_erdc_extra_input_targets} + WORKING_DIRECTORY ${_erdc_build_dir} + COMMENT "EARLY RDC: generate early RDC archive for ${arg_NAME}, calling ${BLT_ROOT_DIR}/scripts/erdc.sh ${_erdc_input} \n\t with ROCM_PATH=${ROCM_PATH}, ARCH_FLAGS=${_erdc_arch_flags}" + ) + + # Create a static library target containing the generated early-RDC object. + add_library(${arg_NAME}${arg_SUFFIX}_device STATIC ${_erdc_output_obj}) + + # add C++ as the linker language for ${arg_NAME}${arg_SUFFIX}, HIP linking has already happened + set_target_properties(${arg_NAME}${arg_SUFFIX}_device PROPERTIES LINKER_LANGUAGE CXX) + + + # Propagate the generated archive to base target consumers. The uber.o emitted + # by erdc.sh contains both the device image and the host code from its input + # archive, so the input host archive must not also be linked transitively. + if(${arg_INTERFACE}) + target_link_libraries(${arg_NAME} INTERFACE ${arg_NAME}${arg_SUFFIX}_device) + else() + target_link_libraries(${arg_NAME} PUBLIC ${arg_NAME}${arg_SUFFIX}_device) + endif() + +endmacro(blt_setup_hip_early_rdc_target) + + ##----------------------------------------------------------------------------- ## blt_make_file_ext_regex( EXTENSIONS [ext1 [ext2 ...]] ## OUTPUT_REGEX ) @@ -677,9 +870,6 @@ macro(blt_print_target_properties_private) set(_target_type_str "${_target_type_str}BLT Registered target") endif() - if (_is_cmake_target OR _is_blt_registered_target) - message(STATUS "[${arg_TARGET} property] '${arg_TARGET}' is a ${_target_type_str}") - endif() unset(_target_type_str) if(_is_cmake_target) @@ -707,7 +897,7 @@ macro(blt_print_target_properties_private) if ("${_propval}" AND "${prop}" MATCHES "${arg_PROPERTY_NAME_REGEX}") get_target_property(_propval ${arg_TARGET} ${prop}) if ("${_propval}" MATCHES "${arg_PROPERTY_VALUE_REGEX}") - message (STATUS "[${arg_TARGET} property] ${prop}: ${_propval}") + message (WARN " [${arg_TARGET} property] ${prop}: ${_propval}") endif() endif() endforeach() @@ -723,7 +913,7 @@ macro(blt_print_target_properties_private) get_cmake_property(_variable_names VARIABLES) foreach (prop ${_variable_names}) if("${prop}" MATCHES "^${_target_prefix}" AND "${prop}" MATCHES "${arg_PROPERTY_NAME_REGEX}" AND "${${prop}}" MATCHES "${arg_PROPERTY_VALUE_REGEX}") - message (STATUS "[${arg_TARGET} property] ${prop}: ${${prop}}") + message (WARN " [${arg_TARGET} property] ${prop}: ${${prop}}") endif() endforeach() unset(_target_prefix) diff --git a/scripts/erdc.sh b/scripts/erdc.sh new file mode 100755 index 000000000..9c7055e0d --- /dev/null +++ b/scripts/erdc.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +# +# 2024-01-25 Created by Mike Pozulp with help from Tom Stitt, +# Lawrence Livermore National Laboratory, Livermore, CA 94550 USA +# +# This script accepts one or more archives containing RDC object files and +# outputs either a single object "uber.o" or an archive "libERDC.a" containing +# one object file with object code instead of LLVM IR bitcode. +# +# Usage examples: +# ROCM_PATH=/opt/rocm-5.7.1 ./erdc.sh libalpha.a libbeta.a +# ROCM_PATH=/opt/rocm-5.7.1 ARCH_FLAGS='--offload-arch=gfx90a --offload-arch=gfx942' ./erdc.sh -m lib -o myERDC.a libalpha.a +# +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: ROCM_PATH=/path/to/rocm [ARCH_FLAGS='--offload-arch=...'] $0 [-m obj|lib] [-o output_name] [-t temp_dir] [-k|--keep-temp] [-v|--verbose] [lib2.a ...]" >&2 + exit 2 +fi + +OUTPUT_MODE=obj +OUTPUT_NAME="" +TEMP_DIR="" +KEEP_TEMP=false +VERBOSE=false +LIBS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + -m|--mode) + [[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 2; } + OUTPUT_MODE="$2" + shift 2 + ;; + --mode=*) + OUTPUT_MODE="${1#*=}" + shift + ;; + -o|--output) + [[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 2; } + OUTPUT_NAME="$2" + shift 2 + ;; + --output=*) + OUTPUT_NAME="${1#*=}" + shift + ;; + -t|--temp-dir) + [[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 2; } + TEMP_DIR="$2" + shift 2 + ;; + --temp-dir=*) + TEMP_DIR="${1#*=}" + shift + ;; + -k|--keep-temp) + KEEP_TEMP=true + shift + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + *) + LIBS+=("$1") + shift + ;; + esac +done +if [[ "$VERBOSE" == true ]]; then + set -x +fi + +if [[ ${#LIBS[@]} -lt 1 ]]; then + echo "Usage: ROCM_PATH=/path/to/rocm [ARCH_FLAGS='--offload-arch=...'] $0 [-m obj|lib] [-o output_name] [-t temp_dir] [-k|--keep-temp] [-v|--verbose] [lib2.a ...]" >&2 + exit 2 +fi + +case "$OUTPUT_MODE" in + obj|lib) ;; + *) + echo "Invalid mode: $OUTPUT_MODE (expected 'obj' or 'lib')" >&2 + exit 2 + ;; +esac + +if [[ -z "$OUTPUT_NAME" ]]; then + if [[ "$OUTPUT_MODE" == "lib" ]]; then + OUTPUT_NAME="libERDC.a" + else + OUTPUT_NAME="uber.o" + fi +fi + +MY_ARCH_FLAGS=${ARCH_FLAGS:-'--offload-arch=gfx942'} +# Uncomment and set as needed to pass extra flags to clang +# MY_ADDITIONAL_FLAGS=${ADDITIONAL_FLAGS:-'-O2'} + +LLVM_PATH="$ROCM_PATH/llvm/bin" + +if [[ -n "$TEMP_DIR" ]]; then + mkdir -p "$TEMP_DIR" + explode="$TEMP_DIR" +else + explode=$(mktemp -d) +fi + +pushd "$explode" > /dev/null +cp "${LIBS[@]}" . +object_file_list=object_files +#echo > "$object_file_list" +for path in "${LIBS[@]}"; do + lib=$(basename "$path") + dir="${lib%.*}" + # preserve the library ordering and prepend the (hopefully) unique output dir to each object in the listing + "$LLVM_PATH/llvm-ar" t "$lib" | sed "s%^%$dir/%" >> "$object_file_list" + # explode each lib in its own dir to avoid issues with common object names + mkdir -p "$dir" + pushd "$dir" > /dev/null + "$LLVM_PATH/llvm-ar" x "../$lib" + popd > /dev/null +done + +# Remove duplicates if they exist +awk -i inplace '!seen[$0]++' "$object_file_list" || true + +rm -f *.a +"$LLVM_PATH/clang++" \ + -r -no-hip-rt -fgpu-rdc --hip-link \ + $MY_ARCH_FLAGS \ + ${MY_ADDITIONAL_FLAGS:-} \ + --no-gpu-bundle-output \ + -o uber.o $(tr '\n' ' ' < "$object_file_list") + +# Remove CLANG_OFFLOAD_BUNDLE sections, otherwise a partial erdc will fail +# during -fgpu-rdc linking with error: Invalid encoding. +cob_sections=$("$LLVM_PATH/llvm-objdump" -h uber.o | grep -o "__CLANG_OFFLOAD_BUNDLE__[^ ]*" || true) +remove_flags="" +for cob in $cob_sections; do + remove_flags="$remove_flags -R $cob" +done + +"$LLVM_PATH/llvm-objcopy" $remove_flags uber.o uber_no_cob_sections.o + +popd > /dev/null + +if [[ "$OUTPUT_MODE" == "lib" ]]; then + "$LLVM_PATH/llvm-ar" rcs "$OUTPUT_NAME" "$explode/uber_no_cob_sections.o" +else + mv "$explode/uber_no_cob_sections.o" "$OUTPUT_NAME" +fi + +#Clean up temporary directory +if [[ "$KEEP_TEMP" != true ]]; then +rm -rf "$explode" +fi