# Copyright (c) Advanced Micro Devices, Inc., or its affiliates. # SPDX-License-Identifier: MIT # # rocke -- C++20 port of the rocke Python IR -> LLVM codegen engine. # Builds a static library `rocke_core` from every src/**/*.cpp. Sources live in # subfolders mirroring the Python package; new modules drop into the matching # src/ subfolder and are picked up automatically by the recursive glob below; # re-run cmake after adding files. cmake_minimum_required(VERSION 3.16) # The engine translation units are C++20 (.cpp). The public ABI surface in # include/ stays extern "C" so emitters, the provider plugin, and the language # bindings link unchanged. project(rocke LANGUAGES CXX) include(GNUInstallDirs) find_package(Python3 COMPONENTS Interpreter QUIET) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # Do not run clang-tidy on rocKE. rocKE is a vendored dual-engine stack whose # public surface is an intentionally flat, snake_case `extern "C"` ABI # (`rocke_*`, `rocke_strbuf`, ...), constrained by the Python<->C++ byte-identity # contract; it cannot satisfy the inherited hipDNN clang-tidy naming rules. # RockCI configures a global -DCMAKE_CXX_CLANG_TIDY, so every C++ target would # otherwise run that gate (and a rocKE-local `.clang-tidy` with `Checks: -*` # fails outright with "no checks enabled"). Clearing the variable in this # subtree (and child scopes: tests/, bindings/) leaves the rocKE targets' # _CLANG_TIDY properties empty so clang-tidy never runs on them. rocKE # keeps its own -Wall -Wextra -Werror build plus tools/check_byte_identity.py # as its quality bar. set(CMAKE_CXX_CLANG_TIDY "") set(CMAKE_C_CLANG_TIDY "") if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() # Build hardening: warnings are errors. Every -Wall/-Wextra diagnostic is # fixed at the root (unused vars removed; unused params/funcs given (void)/ # __attribute__((unused)) with intent comments). Two diagnostic classes are # retained as -Wno-* because they are provably cosmetic and cannot mask a bug: # # -Wno-comment: doc-comments faithfully quote LLVM IR / Python source containing # '/*' sequences (e.g. pointer math, packed-lane notes) inside // line comments. # A '/*' inside a // comment cannot start a block comment, so it is impossible # for this to hide code -- purely a quoting artifact, intentional. # -Wno-missing-field-initializers: every occurrence is the C-idiomatic `= {0}` / # `(T){0}` zero-initializer, which value-initializes ALL members per the C and # C++ standards. GCC still warns that only one member is named in the braces; # this is a well-known false positive for `{0}` and cannot leave a field # uninitialized. Naming every field (or switching to `{}`, a C++-only form) # would add noise without removing any bug class. (Part of -Wextra.) add_compile_options(-Wall -Wextra -Werror -Wno-comment -Wno-missing-field-initializers) # On Windows the MSVC CRT marks standard C functions (getenv/strcpy/fopen/ # fscanf/...) as "deprecated" via __declspec(deprecated); rocKE's -Werror would # turn those -Wdeprecated-declarations diagnostics into hard build errors. rocKE # uses the portable standard names deliberately (shared verbatim with the Linux # build and bound by the Python<->C++ byte-identity contract), so opt out of the # MSVC-only deprecation rather than fork the sources to the non-portable *_s # variants. Defined for the whole rocKE subtree (engine, tests, bindings) so it # also covers call sites not reached before ninja stops on the first failure. if(WIN32) add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_WARNINGS) endif() # Optional Address+UndefinedBehavior sanitizer build (diagnostics only, not for # Release artifacts): # cmake -S . -B build_asan -DROCKE_SANITIZE=ON && cmake --build build_asan -j # Applies to the engine, the unit tests, and (via run_diff --archive) the parity # emitters that link librocke_core.a. # _FORTIFY_SOURCE is disabled here because it conflicts with AddressSanitizer # (officially discouraged). -Wformat-truncation is also suppressed in this build # ONLY: the sanitizer instrumentation perturbs inlining enough that the -O # interprocedural analysis raises a false-positive "null format string" on the # variadic vsnprintf emit helpers (rocke_ll_emitf / rocke_ll_block_emitf, whose # forwarded format is always a literal). The normal/Release build keeps # -Werror=format-truncation, where it does not false-positive. This build is for # memory-safety diagnostics, not format-truncation. option(ROCKE_SANITIZE "Build with -fsanitize=address,undefined" OFF) if(ROCKE_SANITIZE) add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer -g -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wno-format-truncation) add_link_options(-fsanitize=address,undefined) endif() option(ROCKE_BUILD_PYBIND "Build the rocke_engine Python extension module" OFF) option(ROCKE_INSTALL_PYTHON_PACKAGE "Install the rocke Python frontend" ON) option(ROCKE_INSTALL_TESTS "Install rocKE smoke test artifacts and CTest metadata" ON) # The Python rocKE CI tests (import smoke + golden byte-stability) are gated # separately from the C++ smoke tests. Provider mode turns them OFF until their # test-artifact packaging is wired up (the rocke package, the smoke/golden # scripts, and the golden JSON are not yet in TheRock's hipkernelprovider # artifact includes, so staging them there fails at test time). Standalone # runs, which package everything locally, keep them ON. option(ROCKE_INSTALL_PYTHON_TESTS "Install rocKE Python smoke/golden CI tests" ON) option(ROCKE_INSTALL_STANDALONE_CTEST "Install standalone rocKE CTest metadata" ON) if(NOT DEFINED ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR) set(ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR "" CACHE STRING "Provider installed CTest subdir to append rocKE tests to, e.g. hip_kernel_provider") endif() # Where rocKE's installed CTest metadata and standalone Python smoke script land: # * Provider mode: append CTest entries to the provider's installed # bin//CTestTestfile.cmake. Provider-mode C++ smoke binaries use the # bare-bin naming scheme below so TheRock's existing test artifact patterns # capture them. # * Standalone mode: keep artifacts in bin/ with the CTestTestfile under # bin/rocke/, reached via "../". if(ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR) set(ROCKE_TEST_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}/${ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR}") set(ROCKE_TEST_CTEST_REL "./") else() set(ROCKE_TEST_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") set(ROCKE_TEST_CTEST_REL "../") endif() # C++ smoke binaries use a separate scheme so TheRock's test artifact captures # them with NO descriptor edit. The hip-kernel-provider test artifact includes # "bin/*_test*", so in provider mode install the C++ tests at bare bin/ # under a "__test" name (e.g. # hip_kernel_provider_rocke_ir_serialize_roundtrip_test) and reference them from # the provider CTestTestfile via "../". Standalone keeps the plain target name # in bin/. (The Python import smoke and the rocke package, which cannot match # that name pattern, are co-located under bin// and captured by explicit # includes in ml-libs/artifact-hipkernelprovider.toml.) set(ROCKE_CXX_TEST_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") set(ROCKE_CXX_TEST_CTEST_REL "../") # Map a C++ test target to its installed file name for the active mode. function(rocke_cxx_test_installed_name out_var target) if(ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR) set(${out_var} "${ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR}_${target}_test" PARENT_SCOPE) else() set(${out_var} "${target}" PARENT_SCOPE) endif() endfunction() if(Python3_Interpreter_FOUND) set(_ROCKE_DEFAULT_PYTHON_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages") else() set(_ROCKE_DEFAULT_PYTHON_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/python/site-packages") endif() set(ROCKE_PYTHON_INSTALL_DIR "${_ROCKE_DEFAULT_PYTHON_INSTALL_DIR}" CACHE STRING "Relative install destination for rocke and rocke_engine") # Sources live under cpp/, in subfolders mirroring the rocke Python package # (cpp/core/{ir,arch,isa,lower_llvm,lower_hip}, cpp/helpers, cpp/instances/{common,gfxNNNN}, # cpp/support). GLOB_RECURSE picks up every .cpp regardless of depth; the # pybind bindings under cpp/bindings are a separate target and are excluded. file(GLOB_RECURSE ROCKE_CORE_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cpp/*.cpp") list(FILTER ROCKE_CORE_SOURCES EXCLUDE REGEX "/cpp/bindings/") add_library(rocke_core STATIC ${ROCKE_CORE_SOURCES}) target_include_directories(rocke_core PUBLIC $ $) # --- Engine freshness / provenance stamp ------------------------------------- # Compute a deterministic, git-independent content hash of the engine source # tree and inject it (plus a human version) ONLY into src/core/rocke_build_id.cpp via # compile definitions. Scoping the -D to that single TU keeps every other object # untouched: only rocke_build_id.o recompiles when the hash changes, and -- crucially # -- no lowering/emission TU ever sees these values, so the emitted LLVM-IR is # byte-identical. The build_id TU exposes rocke_build_id()/rocke_engine_version() # and is intentionally NOT referenced by any emission path; consumers (harnesses, # the language bindings, the provider) read it purely as an artifact stamp. include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/rocke_build_id.cmake") rocke_compute_build_id(ROCKE_BUILD_ID "${CMAKE_CURRENT_SOURCE_DIR}") # Human-readable version. Bump the leading semver when the IR contract changes; # the date keeps successive builds from the same version distinguishable. string(TIMESTAMP ROCKE_BUILD_DATE "%Y%m%d" UTC) set(ROCKE_ENGINE_VERSION "1.0.0+${ROCKE_BUILD_DATE}") message(STATUS "rocke_core build-id: ${ROCKE_BUILD_ID} version: ${ROCKE_ENGINE_VERSION}") set_source_files_properties( "${CMAKE_CURRENT_SOURCE_DIR}/cpp/core/rocke_build_id.cpp" PROPERTIES COMPILE_DEFINITIONS "ROCKE_BUILD_ID=${ROCKE_BUILD_ID};ROCKE_ENGINE_VERSION=${ROCKE_ENGINE_VERSION}") # Required so the .a can be linked into the shared rocke_provider plugin # (--whole-archive into a .so): some const data tables otherwise emit # R_X86_64_PC32 relocations the linker rejects for a PIC shared object. set_target_properties(rocke_core PROPERTIES POSITION_INDEPENDENT_CODE ON) include(CTest) # C++ test targets are registered in the by-layer tests/ tree (tests/CMakeLists.txt). if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt") add_subdirectory(tests) endif() if(ROCKE_BUILD_PYBIND) add_subdirectory(cpp/bindings) endif() # --- Installable engine + test artifacts ------------------------------------- install(TARGETS rocke_core EXPORT ckcTargets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/cpp/include/" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(EXPORT ckcTargets FILE ckcTargets.cmake NAMESPACE ckc:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ckc) if(ROCKE_INSTALL_PYTHON_PACKAGE) # In provider mode, co-locate the rocke package under the test subdir # (bin//rocke) next to rocke_installed_smoke.py so the Python smoke is # self-contained inside TheRock's test artifact (paired with a matching include # in ml-libs/artifact-hipkernelprovider.toml for bin//rocke/**). # Standalone keeps the lib/pythonX.Y/site-packages layout. if(ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR) set(_ROCKE_PKG_DEST "${ROCKE_TEST_INSTALL_DIR}") else() set(_ROCKE_PKG_DEST "${ROCKE_PYTHON_INSTALL_DIR}") endif() install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/python/rocke" DESTINATION "${_ROCKE_PKG_DEST}" PATTERN "__pycache__" EXCLUDE REGEX "\\.pyc$" EXCLUDE) # Also install pre-built wheels when available. These are not yet consumed # by CI (the existing PYTHONPATH=. approach is still used), but shipping # them in the artifact prepares for a future migration to pip-installed # wheels and enables the library tests to run. if(ROCKE_BUILD_PYENV) install(FILES "${ROCKE_WHEEL_DIR}/rocke-${ROCKE_WHEEL_VERSION}-py3-none-any.whl" "${ROCKE_WHEEL_DIR}/rocke_library-${ROCKE_WHEEL_VERSION}-py3-none-any.whl" DESTINATION "${ROCKE_TEST_INSTALL_DIR}/wheels") endif() endif() if(ROCKE_INSTALL_TESTS) if(ROCKE_INSTALL_PYTHON_TESTS) # The Python import smoke (placeholder for the rocke Python CI test) installs # next to the CTestTestfile in both modes: provider mode -> bin//, # standalone -> bin/. In provider mode it imports the co-located rocke package # installed above; TheRock's test artifact captures both via explicit includes. install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/tools/rocke_installed_smoke.py" DESTINATION ${ROCKE_TEST_INSTALL_DIR}) # Host-only golden IR byte-stability gate. The relocatable runner rebuilds the # representative IR from the co-located rocke package and compares digests to # the committed golden. It needs the parity harness module (imported by name) # and the golden JSON co-located next to it; no GPU and no examples required. install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/tests/rocke_installed_golden_test.py" DESTINATION ${ROCKE_TEST_INSTALL_DIR}) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/tests/instances/rocke_ir_parity_harness.py" DESTINATION ${ROCKE_TEST_INSTALL_DIR}) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/tests/golden/rocke_representative_ir_sha256.json" DESTINATION ${ROCKE_TEST_INSTALL_DIR}/golden) # Install the full pytest suite for installed/artifact test execution. # Excludes: C/C++ parity emitters (compiled against rocke_core at build time), # the differential harness (needs compiler + source), and run_all.py (dev runner). # cmake-lint: disable=E1122 block() install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests/" DESTINATION "${ROCKE_TEST_INSTALL_DIR}/tests" PATTERN "__pycache__" EXCLUDE PATTERN "*.pyc" EXCLUDE PATTERN "*.c" EXCLUDE PATTERN "*.cpp" EXCLUDE PATTERN "differential" EXCLUDE PATTERN "run_all.py" EXCLUDE) endblock() endif() set(_ROCKE_INSTALLED_CTEST_ENTRIES "") get_property(_ROCKE_INSTALL_TEST_TARGETS GLOBAL PROPERTY ROCKE_INSTALL_TEST_TARGETS) foreach(_test_target ${_ROCKE_INSTALL_TEST_TARGETS}) rocke_cxx_test_installed_name(_rocke_installed_name ${_test_target}) string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "add_test(${_test_target} \"${ROCKE_CXX_TEST_CTEST_REL}${_rocke_installed_name}\")\n") string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "set_tests_properties(${_test_target} PROPERTIES LABELS \"ckc;unit;host;quick\")\n") endforeach() if(ROCKE_INSTALL_PYTHON_TESTS) # Python test entries invoke via "python" (resolved from PATH at test time) # rather than executing the .py directly, so they work on Windows where # shebangs are not supported. The CI test environment activates a venv with # requirements-test.txt before running ctest. string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "add_test(rocke_python_import_smoke python \"${ROCKE_TEST_CTEST_REL}rocke_installed_smoke.py\")\n") string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "set_tests_properties(rocke_python_import_smoke PROPERTIES LABELS \"ckc;python;quick\")\n") string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "add_test(rocke_golden_static python \"${ROCKE_TEST_CTEST_REL}rocke_installed_golden_test.py\")\n") string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "set_tests_properties(rocke_golden_static PROPERTIES LABELS \"ckc;python;golden;host;quick\")\n") # Full pytest suite. PYTHONPATH points at the CTestTestfile's working # directory (which contains the installed rocke package) so `import rocke` # resolves without the source-tree sys.path shims. string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "add_test(rocke_pytest python -m pytest \"${ROCKE_TEST_CTEST_REL}tests\" -v --timeout=60)\n") string(APPEND _ROCKE_INSTALLED_CTEST_ENTRIES "set_tests_properties(rocke_pytest PROPERTIES\n" " LABELS \"ckc\\;python\\;quick\"\n" " ENVIRONMENT \"PYTHONPATH=.\"\n" " TIMEOUT 300)\n") endif() if(ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR) set_property(GLOBAL APPEND_STRING PROPERTY "EXTERNAL_TEST_INSTALL_STAGING_${ROCKE_INSTALL_PROVIDER_CTEST_SUBDIR}" "\n# rocKE installed smoke tests\n${_ROCKE_INSTALLED_CTEST_ENTRIES}") endif() if(ROCKE_INSTALL_STANDALONE_CTEST) set(_ROCKE_INSTALLED_CTEST "${CMAKE_CURRENT_BINARY_DIR}/CTestTestfile.cmake.install") file(WRITE "${_ROCKE_INSTALLED_CTEST}" "# Autogenerated CTestTestfile for installed rocKE tests\n") file(APPEND "${_ROCKE_INSTALLED_CTEST}" "# Generated by the rocKE build system\n\n") file(APPEND "${_ROCKE_INSTALLED_CTEST}" "${_ROCKE_INSTALLED_CTEST_ENTRIES}") install(FILES "${_ROCKE_INSTALLED_CTEST}" DESTINATION ${CMAKE_INSTALL_BINDIR}/rocke RENAME CTestTestfile.cmake) endif() endif()