diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..8ecebc3bb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Patches are applied with git apply, which requires LF line endings, so make sure that they are always checked out with +# LF line endings, whatever the platform (e.g., git on Windows checks out text files with CRLF line endings by default). + +*.patch text eol=lf diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 000000000..4ef4f5028 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,109 @@ +name: x86-64-v3 benchmark + +# Compare the speed of our simulations when libOpenCOR and SUNDIALS are built for x86-64 (i.e. SSE2) and for x86-64-v3 +# (i.e. AVX2, FMA, etc.; see X86_64_V3_COMPILER_FLAGS in src/CMakeLists.txt). +# Note #1: both versions are built and benchmarked in the same job, so that they are run on the same CPU. +# Note #2: SUNDIALS is built from source for both versions, so that only the compiler flags differ between them. +# Note #3: the x86-64 version is built by emptying X86_64_V3_COMPILER_FLAGS in src/CMakeLists.txt, which is why we build +# the x86-64-v3 version first (CMake would otherwise reconfigure it). + +on: + workflow_dispatch: + +jobs: + benchmark: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - name: 'Windows (Intel)' + os: windows-2022 + - name: 'Linux (Intel)' + os: ubuntu-24.04 + - name: 'macOS (Intel)' + os: macos-15-intel + steps: + - name: Check out libOpenCOR + uses: actions/checkout@v4 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install CMake and Ninja + uses: lukka/get-cmake@latest + - name: Configure MSVC + if: ${{ runner.os == 'Windows' }} + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64 + - name: Install GCC 16 + if: ${{ runner.os == 'Linux' }} + run: | + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get install -y gcc-16 g++-16 + sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-16 100 + sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-16 100 + echo "CC=gcc-16" >> "$GITHUB_ENV" + echo "CXX=g++-16" >> "$GITHUB_ENV" + - name: Report the CPU + shell: bash + run: | + if [ '${{ runner.os }}' == 'Linux' ]; then lscpu | grep 'Model name'; fi + if [ '${{ runner.os }}' == 'macOS' ]; then sysctl -n machdep.cpu.brand_string; fi + if [ '${{ runner.os }}' == 'Windows' ]; then powershell -Command "(Get-CimInstance Win32_Processor).Name"; fi + - name: Build the x86-64-v3 version + shell: bash + run: | + cmake -G Ninja -S . -B build-x86-64-v3 -DBUILD_TYPE=Release -DCOMPILER_CACHING=OFF -DDOCUMENTATION=OFF -DJAVASCRIPT_BINDINGS=OFF -DPREBUILT_SUNDIALS=OFF -DPYTHON_BINDINGS=OFF -DSHARED_LIBS=OFF -DUNIT_TESTING=ON + cmake --build build-x86-64-v3 --target benchmark_executable + - name: Build the x86-64 version + shell: bash + run: | + sed -i.bak -E 's/set\(X86_64_V3_COMPILER_FLAGS "[^"]*"\)/set(X86_64_V3_COMPILER_FLAGS "")/' src/CMakeLists.txt + grep -n 'set(X86_64_V3_COMPILER_FLAGS' src/CMakeLists.txt + cmake -G Ninja -S . -B build-x86-64 -DBUILD_TYPE=Release -DCOMPILER_CACHING=OFF -DDOCUMENTATION=OFF -DJAVASCRIPT_BINDINGS=OFF -DPREBUILT_SUNDIALS=OFF -DPYTHON_BINDINGS=OFF -DSHARED_LIBS=OFF -DUNIT_TESTING=ON + cmake --build build-x86-64 --target benchmark_executable + - name: Run the benchmarks + shell: bash + run: | + for RUN in 1 2 3; do + for VERSION in x86-64 x86-64-v3; do + build-$VERSION/tests/benchmark_executable | tee benchmark-$VERSION-$RUN.txt + done + done + - name: Compare the benchmarks + shell: bash + run: | + python - <<'EOF' | tee -a "$GITHUB_STEP_SUMMARY" + import collections + import glob + + # Keep, for each benchmark and version, the best median time over our different runs. + + medians = collections.defaultdict(list) + names = [] + + for version in ('x86-64', 'x86-64-v3'): + for file_name in sorted(glob.glob(f'benchmark-{version}-*.txt')): + for line in open(file_name).readlines()[2:]: + fields = [field.strip() for field in line.split('|')] + + if len(fields) >= 5: + medians[(fields[0], version)].append(float(fields[4])) + + if fields[0] not in names: + names.append(fields[0]) + + print('## ${{ matrix.name }}\n') + print('| Benchmark | x86-64 (ms) | x86-64-v3 (ms) | Difference |') + print('|---|---:|---:|---:|') + + for name in names: + x86_64 = min(medians[(name, 'x86-64')]) + x86_64_v3 = min(medians[(name, 'x86-64-v3')]) + + print(f'| {name} | {x86_64:.1f} | {x86_64_v3:.1f} | {100.0 * (x86_64_v3 - x86_64) / x86_64:+.1f}% |') + EOF diff --git a/.github/workflows/buildThirdPartyLibrary.yml b/.github/workflows/buildThirdPartyLibrary.yml index e0df26ac1..f3f49314b 100644 --- a/.github/workflows/buildThirdPartyLibrary.yml +++ b/.github/workflows/buildThirdPartyLibrary.yml @@ -120,10 +120,6 @@ jobs: uses: opencor/buildcache-action@v1 with: cache_key: webassembly - - name: Install LLVM - run: | - brew install llvm - echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH - name: Install Emscripten run: brew install emscripten - name: Configure libOpenCOR (for a specific third-party library) diff --git a/VERSION.txt b/VERSION.txt index e1d62542c..92f922473 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.20260803.0 +1.20260911.1 diff --git a/cmake/common.cmake b/cmake/common.cmake index 463b49cfd..53e510f35 100644 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -32,6 +32,25 @@ function(replace_compiler_flag OLD NEW) endforeach() endfunction() +function(add_cache_flags VARIABLE FLAGS DOCSTRING) + # Add the given flags to the given cache variable, but only if they are not already there. + # Note: the variable may already have them since our JavaScript bindings target passes them to the Emscripten build + # that it kicks off (see src/bindings/javascript/CMakeLists.txt). And, because we must force the cache entry, + # they would otherwise be added once more on every single reconfiguration, making the variable grow without + # bound and, with BUILDCACHE_ACCURACY set to STRICT, invalidating our compiler cache each time. + + string(FIND "${${VARIABLE}}" "${FLAGS}" INDEX) + + if(NOT INDEX EQUAL -1) + return() + endif() + + string(STRIP "${${VARIABLE}} ${FLAGS}" NEW_FLAGS) + + set(${VARIABLE} "${NEW_FLAGS}" CACHE STRING "${DOCSTRING}" FORCE) + set(${VARIABLE} "${NEW_FLAGS}" PARENT_SCOPE) +endfunction() + function(suppress_target_warnings TARGET) target_compile_options(${TARGET} PRIVATE $<$:/W0> diff --git a/cmake/environmentchecks.cmake b/cmake/environmentchecks.cmake index b0ab50156..0e9c63c79 100644 --- a/cmake/environmentchecks.cmake +++ b/cmake/environmentchecks.cmake @@ -15,32 +15,19 @@ # Make sure that we are using a supported compiler. if(WIN32) - if( "${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC" - AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" - AND MSVC_TOOLSET_VERSION EQUAL 143) + if(MSVC AND NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") set(BUILDING_USING_MSVC TRUE) else() - message(FATAL_ERROR "${CMAKE_PROJECT_NAME} can only be built using MSVC 2022 on Windows.") + message(FATAL_ERROR "${CMAKE_PROJECT_NAME} can only be built using MSVC on Windows.") endif() +elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + set(BUILDING_USING_CLANG TRUE) elseif(APPLE) - if( ( "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" - AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - OR ( "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" - AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")) - set(BUILDING_USING_CLANG TRUE) - else() - message(FATAL_ERROR "${CMAKE_PROJECT_NAME} can only be built using (Apple) Clang on macOS.") - endif() + message(FATAL_ERROR "${CMAKE_PROJECT_NAME} can only be built using (Apple) Clang on macOS.") +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(BUILDING_USING_GNU TRUE) else() - if( "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" - AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(BUILDING_USING_GNU TRUE) - elseif( "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" - AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(BUILDING_USING_CLANG TRUE) - else() - message(FATAL_ERROR "${CMAKE_PROJECT_NAME} can only be built using GNU or Clang on Linux.") - endif() + message(FATAL_ERROR "${CMAKE_PROJECT_NAME} can only be built using GNU or Clang on Linux.") endif() # Make sure that we are building libOpenCOR in 64-bit mode. diff --git a/cmake/packages.cmake b/cmake/packages.cmake index 790d8be72..08ca8e17e 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -340,8 +340,22 @@ set(PREBUILT_DIR "${PREBUILT_DIR}" CACHE INTERNAL "Prebuilt directory.") include(ExternalProject) if(NOT WIN32 AND NOT APPLE AND NOT EMSCRIPTEN) - set(CMAKE_C_FLAGS_ARGS -DCMAKE_C_FLAGS=-fPIC) - set(CMAKE_CXX_FLAGS_ARGS -DCMAKE_CXX_FLAGS=-fPIC) + set(THIRD_PARTY_C_FLAGS -fPIC) + set(THIRD_PARTY_CXX_FLAGS -fPIC) +endif() + +# Build our third-party libraries for x86-64-v3 on Intel (see X86_64_V3_COMPILER_FLAGS in src/CMakeLists.txt). +# Note: setting CMAKE_C_FLAGS/CMAKE_CXX_FLAGS replaces the default flags that CMake would otherwise use (e.g., /EHsc for +# MSVC), so we start from those default flags. + +if(X86_64_V3_COMPILER_FLAGS) + string(STRIP "${CMAKE_C_FLAGS_INIT} ${THIRD_PARTY_C_FLAGS} ${X86_64_V3_COMPILER_FLAGS}" THIRD_PARTY_C_FLAGS) + string(STRIP "${CMAKE_CXX_FLAGS_INIT} ${THIRD_PARTY_CXX_FLAGS} ${X86_64_V3_COMPILER_FLAGS}" THIRD_PARTY_CXX_FLAGS) +endif() + +if(THIRD_PARTY_C_FLAGS) + set(CMAKE_C_FLAGS_ARGS -DCMAKE_C_FLAGS=${THIRD_PARTY_C_FLAGS}) + set(CMAKE_CXX_FLAGS_ARGS -DCMAKE_CXX_FLAGS=${THIRD_PARTY_CXX_FLAGS}) endif() set(CMAKE_ARGS diff --git a/src/3rdparty/LLVMClang/CMakeLists.txt b/src/3rdparty/LLVMClang/CMakeLists.txt index edbb0ee7a..405fe5364 100644 --- a/src/3rdparty/LLVMClang/CMakeLists.txt +++ b/src/3rdparty/LLVMClang/CMakeLists.txt @@ -15,7 +15,7 @@ set(PACKAGE_NAME LLVMClang) set(PACKAGE_VERSION 22.1.8) set(PACKAGE_REPOSITORY llvm-project) -set(RELEASE_TAG llvmorg-22.1.8-libopencor) +set(RELEASE_TAG llvmorg-22.1.8) set(INSTALL_DIR ${PREBUILT_DIR}/${PACKAGE_NAME}) # Either retrieve or build our package. @@ -24,58 +24,68 @@ if(LIBOPENCOR_PREBUILT_LLVMCLANG) if(EMSCRIPTEN) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 05cd2766a9164a86fefeed6c54af6ff177024a70) + f2cc407da19873669391aaee0cfa0de87bfd4e45) else() if(WIN32) if(RELEASE_MODE) if(INTEL_MODE) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 9b4c654acab324912b28f30765697477312a83cf) + 38d26a253f5eddfa256be16adad7d1bd4434dd20) else() retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 974ec78f371bf89ce5a475a0855fb428f5724cd6) + 2e0ae5200b16854a5b74ee7482a26ee7700ee023) endif() else() if(INTEL_MODE) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - f57bcc2c99a3cddc423e09d06870ad4243e514bd) + 8b0172b7de13c9a86735778edee503f4f2265a72) else() retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - bd417f60cd0182a33ef4c4c20fc23c03ece70a58) + acd1aeac95765256cff01fca60b9ffff8f49c208) endif() endif() elseif(APPLE) if(INTEL_MODE) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 38cfd874eb9c386e6cfc978979d55fcfed210e3f) + 573e4963e2c73e4ae5bac2f0fa3c37f370fd501b) else() retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - de2862e2f2c238a122e2573e16af2bbcdc37d17f) + 052828c7510107cddf01a119421e0f4d38435508) endif() else() if(INTEL_MODE) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 7cf85ece2ac15636c82e42d285fa4ab49420a8b3) + 309840bef9888908789bdec3bf5e344aafcffc6f) else() retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - a439ad9559ea909f42c192ba1c2033bd0f564877) + a437231186bc1442c92e673f7da5e6fd5f8d8cd2) endif() endif() endif() elseif(NOT ONLY_BUILD_JAVASCRIPT_THIRD_PARTY_LIBRARIES) # Build our package. + # Note #1: we determine Clang's -cc1 arguments ourselves rather than have Clang's driver compute them for us (see + # compiler.cpp), this so that we don't drag all of Clang's toolchains into libOpenCOR. So, we don't need + # to build Clang's driver at all (see CLANG_ENABLE_DRIVER below). + # Note #2: we only ever compile code for the architecture that we run on, so we don't need the target support that + # Clang would otherwise build for every single architecture that it knows about. CLANG_ENABLE_ALL_TARGETS + # (see below) narrows it down to LLVM_TARGETS_TO_BUILD, which is why that variable must name exactly one + # architecture. + # Note #3: libOpenCOR's WASM version doesn't use LLVM+Clang's typeinfo (e.g., the JIT parts of compiler.cpp are + # not compiled for WebAssembly), so we can disable RTTI to reduce the size of our WASM package. On the + # other hand, libOpenCOR's native versions do use LLVM+Clang's typeinfo (e.g., compiler.cpp instantiates + # LLVM+Clang templates whose typeinfo references LLVM+Clang's own typeinfo), so we must enable RTTI for + # them. if(EMSCRIPTEN) - set(LIBCLANG_BUILD_STATIC OFF) - set(LLVM_TARGETS_TO_BUILD WebAssembly) set(LLVM_HOST_TRIPLE wasm32-unknown-emscripten) @@ -83,23 +93,10 @@ elseif(NOT ONLY_BUILD_JAVASCRIPT_THIRD_PARTY_LIBRARIES) -DLLVM_ENABLE_LIBCXX=ON ) - find_program(LLVM_TBLGEN_EXE NAMES llvm-tblgen) - find_program(CLANG_TBLGEN_EXE NAMES clang-tblgen) - - if(NOT LLVM_TBLGEN_EXE OR NOT CLANG_TBLGEN_EXE) - message(FATAL_ERROR "llvm-tblgen and clang-tblgen executables are required to build ${PACKAGE_NAME} with Emscripten.") - endif() - - set(LLVM_TABLEGEN - -DLLVM_TABLEGEN=${LLVM_TBLGEN_EXE} - ) - - set(CLANG_TABLEGEN - -DCLANG_TABLEGEN=${CLANG_TBLGEN_EXE} + set(LLVM_ENABLE_RTTI + -DLLVM_ENABLE_RTTI=OFF ) else() - set(LIBCLANG_BUILD_STATIC ON) - if(INTEL_MODE) set(LLVM_TARGETS_TO_BUILD X86) @@ -121,28 +118,59 @@ elseif(NOT ONLY_BUILD_JAVASCRIPT_THIRD_PARTY_LIBRARIES) set(LLVM_HOST_TRIPLE aarch64-pc-linux-gnu) endif() endif() + + set(LLVM_ENABLE_RTTI + -DLLVM_ENABLE_RTTI=ON + ) + endif() + + # Build our WASM version of LLVM+Clang with size optimisation. + # Note #1: this doesn't affect the quality of the code that LLVM+Clang generates for us. It only affects its own + # size and, to some extent, how fast it can compile code. + # Note #2: we only do this for our WASM version, whose size is what matters most to us since our Web app + # dynamically imports it. Our native versions keep LLVM+Clang's default release flags so that they + # compile our models as fast as possible. + + if(EMSCRIPTEN) + set(LLVMCLANG_RELEASE_FLAGS + "-DCMAKE_CXX_FLAGS_RELEASE=-Oz -DNDEBUG" + "-DCMAKE_C_FLAGS_RELEASE=-Oz -DNDEBUG" + ) endif() + # Build LLVM+Clang. + build_package(${PACKAGE_NAME} URL https://github.com/opencor/${PACKAGE_REPOSITORY}/archive/refs/tags/${RELEASE_TAG}.tar.gz DOWNLOAD_NO_PROGRESS ON SOURCE_SUBDIR llvm + PATCH_COMMAND + ${CMAKE_COMMAND} -DLLVMCLANG_SOURCE_DIR= + -DLLVMCLANG_PATCHES_DIR=${CMAKE_CURRENT_SOURCE_DIR}/patches + -P ${CMAKE_CURRENT_SOURCE_DIR}/applypatches.cmake CMAKE_ARGS -DCLANG_BUILD_TOOLS=OFF + -DCLANG_ENABLE_ALL_TARGETS=OFF + -DCLANG_ENABLE_DRIVER=OFF + -DCLANG_ENABLE_INTERPRETER=OFF -DCLANG_ENABLE_LIBXML2=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF + -DCLANG_ENABLE_TOOLING_LIBS=OFF -DCLANG_INCLUDE_DOCS=OFF -DCLANG_INCLUDE_TESTS=OFF -DCLANG_PLUGIN_SUPPORT=OFF - ${CLANG_TABLEGEN} + -DCLANG_TOOL_CLANG_DIFF_BUILD=OFF + -DCLANG_TOOL_CLANG_IMPORT_TEST_BUILD=OFF + -DCLANG_TOOL_CLANG_SHLIB_BUILD=OFF -DCLANG_TOOL_DICTIONARY_BUILD=OFF -DCLANG_TOOL_HANDLE_CXX_BUILD=OFF -DCLANG_TOOL_HANDLE_LLVM_BUILD=OFF + -DCLANG_TOOL_LIBCLANG_BUILD=OFF ${CMAKE_ARGS} + ${LLVMCLANG_RELEASE_FLAGS} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} - -DLIBCLANG_BUILD_STATIC=${LIBCLANG_BUILD_STATIC} -DLLVM_BUILD_LLVM_C_DYLIB=OFF -DLLVM_BUILD_RUNTIME=OFF -DLLVM_BUILD_RUNTIMES=OFF @@ -163,7 +191,7 @@ elseif(NOT ONLY_BUILD_JAVASCRIPT_THIRD_PARTY_LIBRARIES) -DLLVM_ENABLE_PEDANTIC=OFF -DLLVM_ENABLE_PLUGINS=OFF -DLLVM_ENABLE_PROJECTS=clang - -DLLVM_ENABLE_RTTI=ON + ${LLVM_ENABLE_RTTI} -DLLVM_ENABLE_TELEMETRY=OFF -DLLVM_ENABLE_UNWIND_TABLES=OFF -DLLVM_ENABLE_WARNINGS=OFF @@ -177,24 +205,12 @@ elseif(NOT ONLY_BUILD_JAVASCRIPT_THIRD_PARTY_LIBRARIES) -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_TOOLS=ON -DLLVM_INCLUDE_UTILS=OFF - ${LLVM_TABLEGEN} -DLLVM_TARGETS_TO_BUILD=${LLVM_TARGETS_TO_BUILD} + -DLLVM_TOOL_LTO_BUILD=OFF + -DLLVM_TOOL_REMARKS_SHLIB_BUILD=OFF ) # Create our package. - # Note: we also package llvm-tblgen and clang-tblgen since we need them to build our JavaScript bindings. - - if(NOT EMSCRIPTEN) - set(TABLEGEN_EXES_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/build/LLVMClang-prefix/src/LLVMClang-build) - - file(GLOB TABLEGEN_EXES "${TABLEGEN_EXES_BUILD_DIR}/bin/*tblgen") - - foreach(TABLEGEN_EXE ${TABLEGEN_EXES}) - file(COPY ${TABLEGEN_EXE} DESTINATION ${INSTALL_DIR}/bin) - endforeach() - - string(REPLACE "${TABLEGEN_EXES_BUILD_DIR}/" "" TABLEGEN_EXES "${TABLEGEN_EXES}") - endif() create_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} @@ -203,35 +219,54 @@ endif() # Make our package accessible. +# The libraries that are specific to the platform for which we are building, i.e. the backend of the target for which +# LLVM+Clang generates code and, for our native versions only, LLVM's ORC-based JIT (our WASM version doesn't JIT +# anything; see compiler.cpp). +# Note #1: we never assemble or disassemble any code, so we don't need our target's assembly parser or disassembler. +# As elsewhere in this file, the libraries that we explicitly don't want are commented out rather than +# omitted. +# Note #2: we don't need Clang's driver since we determine Clang's -cc1 arguments ourselves (see compiler.cpp). +# clangOptions, which Clang's option table lives in and which clang::CompilerInvocation::CreateFromArgs() +# needs, is a separate library, so it stays. So does LLVMWindowsDriver, which, despite its name, is not +# only used by Clang's driver: LLVM's ORC-based JIT needs it to locate the MSVC toolchain. + if(EMSCRIPTEN) set(EXTRA_LLVMCLANG_LIBRARIES - LLVMWebAssemblyAsmParser + # LLVMWebAssemblyAsmParser LLVMWebAssemblyCodeGen LLVMWebAssemblyDesc - LLVMWebAssemblyDisassembler + # LLVMWebAssemblyDisassembler LLVMWebAssemblyInfo LLVMWebAssemblyUtils ) else() + set(EXTRA_LLVMCLANG_LIBRARIES + # clangDriver + LLVMExecutionEngine + LLVMJITLink + LLVMOrcJIT + LLVMOrcShared + LLVMOrcTargetProcess + LLVMRuntimeDyld + LLVMWindowsDriver + ) + if(INTEL_MODE) - set(EXTRA_LLVMCLANG_LIBRARIES - LLVMExegesisX86 - LLVMX86AsmParser + list(APPEND EXTRA_LLVMCLANG_LIBRARIES + # LLVMX86AsmParser LLVMX86CodeGen LLVMX86Desc - LLVMX86Disassembler + # LLVMX86Disassembler LLVMX86Info - LLVMX86TargetMCA ) else() - set(EXTRA_LLVMCLANG_LIBRARIES - LLVMAArch64AsmParser + list(APPEND EXTRA_LLVMCLANG_LIBRARIES + # LLVMAArch64AsmParser LLVMAArch64CodeGen LLVMAArch64Desc - LLVMAArch64Disassembler + # LLVMAArch64Disassembler LLVMAArch64Info LLVMAArch64Utils - LLVMExegesisAArch64 ) endif() endif() @@ -242,51 +277,61 @@ set(LLVMCLANG_CMAKE_DIRS ${INSTALL_DIR}/lib/cmake/llvm ${INSTALL_DIR}/lib/cmake/ set(LLVMCLANG_CMAKE_PACKAGE_NAME Clang CACHE INTERNAL "${PACKAGE_NAME}'s CMake package name.") set(LLVMCLANG_CMAKE_PACKAGE_NAMES LLVM Clang CACHE INTERNAL "${PACKAGE_NAME}'s CMake package names.") set(LLVMCLANG_INCLUDE_DIR ${INSTALL_DIR}/include CACHE INTERNAL "${PACKAGE_NAME}'s include directory.") + +# The LLVM+Clang libraries that libOpenCOR needs on every platform, i.e. all of them except for those that are only +# used by LLVM+Clang's own tools and/or by libclang, none of which we use. Those are commented out below, so that it +# is clear which libraries we explicitly don't want. The platform-specific ones are added through +# EXTRA_LLVMCLANG_LIBRARIES above. +# Note: this list is also what decides which of LLVM+Clang's archives get merged into our static native library (see +# src/CMakeLists.txt), so it must be the full transitive closure of the libraries that we link against, and not +# just those that we reference directly. This is why, for instance, LLVMSymbolize and the debug information +# libraries that it needs are in the list even though we never symbolise anything: LLVMProfileData, which +# clangCodeGen needs, depends on them. + set(LLVMCLANG_LIBRARIES clangAnalysis - clangAnalysisFlowSensitive - clangAnalysisFlowSensitiveModels + # clangAnalysisFlowSensitive + # clangAnalysisFlowSensitiveModels clangAnalysisLifetimeSafety - clangAnalysisScalable + # clangAnalysisScalable clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen - clangCrossTU - clangDependencyScanning - clangDirectoryWatcher - clangDriver - clangDynamicASTMatchers + # clangCrossTU + # clangDependencyScanning + # clangDirectoryWatcher + # clangDynamicASTMatchers clangEdit - clangExtractAPI - clangFormat + # clangExtractAPI + # clangFormat clangFrontend - clangFrontendTool - clangIndex - clangIndexSerialization - clangInstallAPI - clangInterpreter + # clangFrontendTool + # clangIndex + # clangIndexSerialization + # clangInstallAPI + # clangInterpreter clangLex clangOptions clangParse - clangRewrite - clangRewriteFrontend + # clangRewrite + # clangRewriteFrontend clangSema clangSerialization - clangStaticAnalyzerCheckers - clangStaticAnalyzerCore - clangStaticAnalyzerFrontend + # clangStaticAnalyzerCheckers + # clangStaticAnalyzerCore + # clangStaticAnalyzerFrontend clangSupport - clangTooling - clangToolingASTDiff - clangToolingCore - clangToolingInclusions - clangToolingInclusionsStdlib - clangToolingRefactoring - clangToolingSyntax - clangTransformer - LLVMABI + # clangTooling + # clangToolingASTDiff + # clangToolingCore + # clangToolingInclusions + # clangToolingInclusionsStdlib + # clangToolingRefactoring + # clangToolingSyntax + # clangTransformer + # LLVMABI LLVMAggressiveInstCombine LLVMAnalysis LLVMAsmParser @@ -295,9 +340,9 @@ set(LLVMCLANG_LIBRARIES LLVMBitReader LLVMBitstreamReader LLVMBitWriter - LLVMCAS + # LLVMCAS LLVMCFGuard - LLVMCFIVerify + # LLVMCFIVerify LLVMCGData LLVMCodeGen LLVMCodeGenTypes @@ -306,89 +351,82 @@ set(LLVMCLANG_LIBRARIES LLVMCoverage LLVMDebugInfoBTF LLVMDebugInfoCodeView - LLVMDebuginfod + # LLVMDebuginfod LLVMDebugInfoDWARF LLVMDebugInfoDWARFLowLevel LLVMDebugInfoGSYM - LLVMDebugInfoLogicalView + # LLVMDebugInfoLogicalView LLVMDebugInfoMSF LLVMDebugInfoPDB LLVMDemangle - LLVMDiff - LLVMDlltoolDriver - LLVMDTLTO - LLVMDWARFCFIChecker - LLVMDWARFLinker - LLVMDWARFLinkerClassic - LLVMDWARFLinkerParallel - LLVMDWP - LLVMExecutionEngine - LLVMExegesis + # LLVMDiff + # LLVMDlltoolDriver + # LLVMDTLTO + # LLVMDWARFCFIChecker + # LLVMDWARFLinker + # LLVMDWARFLinkerClassic + # LLVMDWARFLinkerParallel + # LLVMDWP + # LLVMExegesis LLVMExtensions - LLVMFileCheck + # LLVMFileCheck LLVMFrontendAtomic LLVMFrontendDirective LLVMFrontendDriver LLVMFrontendHLSL LLVMFrontendOffloading - LLVMFrontendOpenACC + # LLVMFrontendOpenACC LLVMFrontendOpenMP - LLVMFuzzerCLI - LLVMFuzzMutate + # LLVMFuzzerCLI + # LLVMFuzzMutate LLVMGlobalISel LLVMHipStdPar LLVMInstCombine LLVMInstrumentation - LLVMInterfaceStub - LLVMInterpreter + # LLVMInterfaceStub + # LLVMInterpreter LLVMipo LLVMIRPrinter LLVMIRReader - LLVMJITLink - LLVMLibDriver - LLVMLineEditor + # LLVMLibDriver + # LLVMLineEditor LLVMLinker LLVMLTO LLVMMC - LLVMMCA + # LLVMMCA LLVMMCDisassembler - LLVMMCJIT + # LLVMMCJIT LLVMMCParser - LLVMMIRParser + # LLVMMIRParser LLVMObjCARCOpts - LLVMObjCopy + # LLVMObjCopy LLVMObject LLVMObjectYAML - LLVMOptDriver + # LLVMOptDriver LLVMOption - LLVMOrcDebugging - LLVMOrcJIT - LLVMOrcShared - LLVMOrcTargetProcess + # LLVMOrcDebugging LLVMPasses LLVMPlugins LLVMProfileData LLVMRemarks - LLVMRuntimeDyld LLVMSandboxIR LLVMScalarOpts LLVMSelectionDAG LLVMSupport - LLVMSupportLSP + # LLVMSupportLSP LLVMSymbolize - LLVMTableGen - LLVMTableGenBasic - LLVMTableGenCommon + # LLVMTableGen + # LLVMTableGenBasic + # LLVMTableGenCommon LLVMTarget LLVMTargetParser - LLVMTelemetry + # LLVMTelemetry LLVMTextAPI - LLVMTextAPIBinaryReader + # LLVMTextAPIBinaryReader LLVMTransformUtils LLVMVectorize - LLVMWindowsDriver - LLVMWindowsManifest - LLVMXRay + # LLVMWindowsManifest + # LLVMXRay ${EXTRA_LLVMCLANG_LIBRARIES} CACHE INTERNAL "${PACKAGE_NAME}'s libraries." ) diff --git a/src/3rdparty/LLVMClang/README.md b/src/3rdparty/LLVMClang/README.md index 8f80d3d52..43ca8182d 100644 --- a/src/3rdparty/LLVMClang/README.md +++ b/src/3rdparty/LLVMClang/README.md @@ -1 +1,13 @@ -Our copy of LLVM+Clang 22.1.8 requires the changes listed [here](https://github.com/opencor/llvm-project/compare/llvmorg-22.1.8...opencor:llvm-project:llvmorg-22.1.8-libopencor) to build and work correctly with libOpenCOR. +libOpenCOR only needs a subset of LLVM+Clang, so we do everything we can to minimise its footprint. This matters most to our WASM version since our Web app dynamically imports it, but our native versions benefit from it too since we merge LLVM+Clang into our static library. + +To this end, our WASM version of LLVM+Clang is built with size optimisation (`-Oz`) and without RTTI, while our native versions keep LLVM+Clang's default release flags so that they compile our models as fast as possible. Every version is built with only the backend of the target for which it generates code, and with the patches in `patches`, which are applied by `applypatches.cmake`: + +- `clang-optional-libraries.patch` adds four options, all `ON` by default so that LLVM+Clang otherwise builds exactly as it normally would: + - `CLANG_ENABLE_TOOLING_LIBS` and `CLANG_ENABLE_INTERPRETER` skip the clang libraries that only the clang tools and libclang need. The patch also makes `clangAnalysis` only build `MacroExpansionContext.cpp` when the tooling libraries are built, since that one file is what would otherwise make every clang embedder depend on `clangFormat` and `clangToolingCore`. + - `CLANG_ENABLE_DRIVER` skips Clang's driver, which we don't use (see below). + - `CLANG_ENABLE_ALL_TARGETS` narrows Clang's target support down to the one architecture named by `LLVM_TARGETS_TO_BUILD`. Indeed, Clang builds a `TargetInfo` (`clang/lib/Basic`) and a target code generation info (`clang/lib/CodeGen`) for every architecture that it knows about, whatever `LLVM_TARGETS_TO_BUILD` says, and the functions that allocate them reference all of them, so the linker cannot drop any of them. +- `llvm-lib-support-commandline.patch` makes LLVM ignore, rather than report, an option that gets registered more than once. + +On top of this, we determine Clang's `-cc1` arguments ourselves rather than have Clang's driver compute them for us (see `src/misc/compiler.cpp`). Indeed, `clang::driver::Driver` references every single one of Clang's toolchains, which would otherwise all end up in libOpenCOR even though only one of them can ever run. Our code never includes a system header and we JIT the result (or, for our WASM version, emit it directly), so we don't need anything that the driver works out for us. On macOS, this also means that we no longer ask Xcode for an SDK path and a linker version every time we compile some code. + +Note that `clangOptions` (Clang's option table, which `clang::CompilerInvocation::CreateFromArgs()` needs) and `LLVMWindowsDriver` (which LLVM's ORC-based JIT uses to locate the MSVC toolchain) are separate libraries, so we still need both. diff --git a/src/3rdparty/LLVMClang/applypatches.cmake b/src/3rdparty/LLVMClang/applypatches.cmake new file mode 100644 index 000000000..95accb5c7 --- /dev/null +++ b/src/3rdparty/LLVMClang/applypatches.cmake @@ -0,0 +1,121 @@ +# Copyright libOpenCOR contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Apply the libOpenCOR patches to the LLVM+Clang source code, if needed. +# Note: the patches are applied by libOpenCOR's CMakeLists.txt when building LLVM+Clang from source. They are also +# meant to be applied (and committed) to the opencor/llvm-project repository so that our prebuilt packages +# include them. + +if(NOT DEFINED LLVMCLANG_SOURCE_DIR) + message(FATAL_ERROR "LLVMCLANG_SOURCE_DIR must be set to the LLVM+Clang source directory.") +endif() + +if(NOT DEFINED LLVMCLANG_PATCHES_DIR) + message(FATAL_ERROR "LLVMCLANG_PATCHES_DIR must be set to the directory that contains the libOpenCOR patches to be applied to LLVM+Clang.") +endif() + +if(NOT EXISTS "${LLVMCLANG_PATCHES_DIR}") + message(FATAL_ERROR "The directory that contains the libOpenCOR patches to be applied to LLVM+Clang could not be found (${LLVMCLANG_PATCHES_DIR}).") +endif() + +if(NOT EXISTS "${LLVMCLANG_SOURCE_DIR}/clang/lib/CMakeLists.txt") + message(FATAL_ERROR "The LLVM+Clang source directory could not be found (${LLVMCLANG_SOURCE_DIR}).") +endif() + +find_program(GIT_EXECUTABLE NAMES git) + +if(NOT GIT_EXECUTABLE) + message(FATAL_ERROR "git could not be found, so the LLVM+Clang patches could not be applied.") +endif() + +# Make sure that the source directory is a git repository, otherwise git apply would silently skip the patches (it +# only applies them if the current or a parent directory is a git repository, which is not something we can rely on, +# e.g. if libOpenCOR's source is a tarball rather than a clone). + +execute_process(COMMAND ${GIT_EXECUTABLE} init -q + WORKING_DIRECTORY "${LLVMCLANG_SOURCE_DIR}" + RESULT_VARIABLE RESULT + ERROR_VARIABLE ERROR) + +if(NOT RESULT EQUAL 0) + message(FATAL_ERROR "git init failed, so the LLVM+Clang patches could not be applied (${ERROR}).") +endif() + +# Determine the libOpenCOR patches to be applied to LLVM+Clang. + +file(GLOB_RECURSE LLVMCLANG_PATCH_FILES "${LLVMCLANG_PATCHES_DIR}/*.patch") + +foreach(LLVMCLANG_PATCH_FILE IN LISTS LLVMCLANG_PATCH_FILES) + get_filename_component(LLVMCLANG_PATCH_NAME "${LLVMCLANG_PATCH_FILE}" NAME) + + # Normalise the line endings of the patch file and of the files to be patched, if needed. + # Note: on Windows, git checks out text files (such as our patch files) with CRLF line endings by default, while + # the LLVM+Clang source files (as extracted from their archive) have LF line endings. git apply cannot + # apply a patch with CRLF line endings to files with LF line endings (and vice versa), so we make sure + # that both the patch file and the files to be patched use LF line endings. Also, we don't want git to + # convert the line endings of the patched files (e.g. on Windows), so we disable any automatic line ending + # conversion and ask git to ignore any remaining whitespace differences. + + file(READ "${LLVMCLANG_PATCH_FILE}" LLVMCLANG_PATCH_CONTENT) + string(REPLACE "\r\n" "\n" LLVMCLANG_PATCH_CONTENT "${LLVMCLANG_PATCH_CONTENT}") + + set(LLVMCLANG_NORMALISED_PATCH_FILE "${LLVMCLANG_SOURCE_DIR}/.llvmclang-patch") + + file(WRITE "${LLVMCLANG_NORMALISED_PATCH_FILE}" "${LLVMCLANG_PATCH_CONTENT}") + + # Normalise the line endings of the files to be patched. They are the files listed in the patch file. + + string(REGEX MATCHALL "diff --git a/[^ ]+ b/[^ ]+" + LLVMCLANG_PATCHED_FILES "${LLVMCLANG_PATCH_CONTENT}") + + foreach(LLVMCLANG_PATCHED_FILE ${LLVMCLANG_PATCHED_FILES}) + string(REPLACE "diff --git a/" "" LLVMCLANG_PATCHED_FILE "${LLVMCLANG_PATCHED_FILE}") + string(REGEX REPLACE " b/.*" "" LLVMCLANG_PATCHED_FILE "${LLVMCLANG_PATCHED_FILE}") + + if(EXISTS "${LLVMCLANG_SOURCE_DIR}/${LLVMCLANG_PATCHED_FILE}") + file(READ "${LLVMCLANG_SOURCE_DIR}/${LLVMCLANG_PATCHED_FILE}" LLVMCLANG_PATCHED_FILE_CONTENT) + string(REPLACE "\r\n" "\n" LLVMCLANG_PATCHED_FILE_CONTENT "${LLVMCLANG_PATCHED_FILE_CONTENT}") + file(WRITE "${LLVMCLANG_SOURCE_DIR}/${LLVMCLANG_PATCHED_FILE}" "${LLVMCLANG_PATCHED_FILE_CONTENT}") + endif() + endforeach() + + # Check whether the patch has already been applied (e.g. it may have been committed to the opencor/llvm-project + # repository that we download from). Indeed, if the patch can be reverse-applied, then it has already been applied. + + execute_process(COMMAND ${GIT_EXECUTABLE} -c core.autocrlf=input apply --reverse --check --whitespace=nowarn --ignore-space-change "${LLVMCLANG_NORMALISED_PATCH_FILE}" + WORKING_DIRECTORY "${LLVMCLANG_SOURCE_DIR}" + RESULT_VARIABLE RESULT + ERROR_VARIABLE ERROR) + + if(RESULT EQUAL 0) + message(STATUS "Applying the LLVM+Clang patch ${LLVMCLANG_PATCH_NAME} - already applied") + + file(REMOVE "${LLVMCLANG_NORMALISED_PATCH_FILE}") + + continue() + endif() + + execute_process(COMMAND ${GIT_EXECUTABLE} -c core.autocrlf=input apply --whitespace=nowarn --ignore-space-change "${LLVMCLANG_NORMALISED_PATCH_FILE}" + WORKING_DIRECTORY "${LLVMCLANG_SOURCE_DIR}" + RESULT_VARIABLE RESULT + ERROR_VARIABLE ERROR) + + file(REMOVE "${LLVMCLANG_NORMALISED_PATCH_FILE}") + + if(NOT RESULT EQUAL 0) + message(FATAL_ERROR "The LLVM+Clang patch ${LLVMCLANG_PATCH_NAME} could not be applied (${ERROR}).") + endif() + + message(STATUS "Applying the LLVM+Clang patch ${LLVMCLANG_PATCH_NAME} - Success") +endforeach() diff --git a/src/3rdparty/LLVMClang/patches/clang-optional-libraries.patch b/src/3rdparty/LLVMClang/patches/clang-optional-libraries.patch new file mode 100644 index 000000000..8a063c60b --- /dev/null +++ b/src/3rdparty/LLVMClang/patches/clang-optional-libraries.patch @@ -0,0 +1,775 @@ +diff --git a/clang/CMakeLists.txt b/clang/CMakeLists.txt +index e4cb1a3..e5121e2 100644 +--- a/clang/CMakeLists.txt ++++ b/clang/CMakeLists.txt +@@ -451,6 +451,26 @@ option(CLANG_ENABLE_STATIC_ANALYZER + + option(CLANG_ENABLE_PROTO_FUZZER "Build Clang protobuf fuzzer." OFF) + ++# libOpenCOR: allow the libraries that are only needed by the clang tools and/or libclang to be excluded from the ++# build. libOpenCOR only needs a small subset of the clang libraries, so it disables these options to minimise its ++# footprint. CLANG_ENABLE_DRIVER can also be disabled by an embedder that computes the -cc1 arguments itself, which ++# is what libOpenCOR does so as not to drag all of the clang toolchains into it. ++option(CLANG_ENABLE_DRIVER ++ "Build the clang driver library." ON) ++option(CLANG_ENABLE_INTERPRETER ++ "Build the clang interpreter library." ON) ++option(CLANG_ENABLE_TOOLING_LIBS ++ "Build the clang libraries that are only needed by the clang tools and libclang." ON) ++ ++# libOpenCOR: clang builds a TargetInfo (clang/lib/Basic) and target code generation info (clang/lib/CodeGen) for ++# every architecture that it knows about, whatever LLVM_TARGETS_TO_BUILD says, and the functions that allocate them ++# reference all of them, so the linker cannot drop any. That is several megabytes of builtin, CPU and ABI tables that ++# an embedder which only ever compiles for the one architecture that it runs on, as libOpenCOR does, can never use. ++# When this option is disabled, the target support is narrowed down to LLVM_TARGETS_TO_BUILD, which must then name ++# exactly one of the architectures that clang/lib/Basic/CMakeLists.txt knows how to narrow down to. ++option(CLANG_ENABLE_ALL_TARGETS ++ "Build the target support for every architecture that clang knows about, rather than for LLVM_TARGETS_TO_BUILD only." ON) ++ + if (DEFINED CLANG_ENABLE_ARCMT) + set(CLANG_ENABLE_OBJC_REWRITER ${CLANG_ENABLE_ARCMT}) + message(DEPRECATION "'CLANG_ENABLE_ARCMT' is deprecated as ARCMigrate has been removed from Clang. Please use 'CLANG_ENABLE_OBJC_REWRITER' instead to enable or disable the Objective-C rewriter.") +diff --git a/clang/lib/ASTMatchers/CMakeLists.txt b/clang/lib/ASTMatchers/CMakeLists.txt +index 29ad27d..fd382d5 100644 +--- a/clang/lib/ASTMatchers/CMakeLists.txt ++++ b/clang/lib/ASTMatchers/CMakeLists.txt +@@ -1,4 +1,6 @@ +-add_subdirectory(Dynamic) ++if(CLANG_ENABLE_TOOLING_LIBS) ++ add_subdirectory(Dynamic) ++endif() + + set(LLVM_LINK_COMPONENTS + FrontendOpenMP +diff --git a/clang/lib/Analysis/CMakeLists.txt b/clang/lib/Analysis/CMakeLists.txt +index c41f1fd..519c418 100644 +--- a/clang/lib/Analysis/CMakeLists.txt ++++ b/clang/lib/Analysis/CMakeLists.txt +@@ -3,6 +3,25 @@ set(LLVM_LINK_COMPONENTS + Support + ) + ++# libOpenCOR: MacroExpansionContext is only used by clangCrossTU and the static analyzer, yet it is what makes ++# clangAnalysis (which every clang embedder needs) depend on clangFormat and clangToolingCore. So, only build it when ++# the tooling libraries are built. ++ ++if(CLANG_ENABLE_TOOLING_LIBS) ++ set(CLANG_ANALYSIS_TOOLING_SOURCES ++ MacroExpansionContext.cpp ++ ) ++ set(CLANG_ANALYSIS_TOOLING_LIBS ++ clangFormat ++ clangToolingCore ++ ) ++else() ++ # Let llvm_check_source_file_list() know that MacroExpansionContext.cpp is deliberately not built. ++ set(LLVM_OPTIONAL_SOURCES ++ MacroExpansionContext.cpp ++ ) ++endif() ++ + add_clang_library(clangAnalysis + AnalysisDeclContext.cpp + BodyFarm.cpp +@@ -22,7 +41,6 @@ add_clang_library(clangAnalysis + IntervalPartition.cpp + IssueHash.cpp + LiveVariables.cpp +- MacroExpansionContext.cpp + ObjCNoReturn.cpp + PathDiagnostic.cpp + PostOrderCFGView.cpp +@@ -35,14 +53,14 @@ add_clang_library(clangAnalysis + ThreadSafetyTIL.cpp + UninitializedValues.cpp + UnsafeBufferUsage.cpp ++ ${CLANG_ANALYSIS_TOOLING_SOURCES} + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic +- clangFormat + clangLex +- clangToolingCore ++ ${CLANG_ANALYSIS_TOOLING_LIBS} + + DEPENDS + omp_gen +@@ -50,6 +68,8 @@ add_clang_library(clangAnalysis + ) + + add_subdirectory(plugins) +-add_subdirectory(FlowSensitive) + add_subdirectory(LifetimeSafety) +-add_subdirectory(Scalable) ++if(CLANG_ENABLE_TOOLING_LIBS) ++ add_subdirectory(FlowSensitive) ++ add_subdirectory(Scalable) ++endif() +diff --git a/clang/lib/Basic/CMakeLists.txt b/clang/lib/Basic/CMakeLists.txt +index adfc6ee..c40e057 100644 +--- a/clang/lib/Basic/CMakeLists.txt ++++ b/clang/lib/Basic/CMakeLists.txt +@@ -54,6 +54,70 @@ if(CLANG_VENDOR) + PROPERTIES COMPILE_DEFINITIONS "CLANG_VENDOR=\"${CLANG_VENDOR} \"") + endif() + ++# libOpenCOR: clang always builds a TargetInfo for every architecture it knows about, whatever LLVM_TARGETS_TO_BUILD ++# says, and AllocateTarget() references all of them, so the linker cannot drop any. That is several megabytes of ++# builtin and CPU tables that an embedder which only ever compiles for one architecture can never use. So, allow the ++# target infos to be narrowed down to the one architecture named by LLVM_TARGETS_TO_BUILD. ++# Note: OSTargets.cpp is needed whatever the architecture. Its AArch64.h include is unused, so it does not bring the ++# AArch64 target info back in. ++ ++if(CLANG_ENABLE_ALL_TARGETS) ++ set(CLANG_BASIC_TARGET_SOURCES ++ Targets/AArch64.cpp ++ Targets/AMDGPU.cpp ++ Targets/ARC.cpp ++ Targets/ARM.cpp ++ Targets/AVR.cpp ++ Targets/BPF.cpp ++ Targets/CSKY.cpp ++ Targets/DirectX.cpp ++ Targets/Hexagon.cpp ++ Targets/Lanai.cpp ++ Targets/LoongArch.cpp ++ Targets/M68k.cpp ++ Targets/MSP430.cpp ++ Targets/Mips.cpp ++ Targets/NVPTX.cpp ++ Targets/OSTargets.cpp ++ Targets/PPC.cpp ++ Targets/RISCV.cpp ++ Targets/SPIR.cpp ++ Targets/Sparc.cpp ++ Targets/SystemZ.cpp ++ Targets/TCE.cpp ++ Targets/VE.cpp ++ Targets/WebAssembly.cpp ++ Targets/X86.cpp ++ Targets/XCore.cpp ++ Targets/Xtensa.cpp ++ ) ++elseif("${LLVM_TARGETS_TO_BUILD}" STREQUAL "AArch64") ++ set(CLANG_BASIC_TARGET_SOURCES ++ Targets/AArch64.cpp ++ Targets/OSTargets.cpp ++ ) ++ ++ add_compile_definitions(CLANG_SINGLE_TARGET_ONLY CLANG_AARCH64_TARGET_ONLY) ++elseif("${LLVM_TARGETS_TO_BUILD}" STREQUAL "X86") ++ set(CLANG_BASIC_TARGET_SOURCES ++ Targets/OSTargets.cpp ++ Targets/X86.cpp ++ ) ++ ++ add_compile_definitions(CLANG_SINGLE_TARGET_ONLY CLANG_X86_TARGET_ONLY) ++elseif("${LLVM_TARGETS_TO_BUILD}" STREQUAL "WebAssembly") ++ set(CLANG_BASIC_TARGET_SOURCES ++ Targets/OSTargets.cpp ++ Targets/WebAssembly.cpp ++ ) ++ ++ add_compile_definitions(CLANG_SINGLE_TARGET_ONLY CLANG_WEBASSEMBLY_TARGET_ONLY) ++else() ++ message(FATAL_ERROR ++ "CLANG_ENABLE_ALL_TARGETS is disabled, so LLVM_TARGETS_TO_BUILD must name exactly one of AArch64, X86 and " ++ "WebAssembly, while it is '${LLVM_TARGETS_TO_BUILD}'.") ++endif() ++ + add_clang_library(clangBasic + ASTSourceDescriptor.cpp + Attributes.cpp +@@ -95,33 +159,7 @@ add_clang_library(clangBasic + TargetID.cpp + TargetInfo.cpp + Targets.cpp +- Targets/AArch64.cpp +- Targets/AMDGPU.cpp +- Targets/ARC.cpp +- Targets/ARM.cpp +- Targets/AVR.cpp +- Targets/BPF.cpp +- Targets/CSKY.cpp +- Targets/DirectX.cpp +- Targets/Hexagon.cpp +- Targets/Lanai.cpp +- Targets/LoongArch.cpp +- Targets/M68k.cpp +- Targets/MSP430.cpp +- Targets/Mips.cpp +- Targets/NVPTX.cpp +- Targets/OSTargets.cpp +- Targets/PPC.cpp +- Targets/RISCV.cpp +- Targets/SPIR.cpp +- Targets/Sparc.cpp +- Targets/SystemZ.cpp +- Targets/TCE.cpp +- Targets/VE.cpp +- Targets/WebAssembly.cpp +- Targets/X86.cpp +- Targets/XCore.cpp +- Targets/Xtensa.cpp ++ ${CLANG_BASIC_TARGET_SOURCES} + TokenKinds.cpp + TypeTraits.cpp + Version.cpp +diff --git a/clang/lib/Basic/Targets.cpp b/clang/lib/Basic/Targets.cpp +index 2632539..24e0d6b 100644 +--- a/clang/lib/Basic/Targets.cpp ++++ b/clang/lib/Basic/Targets.cpp +@@ -112,6 +112,274 @@ std::unique_ptr AllocateTarget(const llvm::Triple &Triple, + const TargetOptions &Opts) { + llvm::Triple::OSType os = Triple.getOS(); + ++#ifdef CLANG_SINGLE_TARGET_ONLY ++ // libOpenCOR: only one architecture can ever be targeted (see clang/lib/Basic/CMakeLists.txt), so the arms below ++ // are verbatim copies of that architecture's arms in the full switch that follows. Every other architecture ++ // returns nullptr, which is exactly what the full switch does for an architecture that clang has no target for. ++ ++ switch (Triple.getArch()) { ++ default: ++ return nullptr; ++ ++#ifdef CLANG_AARCH64_TARGET_ONLY ++ case llvm::Triple::aarch64_32: ++ if (Triple.isOSDarwin()) ++ return std::make_unique(Triple, Opts); ++ else if (Triple.isAppleMachO()) ++ return std::make_unique(Triple, Opts); ++ ++ return nullptr; ++ case llvm::Triple::aarch64: ++ if (Triple.isOSDarwin()) ++ return std::make_unique(Triple, Opts); ++ else if (Triple.isAppleMachO()) ++ return std::make_unique(Triple, Opts); ++ ++ switch (os) { ++ case llvm::Triple::FreeBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Fuchsia: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Haiku: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Linux: ++ switch (Triple.getEnvironment()) { ++ default: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::OpenHOS: ++ return std::make_unique>(Triple, ++ Opts); ++ } ++ case llvm::Triple::Managarm: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::NetBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::OpenBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Hurd: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Win32: ++ switch (Triple.getEnvironment()) { ++ case llvm::Triple::GNU: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::MSVC: ++ default: // Assume MSVC for unknown environments ++ return std::make_unique(Triple, Opts); ++ } ++ default: ++ return std::make_unique(Triple, Opts); ++ } ++ ++ case llvm::Triple::aarch64_be: ++ switch (os) { ++ case llvm::Triple::FreeBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Fuchsia: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Linux: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::NetBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ default: ++ return std::make_unique(Triple, Opts); ++ } ++#endif ++ ++#ifdef CLANG_X86_TARGET_ONLY ++ case llvm::Triple::x86: ++ if (Triple.isOSDarwin()) ++ return std::make_unique(Triple, Opts); ++ else if (Triple.isAppleMachO()) ++ return std::make_unique(Triple, Opts); ++ ++ switch (os) { ++ case llvm::Triple::Linux: { ++ switch (Triple.getEnvironment()) { ++ default: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Android: ++ return std::make_unique(Triple, Opts); ++ } ++ } ++ case llvm::Triple::DragonFly: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::NetBSD: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::OpenBSD: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::FreeBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Fuchsia: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::KFreeBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Solaris: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Win32: { ++ switch (Triple.getEnvironment()) { ++ case llvm::Triple::Cygnus: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::GNU: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::Itanium: ++ case llvm::Triple::MSVC: ++ default: // Assume MSVC for unknown environments ++ return std::make_unique(Triple, Opts); ++ } ++ } ++ case llvm::Triple::Haiku: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::RTEMS: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::ELFIAMCU: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::Hurd: ++ return std::make_unique>(Triple, Opts); ++ default: ++ return std::make_unique(Triple, Opts); ++ } ++ ++ case llvm::Triple::x86_64: ++ if (Triple.isOSDarwin() || Triple.isOSBinFormatMachO()) ++ return std::make_unique(Triple, Opts); ++ ++ switch (os) { ++ case llvm::Triple::Linux: { ++ switch (Triple.getEnvironment()) { ++ default: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Android: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::OpenHOS: ++ return std::make_unique(Triple, Opts); ++ } ++ } ++ case llvm::Triple::DragonFly: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::NetBSD: ++ return std::make_unique>(Triple, Opts); ++ case llvm::Triple::OpenBSD: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::FreeBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Fuchsia: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::KFreeBSD: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Solaris: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::UEFI: ++ return std::make_unique(Triple, Opts); ++ ++ case llvm::Triple::Win32: { ++ switch (Triple.getEnvironment()) { ++ case llvm::Triple::Cygnus: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::GNU: ++ return std::make_unique(Triple, Opts); ++ case llvm::Triple::MSVC: ++ default: // Assume MSVC for unknown environments ++ return std::make_unique(Triple, Opts); ++ } ++ } ++ case llvm::Triple::Haiku: ++ return std::make_unique>(Triple, Opts); ++ case llvm::Triple::PS4: ++ return std::make_unique>(Triple, Opts); ++ case llvm::Triple::PS5: ++ return std::make_unique>(Triple, Opts); ++ case llvm::Triple::Hurd: ++ return std::make_unique>(Triple, Opts); ++ case llvm::Triple::Managarm: ++ return std::make_unique>(Triple, ++ Opts); ++ default: ++ return std::make_unique(Triple, Opts); ++ } ++#endif ++ ++#ifdef CLANG_WEBASSEMBLY_TARGET_ONLY ++ case llvm::Triple::wasm32: ++ if (Triple.getSubArch() != llvm::Triple::NoSubArch || ++ Triple.getVendor() != llvm::Triple::UnknownVendor || ++ !Triple.isOSBinFormatWasm()) ++ return nullptr; ++ switch (os) { ++ case llvm::Triple::WASI: // Treat "wasi" as "wasip1" for now. ++ case llvm::Triple::WASIp1: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::WASIp2: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::WASIp3: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Emscripten: ++ return std::make_unique>( ++ Triple, Opts); ++ ++ case llvm::Triple::Linux: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::UnknownOS: ++ return std::make_unique>( ++ Triple, Opts); ++ default: ++ return nullptr; ++ } ++ case llvm::Triple::wasm64: ++ if (Triple.getSubArch() != llvm::Triple::NoSubArch || ++ Triple.getVendor() != llvm::Triple::UnknownVendor || ++ !Triple.isOSBinFormatWasm()) ++ return nullptr; ++ switch (os) { ++ case llvm::Triple::WASI: // Treat "wasi" as "wasip1" for now. ++ case llvm::Triple::WASIp1: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::WASIp2: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::WASIp3: ++ return std::make_unique>(Triple, ++ Opts); ++ case llvm::Triple::Emscripten: ++ return std::make_unique>( ++ Triple, Opts); ++ case llvm::Triple::UnknownOS: ++ return std::make_unique>( ++ Triple, Opts); ++ default: ++ return nullptr; ++ } ++#endif ++ } ++#else + switch (Triple.getArch()) { + default: + return nullptr; +@@ -796,6 +1064,7 @@ std::unique_ptr AllocateTarget(const llvm::Triple &Triple, + case llvm::Triple::xtensa: + return std::make_unique(Triple, Opts); + } ++#endif + } + } // namespace targets + } // namespace clang +diff --git a/clang/lib/CMakeLists.txt b/clang/lib/CMakeLists.txt +index 2fc69e4..eccec9c 100644 +--- a/clang/lib/CMakeLists.txt ++++ b/clang/lib/CMakeLists.txt +@@ -5,30 +5,44 @@ add_subdirectory(Lex) + add_subdirectory(Parse) + add_subdirectory(AST) + add_subdirectory(ASTMatchers) +-add_subdirectory(CrossTU) ++if(CLANG_ENABLE_TOOLING_LIBS) ++ add_subdirectory(CrossTU) ++endif() + add_subdirectory(Sema) + add_subdirectory(CodeGen) + add_subdirectory(Analysis) + add_subdirectory(Edit) +-add_subdirectory(ExtractAPI) +-add_subdirectory(Rewrite) +-add_subdirectory(Driver) ++if(CLANG_ENABLE_TOOLING_LIBS) ++ add_subdirectory(ExtractAPI) ++ add_subdirectory(Rewrite) ++endif() ++if(CLANG_ENABLE_DRIVER) ++ add_subdirectory(Driver) ++endif() + add_subdirectory(Options) + add_subdirectory(Serialization) + add_subdirectory(Frontend) +-add_subdirectory(FrontendTool) +-add_subdirectory(Tooling) +-add_subdirectory(DependencyScanning) +-add_subdirectory(DirectoryWatcher) +-add_subdirectory(Index) +-add_subdirectory(IndexSerialization) +-add_subdirectory(InstallAPI) +-add_subdirectory(StaticAnalyzer) +-add_subdirectory(Format) ++if(CLANG_ENABLE_TOOLING_LIBS) ++ add_subdirectory(FrontendTool) ++ add_subdirectory(Tooling) ++ add_subdirectory(DependencyScanning) ++ add_subdirectory(DirectoryWatcher) ++ add_subdirectory(Index) ++ add_subdirectory(IndexSerialization) ++ add_subdirectory(InstallAPI) ++endif() ++if(CLANG_ENABLE_STATIC_ANALYZER) ++ add_subdirectory(StaticAnalyzer) ++endif() ++if(CLANG_ENABLE_TOOLING_LIBS) ++ add_subdirectory(Format) ++endif() + if(CLANG_INCLUDE_TESTS) + add_subdirectory(Testing) + endif() +-add_subdirectory(Interpreter) ++if(CLANG_ENABLE_INTERPRETER) ++ add_subdirectory(Interpreter) ++endif() + add_subdirectory(Support) + + if(CLANG_ENABLE_CIR) +diff --git a/clang/lib/CodeGen/CMakeLists.txt b/clang/lib/CodeGen/CMakeLists.txt +index dbbc35b..abf0ec1 100644 +--- a/clang/lib/CodeGen/CMakeLists.txt ++++ b/clang/lib/CodeGen/CMakeLists.txt +@@ -55,6 +55,71 @@ if(MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES Clang + endif() + endif() + ++# libOpenCOR: as in clang/lib/Basic, clang builds the target-specific ABI/code generation info for every architecture ++# it knows about and createTargetCodeGenInfo() references all of them, so the linker cannot drop any. See ++# CLANG_ENABLE_ALL_TARGETS in clang/lib/Basic/CMakeLists.txt, where that option is declared. ++# Note: AMDGPU.cpp, NVPTX.cpp and SPIR.cpp are kept whatever the architecture that we narrow down to. They don't only ++# provide their target's code generation info: they also define CodeGenModule::handleAMDGPU*Attr(), ++# CodeGenModule::handleCUDALaunchBoundsAttr() and computeSPIRKernelABIInfo(), which CGOpenMPRuntime.cpp and ++# CGCall.cpp call unconditionally. ++ ++if(CLANG_ENABLE_ALL_TARGETS) ++ set(CLANG_CODEGEN_TARGET_SOURCES ++ Targets/AArch64.cpp ++ Targets/AMDGPU.cpp ++ Targets/ARC.cpp ++ Targets/ARM.cpp ++ Targets/AVR.cpp ++ Targets/BPF.cpp ++ Targets/CSKY.cpp ++ Targets/DirectX.cpp ++ Targets/Hexagon.cpp ++ Targets/Lanai.cpp ++ Targets/LoongArch.cpp ++ Targets/M68k.cpp ++ Targets/MSP430.cpp ++ Targets/Mips.cpp ++ Targets/NVPTX.cpp ++ Targets/PPC.cpp ++ Targets/RISCV.cpp ++ Targets/SPIR.cpp ++ Targets/Sparc.cpp ++ Targets/SystemZ.cpp ++ Targets/TCE.cpp ++ Targets/VE.cpp ++ Targets/WebAssembly.cpp ++ Targets/X86.cpp ++ Targets/XCore.cpp ++ ) ++else() ++ set(CLANG_CODEGEN_TARGET_SOURCES ++ Targets/AMDGPU.cpp ++ Targets/NVPTX.cpp ++ Targets/SPIR.cpp ++ ) ++ ++ if("${LLVM_TARGETS_TO_BUILD}" STREQUAL "AArch64") ++ # Note: X86.cpp is needed too. AArch64.cpp calls createWinX86_64TargetCodeGenInfo() for ARM64EC and ++ # TargetCodeGenInfo::qualifyWindowsLibrary(), which X86.cpp defines. ++ ++ list(APPEND CLANG_CODEGEN_TARGET_SOURCES Targets/AArch64.cpp Targets/X86.cpp) ++ ++ add_compile_definitions(CLANG_SINGLE_TARGET_ONLY CLANG_AARCH64_TARGET_ONLY) ++ elseif("${LLVM_TARGETS_TO_BUILD}" STREQUAL "X86") ++ list(APPEND CLANG_CODEGEN_TARGET_SOURCES Targets/X86.cpp) ++ ++ add_compile_definitions(CLANG_SINGLE_TARGET_ONLY CLANG_X86_TARGET_ONLY) ++ elseif("${LLVM_TARGETS_TO_BUILD}" STREQUAL "WebAssembly") ++ list(APPEND CLANG_CODEGEN_TARGET_SOURCES Targets/WebAssembly.cpp) ++ ++ add_compile_definitions(CLANG_SINGLE_TARGET_ONLY CLANG_WEBASSEMBLY_TARGET_ONLY) ++ else() ++ message(FATAL_ERROR ++ "CLANG_ENABLE_ALL_TARGETS is disabled, so LLVM_TARGETS_TO_BUILD must name exactly one of AArch64, X86 " ++ "and WebAssembly, while it is '${LLVM_TARGETS_TO_BUILD}'.") ++ endif() ++endif() ++ + add_clang_library(clangCodeGen + ABIInfo.cpp + ABIInfoImpl.cpp +@@ -130,31 +195,7 @@ add_clang_library(clangCodeGen + TargetBuiltins/WebAssembly.cpp + TargetBuiltins/X86.cpp + TargetInfo.cpp +- Targets/AArch64.cpp +- Targets/AMDGPU.cpp +- Targets/ARC.cpp +- Targets/ARM.cpp +- Targets/AVR.cpp +- Targets/BPF.cpp +- Targets/CSKY.cpp +- Targets/DirectX.cpp +- Targets/Hexagon.cpp +- Targets/Lanai.cpp +- Targets/LoongArch.cpp +- Targets/M68k.cpp +- Targets/MSP430.cpp +- Targets/Mips.cpp +- Targets/NVPTX.cpp +- Targets/PPC.cpp +- Targets/RISCV.cpp +- Targets/SPIR.cpp +- Targets/Sparc.cpp +- Targets/SystemZ.cpp +- Targets/TCE.cpp +- Targets/VE.cpp +- Targets/WebAssembly.cpp +- Targets/X86.cpp +- Targets/XCore.cpp ++ ${CLANG_CODEGEN_TARGET_SOURCES} + TrapReasonBuilder.cpp + VarBypassDetector.cpp + +diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp +index 85ed38f..74edf3e 100644 +--- a/clang/lib/CodeGen/CodeGenModule.cpp ++++ b/clang/lib/CodeGen/CodeGenModule.cpp +@@ -113,6 +113,72 @@ createTargetCodeGenInfo(CodeGenModule &CGM) { + const llvm::Triple &Triple = Target.getTriple(); + const CodeGenOptions &CodeGenOpts = CGM.getCodeGenOpts(); + ++#ifdef CLANG_SINGLE_TARGET_ONLY ++ // libOpenCOR: only one architecture can ever be targeted (see clang/lib/CodeGen/CMakeLists.txt), so the arms below ++ // are verbatim copies of that architecture's arms in the full switch that follows. ++ ++ switch (Triple.getArch()) { ++ default: ++ return createDefaultTargetCodeGenInfo(CGM); ++ ++#ifdef CLANG_AARCH64_TARGET_ONLY ++ case llvm::Triple::aarch64: ++ case llvm::Triple::aarch64_32: ++ case llvm::Triple::aarch64_be: { ++ AArch64ABIKind Kind = AArch64ABIKind::AAPCS; ++ if (Target.getABI() == "darwinpcs") ++ Kind = AArch64ABIKind::DarwinPCS; ++ else if (Triple.isOSWindows()) ++ return createWindowsAArch64TargetCodeGenInfo(CGM, AArch64ABIKind::Win64); ++ else if (Target.getABI() == "aapcs-soft") ++ Kind = AArch64ABIKind::AAPCSSoft; ++ ++ return createAArch64TargetCodeGenInfo(CGM, Kind); ++ } ++#endif ++ ++#ifdef CLANG_X86_TARGET_ONLY ++ case llvm::Triple::x86: { ++ bool IsDarwinVectorABI = Triple.isOSDarwin(); ++ bool IsWin32FloatStructABI = Triple.isOSWindows() && !Triple.isOSCygMing(); ++ ++ if (Triple.getOS() == llvm::Triple::Win32) { ++ return createWinX86_32TargetCodeGenInfo( ++ CGM, IsDarwinVectorABI, IsWin32FloatStructABI, ++ CodeGenOpts.NumRegisterParameters); ++ } ++ return createX86_32TargetCodeGenInfo( ++ CGM, IsDarwinVectorABI, IsWin32FloatStructABI, ++ CodeGenOpts.NumRegisterParameters, CodeGenOpts.FloatABI == "soft"); ++ } ++ ++ case llvm::Triple::x86_64: { ++ StringRef ABI = Target.getABI(); ++ X86AVXABILevel AVXLevel = (ABI == "avx512" ? X86AVXABILevel::AVX512 ++ : ABI == "avx" ? X86AVXABILevel::AVX ++ : X86AVXABILevel::None); ++ ++ switch (Triple.getOS()) { ++ case llvm::Triple::UEFI: ++ case llvm::Triple::Win32: ++ return createWinX86_64TargetCodeGenInfo(CGM, AVXLevel); ++ default: ++ return createX86_64TargetCodeGenInfo(CGM, AVXLevel); ++ } ++ } ++#endif ++ ++#ifdef CLANG_WEBASSEMBLY_TARGET_ONLY ++ case llvm::Triple::wasm32: ++ case llvm::Triple::wasm64: { ++ WebAssemblyABIKind Kind = WebAssemblyABIKind::MVP; ++ if (Target.getABI() == "experimental-mv") ++ Kind = WebAssemblyABIKind::ExperimentalMV; ++ return createWebAssemblyTargetCodeGenInfo(CGM, Kind); ++ } ++#endif ++ } ++#else + switch (Triple.getArch()) { + default: + return createDefaultTargetCodeGenInfo(CGM); +@@ -324,6 +390,7 @@ createTargetCodeGenInfo(CodeGenModule &CGM) { + CGM, Target.getPointerWidth(LangAS::Default), ABIFRLen); + } + } ++#endif + } + + const TargetCodeGenInfo &CodeGenModule::getTargetCodeGenInfo() { +diff --git a/clang/lib/Frontend/CMakeLists.txt b/clang/lib/Frontend/CMakeLists.txt +index c40baa3..9378c93 100644 +--- a/clang/lib/Frontend/CMakeLists.txt ++++ b/clang/lib/Frontend/CMakeLists.txt +@@ -1,4 +1,6 @@ +-add_subdirectory(Rewrite) ++if(CLANG_ENABLE_TOOLING_LIBS) ++ add_subdirectory(Rewrite) ++endif() + + set(LLVM_LINK_COMPONENTS + BitReader diff --git a/src/3rdparty/LLVMClang/patches/llvm-lib-support-commandline.patch b/src/3rdparty/LLVMClang/patches/llvm-lib-support-commandline.patch new file mode 100644 index 000000000..e3b6dcb00 --- /dev/null +++ b/src/3rdparty/LLVMClang/patches/llvm-lib-support-commandline.patch @@ -0,0 +1,24 @@ +diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp +index f4adb00..49e5b01 100644 +--- a/llvm/lib/Support/CommandLine.cpp ++++ b/llvm/lib/Support/CommandLine.cpp +@@ -229,16 +229,12 @@ public: + void addOption(Option *O, SubCommand *SC) { + bool HadErrors = false; + if (O->hasArgStr()) { +- // If it's a DefaultOption, check to make sure it isn't already there. +- if (O->isDefaultOption() && SC->OptionsMap.contains(O->ArgStr)) ++ // Make sure it isn't already there. ++ if (SC->OptionsMap.contains(O->ArgStr)) + return; + + // Add argument to the argument map! +- if (!SC->OptionsMap.insert(std::make_pair(O->ArgStr, O)).second) { +- errs() << ProgramName << ": CommandLine Error: Option '" << O->ArgStr +- << "' registered more than once!\n"; +- HadErrors = true; +- } ++ SC->OptionsMap.insert(std::make_pair(O->ArgStr, O)); + } + + // Remember information about positional options. diff --git a/src/3rdparty/SUNDIALS/CMakeLists.txt b/src/3rdparty/SUNDIALS/CMakeLists.txt index 3e3075990..119591367 100644 --- a/src/3rdparty/SUNDIALS/CMakeLists.txt +++ b/src/3rdparty/SUNDIALS/CMakeLists.txt @@ -24,56 +24,65 @@ if(LIBOPENCOR_PREBUILT_SUNDIALS) if(EMSCRIPTEN) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 770a7e84f8e986310c6ece0ed662e4ef11035c8d) + f3ef4afe47f722699c55a2221faf65c1f17fd5a0) else() if(WIN32) if(RELEASE_MODE) if(INTEL_MODE) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - ea5e25fbe26a8f1309f9c9424e688abe626700c5) + cf87f196975c1d3d62b5733e64028e00d20bc61c) else() retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 9526d5b7723b31f77f8c68ed8a8e356ae7029503) + 54edefd4839b544fcda11ffbea6c410e328fd812) endif() else() if(INTEL_MODE) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - a84a217438bd69c61ba7649ccf54ea3def6f0cdb) + 3b2d251b1b51b558594347e10b631542f60903f5) else() retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 0ce0997d40fec38b3f2a6ecea7f39a29a66bef79) + 1685edc065fd7b323c7cd49956dcf0df188329d4) endif() endif() elseif(APPLE) if(INTEL_MODE) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 0cfb4f6722c19acd12935b9d2a2f9dbea9e44cba) + a2494dd173e01f513e9d1ce1eb9bee89440a7dbf) else() retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 6e36a58950250682f16117715d780adb1a9588f5) + 56ccaf46db0f853f601dfc70415481009d0fe89d) endif() else() if(INTEL_MODE) retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - e4501231ad79082f46c031a34c2da1c50d6e7db3) + ff9a7ccb8998222bf69af9c2473c4748933c531a) else() retrieve_package(${PACKAGE_NAME} ${PACKAGE_VERSION} ${PACKAGE_REPOSITORY} ${RELEASE_TAG} - 68ca5feee035f3a958225fdaaea6bfab3bae7aa2) + b18b19b6a0a22bb9ecfc1970372422ba4a94cf52) endif() endif() endif() elseif(NOT ONLY_BUILD_JAVASCRIPT_THIRD_PARTY_LIBRARIES) # Build our package. - set(PACKAGE_C_FLAGS -DNO_FPRINTF_OUTPUT ${CMAKE_C_FLAGS}) + set(PACKAGE_C_FLAGS "-DNO_FPRINTF_OUTPUT ${CMAKE_C_FLAGS}") + + # Enable WebAssembly SIMD for our WASM version. + # Note: most of the simulation time is spent in SUNDIALS' dense linear algebra, which the compiler can then + # auto-vectorise. This makes our stiff models run 8-16% faster. WebAssembly SIMD is supported by all modern + # browsers (Chrome 91+, Firefox 89+, and Safari 16.4+). + + if(EMSCRIPTEN) + set(PACKAGE_C_FLAGS "${PACKAGE_C_FLAGS} -msimd128") + endif() build_package(${PACKAGE_NAME} URL diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ceac4bbf9..a0950472e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -64,16 +64,31 @@ if(RELEASE_MODE AND NOT BUILDING_USING_MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-math-errno -fno-trapping-math") endif() -# Enable auto-vectorisation (useful for fixed-step solver loops on x86-64 release builds). -# Note: we use -march=x86-64-v3 (AVX2) rather than -march=native so that binaries built on one x86-64 machine run on any -# other modern x86-64 machine. x86-64-v3 is supported by all CPUs since Intel Haswell (2013) and AMD Excavator -# (2015). For ARM64, the compiler defaults already include NEON/ASIMD. - -if(RELEASE_MODE AND NOT EMSCRIPTEN AND NOT BUILDING_USING_MSVC) - if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|amd64") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64-v3") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=x86-64-v3") +# Target x86-64-v3 (i.e. AVX2, FMA, etc.) on Intel, so that the compiler can auto-vectorise our code and that of our +# third-party libraries (e.g., SUNDIALS' dense linear algebra, which is where most of the simulation time is spent). +# Note #1: we use x86-64-v3 rather than the host CPU so that binaries built on one x86-64 machine run on any other +# modern x86-64 machine. x86-64-v3 is supported by all CPUs since Intel Haswell (2013) and AMD Excavator +# (2015). For ARM64, the compiler defaults already include NEON/ASIMD. +# Note #2: MSVC only uses FMA instructions if it is allowed to contract floating-point operations, hence /fp:contract. +# Note #3: we use those flags whatever the build type, so that our debug and release builds give the same results. +# Note #4: the flags are also passed on to our third-party libraries (see packages.cmake and, for SUNDIALS, which uses +# CMAKE_C_FLAGS, src/3rdparty/SUNDIALS/CMakeLists.txt). +# Note #5: on macOS, we may build for Intel on an ARM machine. Some third-party libraries (e.g., SUNDIALS) run checks +# that use CMAKE_C_FLAGS but not CMAKE_OSX_ARCHITECTURES, i.e. that compile for the host architecture, which +# fails with -march=x86-64-v3 on an ARM machine. So, we use -Xarch_x86_64 to only use -march=x86-64-v3 when +# compiling for Intel. + +if(INTEL_MODE AND NOT EMSCRIPTEN) + if(BUILDING_USING_MSVC) + set(X86_64_V3_COMPILER_FLAGS "/arch:AVX2 /fp:contract") + elseif(APPLE) + set(X86_64_V3_COMPILER_FLAGS "-Xarch_x86_64 -march=x86-64-v3") + else() + set(X86_64_V3_COMPILER_FLAGS "-march=x86-64-v3") endif() + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${X86_64_V3_COMPILER_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${X86_64_V3_COMPILER_FLAGS}") endif() # Enable link-time optimisation (LTO) for release builds to allow cross translation unit inlining and devirtualisation. @@ -84,7 +99,23 @@ if(RELEASE_MODE AND NOT EMSCRIPTEN AND NOT LIBOPENCOR_CODE_ANALYSIS) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) endif() +# The flags that our WASM version must be built with. +# Note: they are needed both here, when we are the Emscripten build, and in src/bindings/javascript/CMakeLists.txt, +# which passes them on to the Emscripten build that it kicks off. So, we define them once, here, rather than +# repeat them in both places and risk them drifting apart. + +set(EMSCRIPTEN_COMPILER_FLAGS "-matomics -mbulk-memory -pthread -sSHARED_MEMORY=1") +set(EMSCRIPTEN_EXE_LINKER_FLAGS "-sALLOW_TABLE_GROWTH -sINITIAL_TABLE=65536 -sSHARED_MEMORY=1") +set(EMSCRIPTEN_SHARED_LINKER_FLAGS "-sSHARED_MEMORY=1") + # Build our different third-party libraries. +# Note: for Emscripten, we set the C/C++ flags before building our third-party libraries so that they are built with the +# same flags as libOpenCOR (see packages.cmake, which passes them on to each third-party library). + +if(EMSCRIPTEN) + add_cache_flags(CMAKE_C_FLAGS "${EMSCRIPTEN_COMPILER_FLAGS}" "Emscripten C flags") + add_cache_flags(CMAKE_CXX_FLAGS "${EMSCRIPTEN_COMPILER_FLAGS}" "Emscripten C++ flags") +endif() add_subdirectory(3rdparty) @@ -308,12 +339,10 @@ if(EMSCRIPTEN) ${SOURCE_FILES} ${HEADER_FILES}) - # Preserve Emscripten thread/shared-memory support for libOpenCOR and for its third-party libraries. + # Preserve Emscripten thread/shared-memory support for libOpenCOR. - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -matomics -mbulk-memory -pthread -sSHARED_MEMORY=1" CACHE STRING "Emscripten C flags" FORCE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -matomics -mbulk-memory -pthread -sSHARED_MEMORY=1" CACHE STRING "Emscripten C++ flags" FORCE) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sALLOW_TABLE_GROWTH -sINITIAL_TABLE=65536 -sSHARED_MEMORY=1" CACHE STRING "Emscripten exe linker flags" FORCE) - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -sSHARED_MEMORY=1" CACHE STRING "Emscripten shared linker flags" FORCE) + add_cache_flags(CMAKE_EXE_LINKER_FLAGS "${EMSCRIPTEN_EXE_LINKER_FLAGS}" "Emscripten exe linker flags") + add_cache_flags(CMAKE_SHARED_LINKER_FLAGS "${EMSCRIPTEN_SHARED_LINKER_FLAGS}" "Emscripten shared linker flags") # Set the flags for our generated .js and .wasm files. # Note #1: for compiling, we use -O3 to get the best performance, -matomics to enable atomic operations, @@ -464,6 +493,35 @@ else() file(GLOB_RECURSE PACKAGES ${PREBUILT_DIR}/*${CMAKE_STATIC_LIBRARY_SUFFIX}) + # Only keep LLVM+Clang's libraries that we need (see LLVMCLANG_LIBRARIES in + # src/3rdparty/LLVMClang/CMakeLists.txt), together with our other packages' libraries. + # Note: we cannot remove LLVM+Clang's libraries that we don't need from the package itself since its + # installed CMake files reference them all. So, we simply exclude them from the object files that + # are merged into libOpenCOR's static version. + + set(LLVMCLANG_ARCHIVE_NAMES) + + foreach(LLVMCLANG_LIBRARY ${LLVMCLANG_LIBRARIES}) + list(APPEND LLVMCLANG_ARCHIVE_NAMES + "${CMAKE_STATIC_LIBRARY_PREFIX}${LLVMCLANG_LIBRARY}${CMAKE_STATIC_LIBRARY_SUFFIX}") + endforeach() + + set(KEPT_PACKAGES) + + foreach(PACKAGE ${PACKAGES}) + if(PACKAGE MATCHES "/LLVMClang/lib/") + get_filename_component(PACKAGE_ARCHIVE_NAME ${PACKAGE} NAME) + + if(NOT PACKAGE_ARCHIVE_NAME IN_LIST LLVMCLANG_ARCHIVE_NAMES) + continue() + endif() + endif() + + list(APPEND KEPT_PACKAGES ${PACKAGE}) + endforeach() + + set(PACKAGES ${KEPT_PACKAGES}) + # Extract the object files from our various packages, handling duplicate object names. list(LENGTH PACKAGES PACKAGE_COUNT) diff --git a/src/bindings/javascript/CMakeLists.txt b/src/bindings/javascript/CMakeLists.txt index adcdc6f3d..c0bfb0dec 100644 --- a/src/bindings/javascript/CMakeLists.txt +++ b/src/bindings/javascript/CMakeLists.txt @@ -67,10 +67,10 @@ if(EMSCRIPTEN OR LIBOPENCOR_JAVASCRIPT_BINDINGS) -DBUILDCACHE_EXE=${BUILDCACHE_EXE} -DCCACHE_EXE=${CCACHE_EXE} -DCLCACHE_EXE=${CLCACHE_EXE} - -DCMAKE_C_FLAGS=-matomics\ -mbulk-memory\ -pthread\ -sSHARED_MEMORY=1 - -DCMAKE_CXX_FLAGS=-matomics\ -mbulk-memory\ -pthread\ -sSHARED_MEMORY=1 - -DCMAKE_EXE_LINKER_FLAGS=-sALLOW_TABLE_GROWTH\ -sINITIAL_TABLE=65536\ -sSHARED_MEMORY=1 - -DCMAKE_SHARED_LINKER_FLAGS=-sSHARED_MEMORY=1 + "-DCMAKE_C_FLAGS=${EMSCRIPTEN_COMPILER_FLAGS}" + "-DCMAKE_CXX_FLAGS=${EMSCRIPTEN_COMPILER_FLAGS}" + "-DCMAKE_EXE_LINKER_FLAGS=${EMSCRIPTEN_EXE_LINKER_FLAGS}" + "-DCMAKE_SHARED_LINKER_FLAGS=${EMSCRIPTEN_SHARED_LINKER_FLAGS}" -DEMCMAKE_EXE=${EMCMAKE_EXE} -DLIBOPENCOR_BUILD_TYPE=${LIBOPENCOR_BUILD_TYPE} -DLIBOPENCOR_COMPILER_CACHING=${LIBOPENCOR_COMPILER_CACHING} diff --git a/src/misc/compiler.cpp b/src/misc/compiler.cpp index 9f7966cc4..c0a2886f8 100644 --- a/src/misc/compiler.cpp +++ b/src/misc/compiler.cpp @@ -18,9 +18,6 @@ limitations under the License. #include "clang/Basic/TargetInfo.h" #include "clang/CodeGen/CodeGenAction.h" -#include "clang/Driver/Compilation.h" -#include "clang/Driver/Driver.h" -#include "clang/Driver/Tool.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Lex/PreprocessorOptions.h" @@ -321,81 +318,107 @@ bool Compiler::Impl::compile(const std::string &pCode) diagnosticsEngine->setWarningsAsErrors(true); - // Get a driver object and ask it not to check that input files exist. - - clang::driver::Driver driver("clang", llvm::sys::getProcessTriple(), *diagnosticsEngine); - - driver.setCheckInputsExist(false); - - // Get a compilation object to which we pass some arguments. + // Determine the -cc1 arguments with which to compile our code. + // + // Build them ourselves rather than have Clang's driver compute them for us. Indeed, the driver references every + // single one of its toolchains (~65,000 lines of code for platforms that we can never target), which would + // otherwise all end up in libOpenCOR. Here, our code never includes a system header and we JIT the result rather + // than emit an object file, so the arguments below are exactly those that the driver would compute for + // clang -fsyntax-only -O3 -fno-math-errno -fno-trapping-math -fno-stack-protector -funroll-loops dummy.c + // minus: + // - the resource/sysroot include paths, which we have no use for (and which, on macOS, made us ask Xcode for an + // SDK path and a linker version every time we compiled some code); + // - the debug/coverage compilation directories, the debugger tuning, the target linker version and the address + // significance table, none of which mean anything for code that we JIT rather than emit as an object file; + // - the diagnostic options that only apply to Objective-C and to system headers; + // - -target-cpu, so that the CPU that we give our ORC-based JIT's target machine (i.e. the host CPU; see below) + // is the one that drives code generation. The driver picks a conservative CPU (e.g. x86-64 on Linux, which + // means no AVX) and puts it in a per-function "target-cpu" attribute, which LLVM prefers over the target + // machine's CPU, so leaving it out is what makes our host CPU setting effective; + // - on Windows, -relaxed-aliasing, which the driver adds for MSVC compatibility and which disables TBAA. Our + // code is well-typed, so we'd rather keep TBAA and generate the same code on all platforms. + // Note #1: -fno-math-errno, -fno-trapping-math and -fno-stack-protector have no -cc1 counterpart. They are, + // instead, expressed as the absence of -fmath-errno and -stack-protector, and as + // -ffp-exception-behavior=ignore. + // Note #2: our WASM version always targets wasm32-unknown-emscripten and doesn't JIT anything, hence its + // arguments are both fixed and rather different from our native versions'. static constexpr auto DUMMY_FILE_NAME {"dummy.c"}; - static const std::vector COMPILATION_ARGUMENTS {{"clang", "-fsyntax-only", - "-O3", - "-fno-math-errno", - "-fno-trapping-math", - "-fno-stack-protector", - "-funroll-loops", - DUMMY_FILE_NAME}}; - - std::unique_ptr compilation(driver.BuildCompilation(COMPILATION_ARGUMENTS)); - -#ifndef CODE_COVERAGE_ENABLED - if (compilation == nullptr) { - addError("A compilation object could not be created."); - - return false; - } -#endif - - // The compilation object should have one command, so if it doesn't then something went wrong. - - clang::driver::JobList &jobs {compilation->getJobs()}; - -#ifndef CODE_COVERAGE_ENABLED - if ((jobs.size() != 1) || !llvm::isa(*jobs.begin())) { - addError("The compilation object must have one command."); - - return false; - } -#endif - - // Retrieve the command and make sure that its name is "clang". - - auto &command {llvm::cast(*jobs.begin())}; -#ifndef CODE_COVERAGE_ENABLED - static constexpr auto CLANG {"clang"}; - - if (strcmp(command.getCreator().getName(), CLANG) != 0) { - const std::string commandName(command.getCreator().getName()); - std::string error; - - error.reserve(commandName.size() + 47); // NOLINT - - error += "The command name must be 'clang' while it is '"; - error += commandName; - error += "'."; - - addError(error); - - return false; - } -#endif - - // Prevent the Clang driver from asking cc1 to leak memory, this by removing -disable-free from the command - // arguments. - - auto commandArguments {command.getArguments()}; - -#ifdef CODE_COVERAGE_ENABLED - commandArguments.erase(find(commandArguments, llvm::StringRef("-disable-free"))); +#ifdef __EMSCRIPTEN__ + static const std::vector commandArguments {{"-triple", "wasm32-unknown-emscripten", + "-O3", + "-clear-ast-before-backend", + "-disable-llvm-verifier", + "-discard-value-names", + "-main-file-name", DUMMY_FILE_NAME, + "-mrelocation-model", "static", + "-mframe-pointer=none", + "-ffp-contract=on", + "-fno-rounding-math", + "-ffp-exception-behavior=ignore", + "-mconstructor-aliases", + "-target-cpu", "generic", + "-fvisibility=hidden", + "-ferror-limit", "19", + "-funroll-loops", + "-fgnuc-version=4.2.1", + "-vectorize-loops", + "-vectorize-slp", + "-x", "c", + DUMMY_FILE_NAME}}; #else - auto *commandArgument {find(commandArguments, llvm::StringRef("-disable-free"))}; - - if (commandArgument != commandArguments.end()) { - commandArguments.erase(commandArgument); - } + // Note: the triple is that of the process rather than a literal since, unlike our WASM version, our native + // versions can be built for several architectures and operating system versions. + + static const std::string triple {llvm::sys::getProcessTriple()}; + + static const std::vector commandArguments {{"-triple", triple.c_str(), + "-O3", + "-clear-ast-before-backend", + "-disable-llvm-verifier", + "-discard-value-names", + "-main-file-name", DUMMY_FILE_NAME, + "-mrelocation-model", "pic", + "-pic-level", "2", +# if defined(__APPLE__) && defined(__aarch64__) + "-mframe-pointer=non-leaf-no-reserve", + "-funwind-tables=1", +# elif defined(__APPLE__) + "-mframe-pointer=all", + "-funwind-tables=2", +# elif defined(_WIN32) && (defined(_M_ARM64) || defined(__aarch64__)) + "-mframe-pointer=reserved", + "-funwind-tables=2", + "-mconstructor-aliases", +# elif defined(_WIN32) + "-mframe-pointer=none", + "-funwind-tables=2", + "-mconstructor-aliases", +# elif defined(__aarch64__) + // Note: char is unsigned on AArch64 Linux. + + "-mframe-pointer=non-leaf-no-reserve", + "-funwind-tables=2", + "-mconstructor-aliases", + "-pic-is-pie", + "-fno-signed-char", +# else + "-mframe-pointer=none", + "-funwind-tables=2", + "-mconstructor-aliases", + "-pic-is-pie", +# endif + "-ffp-contract=on", + "-fno-rounding-math", + "-ffp-exception-behavior=ignore", + "-ferror-limit", "19", + "-funroll-loops", + "-fgnuc-version=4.2.1", + "-vectorize-loops", + "-vectorize-slp", + "-x", "c", + DUMMY_FILE_NAME}}; #endif // Create a compiler instance. @@ -635,7 +658,9 @@ extern double atanh(double); // Create an ORC-based JIT with a target machine builder for the host system. // Note: we set the CPU to the host CPU name and the optimisation level to aggressive. This is because the default // CPU is generic and the default optimisation level is none, which can lead to suboptimal performance for the - // generated code. + // generated code. The CPU that we set here is the one that LLVM uses since, as mentioned above, we make a + // point of not passing -target-cpu to the frontend (a per-function "target-cpu" attribute would otherwise + // take precedence over it). auto jitTargetMachineBuilder {llvm::orc::JITTargetMachineBuilder(llvm::Triple(llvm::sys::getProcessTriple()))}; diff --git a/src/solver/solvercvode.cpp b/src/solver/solvercvode.cpp index 9cf5e7ca9..ad4fbbaf7 100644 --- a/src/solver/solvercvode.cpp +++ b/src/solver/solvercvode.cpp @@ -28,6 +28,7 @@ limitations under the License. #include "sunlinsol/sunlinsol_sptfqmr.h" #include "sunnonlinsol/sunnonlinsol_fixedpoint.h" +#include #include namespace libOpenCOR { diff --git a/src/solver/solverodefixedstep.cpp b/src/solver/solverodefixedstep.cpp index ffb451100..4a6fa46e8 100644 --- a/src/solver/solverodefixedstep.cpp +++ b/src/solver/solverodefixedstep.cpp @@ -18,6 +18,8 @@ limitations under the License. #include "sedml/SedAlgorithm.h" +#include + namespace libOpenCOR { // Solver. diff --git a/tests/api/sed/instancetests.cpp b/tests/api/sed/instancetests.cpp index d33db59c2..bd0586c20 100644 --- a/tests/api/sed/instancetests.cpp +++ b/tests/api/sed/instancetests.cpp @@ -533,17 +533,7 @@ TEST(InstanceSedTest, startRunAfterPreviousRunCompleted) TEST(InstanceSedTest, odeModel) { const libOpenCOR::ExpectedIssues EXPECTED_ISSUES {{ -#ifdef BUILDING_ON_INTEL {libOpenCOR::Issue::Type::ERROR, "Task | CVODE: at t = 0.00140013827899996, mxstep steps taken before reaching tout."}, -#else - {libOpenCOR::Issue::Type::ERROR, -# ifdef BUILDING_ON_WINDOWS - "Task | CVODE: at t = 0.00140013827899821, mxstep steps taken before reaching tout." -# else - "Task | CVODE: at t = 0.00140013827899707, mxstep steps taken before reaching tout." -# endif - }, -#endif }}; auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("cellml_2.cellml"))}; diff --git a/tests/api/solver/cvodetests.cpp b/tests/api/solver/cvodetests.cpp index c8cca0413..8419d8ea7 100644 --- a/tests/api/solver/cvodetests.cpp +++ b/tests/api/solver/cvodetests.cpp @@ -310,8 +310,8 @@ TEST(CvodeSolverTest, solveWithBandedLinearSolver) TEST(CvodeSolverTest, solveWithDiagonalLinearSolver) { - static const auto STATE_VALUES {std::vector({-63.887, 0.13501, 0.984334, 0.74097})}; - static const auto STATE_ABS_TOLS {std::vector({0.001, 0.00001, 0.000001, 0.00001})}; + static const auto STATE_VALUES {std::vector({-63.887, 0.13501, 0.98433, 0.74097})}; + static const auto STATE_ABS_TOLS {std::vector({0.001, 0.00001, 0.00001, 0.00001})}; static const auto RATE_VALUES {std::vector({49.72, -0.12812, -0.05099, 0.09854})}; static const auto RATE_ABS_TOLS {std::vector({0.01, 0.0001, 0.00001, 0.00001})}; static const auto CONSTANT_VALUES {std::vector({1.0, 0.0, 0.3, 120.0, 36.0})}; @@ -451,8 +451,8 @@ TEST(CvodeSolverTest, solveWithGmresLinearSolverAndNoPreconditioner) TEST(CvodeSolverTest, solveWithBicgstabLinearSolverAndNoPreconditioner) { - static const auto STATE_VALUES {std::vector({-63.886, 0.13501, 0.984333, 0.740972})}; - static const auto STATE_ABS_TOLS {std::vector({0.001, 0.00001, 0.000001, 0.000001})}; + static const auto STATE_VALUES {std::vector({-63.886, 0.13501, 0.984333, 0.74097})}; + static const auto STATE_ABS_TOLS {std::vector({0.001, 0.00001, 0.000001, 0.00001})}; static const auto RATE_VALUES {std::vector({49.719, -0.12812, -0.050992, 0.09854})}; static const auto RATE_ABS_TOLS {std::vector({0.001, 0.00001, 0.0001, 0.00001})}; static const auto CONSTANT_VALUES {std::vector({1.0, 0.0, 0.3, 120.0, 36.0})}; diff --git a/tests/bindings/javascript/res/index.html b/tests/bindings/javascript/res/index.html index 88e5a4449..129ef8acc 100644 --- a/tests/bindings/javascript/res/index.html +++ b/tests/bindings/javascript/res/index.html @@ -1,4 +1,4 @@ - + libOpenCOR - JavaScript bindings diff --git a/tests/bindings/javascript/utils.in.js b/tests/bindings/javascript/utils.in.js index 614b58ea8..3d81eab5e 100644 --- a/tests/bindings/javascript/utils.in.js +++ b/tests/bindings/javascript/utils.in.js @@ -25,6 +25,14 @@ export const RESOURCE_LOCATION = '@CMAKE_SOURCE_DIR@/tests/res'; export const REMOTE_BASE_PATH = 'https://raw.githubusercontent.com/opencor/libopencor/master/tests/res'; export const REMOTE_FILE = 'https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml'; +export function normaliseDescription(description) { + // Normalise the value of any time (t = ...) and step size (h = ...) in the given description so that + // differences at the last few significant digits are ignored. Indeed, the exact value of a time or step size + // can vary slightly depending on the platform and the CPU used to generate the model code. + + return description.replace(/([th] = )([-+0-9.eE]+)/g, (m, prefix, value) => prefix + Number(value).toPrecision(6)); +} + export function assertIssues(loc, logger, expectedIssues) { const issues = logger.issues; @@ -32,7 +40,7 @@ export function assertIssues(loc, logger, expectedIssues) { for (const [i, issue] of Array.from(issues).entries()) { assert.strictEqual(issue.type.value, expectedIssues[i][0].value); - assert.strictEqual(issue.description, expectedIssues[i][1]); + assert.strictEqual(normaliseDescription(issue.description), normaliseDescription(expectedIssues[i][1])); if (issue.type === loc.Issue.Type.ERROR) { assert.strictEqual(issue.typeAsString, 'Error'); diff --git a/tests/bindings/python/test_sed_instance.py b/tests/bindings/python/test_sed_instance.py index e99254fef..b51fa5a00 100644 --- a/tests/bindings/python/test_sed_instance.py +++ b/tests/bindings/python/test_sed_instance.py @@ -15,7 +15,6 @@ import libopencor as loc import math -import platform import time import utils from utils import assert_issues @@ -509,11 +508,7 @@ def run_ode_model(): expected_issues = [ [ loc.Issue.Type.Error, - ( - "Task | CVODE: at t = 0.00140013827899707, mxstep steps taken before reaching tout." - if platform.system() == "Darwin" - else "Task | CVODE: at t = 0.00140013827899996, mxstep steps taken before reaching tout." - ), + "Task | CVODE: at t = 0.00140013827899996, mxstep steps taken before reaching tout.", ], ] diff --git a/tests/bindings/python/test_sed_serialise.py b/tests/bindings/python/test_sed_serialise.py index 9b674ef5c..6f26f077f 100644 --- a/tests/bindings/python/test_sed_serialise.py +++ b/tests/bindings/python/test_sed_serialise.py @@ -598,6 +598,8 @@ def test_sedml_file(): """ + loc.FileManager.instance().reset() + file = loc.File(utils.resource_path("cellml_2.sedml")) document = loc.SedDocument(file) diff --git a/tests/bindings/python/test_solver_cvode.py b/tests/bindings/python/test_solver_cvode.py index 6d347aa92..e38afb026 100644 --- a/tests/bindings/python/test_solver_cvode.py +++ b/tests/bindings/python/test_solver_cvode.py @@ -431,8 +431,8 @@ def test_solve_with_functional_iteration_type(): def test_solve_with_banded_linear_solver(): - state_values = [-54.958, 0.114716, 0.971365, 0.756967] - state_abs_tols = [0.001, 0.000001, 0.000001, 0.000001] + state_values = [-54.958, 0.11471, 0.97136, 0.756967] + state_abs_tols = [0.001, 0.00001, 0.00001, 0.000001] rate_values = [47.190, -0.10201, -0.09312, 0.06289] rate_abs_tols = [0.001, 0.00001, 0.00001, 0.00001] constant_values = [1.0, 0.0, 0.3, 120.0, 36.0] @@ -721,11 +721,11 @@ def test_solve_with_gmres_linear_solver_and_no_preconditioner(): computed_constant_abs_tols = [0.0, 0.0, 0.0] algebraic_values = [ 0.0, - -15.9821, + -15.98, -823.517, 789.780, 3.9700, - 0.114985, + 0.11498, 0.002869, 0.967348, 0.54134, @@ -733,11 +733,11 @@ def test_solve_with_gmres_linear_solver_and_no_preconditioner(): ] algebraic_abs_tols = [ 0.0, - 0.0001, + 0.01, 0.001, 0.001, 0.0001, - 0.000001, + 0.00001, 0.000001, 0.000001, 0.00001, @@ -770,8 +770,8 @@ def test_solve_with_gmres_linear_solver_and_no_preconditioner(): def test_solve_with_bicgstab_linear_solver_and_no_preconditioner(): state_values = [-63.886, 0.13501, 0.984333, 0.740972] state_abs_tols = [0.001, 0.00001, 0.000001, 0.000001] - rate_values = [49.719, -0.12812, -0.050992, 0.098543] - rate_abs_tols = [0.001, 0.00001, 0.000001, 0.000001] + rate_values = [49.719, -0.12812, -0.05099, 0.09854] + rate_abs_tols = [0.001, 0.00001, 0.00001, 0.00001] constant_values = [1.0, 0.0, 0.3, 120.0, 36.0] constant_abs_tols = [0.0, 0.0, 0.0, 0.0, 0.0] computed_constant_values = [-10.613, -115.0, 12.0] @@ -825,8 +825,8 @@ def test_solve_with_bicgstab_linear_solver_and_no_preconditioner(): def test_solve_with_tfqmr_linear_solver_and_no_preconditioner(): - state_values = [-63.886, 0.13501, 0.984333, 0.740972] - state_abs_tols = [0.001, 0.00001, 0.000001, 0.000001] + state_values = [-63.886, 0.13501, 0.98433, 0.740972] + state_abs_tols = [0.001, 0.00001, 0.00001, 0.000001] rate_values = [49.719, -0.12812, -0.05099, 0.09854] rate_abs_tols = [0.001, 0.00001, 0.00001, 0.00001] constant_values = [1.0, 0.0, 0.3, 120.0, 36.0] diff --git a/tests/bindings/python/utils.in.py b/tests/bindings/python/utils.in.py index 305ce4e7e..21d5048a2 100644 --- a/tests/bindings/python/utils.in.py +++ b/tests/bindings/python/utils.in.py @@ -20,6 +20,7 @@ import pathlib import platform import pytest +import re ResourceLocation = "@RESOURCE_LOCATION@" @@ -28,6 +29,18 @@ RemoteFile = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml" +def normalise_description(description): + # Normalise the value of any time (t = ...) and step size (h = ...) in the given description so that + # differences at the last few significant digits are ignored. Indeed, the exact value of a time or step size + # can vary slightly depending on the platform and the CPU used to generate the model code. + + return re.sub( + r"([th] = )([-+0-9.eE]+)", + lambda m: m.group(1) + f"{float(m.group(2)):g}", + description, + ) + + def assert_issues(logger, expected_issues): issues = logger.issues @@ -35,7 +48,9 @@ def assert_issues(logger, expected_issues): for i in range(len(issues)): assert issues[i].type == expected_issues[i][0] - assert issues[i].description == expected_issues[i][1] + assert normalise_description(issues[i].description) == normalise_description( + expected_issues[i][1] + ) if issues[i].type == loc.Issue.Type.Error: assert issues[i].type_as_string == "Error" diff --git a/tests/utils.cpp b/tests/utils.cpp index ab939c5ec..616a78cbe 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -20,12 +20,48 @@ limitations under the License. #include "../extern/modp_b64/modp_b64.h" +#include #include #include #include +#include namespace libOpenCOR { +namespace { + +std::string normaliseDescription(const std::string &pDescription) +{ + // Normalise the value of any time (t = ...) and step size (h = ...) in the given description so that + // differences at the last few significant digits are ignored. Indeed, the exact value of a time or step size + // can vary slightly depending on the platform and the CPU used to generate the model code. + + static const std::regex TIME_VALUE_REGEX {"([th] = )([-+0-9.eE]+)"}; + std::string res; + std::string suffix {pDescription}; + std::smatch match; + + while (std::regex_search(suffix, match, TIME_VALUE_REGEX)) { + res += match.prefix(); + res += match[1].str(); + + static constexpr auto FORMATTED_VALUE_SIZE {64}; + char formattedValue[FORMATTED_VALUE_SIZE]; + const auto formattedValueResult {std::to_chars(formattedValue, formattedValue + FORMATTED_VALUE_SIZE, + std::stod(match[2].str()), std::chars_format::general, 6)}; + + res += std::string(formattedValue, formattedValueResult.ptr); + + suffix = match.suffix(); + } + + res += suffix; + + return res; +} + +} // namespace + void expectEqualIssues(const LoggerPtr &pLogger, const ExpectedIssues &pExpectedIssues) { const auto &issues {pLogger->issues()}; @@ -34,7 +70,7 @@ void expectEqualIssues(const LoggerPtr &pLogger, const ExpectedIssues &pExpected for (size_t i {0}; i < issues.size(); ++i) { EXPECT_EQ(issues[i]->type(), pExpectedIssues[i].type); - EXPECT_EQ(issues[i]->description(), pExpectedIssues[i].description); + EXPECT_EQ(normaliseDescription(issues[i]->description()), normaliseDescription(pExpectedIssues[i].description)); if (issues[i]->type() == Issue::Type::ERROR) { EXPECT_EQ(issues[i]->typeAsString(), "Error");